This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SAADC_ACQTIME and conversion timing

Context: in my application, I need to know rather precisely when the SAADC is taking a sample.  So I did a test and discovered something interesting.

I'm triggering the SAADC with a timer measuring the total conversion time from the end of the call to `nrf_drv_saadc_sample()` to the entry into the `adc_event_handler()`.  What I found was a constant overhead of 5 microseconds, except when the SAADC_ACQTIME was set to 5uS or 3uS, in which case the overhead was 6uS and 8uS respectively.

In the table below, T0 is the time at which the call to `nrf_drv_saadc_sample()` returned (thus initiating the conversion) and T1 is the time at which `adc_event_handler()` gets called (deduced by toggling a GPIO pin and measuring the time on an oscilloscope):

SAADC_ACQTIME T1-T0
3 µsec 11 µsec
µsec 11 µsec
10 µsec 15 µsec
15 µsec 20 µsec
20 µsec 25 µsec
40 µsec 45 µsec

So my question: is there a reason that setting the acquisition time to NRF_SAADC_ACQTIME_3US or NRF_SAADC_ACQTIME_5US results in a longer overhead?  Does the extra overhead happen before the adc starts to sample, or does it happen after the sampling is complete?

  • Hello,

    I have not set up and done this test myself, but I suspect that the overhead that you see on 3µs is not actually the SAADC taking as long as with the 5µs ACQTIME, but rather the timing delay from calling nrf_drv_saadc_sample() until the SAADC starts, then the ACQTIME, and then the time it takes for the adc_event_handler() to get called.

     

    I am not sure exactly how you measure this, but may I suggest that you try to use the PPI together with a timer? Try to fork the events from the SAADC to the timer:

    #define CH_A 0
    #define CH_B 1
    
    NRF_PPI->CH[0].EEP = (uint32_t)&NRF_SAADC->EVENTS_STARTED
    NRF_PPI->CH[0].TEP = (uint32_t)&NRF_TIMER3->TASKS_CAPTURE[1];
    NRF_PPI->CH[1].EEP = (uint32_t)&NRF_SAADC->EVENTS_DONE;
    NRF_PPI->CH[1].TEP = (uint32_t)&NRF_TIMER3->TASKS_CAPTURE[2];
    
    NRF_PPI->CHENSET = (1 << CH_A) | (1 << CH_B);

     

    Remember to configure and start TIMER3 before calling nrf_drv_saadc_sample.

    Then when you get the event, you can compare NRF_TIMER3->CC[1] and NRF_TIMER3->CC[2] and see how many ticks that passed in between, to calculate the time used.

     

    The advantage of using the PPI is that this can run independently from the CPU, so you don't have to wait in the application to get the applications "attention" to measure the time. It is quite useful when you are dealing with such short time intervals.

     

    Best regards,

    Edvin

     

  • Thanks -- I independently came to the conclusion that I should be using the PPI; your explanation confirms my reasoning.  Slight smile

    I'm actually going one step further: for my app, I need to know the exact timing when the SAADC input switch connects to the sampling capacitor.  So I'll be using TIMER COMPARE events to toggle a GPIO pin, connected in series with a resistor, to the input of the SAADC.  Using the PPI allows for precise timing, so I'll be able to make that measurement.

    One lingering question: you've couched your answer using low-level register access.  Is there a reason to prefer that style over the nRF SDK 15.0 driver functions (e.g. `nrf_drv_ppi_channel_assign()`, etc)?

  • Hello,

    No, not really a reason for this. I just find it easier than getting to know all the different libraries for the differnet peripherals. It is probably easier to use the dedicated functions for this Slight smile No problem using the functions, such as nrf_drv_ppi_channel_assign(), etc. They do the same thing.

     

    BR,
    Edvin

Related