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

How to uninit SAADC on nRF52 Devkit Using Nordic's saadc Example

I have searched Nordic and the web and have seen several instances of "can't uninit saadc successfully". Neither Nordic support nor I am aware of a single example of saadc uninit working. My private thread with Nordic (in which I share more proprietary code details) has reached a dead end so I am looking for a solution from the community based only on Nordic's saadc example.

I see about ten different library calls that may be relevant to adding an uninit sequence to the SAADC example in SDK 14.2.0. My goal is, using Nordic's example on their DK board, to:

  1. measure DK power consumption before SAADC comes up
  2. measure DK power consumption while SAADC is up
  3. measure DK power consumption after SAADC has been uninit'd
  4. verify that #1 and #3 current measurements are the same

All I am asking for is a recommended uninit sequence for the Nordic SDK 14.2.0 saadc example. Then I will expand on that solution and apply it to my dual ADC channel code below.

Except for the fact that current consumption is (usually) too high after the uninit completes the following code sequence is working as intended. This code is a two adc channel extension of the Nordic example. The #3 current measurement is always 5,10,15, or 20 mA higher than the #1 current measurement which leads me to think that the random timing of the uninit relative to the sampling and clock may be the problem.

// Nordic SDK 14.2.0 example saadc - modified for TWO ADC channels
nrf_drv_saadc_init(NULL, saadc_bemf_callback);

// Two adc channels
nrf_drv_saadc_channel_init(FR_ADC_ISEN_CHANNEL, &m_adc_channel_config0);
nrf_drv_saadc_channel_init(FR_ADC_BEMF_CHANNEL, &m_adc_channel_config1);

nrf_drv_saadc_buffer_convert(l_buffer_pool[0], FR_ADC_BUFFER_SIZE);
nrf_drv_saadc_buffer_convert(l_buffer_pool[1], FR_ADC_BUFFER_SIZE);

nrf_drv_ppi_init();
nrf_drv_timer_init(&l_timer, &timer_cfg, timer_handler);
nrf_drv_timer_extended_compare(
    &l_timer,
    NRF_TIMER_CC_CHANNEL0,
    ticks,
    NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
    false
);

nrf_drv_timer_enable(&l_timer);
nrf_drv_timer_compare_event_address_get(
    &l_timer,
    NRF_TIMER_CC_CHANNEL0
);
nrf_drv_saadc_sample_task_get();
nrf_drv_ppi_channel_alloc(&l_ppi_channel();
nrf_drv_ppi_channel_enable(l_ppi_channel);

// The above, similar to Nordic's example but with TWO ADC channels works
// perfectly

// Init done, now try uninit

nrf_drv_ppi_channel_disable(l_ppi_channel);

nrf_drv_ppi_channel_free(l_ppi_channel);
nrf_drv_timer_disable(&l_timer);
nrf_drv_timer_uninit(&l_timer);
nrf_drv_ppi_uninit();

// Again, I'm using Two ADC channels
nrf_drv_saadc_channel_uninit(FR_ADC_BEMF_CHANNEL);
nrf_drv_saadc_channel_uninit(FR_ADC_ISEN_CHANNEL);

nrf_drv_saadc_uninit();

// Current draw is NOT back to initial levels, and the resulting draw varies
// by multiples of 5mA !!! What would cause the 5mA granularity (I'm using a
// microamp capable meter)

  • This ticket has nothing to do with my hardware or my code. This ticket is about two things:

    • Nordic nRF52 Devkit, and
    • Nordic SDK 14.2.0 SAADC Example

    It appears that no one knows how to add an uninit sequence for the combined SAADC and PPI in that example. It is not obvious what calls have to be made and when they have to be made. If you or anyone else knows how to add to the Nordic 14.2.0 saadc.c an uninit sequence that will return the DK back to the power level that existed before the initialization sequence I would appreciate you sharing it.

    On the other hand if you believe that no such solution exists and that I should use SAADC without PPI in order to revert the current draw back to pre saadc setup levels then just say so and I'll use SAADC without PPI.

    I am perfectly happy with either outcome:

    1. Either DK+14.2.0 code demonstrating uninit of SAADC+PPI resulting in current equal to pre-SAADC+PPI init levels, or
    2. a statement that no one knows how to do that
  • How about this one then

    /**
     * Copyright (c) 2014 - 2017, 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 "nrf_drv_ppi.h"
    #include "nrf_drv_timer.h"
    #include "boards.h"
    #include "app_error.h"
    #include "nrf_delay.h"
    #include "app_util_platform.h"
    #include "nrf_pwr_mgmt.h"
    #include "nrf_drv_power.h"
    
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define SAMPLES_IN_BUFFER 5
    volatile uint8_t state = 1;
    
    static const nrf_drv_timer_t m_timer = NRF_DRV_TIMER_INSTANCE(0);
    static nrf_saadc_value_t     m_buffer_pool[2][SAMPLES_IN_BUFFER];
    static nrf_ppi_channel_t     m_ppi_channel;
    static uint32_t              m_adc_evt_counter;
    
    
    void timer_handler(nrf_timer_event_t event_type, void * p_context)
    {
    
    }
    
    
    void saadc_sampling_event_init(void)
    {
        ret_code_t err_code;
    
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
        err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
        APP_ERROR_CHECK(err_code);
    
        /* setup m_timer for compare event every 400ms */
        uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 400);
        nrf_drv_timer_extended_compare(&m_timer,
                                       NRF_TIMER_CC_CHANNEL0,
                                       ticks,
                                       NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                       false);
        nrf_drv_timer_enable(&m_timer);
    
        uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer,
                                                                                    NRF_TIMER_CC_CHANNEL0);
        uint32_t saadc_sample_task_addr   = nrf_drv_saadc_sample_task_get();
    
        /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
        err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
                                              timer_compare_event_addr,
                                              saadc_sample_task_addr);
        APP_ERROR_CHECK(err_code);
    }
    
    
    void saadc_sampling_event_enable(void)
    {
        ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    
        APP_ERROR_CHECK(err_code);
    }
    
    
    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);
    
            int i;
            NRF_LOG_INFO("ADC event number: %d", (int)m_adc_evt_counter);
    
            for (i = 0; i < SAMPLES_IN_BUFFER; i++)
            {
                NRF_LOG_INFO("%d", p_event->data.done.p_buffer[i]);
            }
            m_adc_evt_counter++;
        }
    }
    
    
    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);
    
        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);
    
    }
    
    
    /**
     * @brief Function for main application entry.
     */
    int main(void)
    {
        // Button to be used to uninit SAADC (pin 13 is button 1 on nRF52-DK), hard-coded.    
        nrf_gpio_cfg_input(13, NRF_GPIO_PIN_PULLUP);
    
        uint32_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
        err_code = nrf_drv_power_init(NULL);
        APP_ERROR_CHECK(err_code);
    
        ret_code_t ret_code = nrf_pwr_mgmt_init();
        APP_ERROR_CHECK(ret_code);
    
        NRF_LOG_INFO("SAADC HAL simple example.");
    
        saadc_init();
        saadc_sampling_event_init();
        saadc_sampling_event_enable();
    
        while (1)
        {
            nrf_pwr_mgmt_run();
            NRF_LOG_FLUSH();
          
            // Check if button is pressed (pin 13 is button 1 on nRF52-DK), hard-coded.        
            if(nrf_gpio_pin_read(13) == 0)
            {
                // Wait for button to be released before starting uninit.
                while(nrf_gpio_pin_read(13) == 0)
                  ;  
    
                NRF_LOG_INFO("Uninit SAADC driver");  
                nrf_drv_timer_disable(&m_timer);
    
                err_code = nrf_drv_ppi_channel_free(m_ppi_channel);
                APP_ERROR_CHECK(err_code);
              
                nrf_drv_saadc_uninit();
    
                // In case logging was enabled, turn off UART to reach low power
                // Also make sure to set #define NRF_LOG_DEFERRED 0 in sdk_config
                NRF_UART0->ENABLE = 0;	
            }
        }
    }
    
    
    /** @} */
    

  • First of all you were absolutely right on the GPIO lines. I had a mux with three GPIO address lines left at an arbitrary address when the mux chip got de-powered. In that state the CPU was delivering ~5mA per high address line (zero mA for each low address line). The CPU was apparently powering multiple "de-powered" ICs by dumping current thru that mux. A newer version of our board demonstrated that nrf_drv_ppi_channel_disable() is sufficient for my purposes so I've back-ported that to the board I was having problems with for this ticket. Thanks for the advice and patience with me!

  • My discovery was that leaving the address GPIO lines high solved the problem but for the production solution I un-init those GPIO outs and re-init after powering up the mux again..

  • Hello,

    as I have lost quite some time with a similar problem (unable to correctly uninitialize the SAADC, therefore current not returning to the value it had before initiailzing the SAADC the first time), I wanted to share my findings, in the hope that it may help some colleague....

    The following observations were taken with SDK 15.0.0 and the Nordic chip is an nRF52840.

    My design is powered from USB and In order to meet the maximum allowable current limit of 500uA when in USB SUSPEND state, I had to find a way to disable the SAADC. I thought that (asynchronously) calling nrf_drv_saadc_uninit() after entering into USB SUSPEND state would suffice, but was proven widely wrong!

    I used the SAADC to sample 5 analog inputs and set SAADC_SAMPLES_IN_BUFFER to a multiple of the number of analog inputs (=10).

    I had BURST and OVERSAMPLE turned off.

    This means that the SAADC needs to be triggered twice (by the associated timer, either HF or a low power application timer) before a conversion is complete.

    Calling nrf_drv_saadc_uninit() after the first trigger (i.e. call of nrf_drv_saadc_sample() ) - therefore with only 5 of the 10 samples taken - but before the second one (and perhaps also stopping the timer in the hope to further reduce the power consumption) results in the SAADC actually left "half way enabled" with an extra current of at least 200-400uA.

    Also, any attempt to re-initialize the SAADC and start sampling again afterwards may lead to a fatal error, since the SAADC was actually not in a clean "uninitialized" state.

    Uninitializing the SAADC only after completing both sampling rounds (all 10 samples taken) eventually fixed the problem.

    My conclusions:

    • it is better to configure a 1:1 relationship between the number of analog input channels and SAADC_SAMPLES_IN_BUFFER. If there is a need to do some average filtering on the samples, it is better to do it in software and not rely on how the hardware would do it.
    • never attempt to "asynchronously" uninitialize the SAADC, if a conversion is still waiting for a sample (trigger) command to be complete: it will not uniinitialize the SAADC completely. The best place to uninitialize the SAADC is directly inside the SAADC event handler, at the end of a complete conversion (many of the "low power SAADC" Nordic examples do this).

    Good luck!

Related