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

Calibrating SAADC on nrf52832

Hi,

I am using an nRF52832 with S132 and SDK v15.0.0 and already read in the forum about issues related to the SAADC calibration (inlcuing the erratum  [86]). The problem I have is that the ADC constantly (not only once!) delivers wrong data after I call the calibrate_offset function.

The stuff that happens over time is basically:

-> The SAADC is initialised

-> the channels (2) are initialised

-> the buffer_convert function is called

-> (because the buffer-convert funtion was already called and the SAADC is in IDLE although no START or SAMPLE task was triggered yet) -> the nrfx_saadc_abort() function is called

This is the first point, where somthing goes wrong since sometimes the nrfx_saadc_abort() itself hangs when waiting for the SAADC to become IDLE again. Do I have to do a dummy TASK_SAMPLE before calling the abort function so that a conversion is in progress? it seems that simply aborting without issuing a SAMPLE before fails in waiting for "NRFX_WAIT_FOR((m_cb.adc_state != NRF_SAADC_STATE_IDLE), HW_TIMEOUT, 0, result);". (But I have to call the abort function to get into IDLE after calling buffer_convert?)

->  nrfx_saadc_calibrate_offset() is called

-> The values i get from this time by calling TASK_SAMPLE  are wrong

Besides of the concrete problem, I have the following questions, wo which I did not yet find an answer to.

1.) I am NOT using SAADC in LP Mode. Is the workaround for the erratum [86] still necessary for SDK V15.0.0 or is ia patch alredy somehow included in v15?

2.) I am unsure about how to implement the workaround. It is described as Stopping the SAADC after calibration, but stopping seems to invole waiting for an STOPPED EVENT. Do i have to wait and therefore somehow poll for that event? What is an easy way to do that?

3.) In all the SAADC examples, I see that the event handler checks for the NRFX_SAADC_EVT_DONE event and gets the data from the buffer. Why is that done, when there is explicitly stated that this event does not guaratee that the samples are already in RAM? Why not waiting for the END event? The p_buffer->type does not even seem to expose the END event? Why is that?

4.) I see that in examples, in the Event handler the occurence of the event CALIBRATEDONE is used to again call buffer_convert to set up the buffer again. If i call the calibrate_offset function before a call to buffer_convert, i guess thats a problem because in the event handler the buffer is set to the last one used, but there is no (useful) "last" buffer in this case?)

5.) Could someone provide a minimal working example (minimal main program plus callback) for just initialising the ADC / the ADC channels, perform calibration and set up the buffer for a "real" conversion directly after a startup? All the examples I found use cyclic turning on/off or inti / uninits that make things a bit unclear for a basic understanding.

Thanks a lot!

  • Hi, 

    Do I have to do a dummy TASK_SAMPLE before calling the abort function so that a conversion is in progress?

    See this case: https://devzone.nordicsemi.com/f/nordic-q-a/29806/nrf52832-sdk14---saadc-calibration-issues 

    1. You should implement according to Errata 86 if you are using calibrate offset yes.

    2. This is already handled in nrfx_saadc_abort().

    3. The transfer to RAM may be a problem if you are directly using this as a pointer value for other peripheral, e.g. have a saadc done event to uart start tx task. But for interrupt processing it's not a problem.

    4. Not sure if I see that, what examples project and source file are you referring to?

    5. A colleague made this example, maybe it can help you also:

    /**
     * Copyright (c) 2014 - 2018, Nordic Semiconductor ASA
     * 
     * All rights reserved.
     * 
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     * 
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     * 
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     * 
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     * 
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     * 
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     * 
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     * 
     */
    /** @file
     * @defgroup nrf_adc_example main.c
     * @{
     * @ingroup nrf_adc_example
     * @brief ADC Example Application main file.
     *
     * This file contains the source code for a sample application using ADC.
     *
     * @image html example_board_setup_a.jpg "Use board setup A for this example."
     */
    
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include "nrf.h"
    #include "nrf_drv_saadc.h"
    
    #include "boards.h"
    #include "app_error.h"
    #include "nrf_delay.h"
    #include "app_util_platform.h"
    #include "nrf_pwr_mgmt.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define SAMPLES_IN_BUFFER 5
    #define HW_TIMEOUT 10000
    
    
    nrf_saadc_value_t m_buffer_pool[2][SAMPLES_IN_BUFFER];
    
    void calibrate_saadc(void);
    
    void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ret_code_t err_code;
    
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
    
            for (uint8_t i = 0; i < SAMPLES_IN_BUFFER; i++)
            {
                NRF_LOG_INFO("%d", p_event->data.done.p_buffer[i]);
            }
        }
    }
    
    
    void saadc_init(void)
    {
        ret_code_t err_code;
        nrf_saadc_channel_config_t channel_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
    
        err_code = nrf_drv_saadc_init(NULL, saadc_callback);
        APP_ERROR_CHECK(err_code);
    
        calibrate_saadc();
    
        err_code = nrf_drv_saadc_channel_init(0, &channel_config);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    }
    
    void calibrate_saadc(void)
    {
        nrfx_err_t nrfx_err_code = NRFX_SUCCESS;
    
        // Stop ADC
        nrf_saadc_int_disable(NRF_SAADC_INT_ALL);
        NRFX_IRQ_DISABLE(SAADC_IRQn);
        nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    
        // Wait for ADC being stopped.
        bool result;
        NRFX_WAIT_FOR(nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED), HW_TIMEOUT, 0, result);
        NRFX_ASSERT(result);
    
        // Start calibration
        NRFX_IRQ_ENABLE(SAADC_IRQn);
        nrfx_err_code = nrfx_saadc_calibrate_offset();
        APP_ERROR_CHECK(nrfx_err_code);
        while(nrfx_saadc_is_busy()){};
        
        // Stop ADC
        nrf_saadc_int_disable(NRF_SAADC_INT_ALL);
        NRFX_IRQ_DISABLE(SAADC_IRQn);
        nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    
        // Wait for ADC being stopped. 
        NRFX_WAIT_FOR(nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED), HW_TIMEOUT, 0, result);
        NRFX_ASSERT(result);
        
        // Enable IRQ
        NRFX_IRQ_ENABLE(SAADC_IRQn);
    
    }
    
    
    /**
     * @brief Function for main application entry.
     */
    int main(void)
    {
        uint32_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
        ret_code_t ret_code = nrf_pwr_mgmt_init();
        APP_ERROR_CHECK(ret_code);
    
        saadc_init();
    
        NRF_LOG_INFO("SAADC calibration example started.");
    
        while (1)
        {
            nrf_pwr_mgmt_run();
            NRF_LOG_FLUSH();
        }
    }
    
    
    /** @} */
    

  • Hi Kenneth,

    Thanks for the reply.

    According to the numbering of my initial questions:

    3.) Good to know, so it is no problem with IR processing because the delay until the IR is handled, is big enough in any case?

    4.) Will have to check where in the forum I saw it, I will come back to that.

    5.) Well there is one thing I want to ask about this example which is also somthing I did not understand in another example: How is the call to err_code = nrf_drv_saadc_init(NULL, saadc_callback); supposed to work?

    I use nrfx_saadc_init(NULL,saadc_callback); according to the new API in SDK V15, but there is a line in the function "nrfx_saadc_init(NULL,saadc_callback);" that obviously returns immediately if the function is called with a NULL pointer. What is the reason for nto initialising with a proper "ADC config structure" ?

    Thanks

  • About 5) What file and line number are you referring to?

    Kenneth

  • E.g. line number 99 in the code example you shared above.

  • It is example code so please change this as you see fit, but basically using NULL will set default settings from sdk_config.h

    __STATIC_INLINE ret_code_t nrf_drv_saadc_init(nrf_drv_saadc_config_t const * p_config,
                                                  nrf_drv_saadc_event_handler_t  event_handler)
    {
        if (p_config == NULL)
        {
            static const nrfx_saadc_config_t default_config = NRFX_SAADC_DEFAULT_CONFIG;
            p_config = &default_config;
        }
        return nrfx_saadc_init(p_config, event_handler);
    }

Related