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

SAADC Offset Calibration not working?

Hello,

I am using the nRF52832 on a custom board, with SDK 15.2 and SD132 v6.1. My BLE application uses the SAADC to measure VCC voltage every 6 hours. I am using an app timer to initialize SAADC, kick off either an offset calibration or an ADC conversion, and then uninit SAADC once the NRF_DRV_SAADC_EVT_DONE/NRF_DRV_SAADC_EVT_CALIBRATEDONE  events fire. However, I am finding that SAADC offset calibration is not firing the SAADC_callback function at all:

Here is my SAADC init function:

void saadc_init(void)
{
    ret_code_t err_code;
    nrf_drv_saadc_config_t saadc_config;
    nrf_saadc_channel_config_t channel_config;


	
    //Configure SAADC
    saadc_config.low_power_mode = true;                                                   //Enable low power mode.
    saadc_config.resolution = NRF_SAADC_RESOLUTION_10BIT;                                 //Set SAADC resolution to 12-bit. This will make the SAADC output values from 0 (when input voltage is 0V) to 2^12=2048 (when input voltage is 3.6V for channel gain setting of 1/6).
    // saadc_config.oversample = NRF_SAADC_OVERSAMPLE_4X;                                           //Set oversample to 4x. This will make the SAADC output a single averaged value when the SAMPLE task is triggered 4 times.
    saadc_config.oversample = NRF_SAADC_OVERSAMPLE_32X;
    saadc_config.interrupt_priority = APP_IRQ_PRIORITY_LOW;                               //Set SAADC interrupt to low priority.
	
    //Initialize SAADC
    err_code = nrf_drv_saadc_init(&saadc_config, saadc_callback);                         //Initialize the SAADC with configuration and callback function. The application must then implement the saadc_callback function, which will be called when SAADC interrupt is triggered                      //Initialize the SAADC with configuration and callback function. The application must then implement the saadc_callback function, which will be called when SAADC interrupt is triggered
    APP_ERROR_CHECK(err_code);
		
    //Configure SAADC channel
    channel_config.reference = NRF_SAADC_REFERENCE_INTERNAL;                              //Set internal reference of fixed 0.6 volts
    channel_config.gain = NRF_SAADC_GAIN1_6;                                              //Set input gain to 1/6. The maximum SAADC input voltage is then 0.6V/(1/6)=3.6V. The single ended input range is then 0V-3.6V
    channel_config.acq_time = NRF_SAADC_ACQTIME_40US;                                     //Set acquisition time. Set low acquisition time to enable maximum sampling frequency of 200kHz. Set high acquisition time to allow maximum source resistance up to 800 kohm, see the SAADC electrical specification in the PS. 
    channel_config.mode = NRF_SAADC_MODE_SINGLE_ENDED;                                    //Set SAADC as single ended. This means it will only have the positive pin as input, and the negative pin is shorted to ground (0V) internally.
    channel_config.pin_p = NRF_SAADC_INPUT_VDD;                                          //Select the input pin for the channel. AIN0 pin maps to physical pin P0.02.
    channel_config.pin_n = NRF_SAADC_INPUT_DISABLED;                                      //Since the SAADC is single ended, the negative pin is disabled. The negative pin is shorted to ground internally.
    channel_config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;                              //Disable pullup resistor on the input pin
    channel_config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;                              //Disable pulldown resistor on the input pin

	
    //Initialize SAADC channel
    err_code = nrf_drv_saadc_channel_init(0, &channel_config);                            //Initialize SAADC channel 0 with the channel configuration
    APP_ERROR_CHECK(err_code);
		
    //if(SAADC_BURST_MODE)
    {
        NRF_SAADC->CH[0].CONFIG |= 0x01000000;                                            //Configure burst mode for channel 0. Burst is useful together with oversampling. When triggering the SAMPLE task in burst mode, the SAADC will sample "Oversample" number of times as fast as it can and then output a single averaged value to the RAM buffer. If burst mode is not enabled, the SAMPLE task needs to be triggered "Oversample" number of times to output a single averaged value to the RAM buffer.		
    }

    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0],1);    //Set SAADC buffer 1. The SAADC will start to write to this buffer
    APP_ERROR_CHECK(err_code);


}

Here is the SAADC callback function:

void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{
    ret_code_t err_code;
    uint8_t temp_arr[2];
    uint16_t temp_adc;

    // bsp_board_led_on(ADVERTISING_LED);
    //if (p_event->type == NRF_SAADC_EVENT_RESULTDONE)                                                        //Capture offset calibration complete event
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)                                                        //Capture offset calibration complete event
    {		
        temp_adc = 	m_buffer_pool[0][0];

        //memset(&temp_arr, 0, sizeof(temp_arr));
        
        temp_arr[0] = (uint8_t)((temp_adc & 0xFF00) >> 8);
        temp_arr[1] = (uint8_t)(temp_adc & 0x00FF);
        
        //if(!_isFastAdv && nrf_mtx_trylock(&adv_mutex))
        if(!_isFastAdv)
        {         
            //_isFastAdv = true;    
            send_adc_pkt(temp_arr, sizeof(temp_arr), 800);     //~500 ms    CHANGE FUNCTION NAME!!!!!!!!!!!!!!!!!
            APP_ERROR_CHECK(sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG)); //Restart advertising
            nrf_mtx_unlock(&adv_mutex);        
            app_timer_start(magic_word_timer_id, APP_TIMER_TICKS(2000), NULL); //start 2 second timer to go back to idle advertising
        }
        
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], 1);             //Set buffer so the SAADC can write to it again. 
        APP_ERROR_CHECK(err_code);
        nrf_drv_saadc_uninit();
    }
    else if(p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE)
    {
        bsp_board_led_on(ADVERTISING_LED);
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], 1);             //Set buffer so the SAADC can write to it again. 
        APP_ERROR_CHECK(err_code);
        nrf_drv_saadc_uninit();
    }
}

And here is how I am calling the calibrate_offset function from within an app_timer handler:

static void adc_timeout_handler(void * p_context)
{
    if(adc_counter==358)
    {
        saadc_init();
        nrf_drv_saadc_calibrate_offset();
        // NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;

        adc_counter++;
    }
    else if(adc_counter==359) // every 6 hours
    {
        adc_counter=0;
        if(!_isFastAdv && !_isConnected && nrf_mtx_trylock(&adv_mutex))
        {
            sd_ble_gap_adv_stop(m_adv_handle); //Stop advertising
            APP_ERROR_CHECK(app_timer_start(adc_delay_timer_id, APP_TIMER_TICKS(1000), NULL)); //kick off ADC conversion in 1000 ms. Allows voltage to settle
            //nrf_drv_saadc_sample(); //consider kicking off a ~20 ms timer that starts ADC conversion to make sure advertising has stopped
        }
    }
    else
    { 
        adc_counter++;
    }
        
}

I have the 2 following questions:

  1. When I call nrf_drv_saadc_calibrate_offset(), I expect to catch the NRF_DRV_SAADC_EVT_CALIBRATEDONE event in my SAADC_callback, however that event is not firing at all. I am using an LED to see if NRF_DRV_SAADC_EVT_CALIBRATEDONE event is occurring, and it does not seem to fire at all. Furthermore, I also tried turning on the LED when there is any call to the saadc_callback function, regardless of the adc event, and I am seeing that the only time LED comes on is when an ADC conversion finishes, not when a calibration happens. Am I missing something here?
  2. I am using 32x oversampling with burst mode enabled. I am using the NRF_DRV_SAADC_EVT_DONE event in my callback function to detect when the conversion is finished and ready for me to process. Can you clarify if NRF_DRV_SAADC_EVT_DONE fires when a single conversion takes place, or when all 32 oversample samples have taken place?

Thanks!

Parents Reply Children
  • Thanks for the suggestions. I tried a few things based on your last comment:

    • I tried calling abort after initializing SAADC, and then calling calibration afterwards. However, calibration call still returns NRFX_ERROR_BUSY when called after the abort. I even tried adding some delays in between and no luck. I also tried calling nrfx_saadc_buffer_convert between the abort and the calibration call, but no go here either. Didn't make a difference.
    • Calling abort does not create an NRF_DRV_SAADC_EVT_DONE event, since there was already no conversion taking place when I called it. I am calling abort right after initializing the SAADC so I wouldn't expect there to be conversions taking place anyway.
    • When I omit the calibration call altogether, and just call abort after initializing SAADC, and then try to kick off a conversion 1 second after calling abort, nrf_drv_saadc_sample returns NRFX_ERROR_INVALID_STATE. This happens regardless of whether I call nrfx_saadc_buffer_convert between the abort and the nrf_drv_saadc_sample functions. Somehow, using the abort function puts the SAADC in a bad state that I can't seem to recover from.
    • Finally, everything works fine if I just give up on calibration. I can init the SAADC every minute, then call nrfx_saadc_buffer_convert, and then deinit SAADC and go back to sleep without any issues. It's when I try to do calibration and abort functions when things go wrong.

    Could I ask that you take another look at my SAADC initialization function I shared in my original post, and let me know if you see anything wrong that I am doing?

  • You can try to wait for the nrfx_saadc_is_busy flag with a 'while' loop. 
    while(!nrfx_saadc_is_busy ())
    {
        // Calibrate offset
    }

    I also suggest that you calibrate the offset before the first time you call nrf_drv_saadc_buffer_convert (in your init function).

    A third option is to use the new SAADC v2 driver in nRF5 SDK7.

  • Thank you for the suggestion. It seems that my issue was calling nrf_drv_saadc_buffer_convert before calling the calibrate function. It looks like after calling nrf_drv_saadc_buffer_convert, SAADC expects me to start a conversion instead of calibration.

Related