SAADC with EasyDMA, but some data is missed

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…:grin: :index_pointing_up: 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.

  1. SAADC is being re-STARTed every buffer.
    In your SAADC_IRQHandler you call NRF_SAADC->TASKS_START = 1; after EVENTS_END. Once MAXCNT is reached, further SAMPLE triggers are ignored until you START again, so there is inherently dead time between buffers.
    Fix: call TASKS_START once in setupSAADC() and remove it from the IRQ. Just flip RESULT.PTR between bufferA/bufferB on each EVENTS_END.

  2. 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 in setupSAADC().
  • Never call START again in the IRQ.
  • Just use EVENTS_END to flip RESULT.PTR between 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:

  1. Timer keeps firing at 100 kHz, PPI keeps poking TASKS_SAMPLE.
  2. Once MAXCNT hits 1024, SAADC stops writing new samples.
  3. In IRQ they set new RESULT.PTR and then call TASKS_START.
  4. 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 :santa_claus: PJ :v: