Hi
I am using XIAO nRF52840 Sense. I want to sample from SAADC @ 100ksps then send the FFT result via BLE. when I try to combine BLE function, it seems that softdevice (the BLE I want to add) is confilict with Direct Register Access (the code as below). Any good idea the combine them together?
#include "nrf.h"
#include <Arduino.h>
#include <arduinoFFT.h>
#include <Adafruit_TinyUSB.h>
#define SAMPLE_RATE_HZ 100000
#define BUF_SIZE 1024
// 双缓冲区
static int16_t bufferA[BUF_SIZE];
static int16_t bufferB[BUF_SIZE];
static volatile bool bufferA_ready = false;
static volatile bool bufferB_ready = false;
static volatile bool activeA = true;
// FFT
double vReal[BUF_SIZE];
double vImag[BUF_SIZE];
ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, BUF_SIZE, SAMPLE_RATE_HZ);
// ========== SAADC 中断 ==========
extern "C" void SAADC_IRQHandler(void)
{
if (NRF_SAADC->EVENTS_END)
{
NRF_SAADC->EVENTS_END = 0;
if (activeA)
{
bufferA_ready = true;
NRF_SAADC->RESULT.PTR = (uint32_t)bufferB;
activeA = false;
}
else
{
bufferB_ready = true;
NRF_SAADC->RESULT.PTR = (uint32_t)bufferA;
activeA = true;
}
// 重新启动下一轮采样
NRF_SAADC->TASKS_START = 1;
}
if (NRF_SAADC->EVENTS_STARTED)
{
NRF_SAADC->EVENTS_STARTED = 0;
}
}
// ========== 定时器 ==========
void setupTimer()
{
NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;
NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
NRF_TIMER1->PRESCALER = 0;
uint32_t compare = 16000000 / SAMPLE_RATE_HZ;
NRF_TIMER1->CC[0] = compare;
NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk;
NRF_TIMER1->TASKS_START = 1;
}
// ========== SAADC ==========
void setupSAADC()
{
NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled;
NRF_SAADC->CH[0].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput0;
NRF_SAADC->CH[0].CONFIG =
(SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos) |
(SAADC_CH_CONFIG_MODE_SE << SAADC_CH_CONFIG_MODE_Pos) |
(SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
(SAADC_CH_CONFIG_TACQ_5us << SAADC_CH_CONFIG_TACQ_Pos);
NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit;
NRF_SAADC->RESULT.PTR = (uint32_t)bufferA;
NRF_SAADC->RESULT.MAXCNT = BUF_SIZE;
NRF_SAADC->INTENSET = SAADC_INTENSET_END_Msk | SAADC_INTENSET_STARTED_Msk;
NVIC_SetPriority(SAADC_IRQn, 1);
NVIC_EnableIRQ(SAADC_IRQn);
// 定时器 → 采样任务
NRF_PPI->CH[0].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];
NRF_PPI->CH[0].TEP = (uint32_t)&NRF_SAADC->TASKS_SAMPLE;
NRF_PPI->CHENSET = (1 << 0);
// NRF_PPI->CHENSET = PPI_CHENSET_CH0_Msk;
NRF_SAADC->TASKS_START = 1;
}
// ========================
// 获取特定频率幅值
// ========================
float getMagnitudeAtFrequency(float freq)
{
float binWidth = (float)SAMPLE_RATE_HZ / BUF_SIZE;
int index = (int)(freq / binWidth);
if (index < 0) index = 0;
if (index >= BUF_SIZE / 2 - 1) index = BUF_SIZE / 2 - 2;
// 线性插值
float f1 = index * binWidth;
float f2 = (index + 1) * binWidth;
float mag1 = vReal[index];
float mag2 = vReal[index + 1];
float mag = mag1 + (mag2 - mag1) * ((freq - f1) / (f2 - f1));
return mag;
}
// ========== FFT 处理 ==========
void processFFT(int16_t *buffer)
{
const float V_REF = 0.6; // 内部参考电压
const float GAIN = 1.0 / 6; // 通道增益配置
// 1. 转换为真实电压
uint32_t mode = (NRF_SAADC->CH[0].CONFIG & SAADC_CH_CONFIG_MODE_Msk) >> SAADC_CH_CONFIG_MODE_Pos;
float res = (float)((mode == 0)? 4096 : 2048);
for (int i = 0; i < BUF_SIZE; i++) {
// buffer[i] 范围: -2048 ~ 2047 (signed 12-bit)
vReal[i] = (buffer[i] / res) * V_REF / GAIN;
vImag[i] = 0.0;
}
// 1b. 输出实时电压值(可选)
Serial.println("RAW_START");
for (int i = 0; i < BUF_SIZE; i++) {
Serial.println(vReal[i], 6); // 保留6位小数
}
Serial.println("RAW_END");
// 2. FFT
FFT.windowing(vReal, BUF_SIZE, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.compute(vReal, vImag, BUF_SIZE, FFT_FORWARD);
FFT.complexToMagnitude(vReal, vImag, BUF_SIZE);
// 3. 输出频谱
Serial.println("FFT_START");
for (int f = 0; f <= 10000; f += 10) {
float mag = getMagnitudeAtFrequency((float)f);
Serial.print(f);
Serial.print(",");
Serial.println(mag, 6);
}
Serial.println("FFT_END");
}
// ========== 主程序 ==========
void setup()
{
Serial.begin(921600);
while (!Serial);
Serial.println("🔹 初始化 nRF52840 SAADC 双缓冲 DMA + 实时 FFT");
setupTimer();
setupSAADC();
Serial.println("✅ 系统启动");
}
void loop()
{
if (bufferA_ready)
{
bufferA_ready = false;
processFFT(bufferA);
}
if (bufferB_ready)
{
bufferB_ready = false;
processFFT(bufferB);
}
}