Short answer Yes. However You break one of the Basic coding tennants (Interrupts ISR’s ) need to be short and sweet, set a flag and leave. You restart the sample in the INT. ISR and that blows up the Timing and the Buffer,NO-NO-NO…
So yes: their “dead time between frames” is caused by re-STARTing once per buffer.
The SAADC itself can handle 100 ksps – that’s below the 200 ksps max for nRF52840, even with TACQ=5 µs. The gaps and phase jumps you’re seeing are mostly due to how the SAADC and Serial output are set up, not because 100 kHz is too fast.
-
SAADC is being re-STARTed every buffer.
In yourSAADC_IRQHandleryou callNRF_SAADC->TASKS_START = 1;afterEVENTS_END. OnceMAXCNTis reached, further SAMPLE triggers are ignored until you START again, so there is inherently dead time between buffers.
Fix: callTASKS_STARTonce insetupSAADC()and remove it from the IRQ. Just flipRESULT.PTRbetween bufferA/bufferB on eachEVENTS_END. -
Serial output is far too slow for 100 ksps.
Each 1024-sample frame represents ~10 ms of real-time data, but you’re printing tens of kilobytes of ASCII per frame at 921600 bps. That takes ~0.2 s per frame to transmit, so any waveform you reconstruct on the PC will have big gaps by design. The ADC + DMA keep running in the background, but your logging cannot keep up.
Fix: either lower the sample rate, log only a subset of samples (e.g., one frame every N frames), or store raw data to RAM/SD instead of printing every sample.
With those two changes (no re-START in IRQ + much lighter logging) you should be able to get continuous, gap-free acquisition at 100 ksps and then run your FFT on buffers without the visible phase jumps.
Try this one…
- Call
NRF_SAADC->TASKS_START = 1;once insetupSAADC(). - Never call START again in the IRQ.
- Just use
EVENTS_ENDto flipRESULT.PTRbetween bufferA and bufferB.
Something like:
void setupSAADC() {
// ... same config ...
NRF_SAADC->RESULT.PTR = (uint32_t)bufferA;
NRF_SAADC->RESULT.MAXCNT = BUF_SIZE;
NRF_SAADC->INTENSET = SAADC_INTENSET_END_Msk;
NVIC_SetPriority(SAADC_IRQn, 1);
NVIC_EnableIRQ(SAADC_IRQn);
// Timer -> SAMPLE
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);
// Start SAADC once, then let timer+PPI keep it going
NRF_SAADC->TASKS_START = 1;
}
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;
}
// no TASKS_START here
}
}
At EVENTS_END, the SAADC considers the current buffer full and ignores further SAMPLE triggers until you START again. So the sequence is:
- Timer keeps firing at 100 kHz, PPI keeps poking
TASKS_SAMPLE. - Once
MAXCNThits 1024, SAADC stops writing new samples. - In IRQ they set new
RESULT.PTRand then callTASKS_START. - Only after the next START + next timer event does sampling resume.
That gap is not huge in absolute time, but at 100 kHz you’re definitely dropping some samples between frames.
HTH
GL
PJ ![]()