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);
}
  • 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

  • Thanks- this does appear to fix the abort timeout, but now there's another issue that may be related. In my SAADC event handler, on NRF_DRV_SAADC_EVT_CALIBRATEDONE, I schedule an event that will call nrf_drv_saadc_sample() the next time the scheduler is processed. When this scheduled event happens, I can confirm that nrf_drv_saadc_sample() does return NRF_SUCCESS, but then I never get any SAADC events. So the next time my timer expires and wants to call nrf_drv_saadc_sample() again, my code can tell that the last sample request never returned. When this happens I can abort, buffer_convert(), and sample() again and it works. But is there a way to detect that nrf_drv_saadc_sample() isn't going to generate any events even though it returned NRF_SUCCESS?

    Also of interest- I noticed if I call nrf_delay_us(100) at the end of my CALIBRATEDONE handler, the problem doesn't happen.

  • Are you setting up a buffer for conversion before you call nrf_drv_saadc_sample? Could you post a code snippet of the NRF_DRV_SAADC_EVT_CALIBRATEDONE event handling?

  • edit: sorry I had trouble getting my code to format here, so I edited the relevant code into the original post. But yes, I am doing the buffer conversion on my two buffers, and then I schedule a call to a function that calls nrf_drv_saadc_sample().

  • There is an errata on this, PAN-86: SAADC: Triggering START task after offset calibration may write a sample to RAM. You need to stop the SAADC after calibration. I thought this was implemented in the driver, but it does not look like it is. I will check this.

Related