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

  • 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