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

  • 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

Related