[SAADC] Zephyr's SAADC Driver (DMA Design choices analysis)

Setup:
NCS v2.8.0
nRF52840

Hi,

I would like to ask about the implementation of Zephyr's SAADC driver (`adc_nrfx_saadc.c`).

In the project I'm working on, I need to sample the ADC at 16 kHz.
However, I don't need to process every individual sample in the application. Instead, I would prefer to collect around 320 samples (equivalent to 20 ms of data) and then raise an interrupt. DMA seems like a perfect fit for this use case.

Initially, I thought that by using the `struct adc_sequence_options`—specifically the `extra_samplings` field—the SAADC driver would configure the `RESULT.MAXCNT` register to match the total number of samples (including `extra_samplings`), and thus trigger an interrupt only after all samples had been collected.

However, this does not appear to be the case. For each `adc_sequence` triggered by `adc_read()` or `adc_async_read()`, the driver calls `nrf_saadc_buffer_init()` with a buffer size (`RESULT.MAXCNT`) equal only to the number of channels selected for that sequence.
In my case, with a single channel, this results in `RESULT.MAXCNT = 1`, which causes the SAADC interrupt to fire after each individual sample—even if `extra_samplings` is set to a higher value.

This means the MCU is being interrupted every 62.5 μs, which is far too frequent for my application.

**My questions are:**

0. Am I correct in my understanding above?

1. Why was this design decision made in the SAADC driver implementation? What benefit does it provide?

2. What would be the best way to extend the SAADC driver to fully support single-channel continuous conversion mode, without needing to fork the driver?

3. What happens if an ADC sequence is triggered while another is still running? Will the previous sequence be aborted?

I look forward to your response.

Best regards,
Pawel

  • I understand your concerns but there are no ideal solutions, everything comes with a cost.

    By doing that in the way you proposed, we significantly reduce the maximum amount of samples collected for single channel, reducing the capturing window time (RESULT.MAXCNT / channels used)

    Battery measurement is something that could be called even once per few minutes.

    If maximum allowed frequency is going to be 16.666kHz there is ~60us in between sampling. 
    Do you feel that it is impossible to re-run the ADC  with different configuration in that time? 

    I got your point clearly and I am partly convinced, I would like to avoid unnecessary work at this point, that is why I investigate alternative methods 

  • I don't think you can reliably hit that tiny window with the BTLE radio in operation. Keep in mind that the radio (softdevice) code has to run at the highest priority and could thus preempt ADC code.

    Also seems like its the solution which would require more development work and not less to me.

    Edit: You also wanted to use a dedicated chip in case you intend to use Li-Ion or LiPo battery chemistry. These need charging ICs anyway and you can get those that can tell you the voltage (and battery percent) via a digital bus, typically I²C.

    No, I don't think you could run your code on a CR2032 - these won't provide enough power to run an ADC this fast.

  • Dear Sultana,
    could you please let me know if by using same approach, then double buffering and PPI, instead to sample one single ADC channel as into example it is possible to sample more channels in sequence? My need is to take samples every 5ms of 4 analog input channels for a total of 400 samples for each channel, so the final buffer will be equal to 1600 samples, in such way every 2 seconds I should catch an event and then retrieve all the 1600 samples (that at the end will give to me 400 samples for each channel).

    Thanks for your suggest, best regards.

  • Hi again, 

    I am back here to share thoughts and decisions I finally made. 

    I ended up with implementing NRFX_SAADC_PPI driver compatible with Zephyr's ADC API.
    The driver utilizes the NRFX_SAADCNRFX_TIMER and NRFX_GPPI APIs.
    SAADC works in scan mode with double-buffering. All defined channels are sampled with specified frequency.
    Users of the driver pull samples associated with requested channel/channels.

    It is important to know which timer instances are used by other components of the system.
    On my side it turned out that I was using the same timer instance as BLE controller, so I had to change it to different one.
    Next challenge I encountered was validation of acquisition time, resolution, oversampling during build-time via preprocessor macros.
    I failed at this point and the validation is done on runtime at initialization.

    Thank you all for pointing me into right direction

Related