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

Parents
  • Hi,

    A1) I would not recommend that you trigger the tasks directly when you use the driver, as this can leave the driver in an unwanted state. If you follow the example, it should, however, be possible to do this inside the handler, when you receive the NRF_DRV_SAADC_EVT_CALIBRATEDONE event, before setting up the buffers again. The driver will at this point be in IDLE state while setting up the buffers should trigger the START task again.

    A2) Unless you are sampling a signal close to GND, this is not expected and indicates that the workaround for the errata did not work.

    A3) This looks a bit strange, but it could be that the calibration algorithm will end up with different offset value, based on noise levels. In general, it should be sufficient to run offset calibration at the start of the program, and if there are major temperature changes.

    A4) Any gain settings should work, as long as the sampled signal is within the input range and below VDD. What exactly stops working when you set gain=1/3?

    Best regards,
    Jørgen

  • Is the only modification that you change gain from 1/6 to 1/3? I tested with the SAADC example in the SDK but I did not have any issues with using gain = 1/3. I was not able to test your code, is it based on an SDK example?

  • Hi - yes it is based on the SAADC example. I only added advertising function to it. May be you will find something obviously wrong, but I couldn't find it. I think the calibration routine combined with the advertising function is causing me all this trouble. If I don't do any calibraiton, the app runs smoothly, but if enabled, it stops after the first calibration is triggered. The gain problem also disappears when I disable calibration in this app. Do you have any hints on why this may be happening?

  • It seems that you trigger the STOP task inside the calibration event after you have setup the buffers again:

    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->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));
    }

    As I explained initially, you will have to trigger the STOP task before setting up the buffers again, as the START task is triggered when the first buffer is setup for conversion. If you trigger the STOP task after this, the SAADC will not be started again, even though the driver thinks that it is started and everything is OK.

  • 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?

Reply Children
Related