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

NRF52840 SAADC Calibration

SDK: nRF5 SDK for Thread and Zigbee 1.0.0

I have code for the SAADC based on the Low Power SAADC example here.

I have modified it to sample 4 inputs, and changed the settings as follows:

#define UART_PRINTING_ENABLED                     //Enable to see SAADC output on UART. Comment out for low power operation.
#define RTC_FREQUENCY 32                          //Determines the RTC frequency and prescaler
#define RTC_CC_VALUE 32                           //Determines the RTC interrupt frequency and thereby the SAADC sampling frequency
#define SAADC_CALIBRATION_INTERVAL 10             //Determines how often the SAADC should be calibrated relative to NRF_DRV_SAADC_EVT_DONE event. E.g. value 5 will make the SAADC calibrate every fifth time the NRF_DRV_SAADC_EVT_DONE is received.
#define SAADC_SAMPLES_IN_BUFFER 1                 //Number of SAADC samples in RAM before returning a SAADC event. For low power SAADC set this constant to 1. Otherwise the EasyDMA will be enabled for an extended time which consumes high current.
#define SAADC_OVERSAMPLE NRF_SAADC_OVERSAMPLE_DISABLED  //Oversampling setting for the SAADC. Setting oversample to 4x 
                                                    // This will make the SAADC output a single averaged value when the SAMPLE task is triggered 4 times. 
                                                    // Enable BURST mode to make the SAADC sample 4 times when triggering SAMPLE task once.
                                                    // Do not use oversampling if more than one channel is active
#define SAADC_BURST_MODE 0                        //Set to 1 to enable BURST mode, otherwise set to 0.

I further modified the code to calibrate when Button 0 is pressed, though eventually calibration will be done as needed based on the measured temperature.

I find that after calibration, the data in the buffer is shifted by one channel.  That is the data at channel[0] has the values I would expect for channel[3], the data at channel[1] has the value I would expect for channel[0], etc

Like this:

Before calibration:

{0xFFFA, 0x0804, 0x0A20, 0x0705}

After calibration:

{0x0705, 0xFFFA, 0x0804, 0x0A20}

The SAADC is initialized as follows:

void saadc_init(void)
{
    int i;
    ret_code_t err_code;
    nrf_drv_saadc_config_t saadc_config;
    nrf_saadc_channel_config_t channel_config[NUM_ADC_CHANNELS];
	
    //Configure SAADC
    saadc_config.low_power_mode = true;                                                   //Enable low power mode.
    saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;                                 //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_DISABLED;                              //Disable oversample. Can't oversample with more than one channel active.
    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
    APP_ERROR_CHECK(err_code);
		
    for (i=0; i<NUM_ADC_CHANNELS; i++)
    {
        //Configure SAADC channel
        channel_config[i].reference = NRF_SAADC_REFERENCE_INTERNAL;             //Set internal reference of fixed 0.6 volts
        channel_config[i].gain = NRF_SAADC_GAIN1;                               //Set input gain to 1. The maximum SAADC input voltage is then 0.6V/(1)=0.6V. The single ended input range is then 0V-0.6V
        channel_config[i].acq_time = NRF_SAADC_ACQTIME_10US;                    //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[i].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.
        
        // Select the input pin for the channel
        switch(i)
        {
            case 0: channel_config[i].pin_p = NRF_SAADC_INPUT_AIN0; break;                 // LOOP: AIN0 pin maps to physical pin P0.02.
            case 1: channel_config[i].pin_p = NRF_SAADC_INPUT_AIN1; break;                 // VOLT: AIN1 pin maps to phsyical pin P0.03
            case 2: channel_config[i].pin_p = NRF_SAADC_INPUT_AIN4; channel_config[i].gain = NRF_SAADC_GAIN1_2;    break;  // VMON: AIN4 pin maps to physical pin P0.28
            case 3: channel_config[i].pin_p = NRF_SAADC_INPUT_AIN5; channel_config[i].gain = NRF_SAADC_GAIN1_2;  break;  // TEMP: AIN5 pin maps to physical pin P0.29
        }
        channel_config[i].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[i].resistor_p = NRF_SAADC_RESISTOR_DISABLED;                         //Disable pullup resistor on the input pin
        channel_config[i].resistor_n = NRF_SAADC_RESISTOR_DISABLED;                         //Disable pulldown resistor on the input pin
	
        //Initialize SAADC channel
        err_code = nrf_drv_saadc_channel_init(i, &channel_config[i]);                       //Initialize SAADC channel 0 with the channel configuration
        APP_ERROR_CHECK(err_code);
 		
        if(SAADC_BURST_MODE)
        {
 //           NRF_SAADC->CH[i].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],SAADC_SAMPLES_IN_BUFFER * NUM_ADC_CHANNELS);      //Set SAADC buffer 1. The SAADC will start to write to this buffer
    APP_ERROR_CHECK(err_code);
  
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1],SAADC_SAMPLES_IN_BUFFER * NUM_ADC_CHANNELS);      //Set SAADC buffer 2. The SAADC will write to this buffer when buffer 1 is full. This will give the applicaiton time to process data in buffer 1.
    APP_ERROR_CHECK(err_code);

}

Is there a work around? or fix for this?

Mary

  • Do you mind sharing the code relevant to the calibration? 

  • Below are the methods that I modified or added to the example.

    saadc_callback() (modified) plus additional auto variable, m_saadc_cal_prepare, and some definitions

    #define NUM_ADC_CHANNELS    4
    
    typedef enum {
        CAL_PREPARE = 0,
        CAL_START
    } CAL_ACTION_TYPE; 
    
    
    static bool                    m_saadc_cal_prepare = false; 
    
    void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
    {
        nrf_saadc_value_t *p_samples;
    
        ret_code_t err_code;
    
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)                                                        //Capture offset calibration complete event
        {
    			
            LEDS_INVERT(BSP_LED_2_MASK);                                                                    //Toggle LED3 to indicate SAADC buffer full		
    
            //if((m_adc_evt_counter % SAADC_CALIBRATION_INTERVAL) == 0)                                  //Evaluate if offset calibration should be performed. Configure the SAADC_CALIBRATION_INTERVAL constant to change the calibration frequency
            if (m_saadc_cal_prepare)
            {
                nrf_drv_saadc_abort();                                                                      // Abort all ongoing conversions. Calibration cannot be run if SAADC is busy
                m_saadc_calibrate = true;                                                                   // Set flag to trigger calibration in main context when SAADC is stopped
            }
            
    
    #ifdef UART_PRINTING_ENABLED
            NRF_LOG_INFO("ADC event number: %d \r\n",(int)m_adc_evt_counter);                                //Print the event number on UART
    
            for (int i = 0; i < p_event->data.done.size; i++)
            {
                NRF_LOG_INFO("%d\r\n", p_event->data.done.p_buffer[i]);                                     //Print the SAADC result on UART
                
            }
    #endif //UART_PRINTING_ENABLED   
    
            if(m_saadc_calibrate == false)
            {
                err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER * NUM_ADC_CHANNELS);             //Set buffer so the SAADC can write to it again. 
                APP_ERROR_CHECK(err_code);
            }
            
            m_adc_evt_counter++;
    
        }
        else if (p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE)
        {
            LEDS_INVERT(BSP_LED_2_MASK);                                                                    //Toggle LED3 to indicate SAADC calibration complete
    
            err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAADC_SAMPLES_IN_BUFFER * NUM_ADC_CHANNELS);             //Set buffer so the SAADC can write to it again. 
            APP_ERROR_CHECK(err_code);
            err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAADC_SAMPLES_IN_BUFFER * NUM_ADC_CHANNELS);             //Need to setup both buffers, as they were both removed with the call to nrf_drv_saadc_abort before calibration.
            APP_ERROR_CHECK(err_code);
            
    #ifdef UART_PRINTING_ENABLED
            NRF_LOG_INFO("SAADC calibration complete ! \r\n");                                              //Print on UART
    #endif //UART_PRINTING_ENABLED	
            
            m_adc_evt_counter = 0;
    
        }
    
    }
    

    Additional methods

    bool adc_is_cal_prepared()
    {
        return m_saadc_calibrate;
    }
    
    void adc_calibrate(int action)
    {
        switch(action)
        {
            case CAL_PREPARE:
                m_saadc_cal_prepare = true;                             // Stop conversions when this one is done
                break;
    
            case CAL_START:
                NRF_LOG_INFO("SAADC calibration starting...  \r\n");    //Print on UART
                while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS); //Trigger calibration task
                m_saadc_calibrate = false;
                m_saadc_cal_prepare = false;
                break;
        }
    }
    

    Buttons and main loop:

    /***************************************************************************************************
     * @section Buttons
     **************************************************************************************************/
    
    static void bsp_event_handler(bsp_event_t event)
    {
    
        switch (event)
        {
            case BSP_EVENT_KEY_0:
            {
                adc_calibrate(CAL_PREPARE);
                break;
            }
    
    
            default:
                break;
        }
    }
    
    
    
    /***************************************************************************************************
     * @section Main
     **************************************************************************************************/
    
    int main(int argc, char *argv[])
    {
    
        LEDS_CONFIGURE(LEDS_MASK);                       //Configure all leds
        LEDS_OFF(LEDS_MASK);                             //Turn off all leds
    	
        NRF_POWER->DCDCEN = 1;                           //Enabling the DCDC converter for lower current consumption
    	
    #ifdef UART_PRINTING_ENABLED
        uint32_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);                       //Configure Logging. LOGGING is used to show the SAADC sampled result. Default is UART, but RTT can be configured in sdk_config.h
        NRF_LOG_INFO("\n\rSAADC Low Power Example.\r\n");	
    #endif //UART_PRINTING_ENABLED	
    	
    
        lfclk_config();                                  //Configure low frequency 32kHz clock
        rtc_config();                                    //Configure RTC. The RTC will generate periodic interrupts. Requires 32kHz clock to operate.
    
        saadc_init();                                    //Initialize and start SAADC
     
        while(1)
        {
            // Calilbrate if ready
            if (adc_is_cal_prepared())
            {
                adc_calibrate(CAL_START);
            }
    
            nrf_pwr_mgmt_run();
    #ifdef UART_PRINTING_ENABLED
            NRF_LOG_FLUSH();
    #endif //UART_PRINTING_ENABLED
    
        
        }
    
    
    }

    This should be enough for you to repeat it.  

    Mary

  • I think I found your problem, you need to wait for the EVENTS_CALIBRATEDONE because nrfx_saadc_calibrate_offset is non-blocking. 

    You need to add:

    while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS); //Trigger calibration task
    
    while(!nrf_saadc_event_check(NRF_SAADC_EVENT_CALIBRATEDONE ))
    {
        nrf_pwr_mgmt_run();
    }

  • I tried that, but it made no difference.  The data was still shifted after calibration.

    Were you able to reproduce the problem?

Related