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

SAADC wrong readings & impossible to calibrate

Hi,

I am having hard times to make SAADC working on DK-52, SDK13, S132. I need to perform a voltage measurement on command received from a connected device.

AIN pin is connected as in the following picture:

image description

I also have a digital multimeter between AIN pin and ground.

I am facing two issues:

  1. My voltage readings are always different from what I read on the multimeter:

    a) 0V from multimeter - 0.17 from SAADC - Delta 0.17 b) 2.50 from multimeter - 2.539 from SAADC - Delta 0.039 c) 1.25 from multimeter - 1.277 from SAADC - Delta 0.027

(With a completely different multimeter, I get more or less the same deltas).

Difference appears to be relevant. Is it true?

  1. To minimize the error at point 1, I thought I need to calibrate the SAADC. Because readings are not very frequent, I thought that I could start the calibration on device connection:

    static void on_ble_evt(ble_evt_t *p_ble_evt) {

    ...

    case BLE_GAP_EVT_CONNECTED: NRF_LOG_INFO("Connected.\r\n"); err_code = nrf_drv_saadc_calibrate_offset(); if (err_code == NRF_ERROR_BUSY) { NRF_LOG_INFO("\tSAADC BUSY\r\n"); } else { NRF_LOG_INFO("\tCalibration Started...\r\n"); } ... }

Unfortunately I always get NRF_ERROR_BUSY.

Where am I wrong?

Thanks.

UPDATE: I measured the powering voltage, changed the reference voltage to NRF_SAADC_REFERENCE_VDD4 and adjusted refVoltage in get_voltage accordingly. In this way I get much better results. Problem is that powering the device with a battery, reference voltage decreases over time.


This is my SAADC initialization function:

static void saadc_init() {

ret_code_t err_code;

nrf_drv_saadc_config_t saadc_config;

saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;
saadc_config.oversample = NRF_SAADC_OVERSAMPLE_DISABLED;
saadc_config.interrupt_priority = 7;
saadc_config.low_power_mode = false;

err_code = nrf_drv_saadc_init(&saadc_config, &saddc_handler);
APP_ERROR_CHECK(err_code);

nrf_saadc_channel_config_t channel_config =    NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);

channel_config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
channel_config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;

channel_config.gain = NRF_SAADC_GAIN1_6;
channel_config.reference = NRF_SAADC_REFERENCE_INTERNAL; 
channel_config.acq_time = NRF_SAADC_ACQTIME_10US;
channel_config.mode = NRF_SAADC_MODE_SINGLE_ENDED;
channel_config.pin_p = NRF_SAADC_INPUT_AIN0;
channel_config.pin_n = NRF_SAADC_INPUT_DISABLED;
channel_config.burst = NRF_SAADC_BURST_DISABLED;

err_code = nrf_drv_saadc_channel_init(0, &channel_config);
APP_ERROR_CHECK(err_code);

err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0],SAADC_SAMPLES_IN_BUFFER);
APP_ERROR_CHECK(err_code);
}

This is where I start the measurement:

static void on_char_value_received(ble_s1_service_t *p_s1, ble_gatts_evt_write_t *p_evt_write) {

uint32_t err_code;

if (p_evt_write->data[0]=='M') {

 NRF_LOG_DEBUG("Start Measuring\r\n");

  for(int i=0; i<SAADC_SAMPLES_IN_BUFFER; i++) {

    err_code = nrf_drv_saadc_sample();
    if (err_code == NRF_ERROR_INVALID_STATE)
      NRF_LOG_DEBUG("\t SAADC Idle\r\n");

    nrf_delay_ms(10); 
  }
}
}

This is SAADC event handler:

void saddc_handler(nrf_drv_saadc_evt_t const *p_event) {

ret_code_t err_code;
uint16_t adc_value;

if (p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE) {

  NRF_LOG_DEBUG("Calibration Completed\r\n");
  return;
}

if (p_event->type == NRF_DRV_SAADC_EVT_DONE) {

  NRF_LOG_DEBUG("NRF_DRV_SAADC_EVT_DONE\r\n");

  err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
  APP_ERROR_CHECK(err_code);

  NRF_LOG_DEBUG("Buffer Converted\r\n");

  adc_value = 0;

  for (int i = 0; i < SAADC_SAMPLES_IN_BUFFER; i++) {

    adc_value += p_event->data.done.p_buffer[i];
  }

  float v = adc_value/SAADC_SAMPLES_IN_BUFFER;
  int intPart = (int) v;
  int decimalPart = (v - intPart) * 1000;

  NRF_LOG_DEBUG("Value %d.%d\r\n", intPart, decimalPart);

  float voltage = get_voltage(v);
  intPart = (int) voltage;
  decimalPart = (voltage - intPart) * 1000;

  NRF_LOG_DEBUG("Voltage %d.%d\r\n", intPart, decimalPart);

  send_buffer(&m_pollen_service,(uint8_t *)&voltage,sizeof(float));

  NRF_LOG_DEBUG("Finished\r\n");
}

}

This is the function that converts the ADC reading in voltage:

float get_voltage(float adc_value) {

float ret = 0;
float refVoltage = 0.6;
float range = 4096;  // 2^12
float gain = 6;

ret = (adc_value * refVoltage)/(range/gain);

return ret;
}

UPDATE

Noise at ADC pin at about 0V

image description

Noise at ADC pin at about VDDV

image description

  • Hi,

    Your results does not look too far off to me. See this and this thread about expected deviation.

    The reason why you get NRF_ERROR_BUSY when trying to calibrate is most likely that you have a buffer setup for sampling. The SAADC is considered sampling when nrf_drv_saadc_buffer_convert have been called. You will have to abort any ongoing buffer conversions using nrf_drv_saadc_abort, or wait to the operation is finished before starting calibration (when you get the NRF_DRV_SAADC_EVT_DONE event). Note that if you use double buffering, you need to call nrf_drv_saadc_abort or wait for two NRF_DRV_SAADC_EVT_DONE events without setting up a new buffer for sampling, before calling the calibration task.

    Best regards,

    Jørgen

  • I played around with the SAADC parameters (resolution, oversample, acq_time) without much success. I still have an offset of about 0.015 v. A took a look with my scope and apparently there is a lot of noise at the SAADC pin (see the update at my question). Shown measurements are using the DK-52 powered via USB but I have almost the same situation with a third party module powered by battery. Have you any suggestion on how to reduce the noise?

  • If oversampling and offset calibration does not help, I don't have any good advice to come with. You will allways have some noise in such applications, as described in multiple cases here on DevZone. Are you seeing a constant offset of 0.015 V?

  • The offset is not constant but 0.015 provides pretty good readings at least rounding to 2 decimal places. Since I have to read voltages in range 0-5v, I added a voltage divider and this probably increased the noise, the offset increased and is not more constant across the range. To overcome this issue, I measured the offset in 15 points across the range 0-5, I have interpolated the deltas with a 4 grade polynomial and now I get pretty good readings. Last issue I have is that when the voltage is 0 (pin grounded), the readings are completely off value. Probably because the device is measuring only the noise. In my device, voltages never gets close to 0 but I have not figured how to overcome this issue. I should try a active low-pass filter at ADC pin, but I am not sure that it could help. Any suggestion will be appreciated. Thank you for your help.

Related