ADC measurement results change when switching from debug to release

I use one channel of the SAADC on nRF52832 to measure the power supply voltage. Code for configuration & operation of SAADC attached:

#include "adc.h"
#include "main_keyboard.h"
#include "nrf_drv_saadc.h"
#include "nrf_log.h"
#include "app_scheduler.h"

/**
 * Adapted from: https://github.com/NordicPlayground/nRF52-ADC-examples/tree/master/saadc_low_power
 */

#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 1                       // Set to 1 to enable BURST mode, otherwise set to 0.

static nrf_saadc_value_t buffer[SAADC_SAMPLES_IN_BUFFER];

static void saadc_isr_callback(nrf_drv_saadc_evt_t const *p_event);
static void saadc_evt_callback(void *p_event_data, uint16_t event_data_size);

void ADC_Init()
{
    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_12BIT;   // Set SAADC resolution to 12-bit. This will make the SAADC output values from 0 (when input voltage is 0V) to 2^12=4096 (when input voltage is 3.6V for channel gain setting of 1/6).
    saadc_config.oversample = SAADC_OVERSAMPLE;             // Set oversample to 4x. This will make the SAADC output a single averaged value when the SAMPLE task is triggered 4 times.
    saadc_config.interrupt_priority = APP_IRQ_PRIORITY_LOW; // Set SAADC interrupt to low priority.

    // Initialize SAADC
    APP_ERROR_CHECK(nrf_drv_saadc_init(&saadc_config, saadc_isr_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
    // 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_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.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.
    if (SAADC_BURST_MODE)
    {
        channel_config.burst = NRF_SAADC_BURST_ENABLED; // 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.
    }
    channel_config.pin_p = NRF_SAADC_INPUT_AIN0;             // 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
    APP_ERROR_CHECK(nrf_drv_saadc_channel_init(0, &channel_config));                          // Initialize SAADC channel 0 with the channel configuration
    // Note: We don't call nrf_drv_saadc_buffer_convert() here, we'll first do the calibration and call this at the end of it.
}

/**
 * Starts the calibration, measurement will follow afterwards.
 */
void ADC_Start()
{
    nrf_drv_saadc_abort(); // Abort all ongoing conversions. Calibration cannot be run if SAADC is busy
    NRF_LOG_INFO("SAADC: Calibration starting");
    while (nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS) // Trigger calibration task
    {
    }
}

static void saadc_isr_callback(nrf_drv_saadc_evt_t const *p_event)
{
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
    {
        NRF_LOG_INFO("SAADC: Measurement complete");

        uint32_t vin = p_event->data.done.p_buffer[0]; // vin = Digits * Reference / Gain / 2^resolution
        vin = vin * 3600;                              // Vref=0.6V; gain = 1/6 => full scale = 3.6V = 3600mV
        uint16_t voltage_mV = vin >> 12;

        app_sched_event_put(&voltage_mV, sizeof(voltage_mV), saadc_evt_callback);
    }
    else if (p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE)
    {
        NRF_LOG_INFO("SAADC: Calibration complete");

        APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(buffer, SAADC_SAMPLES_IN_BUFFER)); // Set buffer so the SAADC can write to it.

        app_sched_event_put(NULL, 0, saadc_evt_callback);
    }
}

static void saadc_evt_callback(void *p_event_data, uint16_t event_data_size)
{
    if (event_data_size == 0) // End of calibration
    {
        NRF_LOG_INFO("SAADC: Measurement starting");
        nrf_drv_saadc_sample(); // Trigger the SAADC SAMPLE task
    }
    else // End of measurement
    {
        KBD_Evt(EVT_VOLTAGE_MEASUREMENT_DONE, p_event_data, event_data_size);
    }
}

Short description:

- Configure the SAADC once by calling ADC_Init().

- Each time you need a measurement, call ADC_Start(). This will trigger the calibration. Once it completes, a measurement will be triggered.

This works well when I build & flash in debug mode, i.e. I read the true power supply voltage value from voltage_mV. When I switch to release mode, I start getting voltage_mV values that are ~3 times larger. How is this possible?

Parents Reply
  • MartinZ said:
    Yes, it does disappear.

    Sounds like the compiler is doing some optimization on the 'vin' variable then. Try declaring it as volatile.

    Change

    uint32_t vin = p_event->data.done.p_buffer[0]; // vin = Digits * Reference / Gain / 2^resolution

    to

    volatile uint32_t vin = p_event->data.done.p_buffer[0]; // vin = Digits * Reference / Gain / 2^resolution

Children
No Data
Related