nRF52840 current consumption in System ON and active modes of operation

Hello guys!

We are considering using nRF52840 SoC in our future product. In that sense, we wanted to evaluate the current consumption of nRF52840 SoC in different modes of operation.

For that purpose, we created a simple Zephyr-based application (find it attached) that will do the following:

  1. Keep the CPU in System ON mode with RAM retention (using k_cpu_atomic_idle() API, as explained in this thread)
  2. Periodically wake up the CPU with kernel/RTC timer and do some processing in active mode. According to this thread, Zephyr's kernel timer is using the RTC of nRF52840 SoC.

We were also trying to set the nRF52840 SoC in both Normal Voltage and High Voltage modes by using the following setup (the HW setup should be OK according to these threads thread_1, thread_2):

The application has been compiled and flashed using nRF Connect SDK v2.1.0 and it behaves as expected. This table summarizes measured current consumptions for different configurations and modes of operation compared to the values reported in nRF52840 product specification (see page 61, CPU running):

  

If we compare the current consumption values, we can see that our numbers are greater. This is especially true for the Normal Voltage mode with DC/DC regulator enabled (3.3mA vs 6.63mA)

Do you have any idea what we are missing here?

Thanks in advance for your time and efforts.

/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/zephyr.h>

static struct k_sem my_sem;

void my_work_handler(struct k_work *work)
{
    k_sem_give(&my_sem);
}

K_WORK_DEFINE(my_work, my_work_handler);

void my_timer_handler(void *unused)
{
    k_work_submit(&my_work);
}

K_TIMER_DEFINE(my_timer, my_timer_handler, NULL);

static volatile uint32_t active_cnt = 0;

void main(void)
{
    k_sem_init(&my_sem, 0, 1);
    /* start periodic timer */
    k_timer_start(&my_timer, K_SECONDS(3), K_SECONDS(3));

    uint8_t cnt = 0;

    /* Set output voltage from REG0 regulator stage to 1.8V */
    NRF_UICR->REGOUT0 = 0;

    /* Set REG0 stage */
    NRF_POWER->DCDCEN0 = 1;
    /* Set REG1 stage */
    NRF_POWER->DCDCEN = 1;


    printk("MAINREGSTATUS: 0x%X\n", NRF_POWER->MAINREGSTATUS);
    printk("DCDCEN: 0x%X\n", NRF_POWER->DCDCEN); 
    printk("DCDCEN0: 0x%X\n", NRF_POWER->DCDCEN0);

    for (;;) {

        unsigned int key = irq_lock();

        /*
         * Wait for semaphore from ISR; if acquired, do related work, then
         * go to next loop iteration (the semaphore might have been given
         * again); else, make the CPU idle.
         */

        if (k_sem_take(&my_sem, K_NO_WAIT) == 0) {

            irq_unlock(key);

            /* ... do processing */
            printk("Hello World! %s\n", CONFIG_BOARD);
            for(uint32_t i = 0; i < UINT16_MAX; i++){
                ++active_cnt;
            }

        } else {
            // printk("Preparing to enter idle...\n");
            cnt++;
            /* put CPU to sleep to save power */
            k_cpu_atomic_idle(key);
            printk("Counter: %d\n", cnt);
        }
    }
}

8231.prj.conf

Parents
  • Hello,

    Not sure if you have done it already, but I recommend to run through the devAcademy (and Lesson 7) if you haven't already:
    https://academy.nordicsemi.com/courses/nrf-connect-sdk-fundamentals/  

    You should not be calling k_cpu_atomic_idle or k_cpu_idle api directly, it should only be the idle thread responsible to enter sleep when there is no other work to do. This is also written in the documentation: 

    "In a regular system, the idle thread should be the only thread responsible for making the CPU idle and triggering any type of power management. However, in some more constrained systems, such as a single-threaded system, the only thread would be responsible for this if needed."
    https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/kernel/services/scheduling/index.html#c.k_cpu_idle  

    I guess in your example project you are only using a single-thread so it may work, but I mention it in any case.

    In terms of running with or without DCDC this is controlled through the following two kconfig options:

    CONFIG_BOARD_ENABLE_DCDC=y/n
    CONFIG_BOARD_ENABLE_DCDC_HV=y/n

    You can find they are by default enabled for the nRF52840DK board files here:
    https://github.com/nrfconnect/sdk-zephyr/blob/main/boards/arm/nrf52840dk_nrf52840/Kconfig  

    Make sure to power cycle the board (nRF52840 in specific) after programming and before performing any current measurements.

    Best regards,
    Kenneth

  • I guess in your example project you are only using a single-thread so it may work, but I mention it in any case.

    Yes, we are using a simple single-threaded sample application.

    We could simplify our sample application and use the k_msleep() API instead of calling k_cpu_atomic_idle():

    while(1){
       /* Do some work here. CPU will be in Active mode */
       
       /* Sleep in order to set the CPU in System ON mode */
       k_msleep(3000);
    }

    However, this did not change measured current consumption.

    In terms of running with or without DCDC this is controlled through the following two kconfig options:

    CONFIG_BOARD_ENABLE_DCDC=y/n
    CONFIG_BOARD_ENABLE_DCDC_HV=y/n

    Thanks for that info. Even if we enable/disable DC/DC converter through the Kconfig options instead of explicitly changing the registers with NRF_POWER->DCDCEN0 and NRF_POWER->DCDCEN the current consumption in the CPU active state did not change.

    Make sure to power cycle the board (nRF52840 in specific) after programming and before performing any current measurements.

    We power-cycle the nRF52840-DK board after programming because we need to change the position of some switches on the nRF52840-DK before we make the current measurements.

    • Would you mind using the code we shared with you and do the current measurements on your side?
    • What we are missing in our application in order to get ~3.3mA current consumption in the Normal Voltage mode with DC/DC regulator enabled?

    Thanks in advance.

    Sincerely,
    Bojan.

  • Hello,

    How are you measuring the active current here exactly? Are you measuring the max current or the average over some time? If you are measuring max current you could be measuring peaks and noise that is not directly from the CPU current. 

    Can you share some PPK plots that we can look at (with a description of what is running and configuration)?

    Kenneth

  • Hello,  

    How are you measuring the active current here exactly? Are you measuring the max current or the average over some time?

    What I'm measuring is the average current over the time the CPU is active.

    Can you share some PPK plots that we can look at (with a description of what is running and configuration)?

    Below you can find the PPK II screenshots measuring the current consumption when the CPU is in active mode. If you analyze the code I shared here in my initial post, you will see that the CPU is normally in System ON mode waking up every 3 seconds with the kernel/RTC timer and doing the following:

    /* ... do processing */
    printk("Hello World! %s\n", CONFIG_BOARD);
    for(uint32_t i = 0; i < UINT16_MAX; i++){
        ++active_cnt;
    }

    What we are measuring as current consumption is the average current in active mode.

    Here are the PPK II screenshots:

  • Can you try to measure with a regular multimeter on P22 instead for comparison? Maybe also increase the duration to several seconds also for comparison.

    Kenneth

Reply Children
  • Can you try to measure with a regular multimeter on P22 instead for comparison?

    If we use a regular multimeter on P22 from where we are gonna supply the CPU?
    When using PPK II, it is configured in source mode - we give a 1.8V power supply through the same pin while measuring current consumption.

  • Good point, can you add a regular multimeter on P22 (while being powered from PPK2) for comparison?

    Kenneth

  • Should I measure the voltage across the pins of P22? Should I solder some shunt resistor on R90 before doing voltage measurements?

  • You put the multimeter in ampere mode, to measure current. Normally you do it like this:
    https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/hw_measure_ampmeter.html

    But if you are using PPK2 to supply power, then something like this:

    Kenneth

  • Hello, Kenneth.

    I did not have any multimeter at my disposal so I decided to make the current consumption measurement using the Otii Arc tool instead of PPK II.

    In order to stay longer in CPU active mode, I modified the sample application the following way:

    • RTC timer will wake up the CPU every 5 seconds

    • When awake (in CPU active mode), the CPU will stay in active mode until the Button_1 on nRF52840-DK is pressed

    Find attached the new application code below. What I get for the current consumption in CPU active mode is the same as before:

    Regards,
    Bojan

    /*
     * Copyright (c) 2012-2014 Wind River Systems, Inc.
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr/zephyr.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/gpio.h>
    
    /*
     * Get button configuration from the devicetree sw0 alias. This is mandatory.
     */
    #define SW0_NODE	DT_ALIAS(sw0)
    #if !DT_NODE_HAS_STATUS(SW0_NODE, okay)
    #error "Unsupported board: sw0 devicetree alias is not defined"
    #endif
    static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios,
    							      {0});
    static struct gpio_callback button_cb_data;
    
    static volatile uint32_t active_cnt = 0;
    static volatile bool stay_awake = true;
    
    void button_pressed(const struct device *dev, struct gpio_callback *cb,
    		    uint32_t pins)
    {
    	printk("Button pressed\n");
        stay_awake = false;
    }
    
    static struct k_sem my_sem;
    
    void my_work_handler(struct k_work *work)
    {
        k_sem_give(&my_sem);
    }
    
    K_WORK_DEFINE(my_work, my_work_handler);
    
    void my_timer_handler(void *unused)
    {
        k_work_submit(&my_work);
    }
    
    K_TIMER_DEFINE(my_timer, my_timer_handler, NULL);
    
    static void configure_button(void) {
    	int ret;
    
    	if (!device_is_ready(button.port)) {
    		printk("Error: button device %s is not ready\n",
    		       button.port->name);
    		return;
    	}
    
    	ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
    	if (ret != 0) {
    		printk("Error %d: failed to configure %s pin %d\n",
    		       ret, button.port->name, button.pin);
    		return;
    	}
    
    	ret = gpio_pin_interrupt_configure_dt(&button,
    					      GPIO_INT_EDGE_TO_ACTIVE);
    	if (ret != 0) {
    		printk("Error %d: failed to configure interrupt on %s pin %d\n",
    			ret, button.port->name, button.pin);
    		return;
    	}
    
    	gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
    	gpio_add_callback(button.port, &button_cb_data);
    	// printk("Set up button at %s pin %d\n", button.port->name, button.pin);
    }
    
    static void disconnect_button(void){
        int ret = gpio_pin_configure_dt(&button, GPIO_DISCONNECTED);
        if (ret != 0) {
            printk("Error %d: failed to configure %s pin %d\n",
                ret, button.port->name, button.pin);
            return;
        }    
    }
    
    void main(void)
    {
        k_sem_init(&my_sem, 0, 1);
        /* start periodic timer */
        k_timer_start(&my_timer, K_SECONDS(5), K_SECONDS(5));
    
        /* Set output voltage from REG0 regulator stage to 1.8V */
        // NRF_UICR->REGOUT0 = 0;
    
        printk("MAINREGSTATUS: 0x%X\n", NRF_POWER->MAINREGSTATUS);
        printk("DCDCEN: 0x%X\n", NRF_POWER->DCDCEN);
        printk("DCDCEN0: 0x%X\n", NRF_POWER->DCDCEN0);
    
        for (;;) {
    
            unsigned int key = irq_lock();
    
            /*
             * Wait for semaphore from ISR; if acquired, do related work, then
             * go to next loop iteration (the semaphore might have been given
             * again); else, make the CPU idle.
             */
    
            if (k_sem_take(&my_sem, K_NO_WAIT) == 0) {
    
                irq_unlock(key);
    
                /* ... do processing */
                printk("I'm awake!\n");
                configure_button();
                while(stay_awake){
                    ++active_cnt;
                }
                stay_awake = true;
                printk("going to sleep...\n");
    
            } else {
                disconnect_button();
                /* put CPU to sleep to save power */
                k_cpu_atomic_idle(key);
            }
        }
    }
    

    51437.prj.conf

Related