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

SAADC scan + burst & oversmaple

  • SDK14.2
  • Segger Embedded Studio
  • nRF52 development board

According to this post https://devzone.nordicsemi.com/f/nordic-q-a/20556/how-does-the-saadc-scan-mode-use-the-burst-1 the SAADC can use multichannel scan mode with burst enabled  on all channels and oversampling set on any other value other than disabled. The product specification of the nRF52832 is a bit confussion as stated by the linked post.With the necessary changes I got oversamping with burst on three channels working as expected.

However the power consumption direclty increases after the first ADC conversion to roughly 1mA and doesn't go back to the expected 2uA when I only measure a single channel:

Multichannel

Single channel:

From what I read on the forum, this is due to the easyDMA staying active after the conversion completes. I've tried disabling the SAADC peripheral and the ADC channels after the convertion completes, as is done with the low power SAADC multichannel example on Nordic's github, but nothing disables the easyDMA's current consumption.

So my question is if there's a remedy for the power consumption after a scan with burst and oversampling or do I need a different approach to quickly measure all channels?

Code with which I've been testing:

saadc_test.zip

  • Hi,

    There should not be any additional current consumption between the sampling events when you have disabled the SAADC peripheral (calling the nrf_drv_saadc_uninit()). In this case, the SAADC peripheral should not keep the DMA logic active.

    I have not been able to test your code. There are some asserts when in nrf_drv_saadc.c (I use the one you included in saadc_test.zip). Can you make sure to include all modified SDK files and check that they are sufficient in order to run the example on the nRF52832 DK with an otherwise unmodified SDK 14.2?

  • Thank you for your reply Einar,

    In the example the definitions SAMPLES_IN_BUFFER was set  to 1, but it should have been 3, since I used three channels.That's why you got an assert.nRF5_SDK_14.2.zip

    In the saadc callback I un-init the saadc and all channels, the ADC results are as expected and there are no asserts. Still there is a continous 1mA current consumption when I measure with a power profiler after the first saadc initialization.

    Here is the edited project with a stripped down sdk 14.2. The project can be found under nRF5_SDK_14.2\examples\peripheral\saadc_burst.

  • I have tested your code and see the same behavior. I do not have an explanation for it, but we have seen similar issues before. I will look into it and hopefully get back to you tomorrow.

  • Hi,

    I do not have an explanation for this behavior at this point, but I am tempted to believe that there is a hardware limitation/issue of some sort. Your driver modifications come from this post I see. I do not have a workaround either (other than "don't do it").

    Here is you example in a slightly simplified variant where it is easy to see the difference in current consumption between 1 and 2 channels by setting the SAADC_CHANNELS define accordingly (for my own reference).

    /**
     * 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_drv_clock.h"
    #include "app_timer.h"
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    APP_TIMER_DEF(m_app_timer_id);
    
    #define DEAD_BEEF                       0xDEADBEEF                                   /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */
    
    #define SAADC_CHANNELS 2                // 1 or 2
    
    #if !(SAADC_CHANNELS == 1 || SAADC_CHANNELS == 2)
        #error "Set SAADC_CHANNELS to 1 or 2"
    #endif
    
    #define SAMPLES_IN_BUFFER SAADC_CHANNELS
    
    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 channel_init(uint8_t channel);
    
    /**@brief Callback function for asserts in the SoftDevice.
     *
     * @details This function will be called in case of an assert in the SoftDevice.
     *
     * @warning This handler is an example only and does not fit a final product. You need to analyze
     *          how your product is supposed to react in case of Assert.
     * @warning On assert from the SoftDevice, the system can only recover on reset.
     *
     * @param[in]   line_num   Line number of the failing ASSERT call.
     * @param[in]   file_name  File name of the failing ASSERT call.
     */
    void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
    {
        app_error_handler(DEAD_BEEF, line_num, p_file_name);
    }
    
    
    void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
    {
        int16_t voltage;
        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++)
            {
                voltage = (p_event->data.done.p_buffer[i] * 3600) / 4096;
                NRF_LOG_INFO("CH:%u, %d, %i mV", i, p_event->data.done.p_buffer[i], voltage);
            }
            m_adc_evt_counter++;
    
            nrf_drv_saadc_uninit();              // Unintialize SAADC to disable EasyDMA and save power
        }
    }
    
    
    void saadc_init(void)
    {
        ret_code_t err_code;
        nrf_drv_saadc_config_t saadc_config =
        {
          SAADC_RESOLUTION_VAL_12bit,       ///< Resolution configuration.
          NRF_SAADC_OVERSAMPLE_4X,         ///< Oversampling configuration. // NRF_SAADC_OVERSAMPLE_DISABLED    NRF_SAADC_OVERSAMPLE_4X
          7,                                ///< Interrupt priority.
          false,                            ///< Indicates if low power mode is active.
        };
    
        err_code = nrf_drv_saadc_init(&saadc_config, saadc_callback);
        APP_ERROR_CHECK(err_code);
    
        // https://devzone.nordicsemi.com/f/nordic-q-a/11080/nrf52-adc-sampling-multiple-signals
        // https://devzone.nordicsemi.com/f/nordic-q-a/26659/saacd-scan-oversample
    
        nrf_saadc_channel_config_t channel_config_0 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
        err_code = nrf_drv_saadc_channel_init(0, &channel_config_0);
        APP_ERROR_CHECK(err_code);
    
    #if (SAADC_CHANNELS == 2)
        nrf_saadc_channel_config_t channel_config_1 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
        err_code = nrf_drv_saadc_channel_init(1, &channel_config_1);
        APP_ERROR_CHECK(err_code);
    #endif
    
        // Enable burst mode
        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 app_timeout_handler(void * p_context)
    {
        uint32_t ret_code;
        UNUSED_PARAMETER(p_context);
    
        saadc_init();
        ret_code = nrf_drv_saadc_sample();
    }
    
    
    /**
     * @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();
    
        err_code = nrf_drv_clock_init();
        APP_ERROR_CHECK(err_code);
        nrf_drv_clock_lfclk_request(NULL);
    
        err_code = app_timer_init();
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_INFO("SAADC HAL simple example.");
    
        // Start application timers.
        err_code = app_timer_create(&m_app_timer_id, APP_TIMER_MODE_REPEATED, app_timeout_handler);
        APP_ERROR_CHECK(err_code);
        err_code = app_timer_start(m_app_timer_id, APP_TIMER_TICKS(5000), NULL);
        APP_ERROR_CHECK(err_code);
    
        while (1)
        {
            if (!NRF_LOG_PROCESS())
            {
                __SEV();
                __WFE();
                __WFE();
            }
        }
    }
    
    
    /** @} */
    

  • I too am using the SAADC in the same manner (i.e. multiple channels, oversample, and burst enabled) however I do not yet have the equipment to measure the power usage.  This power issue is very concerning to me and this is possible a show stopper for us.  

    If you are looking for a workaround I just have this one idea:  Do a single-shot conversion after you get all your samples and you do all the uninit() calls.  By doing a single-shot conversion on a single channel with burst disabled I would expect the SAADC to release DMA resources and power down normally. 

Related