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

How the timer tick, ADC acquisition time, and sampling rate works

Hello, DevZone!

Recently, I've read a lot of questions and replies related to the nRF5's SAADC and BLE.

Even though I found some pages discuss about the continuous sampling with use of timer, the concept is still confusing me.

First of all, I want to build a code that continuously reads voltage from single channel and send it via Bluetooth.

To do so, my colleague mixed two examples. One is ble_peripheral\ble_app_uart and another is peripheral\saadc of nRF5 SDK v16.0.0.

In the early stage of development, we found that the code is working as we expected–SAADC sampled data and they transmitted to the Android device via bluetooth.

Then, to make use of Bluetooth 5's high throughput, I modified code to sample data with 20 kHz sampling rate.

According to the throughput demo, I can get 1365 Kbps with BLE 5 high speed mode. It means that 20 kHz * 16 bits (actually 12 bits but the packet is built with byte unit) = 320 Kbps should be easy to achieve.

The problem is that my code is not so fast. I'm using 7.5ms connection interval and GAP event length is set to 6. Data length extension is already enabled and the packet consists of 240 data bytes (I don't know but MTU=247 setting keeps crashing so I lowered a little).

Currently, I found that NRF_ERROR_RESOURCES keep appears from ble_nus_data_send() and I think it's because I send data to frequently.

Then, maybe I can slow down the ADC and find out what is the maximum throughput of now.

Phew, here comes my main question:

How can I set sampling rate to the specific value?

If I lower the transfer rate, sampling rate also should be lowered down to guarantee low delay.

So I wrote the code like this

#define SAMPLES_IN_BUFFER               10
#define SAMPLES_TO_SEND                 120

static nrf_saadc_value_t                adc_buf[2][SAMPLES_IN_BUFFER];
static uint8_t                          to_snd_buf[SAMPLES_TO_SEND * 2];
static uint16_t                         idx_snd_buf = 0;

(...)

void saadc_sampling_event_init(void)
{
    (...)
    
    uint32_t ticks = nrf_drv_timer_us_to_ticks(&TIMER_ADC, 100);
    nrf_drv_timer_extended_compare(&TIMER_ADC,
                                   NRF_TIMER_CC_CHANNEL0,
                                   ticks,
                                   NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                   false);
    nrf_drv_timer_enable(&TIMER_ADC);
    
    (...)
}

void saadc_callback(nrf_drv_saadc_evt_t const * p_event)                            // Original version
{
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
    {
        ret_code_t err_code;

        err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
        adc_buf_state ^= 0x01;
        APP_ERROR_CHECK(err_code);

        for(i=0; i<SAMPLES_IN_BUFFER; i++)
        {
            to_snd_buf[idx_snd_buf*2] = adc_buf[adc_buf_state][i] & 0xff;
            to_snd_buf[idx_snd_buf*2 + 1] = (adc_buf[adc_buf_state][i] >> 8) & 0xff;
            idx_snd_buf++;
        }

      
        if (idx_snd_buf >= SAMPLES_TO_SEND)
        {
            uint16_t length = (uint16_t)(SAMPLES_TO_SEND * 2);
            notification_err_code = ble_nus_data_send(&m_nus, to_snd_buf, &length, m_conn_handle);   
            sprintf(error_string, "Error number: %#x\n", notification_err_code);
            SEGGER_RTT_WriteString(0, error_string);
            idx_snd_buf = 0;
        }
    }
}

(...)

The main point of the code is that I get and store 10 ADC samples per NRF_DRV_SAADC_EVT_DONE and ble_nus_data_send() is called only if the stored data exceeds 120 samples.

By doing this, I think both the sampling rate and transfer rate decrease–Previously I got 120 samples in one scoop and sent immediately but now I need to wait 12 timer calls to send data.

In other words, I thought that the timer request ADC to fill the buffer so the sampling rate is controlled by the timer. In my case, as I set the timer tick to 100 us, the sampling rate should be 10 kHz.

But when I test with a function generator, I think the sampling rate differs with what I think.

Also, there are comments that changing the acquisition time will affect ADC's sampling rate–"use 3us acquisition time to make use of 200 kHz sampling rate.

Is there anyone can explain me how the timer tick, ADC acquisition time, and sampling rate works?

How I can get 10 kHz or 20 kHz sampling rate?

And also, why NRF_ERROR_RESOURCES appear and keep me from making use of maximum throughput?

Parents
  • In principle this should work yes. The only problem I see is that the softdevice will be interrupting the application, and this may cause the application interrupts to be blockeds/skipped for a period of time. To speed up things it may help to look at the gpiote example in the nRF5 SDK. This show how to connect a ppi channel from a timer event to a gpiote task, this will let the timer trigger the gpiote directly without cpu intervention. In your case you would need to connect the ppi channel from the timer to the adc sampling task, thereby you should only need to handle the adc callback when the data is available. If you set the interrupt priority of adc to interrupt priority 2, and write a very short interrupt routine for instance only write the adc value to a global buffer and increment a counter, then you can let the actual processing of the adc value and transmission using ble_nus_data_send() be handled in main().

  • Thanks for the reply, but it still confuse me...

    In your case you would need to connect the ppi channel from the timer to the adc sampling task

    As I used nrf_drv_ppi_channel_alloc(), nrf_drv_ppi_channel_assign(), and so on as I uploaded as code, I think I already connected the ppi channel to the adc sampling task. Isn't it?

    If you set the interrupt priority of adc to interrupt priority 2

    I believe this means APP_IRQ_PRIORITY_MID, which is higher than the APP_IRQ_PRIORITY_LOW I formerly used.

    then you can let the actual processing of the adc value and transmission using ble_nus_data_send() be handled in main()

    Currently, I call ble_nus_data_send() in saadc_callback() function which is assigned to the ADC via nrf_drv_saadc_init(&saadc_config, saadc_callback).

    If I move the codes to the main, will be the NRF_ERROR_RESOURCES decrease? And is there any example I can use to do that?

  • Sorry for taking more than weeks...

    A timeout of 100us will sample 1/100us=10kHz

    As Kenneth said, it seems like the tick value used in the following code indirectly indicates the sampling rate.

    uint32_t ticks = nrf_drv_timer_us_to_ticks(&TIMER_ADC, 10);
    nrf_drv_timer_extended_compare(&TIMER_ADC,
                                   NRF_TIMER_CC_CHANNEL0,
                                   ticks,
                                   NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                   false);
    nrf_drv_timer_enable(&TIMER_ADC);
    
    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&TIMER_ADC,
                                                                                    NRF_TIMER_CC_CHANNEL0);
    uint32_t saadc_sample_task_addr   = nrf_drv_saadc_sample_task_get();

    I applied a 1 Hz signal and compared the number of data points during one period with varying ticks value.

    As I increase the ticks 10 times, the number of data decreased 10 times. So, I concluded the ticks value indicates a temporal difference between adjacent ADC values. Am I correct?

  • and sample it 9 times for each callback

    And still, it was not able for me to be certain that multiple samples are sampled for each callback.

    void saadc_init(void)
    {
        ret_code_t err_code;
        nrf_saadc_channel_config_t channel_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3);
            channel_config.acq_time = NRF_SAADC_ACQTIME_10US;
    
        err_code = nrf_drv_saadc_init(NULL, saadc_callback);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_channel_init(3, &channel_config);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_buffer_convert(adc_buf, SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);    
    }
    

    Is the callback function means saadc_callback() which is assigned in the saadc initialization step as the uploaded code?

    If then, I think I'm sampling SAMPLES_IN_BUFFER times for each callback evenly. Is it right?

  • It may help to check out figure 4, 5, and 6 for the task and events for the SAADC:
    https://infocenter.nordicsemi.com/topic/ps_nrf52840/saadc.html#saadc_easydma

    The idea behind configuring an acquisition time of 10us and 9 samples is that if you trigger start task every 100us, they should (hopefully) be spread out evenly across the 100us period (e.g. 0, 10, 20, 30, 40, ..., 90us) before next sampling is started.

  • The idea behind configuring an acquisition time of 10us and 9 samples is that if you trigger start task every 100us, they should (hopefully) be spread out evenly across the 100us period (e.g. 0, 10, 20, 30, 40, ..., 90us) before next sampling is started.

    When I fix the ADC acquisition time to 10us (NRF_SAADC_ACQTIME_10US) and change the timer ticks value from 10us to 100us, it seems like the sampling rate follows 1 / timer tick. Is the start task you mentioned different from the timer and task I uploaded?

  • Sorry, you are right yes. Looking at figure 4 again:
    https://infocenter.nordicsemi.com/topic/ps_nrf52840/saadc.html#saadc_easydma

    You can configure a ppi channel from a 10us timer to the SAMPLE task of the saadc. If you now execute a START task, then the SAMPLE task will be executed every 10us from the timer until buffer is filled up and this will trigger an END event from the saadc. The acquisition time can actually be set to 5us to make sure that there is enough time for the saadc to transfer the value to RAM before next SAMPLE task from the 10us timer. The buffer size can be more than 9bytes also.

    Best regards,
    Kenneth

Reply Children
No Data
Related