Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SAADC V2 sampling using the RTC

Hi there,

I'm having some trouble piecing together an app to use V2 of the NRFX SAADC API, compounded with this being my first time using the SAADC functionality on an nRF board.

In summary, my program receives an "ERROR 8 [NRF_ERROR_INVALID_STATE]" when calling the nrfx_saadc_mode_trigger function.

My understanding is that we first initialise SAADC:

static void saadc_init()
{
    ret_code_t err_code;

    nrfx_saadc_adv_config_t config = {
        .oversampling = NRF_SAADC_OVERSAMPLE_DISABLED,
        .burst = false,
        .internal_timer_cc = 0,
        .start_on_end = false,
    };

    err_code = nrfx_saadc_init(NRFX_SAADC_CONFIG_IRQ_PRIORITY);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_saadc_channels_config(m_accel, 3);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_saadc_advanced_mode_set((1 << 0) | (1 << 1) | (1 << 2),
                                            NRF_SAADC_RESOLUTION_8BIT,
                                            &config,
                                            on_saadc_in_evt);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_saadc_buffer_set(m_accel_sample_bufs[m_accel_current_buffer], ACCEL_SAMPLES_PER_BUFFER);
    APP_ERROR_CHECK(err_code);
}

My intention is to use the RTC to sample three analogue pins previously defined as:

nrfx_saadc_channel_t m_accel[] = {
    NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN5, 0),
    NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN6, 1),
    NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN7, 2)};

A timer is created and, when it fires, it then triggers the sampling:

static void accel_sampler_timer(void *p_context)
{
    ret_code_t err_code = nrfx_saadc_mode_trigger();
    APP_ERROR_CHECK(err_code);
}

...and that's when the invalid state is reported.

The SAADC event handler is as follows:

static void on_saadc_in_evt(nrfx_saadc_evt_t const *p_event)
{
    ret_code_t err_code;

    switch (p_event->type)
    {
    case NRFX_SAADC_EVT_DONE:
        // TODO: Use p_event->data.done.p_buffer[0];
        break;
    case NRFX_SAADC_EVT_BUF_REQ:
        m_accel_current_buffer = 1 - m_accel_current_buffer;
        err_code = nrfx_saadc_buffer_set(m_accel_sample_bufs[m_accel_current_buffer], ACCEL_SAMPLES_PER_BUFFER);
        APP_ERROR_CHECK(err_code);
        break;
    default:
        break;
    }
}

Am I under the false impression that the timer should trigger the sampling?

Thanks for your guidance.

  • You might also want to consider having your sampling trigger by PPI, instead of by CPU intervention - especially if it is important that sampling is not interrupted or delayed due to the SoftDevice controlling the CPU.

    I've been thinking more about this since you suggested it. Here's my use case: I've got a 3 axis analog accelerometer and want to detect if a "significant" force occurs on any one of its three axis points. Each axis value is supplied to an analog pin. The accelerometer reports up to 3G of force, so given 8-bit sampling, a sample value for an axis of 128 or more indicates 1.5G or greater.

    It'd be great to have PPI generate a sampling task for the SAADC if a value on any one of the three axis points exceeds 128. Reading (and re-reading!) the doc seems to indicate that there's an "EVENTS_CH[0].LIMITH" event that can be monitored. I've been looking for examples of how to use PPI specifically for the limit test, but I've not yet found any. It'd be great if you could point me in the right direction. My understanding so far is that I must use nrfx_ppi_channel_alloc, nrfx_ppi_channel_assign, and then nrf_drv_ppi_channel_enable. I'm thinking that I'll also have to set "start_on_end" for the SAADC advanced config and have a buffer sample size of 3; one for each channel. Again, thanks for any pointers here.

  • Here's my PPI/SAADC code so far in case it helps (it isn't fully working though...):

    /**@brief Handle analog events
     */
    static void on_saadc_in_evt(nrfx_saadc_evt_t const *p_event)
    {
        ret_code_t err_code;
    
        switch (p_event->type)
        {
        case NRFX_SAADC_EVT_DONE:
            // Do something with the data
            break;
        case NRFX_SAADC_EVT_BUF_REQ:
            m_accel_current_buffer = 1 - m_accel_current_buffer;
            err_code = nrfx_saadc_buffer_set(m_accel_sample_bufs[m_accel_current_buffer], ACCEL_SAMPLES_PER_BUFFER);
            APP_ERROR_CHECK(err_code);
            break;
        default:
            break;
        }
    }
    
    /**@brief Set up our analogue sensors
     */
    static void saadc_init()
    {
        ret_code_t err_code;
        nrf_saadc_event_t ppi_event;
    
        nrfx_saadc_adv_config_t config = {
            .oversampling = NRF_SAADC_OVERSAMPLE_DISABLED,
            .burst = NRF_SAADC_BURST_DISABLED,
            .internal_timer_cc = 0,
            .start_on_end = true,
        };
    
        err_code = nrfx_saadc_init(NRFX_SAADC_CONFIG_IRQ_PRIORITY);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_channels_config(m_accel, 3);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_advanced_mode_set((1 << 0) | (1 << 1) | (1 << 2),
                                                NRF_SAADC_RESOLUTION_8BIT,
                                                &config,
                                                on_saadc_in_evt);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_buffer_set(m_accel_sample_bufs[m_accel_current_buffer], ACCEL_SAMPLES_PER_BUFFER);
        APP_ERROR_CHECK(err_code);
    
        for (int i = 0; i < 3; ++i)
        {
            err_code = nrfx_ppi_channel_alloc(&m_accel_ppi_channel[i]);
            APP_ERROR_CHECK(err_code);
            nrf_saadc_channel_limits_set(i, 0, 128);
            switch (i)
            {
            case 0:
                ppi_event = NRF_SAADC_EVENT_CH0_LIMITH;
            case 1:
                ppi_event = NRF_SAADC_EVENT_CH1_LIMITH;
            case 2:
                ppi_event = NRF_SAADC_EVENT_CH2_LIMITH;
            }
            err_code = nrfx_ppi_channel_assign(m_accel_ppi_channel[i],
                                               nrf_saadc_event_address_get(ppi_event),
                                               nrf_saadc_task_address_get(NRF_SAADC_TASK_SAMPLE));
            APP_ERROR_CHECK(err_code);
            err_code = nrfx_ppi_channel_enable(m_accel_ppi_channel[i]);
            APP_ERROR_CHECK(err_code);
        }
    
        err_code = nrfx_saadc_mode_trigger();
        APP_ERROR_CHECK(err_code);
    }

  • Hello Christopher,

    Christopher Hunt said:
    Just digesting the other bits, but here's my timer code. I can confirm that the timer is firing.

    No problem - thank you for providing the timer code.
    The general approach seems alright, but bear in mind that I do not know the values of the arguments passed to the different functions.

    Christopher Hunt said:
    I've been thinking more about this since you suggested it. Here's my use case: I've got a 3 axis analog accelerometer and want to detect if a "significant" force occurs on any one of its three axis points. Each axis value is supplied to an analog pin. The accelerometer reports up to 3G of force, so given 8-bit sampling, a sample value for an axis of 128 or more indicates 1.5G or greater.

    Thank you for clarifying - this helps a lot in understanding your issue.

    Christopher Hunt said:
    It'd be great to have PPI generate a sampling task for the SAADC if a value on any one of the three axis points exceeds 128. Reading (and re-reading!) the doc seems to indicate that there's an "EVENTS_CH[0].LIMITH" event that can be monitored. I've been looking for examples of how to use PPI specifically for the limit test, but I've not yet found any. It'd be great if you could point me in the right direction. My understanding so far is that I must use nrfx_ppi_channel_alloc, nrfx_ppi_channel_assign, and then nrf_drv_ppi_channel_enable. I'm thinking that I'll also have to set "start_on_end" for the SAADC advanced config and have a buffer sample size of 3; one for each channel. Again, thanks for any pointers here.

    You are correct that there exists a LIMIT event in the SAADC, which can be generated once a channel exceeds a certain value, please see the Event monitoring using limits section of the SAADC peripheral documentation. However, the limits are not monitored continuously - this would require the SAADC to sample 'continuously' - instead, they are checked against each finished conversion. So, to generate LIMIT events, you will need to be sampling -> You may not use the limit events to begin sampling, because the limits are checked against already acquired samples.
    Does what I have written here make sense? Please do not hesitate to let me know if it is unclear.

    However, what you perhaps could do instead - depending on the constraints and requirements of your application - is to use the LPCOMP peripheral for this.
    Are you familiar with the LPCOMP peripheral?

    You can see the PPI channel allocation - how to configure, setup and enable PPI channels - demonstrated in the SAADC peripheral example from the SDK. In the example the sampling task is connected to the CAPUTRE/COMPARE event of a counter, to have it trigger at a certain interval - without use of the CPU. You can follow the general approach of this example, and just change which event you are connecting the sampling task to.

    Best regards,
    Karl 

  • However, what you perhaps could do instead - depending on the constraints and requirements of your application - is to use the LPCOMP peripheral for this.
    Are you familiar with the LPCOMP peripheral?

    Thanks for the pointer toward LPCOMP - I may well come back to this one.

  • You can see the PPI channel allocation - how to configure, setup and enable PPI channels - demonstrated in the SAADC peripheral example from the SDK. In the example the sampling task is connected to the CAPUTRE/COMPARE event of a counter, to have it trigger at a certain interval - without use of the CPU. You can follow the general approach of this example, and just change which event you are connecting the sampling task to.

    I think I'm getting the hang of this now - PPI was new to me, but I'm excited at the possibilities. :-)

    What I'm now wanting to do is to use the low-powered RTC2 to cause sampling via PPI (RTC1 is used for an app timer). I feel like I'm almost there... I've managed to wire up a compare event to the sampling event... but it only samples once. I'm *reasonably* confident in the RTC being setup ok, as I did do some debugging to confirm this. So, I think my problem is still in the realm of SAADC. Further guidance is really appreciated!

    Here's the updated code (I've also switched to the simpler SAADC interface to, well, make it simpler!):

    /**@brief Handle analog events
     */
    static void on_saadc_in_evt(nrfx_saadc_evt_t const *p_event)
    {
        ret_code_t err_code;
    
        switch (p_event->type)
        {
        case NRFX_SAADC_EVT_DONE:
            // Process the data
            
            // Set the next compare check that'll cause another sample
            next_sample_ticks += ACCEL_SAMPLER_RESOLUTION_TICKS;
            err_code = nrfx_rtc_cc_set(&m_rtc, 0, next_sample_ticks, false);
            APP_ERROR_CHECK(err_code);
            break;
        default:
            break;
        }
    }
    
    static void on_rtc_evt(nrfx_rtc_int_type_t int_type)
    {
    }
    
    /**@brief Set up our analogue sensors
     */
    static void saadc_init()
    {
        ret_code_t err_code;
    
        err_code = nrfx_saadc_init(NRFX_SAADC_CONFIG_IRQ_PRIORITY);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_channels_config(m_accel, 3);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_simple_mode_set((1 << 0) | (1 << 1) | (1 << 2),
                                              NRF_SAADC_RESOLUTION_8BIT,
                                              NRF_SAADC_OVERSAMPLE_DISABLED,
                                              on_saadc_in_evt);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_buffer_set(m_accel_sample_buf, ACCEL_SAMPLE_CHANNELS);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_saadc_mode_trigger();
        APP_ERROR_CHECK(err_code);
    
        nrfx_rtc_config_t rtc_config = NRFX_RTC_DEFAULT_CONFIG;
        err_code = nrfx_rtc_init(&m_rtc, &rtc_config, on_rtc_evt);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_rtc_cc_set(&m_rtc, 0, next_sample_ticks, false);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_ppi_channel_alloc(&m_accel_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_ppi_channel_assign(m_accel_ppi_channel,
                                           nrfx_rtc_event_address_get(&m_rtc, NRF_RTC_EVENT_COMPARE_0),
                                           nrf_saadc_task_address_get(NRF_SAADC_TASK_SAMPLE));
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_ppi_channel_enable(m_accel_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        nrfx_rtc_enable(&m_rtc);
    }

Related