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