52833 adc deviation

Hi

   We are using 52833 adc, but the actual value of the adc is not within our expected value. We want to know the adc accuracy, can you give us some advice? Thanks

sdk: sdk17.1.0

adc: 12bit  NRF_SAADC_REFERENCE_INTERNAL NRF_SAADC_GAIN1_4

real voltage range: 0v - 1.5v
config
    nrf_drv_saadc_config_t p_config = {
        .resolution = (nrf_saadc_resolution_t)NRF_SAADC_RESOLUTION_12BIT,
        .oversample         = (nrf_saadc_oversample_t)SAADC_CONFIG_OVERSAMPLE,
        .interrupt_priority = SAADC_CONFIG_IRQ_PRIORITY,
        .low_power_mode     = SAADC_CONFIG_LP_MODE
    };
    nrf_saadc_channel_config_t channe1_config =
        NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
    channe1_config.gain = NRF_SAADC_GAIN1_3;        
    nrf_saadc_channel_config_t channe2_config =
        NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
    channe2_config.gain = NRF_SAADC_GAIN1_3;
    APP_ERROR_CHECK(nrf_drv_saadc_channel_init(1, &channe1_config));
    APP_ERROR_CHECK(nrf_drv_saadc_channel_init(2, &channe2_config));
start collect 
 APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(m_buffer_pool, SAMPLES_IN_BUFFER));
 nrf_drv_saadc_sample();
process adc value
static void mt_saadc_callback(nrf_drv_saadc_evt_t const *p_event)
{
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE) {
        m_adc_buff[m_adc_collect_times].channel1 = m_buffer_pool[0];
        m_adc_buff[m_adc_collect_times].channel2 = m_buffer_pool[1];
        nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    }
}
Below is my circuit diagram, We only use adc to calculate the resistance of 1MΩ to avoid power supply voltage fluctuations.
But it seem no effect,the adc value changes of ain0 and ain1 are not synchronized sometimes.

R = 27kΩ * (ain1adc / ain0adc -ain1adc)
The attachment is the result of my test and calculation, Can you give me some advice? Thanks
Parents
  • Hello,

      The attachment is my code.

    #include "mt_adc.h"
    #include "app_error.h"
    #include "nrf_gpio.h"
    #include "nrf_drv_saadc.h"
    #include "nrf_saadc.h"
    #include "app_timer.h"
    #include "app_scheduler.h"
    
    #define NRF_LOG_MODULE_NAME mt_adc
    #include "nrf_log.h"
    NRF_LOG_MODULE_REGISTER();
    
    #define ADC_COLLECT_TIMES 40
    
    #define SAMPLES_IN_BUFFER 2
    #define ADC_COLLECR_SMALL_FRE 10
    #define ADC_COLLECR_BIG_FRE 1000
    
    APP_TIMER_DEF(m_adc);
    APP_TIMER_DEF(m_adc_period);
    
    /* clang-format off */
    static nrf_saadc_value_t m_buffer_pool[SAMPLES_IN_BUFFER];           
    static uint8_t m_adc_collect_times;                                 
    static adc_buff_t m_adc_buff[ADC_COLLECT_TIMES];                     
    static adc_statu_t m_is_collecting = ADC_STOP;                      
    static adc_buff_t m_final_adc;
    /* clang-format on */
    
    static void adc_bubble_sort(void)
    {
        uint8_t i, j;
        int16_t tem;
        for (i = 0; i < ADC_COLLECT_TIMES - 1; i++)
        {
            for (j = 0; j < ADC_COLLECT_TIMES - 1 - i; j++)
            {
                if (m_adc_buff[j].channel1 > m_adc_buff[j + 1].channel1)
                {
                    tem = m_adc_buff[j].channel1;
                    m_adc_buff[j].channel1 = m_adc_buff[j + 1].channel1;
                    m_adc_buff[j + 1].channel1 = tem;
                }
                if (m_adc_buff[j].channel2 > m_adc_buff[j + 1].channel2)
                {
                    tem = m_adc_buff[j].channel2;
                    m_adc_buff[j].channel2 = m_adc_buff[j + 1].channel2;
                    m_adc_buff[j + 1].channel2 = tem;
                }
            }
        }
    }
    
    static adc_buff_t mt_adc_collect_process(void)
    {
        adc_buff_t average_adc;
        memset(&average_adc, 0, sizeof(adc_buff_t));
        adc_bubble_sort();
    
        uint32_t tmp1 = 0;
        uint32_t tmp2 = 0;
    
        for (uint8_t i = 10; i < ADC_COLLECT_TIMES - 10; i++)
        {
            tmp1 += m_adc_buff[i].channel1;
            tmp2 += m_adc_buff[i].channel2;
        }
        average_adc.channel1 = (tmp1 / (ADC_COLLECT_TIMES - 20));
        average_adc.channel2 = (tmp2 / (ADC_COLLECT_TIMES - 20));
        average_adc.channel1 = average_adc.channel1 < 0 ? 0 : average_adc.channel1;
        average_adc.channel2 = average_adc.channel2 < 0 ? 0 : average_adc.channel2;
        return average_adc;
    }
    
    static void mt_saadc_callback(nrf_drv_saadc_evt_t const *p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            m_adc_buff[m_adc_collect_times].channel1 = m_buffer_pool[1];
            m_adc_buff[m_adc_collect_times].channel2 = m_buffer_pool[2];
            nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
            m_adc_collect_times++;
            if (m_adc_collect_times >= ADC_COLLECT_TIMES)
            {
                APP_ERROR_CHECK(app_timer_stop(m_adc));
            }
        }
    }
    
    static void mt_timeout_cb(void *ctx)
    {
        UNUSED_PARAMETER(ctx);
        if (m_adc_collect_times < ADC_COLLECT_TIMES)
        {
            mt_adc_start();
        }
    }
    
    static void mt_timeout_period_cb(void *ctx)
    {
        UNUSED_PARAMETER(ctx);
        m_adc_collect_times = 0;
        m_final_adc = mt_adc_collect_process();
        NRF_LOG_INFO("channel1=%d channel2=%d",m_final_adc.channel1,m_final_adc.channel2);
        memset(m_adc_buff, 0, sizeof(adc_buff_t) * ADC_COLLECT_TIMES);
    }
    
    void mt_adc_init(void)
    {
        APP_ERROR_CHECK(app_timer_create(&m_adc, APP_TIMER_MODE_REPEATED, mt_timeout_cb));
        APP_ERROR_CHECK(app_timer_create(&m_adc_period, APP_TIMER_MODE_REPEATED, mt_timeout_period_cb));
    
        nrf_drv_saadc_config_t p_config = {
            .resolution = (nrf_saadc_resolution_t)NRF_SAADC_RESOLUTION_12BIT,
            .oversample = (nrf_saadc_oversample_t)SAADC_CONFIG_OVERSAMPLE,
            .interrupt_priority = SAADC_CONFIG_IRQ_PRIORITY,
            .low_power_mode = SAADC_CONFIG_LP_MODE};
    
        nrf_saadc_channel_config_t channe1_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
        channe1_config.gain = NRF_SAADC_GAIN1_3;
    
        nrf_saadc_channel_config_t channe2_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
        channe2_config.gain = NRF_SAADC_GAIN1_3;
    
        APP_ERROR_CHECK(nrf_drv_saadc_init(&p_config, mt_saadc_callback));
    
        APP_ERROR_CHECK(nrf_drv_saadc_channel_init(1, &channe1_config));
        APP_ERROR_CHECK(nrf_drv_saadc_channel_init(2, &channe2_config));
    }
    
    void mt_adc_start(void)
    {
        if (!nrf_drv_saadc_is_busy())
        {
            nrf_gpio_pin_set(NTC_POWER_PIN);
            APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(m_buffer_pool, SAMPLES_IN_BUFFER));
            nrf_drv_saadc_sample();
        }
    }
    
    void mt_adc_off(void)
    {
        nrf_drv_saadc_uninit();
    }
    
    static void mt_adc_period_timer_start(uint8_t time_in_1sec)
    {
        NRF_LOG_INFO("mt_adc_period_timer_start time_in_1sec = %d\r", time_in_1sec);
        ASSERT(time_in_1sec > 0 && time_in_1sec <= 30);
        APP_ERROR_CHECK(
            app_timer_start(m_adc_period, APP_TIMER_TICKS(time_in_1sec * ADC_COLLECR_BIG_FRE), NULL));
    }
    
    static void mt_adc_period_timer_stop(void)
    {
        APP_ERROR_CHECK(app_timer_stop(m_adc_period));
    }
    
    void mt_start_adc_collect(uint8_t time_in_1sec)
    {
        if (m_is_collecting == ADC_STOP)
        {
            NRF_LOG_INFO("mt_start_adc_collect\r");
            m_is_collecting = ADC_COLLECTING;
            m_adc_collect_times = 0;
            mt_adc_init();
            mt_adc_period_timer_start(time_in_1sec);
            APP_ERROR_CHECK(app_timer_start(m_adc, APP_TIMER_TICKS(ADC_COLLECR_SMALL_FRE), NULL));
        }
    }
    
    void mt_stop_adc_collect(void)
    {
        if (m_is_collecting == ADC_COLLECTING)
        {
            m_is_collecting = ADC_STOP;
            APP_ERROR_CHECK(app_timer_stop(m_adc));
            APP_ERROR_CHECK(app_timer_stop(m_adc_period));
            mt_adc_off();
        }
    }
    
    adc_statu_t mt_get_adc_statu(void)
    {
        return m_is_collecting;
    }
    

Reply
  • Hello,

      The attachment is my code.

    #include "mt_adc.h"
    #include "app_error.h"
    #include "nrf_gpio.h"
    #include "nrf_drv_saadc.h"
    #include "nrf_saadc.h"
    #include "app_timer.h"
    #include "app_scheduler.h"
    
    #define NRF_LOG_MODULE_NAME mt_adc
    #include "nrf_log.h"
    NRF_LOG_MODULE_REGISTER();
    
    #define ADC_COLLECT_TIMES 40
    
    #define SAMPLES_IN_BUFFER 2
    #define ADC_COLLECR_SMALL_FRE 10
    #define ADC_COLLECR_BIG_FRE 1000
    
    APP_TIMER_DEF(m_adc);
    APP_TIMER_DEF(m_adc_period);
    
    /* clang-format off */
    static nrf_saadc_value_t m_buffer_pool[SAMPLES_IN_BUFFER];           
    static uint8_t m_adc_collect_times;                                 
    static adc_buff_t m_adc_buff[ADC_COLLECT_TIMES];                     
    static adc_statu_t m_is_collecting = ADC_STOP;                      
    static adc_buff_t m_final_adc;
    /* clang-format on */
    
    static void adc_bubble_sort(void)
    {
        uint8_t i, j;
        int16_t tem;
        for (i = 0; i < ADC_COLLECT_TIMES - 1; i++)
        {
            for (j = 0; j < ADC_COLLECT_TIMES - 1 - i; j++)
            {
                if (m_adc_buff[j].channel1 > m_adc_buff[j + 1].channel1)
                {
                    tem = m_adc_buff[j].channel1;
                    m_adc_buff[j].channel1 = m_adc_buff[j + 1].channel1;
                    m_adc_buff[j + 1].channel1 = tem;
                }
                if (m_adc_buff[j].channel2 > m_adc_buff[j + 1].channel2)
                {
                    tem = m_adc_buff[j].channel2;
                    m_adc_buff[j].channel2 = m_adc_buff[j + 1].channel2;
                    m_adc_buff[j + 1].channel2 = tem;
                }
            }
        }
    }
    
    static adc_buff_t mt_adc_collect_process(void)
    {
        adc_buff_t average_adc;
        memset(&average_adc, 0, sizeof(adc_buff_t));
        adc_bubble_sort();
    
        uint32_t tmp1 = 0;
        uint32_t tmp2 = 0;
    
        for (uint8_t i = 10; i < ADC_COLLECT_TIMES - 10; i++)
        {
            tmp1 += m_adc_buff[i].channel1;
            tmp2 += m_adc_buff[i].channel2;
        }
        average_adc.channel1 = (tmp1 / (ADC_COLLECT_TIMES - 20));
        average_adc.channel2 = (tmp2 / (ADC_COLLECT_TIMES - 20));
        average_adc.channel1 = average_adc.channel1 < 0 ? 0 : average_adc.channel1;
        average_adc.channel2 = average_adc.channel2 < 0 ? 0 : average_adc.channel2;
        return average_adc;
    }
    
    static void mt_saadc_callback(nrf_drv_saadc_evt_t const *p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            m_adc_buff[m_adc_collect_times].channel1 = m_buffer_pool[1];
            m_adc_buff[m_adc_collect_times].channel2 = m_buffer_pool[2];
            nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
            m_adc_collect_times++;
            if (m_adc_collect_times >= ADC_COLLECT_TIMES)
            {
                APP_ERROR_CHECK(app_timer_stop(m_adc));
            }
        }
    }
    
    static void mt_timeout_cb(void *ctx)
    {
        UNUSED_PARAMETER(ctx);
        if (m_adc_collect_times < ADC_COLLECT_TIMES)
        {
            mt_adc_start();
        }
    }
    
    static void mt_timeout_period_cb(void *ctx)
    {
        UNUSED_PARAMETER(ctx);
        m_adc_collect_times = 0;
        m_final_adc = mt_adc_collect_process();
        NRF_LOG_INFO("channel1=%d channel2=%d",m_final_adc.channel1,m_final_adc.channel2);
        memset(m_adc_buff, 0, sizeof(adc_buff_t) * ADC_COLLECT_TIMES);
    }
    
    void mt_adc_init(void)
    {
        APP_ERROR_CHECK(app_timer_create(&m_adc, APP_TIMER_MODE_REPEATED, mt_timeout_cb));
        APP_ERROR_CHECK(app_timer_create(&m_adc_period, APP_TIMER_MODE_REPEATED, mt_timeout_period_cb));
    
        nrf_drv_saadc_config_t p_config = {
            .resolution = (nrf_saadc_resolution_t)NRF_SAADC_RESOLUTION_12BIT,
            .oversample = (nrf_saadc_oversample_t)SAADC_CONFIG_OVERSAMPLE,
            .interrupt_priority = SAADC_CONFIG_IRQ_PRIORITY,
            .low_power_mode = SAADC_CONFIG_LP_MODE};
    
        nrf_saadc_channel_config_t channe1_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
        channe1_config.gain = NRF_SAADC_GAIN1_3;
    
        nrf_saadc_channel_config_t channe2_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
        channe2_config.gain = NRF_SAADC_GAIN1_3;
    
        APP_ERROR_CHECK(nrf_drv_saadc_init(&p_config, mt_saadc_callback));
    
        APP_ERROR_CHECK(nrf_drv_saadc_channel_init(1, &channe1_config));
        APP_ERROR_CHECK(nrf_drv_saadc_channel_init(2, &channe2_config));
    }
    
    void mt_adc_start(void)
    {
        if (!nrf_drv_saadc_is_busy())
        {
            nrf_gpio_pin_set(NTC_POWER_PIN);
            APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(m_buffer_pool, SAMPLES_IN_BUFFER));
            nrf_drv_saadc_sample();
        }
    }
    
    void mt_adc_off(void)
    {
        nrf_drv_saadc_uninit();
    }
    
    static void mt_adc_period_timer_start(uint8_t time_in_1sec)
    {
        NRF_LOG_INFO("mt_adc_period_timer_start time_in_1sec = %d\r", time_in_1sec);
        ASSERT(time_in_1sec > 0 && time_in_1sec <= 30);
        APP_ERROR_CHECK(
            app_timer_start(m_adc_period, APP_TIMER_TICKS(time_in_1sec * ADC_COLLECR_BIG_FRE), NULL));
    }
    
    static void mt_adc_period_timer_stop(void)
    {
        APP_ERROR_CHECK(app_timer_stop(m_adc_period));
    }
    
    void mt_start_adc_collect(uint8_t time_in_1sec)
    {
        if (m_is_collecting == ADC_STOP)
        {
            NRF_LOG_INFO("mt_start_adc_collect\r");
            m_is_collecting = ADC_COLLECTING;
            m_adc_collect_times = 0;
            mt_adc_init();
            mt_adc_period_timer_start(time_in_1sec);
            APP_ERROR_CHECK(app_timer_start(m_adc, APP_TIMER_TICKS(ADC_COLLECR_SMALL_FRE), NULL));
        }
    }
    
    void mt_stop_adc_collect(void)
    {
        if (m_is_collecting == ADC_COLLECTING)
        {
            m_is_collecting = ADC_STOP;
            APP_ERROR_CHECK(app_timer_stop(m_adc));
            APP_ERROR_CHECK(app_timer_stop(m_adc_period));
            mt_adc_off();
        }
    }
    
    adc_statu_t mt_get_adc_statu(void)
    {
        return m_is_collecting;
    }
    

Children
  • Hi Август, just want to let you know that I have been assigned to support you with this case. There are some details I need to double check first before I give you an answer. I will be back by the end of Thu Jan 12.

  • Hi Август,

    In your implementation, I see there are a few things that can be improved:

    • Add calibration. It is recommended to perform calibration at least once before using SAADC, and every time the ambient temperature changed by 10°C
    • Use a higher resolution. The nRF52833 is capable of 14-bit SAADC resolution if oversampling is used
    • Configure oversampling. In your code, it is unclear how much oversampling you are having

    Another point to consider is, what is the actual exact value of the resistors you are using? 27k and 1M are just the nominal value. There could be some tolerance that you have not accounted for.

    All that is not to say that the SAADC on a nRF52833 is perfectly accurate. It is not intended to be extremely accurate. You can usually expect some fluctuations in some of the LSBits of your ADC readings.

    Considering such fluctuations, the value in your Excel table doesn't look too bad to me. What is the level of accuracy you are expecting?

    Hieu

  • Hi Hieu,

        Thank you very much for informing the adc calibration method.

         Because the adc value keeps jumping with this fixed resistor so I want to know the deviation of the adc not include temperature drift. If I konw the deviation I can use another way to solve this problem, such as android app and so on.

    I am not sure about the deviation of the red box below, is it the deviation of the adc value?

    As far as I know adc oversampling can only be used for one channel, so I gave up on this method.

  • Hi Август,

    Август said:
    I am not sure about the deviation of the red box below, is it the deviation of the adc value?

    Yes. It's because of those that I mentioned that the few least significant bits of your measurement are not reliable.

    Август said:
    As far as I know adc oversampling can only be used for one channel, so I gave up on this method.

    You are right. Oversampling cannot be combined with multiple channel.

    There are two alternatives we can consider.

    1. Application layer level oversampling
    2. Alternating between single channel sampling of each. Essentially uninitialize and initialize SAADC channels each time.

    By the way, is power consumption a concern in your application? Or perhaps is this just a test for the accuracy of SAADC?

  • Hi Hieu,

        Maybe I didn't express my thoughts clearly. For example, I want to know the deviation value of 1/6 parameter adc without considering the temperature.

    My code get adc value I called it adc_value.

    adc_value = 628 

    The allowable deviation is adc_value * 6% = 37.68 right? 

    By the way, is power consumption a concern in your application? Or perhaps is this just a test for the accuracy of SAADC?

    Yes we need low power, we have to find a balance between power consumption and accuracy.

Related