Using the nrf52840 for analog sensor measurements we wanted to increase the accuracy by doing a regular calibration. Doing so we sometimes noticed spurious changes in measurement offset error when the calibration was done. After investigating this behaviour on a devboard it looks like the calibration is just sometimes off!
The following can be used to reproduce the problem on a devboard using AIN0 with an open-input.
/**
This file shows that the adc calibration make the measurements fluctuate
*/
#include "aadc_errors.h"
#include <nrf_delay.h>
#include <nrfx_saadc.h>
#include <stdio.h>
#include <stdlib.h>
#define ADC_RESOLUTION 4096u /// 12 bit adc reolution (14 bit is only accomplished with oversampling)
#define CHANNEL_CONFIG(PIN, RESISTOR_P, GAIN) (nrf_saadc_channel_config_t)\
{\
.resistor_p = RESISTOR_P, \
.resistor_n = NRF_SAADC_RESISTOR_DISABLED, \
.gain = GAIN, \
.reference = NRF_SAADC_REFERENCE_INTERNAL, \
.acq_time = NRF_SAADC_ACQTIME_10US, \
.mode = NRF_SAADC_MODE_SINGLE_ENDED, \
.burst = NRF_SAADC_BURST_DISABLED, \
.pin_p = PIN, \
.pin_n = NRF_SAADC_INPUT_DISABLED \
}
static nrfx_saadc_config_t saadc_config = NRFX_SAADC_DEFAULT_CONFIG;
static void saadc_callback(nrfx_saadc_evt_t const * p_event)
{
UNUSED_PARAMETER(p_event);
}
// sample 1 channel, already configured
static void channel_convert(uint8_t channel, unsigned oversample, nrf_saadc_value_t *adc)
{
int adc_sum = 0;
for( unsigned i = 0; i<oversample+1; i++ )
{
ret_code_t rr = nrfx_saadc_sample_convert(channel, adc);
ASSERT( rr == NRF_SUCCESS );
//printf(" part_adc=%hd\n", *adc);
adc_sum += *adc;
}
int adc_i = adc_sum / (int)(oversample+1);
*adc = adc_i;
}
static void mcu_adc_calibrate(void)
{
ret_code_t r;
printf("Calibrate\n");
r = nrfx_saadc_calibrate_offset();
ASSERT(r == NRF_SUCCESS);
while (nrfx_saadc_is_busy())
{
}
// Nordic errata 86 workaround...
nrfx_saadc_uninit();
r = nrfx_saadc_init(&saadc_config, saadc_callback);
ASSERT(r == NRF_SUCCESS);
// Note: According documentation, the errata is also handled in nrfx_saadc_abort()
}
static void mcu_adc_read_gnd(nrf_saadc_gain_t gain, nrf_saadc_value_t *adc)
{
nrf_saadc_channel_config_t chcfg = CHANNEL_CONFIG(NRF_SAADC_INPUT_AIN0, NRF_SAADC_RESISTOR_PULLDOWN, gain);
nrfx_saadc_channel_init(0, &chcfg);
// get rid of noise by oversampling a lot
channel_convert(0, 1000, adc);
nrfx_saadc_channel_uninit(0);
nrfx_saadc_abort();
// FIXME this is a patch for the fact the that ADC does not stop (reason unknonw).
nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
}
void show_calibration_error(void)
{
ret_code_t err_code = nrfx_saadc_init(&saadc_config, saadc_callback);
ASSERT(err_code == NRF_SUCCESS);
for(unsigned j=0; j<10; j++)
{
mcu_adc_calibrate();
for(unsigned i=0; i<2; i++)
{
nrf_saadc_value_t adc = 0;
const char *sep = " ";
for( nrf_saadc_gain_t gain = NRF_SAADC_GAIN1_6; gain <=NRF_SAADC_GAIN4; gain++ )
{
mcu_adc_read_gnd( gain, &adc );
printf("%sadc:{gain:%d, v:%hd}", sep, gain, adc);
sep = ", ";
}
printf("\n");
}
}
nrfx_saadc_uninit();
}
This results in something like:
Calibrate adc:{gain:0, v:0}, adc:{gain:1, v:0}, adc:{gain:2, v:0}, adc:{gain:3, v:0}, adc:{gain:4, v:3}, adc:{gain:5, v:9}, adc:{gain:6, v:24}, adc:{gain:7, v:51} adc:{gain:0, v:0}, adc:{gain:1, v:0}, adc:{gain:2, v:1}, adc:{gain:3, v:0}, adc:{gain:4, v:3}, adc:{gain:5, v:9}, adc:{gain:6, v:24}, adc:{gain:7, v:53}Calibrate adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-10}, adc:{gain:3, v:-11}, adc:{gain:4, v:-8}, adc:{gain:5, v:-1}, adc:{gain:6, v:13}, adc:{gain:7, v:42} adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-11}, adc:{gain:3, v:-11}, adc:{gain:4, v:-8}, adc:{gain:5, v:-2}, adc:{gain:6, v:14}, adc:{gain:7, v:41}Calibrate adc:{gain:0, v:-1}, adc:{gain:1, v:0}, adc:{gain:2, v:0}, adc:{gain:3, v:0}, adc:{gain:4, v:3}, adc:{gain:5, v:9}, adc:{gain:6, v:24}, adc:{gain:7, v:51} adc:{gain:0, v:0}, adc:{gain:1, v:0}, adc:{gain:2, v:1}, adc:{gain:3, v:0}, adc:{gain:4, v:3}, adc:{gain:5, v:10}, adc:{gain:6, v:25}, adc:{gain:7, v:52}Calibrate adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-10}, adc:{gain:3, v:-10}, adc:{gain:4, v:-8}, adc:{gain:5, v:-2}, adc:{gain:6, v:14}, adc:{gain:7, v:42} adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-10}, adc:{gain:3, v:-11}, adc:{gain:4, v:-8}, adc:{gain:5, v:-1}, adc:{gain:6, v:13}, adc:{gain:7, v:42}Calibrate adc:{gain:0, v:-11}, adc:{gain:1, v:-11}, adc:{gain:2, v:-10}, adc:{gain:3, v:-11}, adc:{gain:4, v:-7}, adc:{gain:5, v:-1}, adc:{gain:6, v:14}, adc:{gain:7, v:42} adc:{gain:0, v:-11}, adc:{gain:1, v:-11}, adc:{gain:2, v:-10}, adc:{gain:3, v:-10}, adc:{gain:4, v:-8}, adc:{gain:5, v:-1}, adc:{gain:6, v:13}, adc:{gain:7, v:42}Calibrate adc:{gain:0, v:-1}, adc:{gain:1, v:0}, adc:{gain:2, v:0}, adc:{gain:3, v:0}, adc:{gain:4, v:4}, adc:{gain:5, v:9}, adc:{gain:6, v:24}, adc:{gain:7, v:53} adc:{gain:0, v:0}, adc:{gain:1, v:0}, adc:{gain:2, v:1}, adc:{gain:3, v:1}, adc:{gain:4, v:3}, adc:{gain:5, v:10}, adc:{gain:6, v:24}, adc:{gain:7, v:52}Calibrate adc:{gain:0, v:0}, adc:{gain:1, v:0}, adc:{gain:2, v:0}, adc:{gain:3, v:1}, adc:{gain:4, v:4}, adc:{gain:5, v:9}, adc:{gain:6, v:24}, adc:{gain:7, v:53} adc:{gain:0, v:0}, adc:{gain:1, v:0}, adc:{gain:2, v:0}, adc:{gain:3, v:0}, adc:{gain:4, v:3}, adc:{gain:5, v:9}, adc:{gain:6, v:24}, adc:{gain:7, v:52}Calibrate adc:{gain:0, v:-11}, adc:{gain:1, v:-10}, adc:{gain:2, v:-11}, adc:{gain:3, v:-11}, adc:{gain:4, v:-8}, adc:{gain:5, v:-2}, adc:{gain:6, v:13}, adc:{gain:7, v:42} adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-11}, adc:{gain:3, v:-10}, adc:{gain:4, v:-7}, adc:{gain:5, v:-1}, adc:{gain:6, v:13}, adc:{gain:7, v:41}Calibrate adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-10}, adc:{gain:3, v:-10}, adc:{gain:4, v:-7}, adc:{gain:5, v:-2}, adc:{gain:6, v:13}, adc:{gain:7, v:42} adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-11}, adc:{gain:3, v:-11}, adc:{gain:4, v:-8}, adc:{gain:5, v:-1}, adc:{gain:6, v:13}, adc:{gain:7, v:42}Calibrate adc:{gain:0, v:-12}, adc:{gain:1, v:-12}, adc:{gain:2, v:-10}, adc:{gain:3, v:-10}, adc:{gain:4, v:-8}, adc:{gain:5, v:-2}, adc:{gain:6, v:13}, adc:{gain:7, v:41} adc:{gain:0, v:-12}, adc:{gain:1, v:-11}, adc:{gain:2, v:-11}, adc:{gain:3, v:-11}, adc:{gain:4, v:-8}, adc:{gain:5, v:-2}, adc:{gain:6, v:13}, adc:{gain:7, v:41}
This makes it clear the the calibration jumps between 2 values (some runs it is even 4 different values values)
- Why does this happen?
- what can we do to fix this?
edit: formatting to improve readability