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

  • maarten v said:
    Thank you for the answer, and my apologies that it took some time.

    That is no problem, I am happy to help!

    maarten v said:
    This is the confirmation i was looking for. Time is not an issue since it only has to be done incidentally and can be scheduled in between other actions.

    Great, I am glad to hear that!

    maarten v said:
    fyi: The remaining offset is compensated with a firmware calculated offset from an average of 1024 samples. This does the trick in keeping the signal stable when the temperature is not changed, so i guess it will also work when it does change.

    Are you here talking about using a static average to weight the samples? If you are achieving the correct readouts with this approach then I suppose it is fine, but I can not speak for the accuracy of the outcome. As I mentioned I would expect some LSB of noise on the SAADC outputs, depending on the SAADC configuration.

    Best regards,
    Karl

  • yes, in 12 bit mode there is about 4 bits noise. That is unfortunate but according spec for the 52840.

    1024 times oversampling reduces the noise by a factor 2^5, which is indeed what i see when i did some experiments with this.

  • maarten v said:
    1024 times oversampling reduces the noise by a factor 2^5, which is indeed what i see when i did some experiments with this.

    You never told me the exact accuracy / precision need of the SAADC for your project, so I not know what your target is.
    Does this mean you are able to achieve the required accuracy for your project?

    Best regards,
    Karl

  • The required accuracy depends on the application. We have developed some IOT nodes (https://xeelas.nl/producten) with capability of connecting external sensors.

    Depending on the sensor and the application we use less or more oversampling.
    With oversampling and extra offset correction the accuracy is good enough for our applications.

  • Ah, now I understand, thank you for clarifying.

    maarten v said:
    Depending on the sensor and the application we use less or more oversampling.
    With oversampling and extra offset correction the accuracy is good enough for our applications.

    That sounds like a good solution - I am happy to hear that it achieved the required accuracy for your applications.

    Please do not hesitate to open a new ticket if you should encounter any issues or questions in the future.

    Good luck with your development!

    Best regards,
    Karl

Related