Hi ,
I‘m using SAADC with DMA for high frequency sampling.
At a nominal sampling rate of 100 kHz with a buffer size of 1024 samples, we see both missing samples within each acquisition frame and dead time between consecutive frames, resulting in visible gaps and phase jumps in the reconstructed waveform.
I want to know whether this data loss can be avoided by adjusting the code.
attached arduino code and its waveform collected via PC.
#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);
}
}
