Table of Contents

Tips for Low Power using Zephyr on Nordic Semiconductor

After working on several low-powered projects with both the nRF9160 (LTE) and nRF52840 (Bluetooth), I have compiled some key tips to make sure you are getting the lowest power consumption from the chip.

For nRF52840 projects, I have fully populated boards that draw ~5-6 uA @ 3.0V in sleep mode. In limited test modes, I have been able to get down to 3-4uA for the nRF52840 only. For nRF9160 projects, I have a fully populated board that draws ~100 uA @ 5V in sleep mode. In limited test modes, I have been able to get current draw down to 10 uA for the nRF9160 only.

The general rules are as follows:

Project Config

In the your main project's prj.conf file, you must enable power management:

CONFIG_PM=y
CONFIG_PM_DEVICE=y

MCUboot Config

If you are using the MCUboot bootloader (very likely the case if your device supports firmware updates), you need to change the configuration for the serial port. By default, the hardware serial port is enabled with MCUboot, and this will increase current consumption by about 600 uA.

This can be done in two ways. The first way is to manually change the MCUboot project config in the SDK.

Find the prj.conf for MCUboot here: (adjust the SDK base path and SDK version as needed)

<SDK BASE>\2.2.0\bootloader\mcuboot\boot\zephyr

Add the following line at the end of the file:

CONFIG_SERIAL=n

If you change SDK versions, you will need to change this again in that SDK path. As another option, you can add this config option as a build argument.

west build -b board_name -- -Dmcuboot_CONFIG_SERIAL=n

Board Device Tree / Overlay

UART1

Uart1 will need to be disabled in the device tree in addition to project config. The follow code can be added in the root board device tree file or in a project based overlay file. To make a project based overlay file, create a “boards” folder in the base path of the project and add a file named <board_name>.overlay. The contents of this file should include the following:

&uart1 {
    status = "disabled";
};

UART0/1 Pins

Confirm that the pins which are assigned to UART0/UART1 in the device tree are not physically to other circuitry on your board. This can cause about 600-700uA current draw increase, even if the UART module is not enabled or configured to be disabled elsewhere.

Check <project_directory>/build/zephyr/zephyr.dts to examine the final device tree output and confirm the pins that are used for the UART, the double check your schematic.

Firmware

Enable power management

In main(), include the following lines after device initialization to enable power management. Note SDK versions, these functions have changed over time. The firmware will not compile if you use the wrong functions.

// IMPORTANT: This code for SDK 1.7.0 or SDK 1.9.0
err = pm_device_state_set(device_get_binding(device_get_binding(zephyr_console), PM_DEVICE_STATE_FORCE_SUSPEND); 
// IMPORTANT: This code for SDK 2.2.0 and greater
err = pm_device_action_run(DEVICE_DT_GET(DT_CHOSEN(zephyr_console)), PM_DEVICE_ACTION_SUSPEND);

Threads

All application logic must be implemented in a thread. It can be main() or other defined threads.

Threads must be suspended as much as possible. The device will only enter power management / low power mode when all threads are suspended and the idle thread takes over.

Use k_sleep() and k_thread_suspend() to manage thread suspension.

Sleeping Thread Example

This thread will blink an LED continuously while remaining asleep between LED control operations.

void led_tasks(void) {
    while(true) {
        LedOn();
        k_sleep(K_MSEC(100));
        LedOff();
        k_sleep(K_MSEC(3000));
    }
}
K_THREAD_DEFINE(led_task_name, THREAD_STACKSIZE, led_tasks, NULL, NULL, NULL, THREAD_PRIORITY_STD, 0, 0);

Suspending Thread Example

When a thread is waiting for an external event to happen before continuing, it is best for the thread to suspend itself indefinitely until it is resumed by another thread.

This thread will wait for another function to wake it up and blink the LED one time before going back to sleep indefinitely. It will only be woken up again when another thread wants to blink the LED.

// blink the LED just once
void led_blink(COLORS_E color)
{
  led_blink_color = color;
  k_thread_resume(LED_blink_thread); 
}
// thread to manage LED blinking
void LED_blink_thread_fn(void) 
{
    while(true) {
      // keep thread suspended until another thread sets the blink color
      while ( led_blink_color == COLOR_OFF) {
        k_thread_suspend(LED_blink_thread);
      }
      led_set_color(led_blink_color);
      k_sleep(K_MSEC(100)); // LED on time
      led_set_color(COLOR_OFF);
      k_thread_suspend(LED_blink_thread);
    }
}
K_THREAD_DEFINE(LED_blink_thread, THREAD_STACKSIZE, LED_blink_thread_fn, NULL, NULL, NULL, THREAD_PRIORITY_STD, 0, 50);

A semaphore could also be used as a way to wait for a blocking event from another thread.