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

Questions on SAADC calibration, gain, best use

Hi,

I am trying to measure a signal using the SAADC driver on SDK 13.
I have used the example from the nrf52 playground for low power ADC

I have a few questions on best use of the driver:

There is errata 86 that needs the calibration task to have a certain pattern.
I have added some code to follow those steps in the CALIBRATEDONE callback event:

nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
while(!nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED)) {
    nrf_pwr_mgmt_run();
}

Q1) Is this the most optimal way to code this workaround? Should I check any condition before calling ths? I get a fatal error at times. 

Q2) The value right after calibration is complete is a negative value. Is this expected? Can this value just be ignored for a valid measurement or can it be improved?

Q3) I have a small offset signal on my adc pin courtesy the analog circuitry. So I would exepect a mean value about 4 or 5.
When the calibration routine is triggered, the ADC value measured jumps up and then goes back down the next time the calibration routine is triggered. This is observed in the following graph. Sometimes, it fluctuates between a couple of calibration routines, and at other instances, it takes a longer time for the shift to happen. Each of the instances where the level shifts is when a calibration was triggered. The analog signal itself remains unchanged. What might be causing this and how can I overcome it? This behavior is not observed when calibration is turned OFF, so it seems like the calibration is somehow causing this.

I am trying oversampling since plain sampling has more noise in it. Here is a graph:

Q4) Is there a limitation on the gain value I can use. For some reason the code stops working for gain of 1/3?

My SAADC code is as follows:

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
    // 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.resolution = NRF_SAADC_RESOLUTION_12BIT;

    // SAADC_OVERSAMPLE Setting oversample to 4x will make the SAADC output a single averaged value when the SAMPLE task is triggered 4 times.
    saadc_config.oversample = NRF_SAADC_OVERSAMPLE_4X;
    // Set SAADC interrupt to low priority.
    saadc_config.interrupt_priority = APP_IRQ_PRIORITY_LOW;

    // Initialize SAADC
    // 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
    err_code = nrf_drv_saadc_init(&saadc_config, saadc_callback);
    APP_ERROR_CHECK(err_code);

    // Configure SAADC channel
    // Set internal reference of fixed 0.6 volts
    channel_config.reference = NRF_SAADC_REFERENCE_INTERNAL;
    // 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.gain = NRF_SAADC_GAIN1_6;
    // 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.acq_time = NRF_SAADC_ACQTIME_10US;
    // Since the SAADC is single ended, the negative pin is disabled. The negative pin is shorted to ground internally.
    channel_config.mode = NRF_SAADC_MODE_SINGLE_ENDED;
    // Since the SAADC is single ended, the negative pin is disabled. The negative pin is shorted to ground internally.
    channel_config.pin_n = NRF_SAADC_INPUT_DISABLED;
    // Disable pullup resistor on the input pin
    channel_config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
    // Disable pulldown resistor on the input pin
    channel_config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;

    // Initialize SAADC channel
    // Select the input pin for the channel. AIN0 pin maps to physical pin P0.02.
    channel_config.pin_p = NRF_SAADC_INPUT_AIN0;
    // Initialize SAADC channel 0 with the channel configuration
    err_code = nrf_drv_saadc_channel_init(0, &channel_config);
    APP_ERROR_CHECK(err_code);



    // 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.
    if(SAADC_BURST_MODE) {
        NRF_SAADC->CH[0].CONFIG |= 0x01000000;
    }

    // Set SAADC buffer 1. The SAADC will start to write to this buffer
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0],SAADC_SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);
    // 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.
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1],SAADC_SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);

}

void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{
    ret_code_t err_code;
    //Capture offset calibration complete event
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
    {
        //Evaluate if offset calibration should be performed. Configure the SAADC_CALIBRATION_INTERVAL constant to change the calibration frequency
        if((m_adc_evt_counter % SAADC_CALIBRATION_INTERVAL) == 0) {
            // Abort all ongoing conversions. Calibration cannot be run if SAADC is busy
            nrf_drv_saadc_abort();
            // Set flag to trigger calibration in main context when SAADC is stopped
            m_saadc_calibrate = true;
            // NRF_LOG_INFO("trigger calibration");
        }


        for (int i = 0; i < p_event->data.done.size; i++)
        {
            NRF_LOG_INFO("%d", p_event->data.done.p_buffer[i]);
        }

        if(m_saadc_calibrate == false)
        {
            //Set buffer so the SAADC can write to it again.
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
        }

        m_adc_evt_counter++;

    }
    else if (p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE)
    {
        //Set buffer so the SAADC can write to it again.
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAADC_SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
        //Need to setup both buffers, as they were both removed with the call to nrf_drv_saadc_abort before calibration.
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAADC_SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
        // NRF_LOG_INFO("SAADC calibration complete!");

        nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
        while(!nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED)) {
            nrf_pwr_mgmt_run();
        }
        // uint32_t timeout = 10000;
        // while (--timeout > 0);
    }
}

static void timers_init(void)
{
    ret_code_t err_code;

    // Initialize timer module
    err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);

    err_code = app_timer_create(&saadc_timer,
                                APP_TIMER_MODE_REPEATED,
                                saadc_handler);

    APP_ERROR_CHECK(err_code);
}

static void timer_start(void)
{

    err_code = app_timer_start(saadc_timer, SAADC_INTERVAL, NULL);
    APP_ERROR_CHECK(err_code);
}


int main(void)
{
    // ret_code_t err_code;

    timers_init();
    log_init();
    // bsp_board_leds_init();
    // bsp_board_buttons_init();
    power_management_init();

    // initialize SoftDevice.
    ble_stack_init();
    peer_manager_init();
    sd_power_dcdc_mode_set(1);

    timer_start();
    NRF_LOG_INFO("starting advertisement");

    saadc_init();
    // advertising_start();

    // Enter main loop.
    for (;;) {

        if(m_saadc_calibrate == true) {
            while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS); //Trigger calibration task
            m_saadc_calibrate = false;
        }

        nrf_pwr_mgmt_run();
        NRF_LOG_FLUSH();
    }
}

  • I made that change, but the app does not still proceed after the first calibration event. I moved the STOP trigger before the buffer conversion, hope that is correct:

    nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    while(NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));
    nrf_delay_us(500);
    //Set buffer so the SAADC can write to it again.
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAADC_SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);
    //Need to setup both buffers, as they were both removed with the call to nrf_drv_saadc_abort before calibration.
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAADC_SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);
    NRF_LOG_INFO("SAADC calibration complete!");

    On my console I see:

    <info> app: starting advertisement     
    <info> app: 63                         
    <info> app: 67                         
    <info> app: 64                         
    <info> app: 62                         
    <info> app: trigger calibration        
    <info> app: 58                         
    <info> app: 1024                       
    <info> app: SAADC calibration starting 
    <info> app: SAADC calibration complete!

    there is no print after calibration complete, so the SAADC is stuck. I can still see the device broadcasting advertising packets though. Also if you notice, the value printed after calibration goes to 1024, which means that the workaround didn't actually work.

  • That sounds a bit strange. The driver should have been in idle-state at this point, and you should be able to stop it.

    One thing you should change is to wait for the STOPPED event, rather than checking if the STATUS register. The STATUS will only be busy when a conversion is ongoing.

    Could you upload the entire project, for debugging this issue?

  • Thanks for offering to look at the project, I have attached it. Meanwhile, I will try to implement your suggestion.

    ble_with_saadc_calibration.zip

  • I tested your application. Either of these solutions should work. You need to remember to clear the STOPPED event, as this is not done automatically:

    nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    while(NRF_SAADC->EVENTS_STOPPED == 0);
    NRF_SAADC->EVENTS_STOPPED = 0;

    or

    nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    while(!nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED)) {
    	nrf_pwr_mgmt_run();
    }
    nrf_saadc_event_clear(NRF_SAADC_EVENT_STOPPED);

  • Thanks for the snippet. This change resumes the saadc conversion!

Related