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

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

Children
  • Hi Jorgen,

    Thanks for the answers.

    - I believe there is something timing or priority issue with my app's advertise routine and the SAADC. When I enable advertising, the moment the calibration routine is completed, my program is off into the weeds. I have a reproducible code attached here. Can you please take a look and may be point me to what may be causing a problem? It also has the errate workaround in the CALIBRATEDONE event of the callback.

    - Why the calibration is jumping between values is still a mystery to me.

    Gain setting issue - I am not sure why, but a gain of 1/3 does not work with this app. May be you can help clarify? The callback never gets called after the first calibration. 

    Here is the main.c - 

    #include <stdint.h>
    #include <string.h>
    
    #include "ble_dfu.h"
    #include "nrf_pwr_mgmt.h"
    
    #include "nrf_dfu_svci.h"
    #include "nrf_svci_async_function.h"
    #include "nrf_svci_async_handler.h"
    #include "ble_conn_state.h"
    #include "peer_manager.h"
    #include "fds.h"
    
    #include "nrf.h"
    #include "nrf_soc.h"
    #include "boards.h"
    #include "ble.h"
    #include "ble_hci.h"
    #include "bsp.h"
    #include "ble_srv_common.h"
    #include "ble_advdata.h"
    #include "ble_conn_params.h"
    #include "nrf_sdh.h"
    #include "nrf_sdh_ble.h"
    #include "nrf_ble_gatt.h"
    #include "app_timer.h"
    #include "app_error.h"
    #include "nordic_common.h"
    #include "nfc_t4t_lib.h"
    #include "ndef_file_m.h"
    #include "nfc_ndef_msg.h"
    #include "ble_radio_notification.h"
    #include "app_util_platform.h"
    
    #include "nrf_drv_saadc.h"
    #include "nrf_drv_twi.h"
    #include "app_util_platform.h"
    #include "nrf_drv_clock.h"
    #include "nrf_drv_rtc.h"
    #include "nrf_delay.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define APP_FEATURE_NOT_SUPPORTED       BLE_GATT_STATUS_ATTERR_APP_BEGIN + 2           /**< Reply when unsupported features are requested. */
    #define APP_BLE_OBSERVER_PRIO           1                                             /**< Application's BLE observer priority. You shouldn't need to modify this value. */
    #define APP_BLE_CONN_CFG_TAG            1                                             /**< A tag identifying the SoftDevice BLE configuration. */
    #define DEAD_BEEF                       0xDEADBEEF                                    /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */
    #define DEVICE_NAME                     "MY_TEST"
    
    //#define SAADC_CALIBRATION_TEST
    #define SAADC_CALIBRATION_INTERVAL     5 // 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_4X // 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.
    #define SAADC_BURST_MODE               0 // Set to 1 to enable BURST mode, otherwise set to 0.
    #define SAADC_INTERVAL                 APP_TIMER_TICKS(100)        /**< ADC interval (ticks). */
    #define CONNECTABLE_ADV_INTERVAL       MSEC_TO_UNITS(350, UNIT_0_625_MS)
    #define ADV_DELAY                      APP_TIMER_TICKS(300)        /**< ADV delay (ticks). */
    
    // function declarations *************************************************************
    static void     saadc_timeout_handler(void * p_context);
    static void     adv_timeout_handler(void * p_context);
    void            saadc_init(void);
    void            saadc_sample(void);
    void            assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name);
    static void     ble_stack_init(void);
    static void     timers_init(void);
    static void     timer_start(void);
    static void     ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context);
    static void     log_init(void);
    static void     ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context);
    static void     power_management_init(void);
    // function declarations *************************************************************
    
    APP_TIMER_DEF(m_saadc_timer_id);
    APP_TIMER_DEF(m_adv_timer_id);
    // variables *************************************************************
    static uint32_t                m_adc_evt_counter = 1;
    volatile bool                  m_timeout = false;
    volatile bool                  m_saadc_calibrate = false;
    static nrf_saadc_value_t       m_buffer_pool[2][SAADC_SAMPLES_IN_BUFFER];
    volatile bool                  adv_update = false;
    static ble_gap_adv_params_t    m_adv_params;
    
    void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
    {
        app_error_handler(DEAD_BEEF, line_num, p_file_name);
    }
    
    static void timer_start(void)
    {
        ret_code_t err_code;
    
        err_code = app_timer_start(m_adv_timer_id, ADV_DELAY, NULL);
        APP_ERROR_CHECK(err_code);
    
        err_code = app_timer_start(m_saadc_timer_id, SAADC_INTERVAL, NULL);
        APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Function for the Timer initialization.
     *
    * @details Initializes the timer module.
    */
    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(&m_adv_timer_id,
                                    APP_TIMER_MODE_REPEATED,
                                    adv_timeout_handler);
        APP_ERROR_CHECK(err_code);
    
        err_code = app_timer_create(&m_saadc_timer_id,
                                    APP_TIMER_MODE_REPEATED,
                                    saadc_timeout_handler);
    
        APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Function for handling BLE events.
     *
     * @param[in]   p_ble_evt   Bluetooth stack event.
     * @param[in]   p_context   Unused.
     */
    static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
    {
        // uint32_t err_code = NRF_SUCCESS;
    }
    
    static void power_management_init(void)
    {
        NRF_LOG_INFO("init power mgmt");
        uint32_t err_code = nrf_pwr_mgmt_init();
        APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Function for initializing the BLE stack.
     *
     * @details Initializes the SoftDevice and the BLE event interrupt.
     */
    static void ble_stack_init(void)
    {
        ret_code_t err_code;
    
        err_code = nrf_sdh_enable_request();
        APP_ERROR_CHECK(err_code);
    
        // Configure the BLE stack using the default settings.
        // Fetch the start address of the application RAM.
        uint32_t ram_start = 0;
        err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
        APP_ERROR_CHECK(err_code);
    
        // Enable BLE stack.
        err_code = nrf_sdh_ble_enable(&ram_start);
        APP_ERROR_CHECK(err_code);
    
        // Register a handler for BLE events.
        NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
    }
    
    
    static void log_init(void)
    {
        ret_code_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    }
    
    static void connectable_adv_init(void)
    {
        // Initialize advertising parameters (used when starting advertising).
        memset(&m_adv_params, 0, sizeof(m_adv_params));
    
        // m_adv_params.type        = BLE_GAP_ADV_TYPE_ADV_NONCONN_IND;
        m_adv_params.type        = BLE_GAP_ADV_TYPE_ADV_IND;
        m_adv_params.p_peer_addr = NULL;                               // Undirected advertisement
        m_adv_params.fp          = BLE_GAP_ADV_FP_ANY;
        m_adv_params.interval    = CONNECTABLE_ADV_INTERVAL    ;
        m_adv_params.timeout     = 0;
    }
    
    
    /**@brief Function for starting advertising.
     */
    static void advertising_start(void)
    {
        ret_code_t err_code;
        err_code = sd_ble_gap_adv_start(&m_adv_params, APP_BLE_CONN_CFG_TAG);
        APP_ERROR_CHECK(err_code);
    }
    
    
    static void advertising_data_init(int mode)
    {
        ret_code_t               err_code;
        ble_advdata_t            advdata;
        ble_advdata_manuf_data_t manuf_data;
        uint8_t                  flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
        uint8_t                  m_addl_adv_manuf_data[0x0c];
    
        ble_gap_conn_sec_mode_t sec_mode;
        BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
    
        APP_ERROR_CHECK_BOOL(sizeof(flags) == 1);  // Assert that these two values of the same.
    
        // Build and set advertising data
        memset(&advdata, 0, sizeof(advdata));
        manuf_data.data.size          = 0x0C;
    
        err_code = sd_ble_gap_device_name_set(&sec_mode,
                                              (const uint8_t *)DEVICE_NAME,
                                              strlen(DEVICE_NAME));
        APP_ERROR_CHECK(err_code);
    
        // append_advertisement_data(mode);
    
        advdata.name_type             = BLE_ADVDATA_FULL_NAME;
        manuf_data.company_identifier = 0xff;
        manuf_data.data.p_data        = m_addl_adv_manuf_data;
        advdata.flags                 = flags;
        advdata.p_manuf_specific_data = &manuf_data;
    
        err_code = ble_advdata_set(&advdata, NULL);
        APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Function for application main entry.
     */
    int main(void)
    {
        // ret_code_t err_code;
    
        timers_init();
        log_init();
        power_management_init();
    
        // initialize SoftDevice.
        ble_stack_init();
    
        timer_start();
        NRF_LOG_INFO("starting advertisement");
    
        connectable_adv_init();
        saadc_init();
        advertising_start();
    
        for (;;) {
    
            if (m_timeout == true) {
                nrf_drv_saadc_sample();
                m_timeout = false;
            }
    
            if (m_saadc_calibrate == true) {
                // NRF_LOG_INFO("SAADC calibration starting");
                while(nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS); //Trigger calibration task
                m_saadc_calibrate = false;
            }
    
            if (adv_update == true) {
                advertising_data_init(0);
                adv_update = false;
            }
    
            nrf_pwr_mgmt_run();
            NRF_LOG_FLUSH();
        }
    }
    
    static void adv_timeout_handler(void * p_context)
    {
        adv_update = true;
    }
    
    static void saadc_timeout_handler(void * p_context)
    {
        m_timeout = true;
    }
    
    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++;
    
            // advertising_data_init(0);
        }
        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));
        }
    }
    
    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;
        // adc_resolution = NRF_SAADC_RESOLUTION_10BIT;
    
        // 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);
    
    }
    

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

Related