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

nRF52832 SDK14 - SAADC calibration issues

I'm using the nRF52832 with softdevice s132 and SDK14 with ARM GCC

I'm trying to set up the SAADC peripheral to read battery voltage, and I can get everything working as expected except periodic calibration. At boot, I call my initialization function, and then the application starts a timer for some number of seconds or minutes. When that timer expires, before trying to start a reading, my code checks to see if I should do calibration first. Due to the way it's set up, it always calibrates on the first timer expiration after boot, and this is where it fails. Here's a simplified version of my logic, which I have confirmed reproduces the issue without any timers or anything. There's more context in the code snippet comments. What am I doing wrong?

I've also tried setting it up like one of the examples where I don't actually check to see if I should calibrate until I get a NRF_DRV_SAADC_EVT_DONE event in the SAADC event handler in case that made a difference for some reason, but it doesn't. I skip the calibration check, start a reading, get the DONE event, check if calibration is needed, and then try to abort, but the abort call still locks up.

ret_code_t err_code;

/*
 * Another configuration I've been testing uses AIN3 and 1/2 gain.
 */
nrf_saadc_channel_config_t channel_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_VDD);

channel_config.gain = NRF_SAADC_GAIN1_6;
channel_config.burst = NRF_SAADC_BURST_ENABLED;
channel_config.acq_time = NRF_SAADC_ACQTIME_40US;

/*
 * sdk_config.h settings
 *      #define SAADC_CONFIG_RESOLUTION 3   // 14-bit
 *      #define SAADC_CONFIG_OVERSAMPLE 3   // 8
 *      #define SAADC_CONFIG_LP_MODE 1      // low power enabled
 *      #define SAADC_CONFIG_IRQ_PRIORITY 7
 */
err_code = nrf_drv_saadc_init(NULL, battery_saadc_callback);
APP_ERROR_CHECK(err_code);

// Initialize SAADC channel 0 with the specified channel configuration
err_code = nrf_drv_saadc_channel_init(0, &channel_config);
APP_ERROR_CHECK(err_code);

// SAMPLES_IN_BUFFER set to 1
err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);

err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);

/* do some stuff to set up application logic */

/*
 * Based on examples and forum posts, I call this to try to abort any pending
 * actions before trying to start calibration. But this call always times out
 * at nrf_drv_saadc.c line 604:
 *  (ERR0204) /source/firmware/nrf5-sdk/components/drivers_nrf/saadc/nrf_drv_saadc.c:604
 */
nrf_drv_saadc_abort();

/*
 * Alternatively, I've tried uninitializing and then reinitializing the SAADC
 * and the channel without aborting. When reinitializing, I make sure _not_ to
 * set up the buffers since registration of the buffers seems to be related to
 * the problem. This actually does allow calibration to start and complete, but
 * the next time after that that I call nrf_drv_saadc_sample(), it always fails
 * with NRF_ERROR_INVALID_STATE.
 */
//nrf_drv_saadc_uninit();
//err_code = nrf_drv_saadc_init(NULL, battery_saadc_callback);
//APP_ERROR_CHECK(err_code);
//err_code = nrf_drv_saadc_channel_init(0, &channel_config);
//APP_ERROR_CHECK(err_code);


while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS);

/*
 * After this, the SAADC event handler expects a NRF_DRV_SAADC_EVT_CALIBRATEDONE.
 * When that is received, I call nrf_drv_saadc_buffer_convert() on my two buffers
 * and then call nrf_drv_saadc_sample(). As described above, if I replace the abort call
 * with the uninit/reinit code commented out above, it does actually get to this point
 * but nrf_drv_saadc_sample() always returns NRF_ERROR_INVALID_STATE.
 */

Edit: Here's the relevant code for the follow-up issue where calibration completes correctly but the subsequent sample doesn't generate any events. This is the CALIBRATEDONE part of my SAADC event handler and the corresponding function that gets scheduled:

static void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{
      ret_code_t err_code;
      switch(p_event->type)
      {
            case NRF_DRV_SAADC_EVT_CALIBRATEDONE:
                  ret_code_t err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);
                  APP_ERROR_CHECK(err_code);
                  // same for m_buffer_pool[1]

                  err_code = app_sched_event_put(NULL, 0, on_cal_complete_handler);
                  APP_ERROR_CHECK(err_code);

                  nrf_delay_us(500); //delay avoids the issue
                  break;
      }
}

static void on_cal_complete_handler(void * p_event_data, uint16_t event_size)
{
    ret_code_t err_code = nrf_drv_saadc_sample();
    APP_ERROR_CHECK(err_code);
}
Parents
  • Hi,

    It looks like this issue is related to the low power mode of the SAADC driver, and what events are generated by the NRF_SAADC_TASK_STOP task triggered within nrf_drv_saadc_abort().

    From the documentation of nrf_drv_saadc_abort(), the following note is given:

    NRF_DRV_SAADC_EVT_DONE event will be generated if there is a conversion in progress. Event will contain number of words in the sample buffer.

    A conversion will be in progress when the NRF_SAADC_TASK_START task have been triggered. In nrf_drv_saadc_init() interrupts are enabled for NRF_SAADC_EVENT_END, and NRF_SAADC_EVENT_STARTED when low power mode is used, but not for NRF_SAADC_EVENT_STOPPED. This is not an issue when low power mode is not used, as a call to nrf_drv_saadc_buffer_convert will trigger NRF_SAADC_TASK_START, but for low power mode, NRF_SAADC_TASK_START is first triggered in call to nrf_drv_saadc_sample().

    In normal operation mode, the END event will generate an interrupt, and call SAADC_IRQHandler(). The handler will also detect the STOPPED event, and set the state to IDLE. In low power mode, no END event will be generated, and therefor no interrupts either.

    The solution to this issue would be to enable interrupt for NRF_SAADC_EVENT_STOPPED,before triggering NRF_SAADC_TASK_STOP. This will also be a better approach for normal operation mode, than relying on the NRF_SAADC_EVENT_END to generate the interruot. This can be achieved with this modified version of nrf_drv_saadc_abort():

    void nrf_drv_saadc_abort(void)
    {
        if (nrf_drv_saadc_is_busy())
        {    
            nrf_saadc_int_enable(NRF_SAADC_INT_STOPPED);
            nrf_saadc_event_clear(NRF_SAADC_EVENT_STOPPED);
            nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    
            if (m_cb.adc_state == NRF_SAADC_STATE_CALIBRATION)
            {
                m_cb.adc_state = NRF_SAADC_STATE_IDLE;
            }
            else
            {
                // Wait for ADC being stopped.
                uint32_t timeout = HW_TIMEOUT;
    
                while ((m_cb.adc_state != NRF_SAADC_STATE_IDLE) && (timeout > 0))
                {
                    --timeout;
                }
                ASSERT(timeout > 0);
            }
            
            nrf_saadc_int_disable(NRF_SAADC_INT_STOPPED);
    
            m_cb.p_buffer           = 0;
            m_cb.p_secondary_buffer = 0;
            NRF_LOG_INFO("Conversion aborted.");
        }
    }
    

    I will report this issue internally. Thanks for reporting it!

    Best regards,

    Jørgen

  • @Jorgen Holmefjord, as of SDK 14, nrf_drv_saadc_abort is not sufficient, as it does not wait for the SAADC to become IDLE when in calibration mode. Instead, it just sets the state to IDLE, which is incorrect.

    @ndarkness, You should manually loop and wait for the SAADC state to be IDLE like this: while(NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));

  • You are correct, but I answered with my previous comment in mind:

    Jørgen Holmefjord said:
    You need to stop the SAADC after calibration.

    If you call the abort task during calibration, you will have to wait for the STOPPED event. If you call it after calibration, the state should not be set to IDLE undtil after the STOPPED event is received.

  • Sorry for a reply to an old topic, but I stumbled upon this also, and aborting in the CALIBRATE_DONE callback is a solution to the workaround... for sample times >=10µs. What is a reasonable workaround for 3µs/5µs?

Reply Children
Related