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.

  • Christopher Hunt said:
    Thanks for the pointer toward LPCOMP - I may well come back to this one.

    No problem at all, I am happy to help!
    I too think that the LPCOMP or COMP peripheral might sound right for your application here ( depending on what real-time and accuracy requirements you are working with ). 
    Please bear in mind that the LPCOMP, COMP and SAADC shares resources, which you will need to keep in mind when working with a combination of these ( LPCOMP and COMP is mutually exclusive, for example ). 

    Please do not hesitate to ask, if you should encounter any issues or questions in the future.

    Good luck with your development!

    Best regards,
    Karl

  • Please do not hesitate to ask, if you should encounter any issues or questions in the future.

    Thanks for everything Karl. I'm still having difficulty understanding why my SAADC sampling fires just once. Any pointers there would be great. I feel like I'm very close, but struggling with this last aspect. Here's my current code again:

    /**@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);
    }

  • Thanks for everything Karl. I'm still having difficulty understanding why my SAADC sampling fires just once. Any pointers there would be great. I feel like I'm very close, but struggling with this last aspect. Here's my current code again:

    I think I've now got to the bottom of the problem I had in understanding SAADC. My assumption was that I could drive the nrfx_saadc V2 state machine by firing SAADC tasks. I paired back everything to the Nordic Playground SAADC simple low power app timer example and replaced the app timer with RTC and PPI behaviour. My conclusion is that the NRF_SAADC_TASK_SAMPLE task has no effect when fired from PPI as the nrfx_saadc state machine is not in a "sampling" state. To attain a sampling state, nrfx_saadc_mode_trigger() must be called i.e. it isn't enough to issue a NRF_SAADC_TASK_SAMPLE task, even when nrfx_saadc_mode_trigger() has been called once to kick everything off.

    I've put my fork of the ADC playground low power app with RTC here for reference: https://github.com/huntc/nRF52-ADC-examples/tree/rtc-ppi/nrfx_saadc_simple_low_power_app_timer. To see my specific changes, please see this commit: https://github.com/huntc/nRF52-ADC-examples/commit/d4075a7ce59f840819b642f93c80af308a1e4f12.

    I think I shall now abandon the RTC/PPI driven approach here as it doesn't seem possible. The earlier suggestion of using LPCOMP seems more useful for my particular use-case anyhow. Thanks, and I hope this dialogue has been useful.

Related