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

Sometimes nrf52840 aadc calibration is wrong

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

  • I forgot to add: this example is with a 12 bit conversion. The error is less that 1 bit when the conversion is done with 8 bit.

  • Hello,

    With GAIN = 4 and using the internal 600 mV reference it seems you get the biggest fluctuation of 12 SAADC values (53 - 41).
    Calculating the input range with these parameters using the formula from the SAADC documentation yields the range 0 - 0.15 V. With a resolution of 12 bit there is a total of 4096 possible values, which means that the fluctuation at worst is 0.4 mV.
    This does not seem out of the ordinary to me.

    Thisis makes it clear the the calibration jumps between 2 values (some runs it is even 4 different values values)

    I see the samplings and how they seem to fluctuate, but I do not see any consistent trend between the different calibrations. Are you saying that the SAADC output values are increasing by 2-4 for every calibration?
    I am not sure I understand what you are saying here, please correct me if I am wrong.

    For future reference, please use the "Insert -> Code" option when attaching code, and upload entire log files instead of copying parts of it, this will drastically increase readability.

    Looking forward to getting to the bottom of this!

    Best regards,
    Karl

  • I see the samplings and how they seem to fluctuate, but I do not see any consistent trend between the different calibrations. Are you saying that the SAADC output values are increasing by 2-4 for every calibration?
    I am not sure I understand what you are saying here, please correct me if I am wrong.

    What the code shows is that an adc conversion of a pin connected to ground (or actually the the average of 1000 conversions to exclude noise as a deviation), sometimes suddenly has a different value after a calibration.

    I found a possible cause and solution:

    I discovered that turning on oversampling (256x) before doing a calibration reduced the variation in the result of the calibration to about 0.5 bit in the 12bit resolution, instead of 1 bit in the 8 bit resolution.
    This is what i would expect and acceptable.

    Apparently the calibration is performed with the oversampling setting which needs to be set to  high value before doing the calibration to have a reliable result.


    It is possible i overlooked this in the documentation. Or is this an undocumented feature?

    Edit:

    I was too quick: the deviation seems to be much less frequent (about 1 in 40) but still happens now and than: suddenly a jump in the offset after a calibration.

  • Hello,

    maarten v said:
    I discovered that turning on oversampling (256x) before doing a calibration reduced the variation in the result of the calibration to about 0.5 bit in the 12bit resolution, instead of 1 bit in the 8 bit resolution.
    This is what i would expect and acceptable.

    That is interesting. I will note this down and attempt to recreate this behavior on my end. To my knowledge, whether you have configured oversampling or not should not affect the offset calibration procedure at all.

    Furthermore, depending on your input range configuration I would more or less expect a possible fluctuation in LSB of an SAADC output.
    Is this remaining fluctuation an issue for your current project - what is your required resolution?

    maarten v said:
    I was too quick: the deviation seems to be much less frequent (about 1 in 40) but still happens now and than: suddenly a jump in the offset after a calibration.

    The deviation seems to be much less frequent ( 1 in 40 ) when you have turned on x256 oversampling, is that what you are saying?

    Best regards,
    Karl

  • The deviation seems to be much less frequent ( 1 in 40 ) when you have turned on x256 oversampling, is that what you are saying?

    Correct. But that was yesterday. The exact same solution results in much more frequent calibration ofset jumps on a different nrf52840. So perhaps this was just coincidental.

    I want a solution for this offset problem.
    I was thinking of measuring the 0V adc deviation myself. Is this possible by setting the input to NRF_SAADC_INPUT_DISABLED, while setting the internal resistor network to NRF_SAADC_RESISTOR_PULLDOWN?

Related