Optimizing Power on nRF5340 SoC Designs

Optimizing Power on nRF5340 SoC Designs

Objectives

The objective of this blog is to guide the user in the optimization of power on the nRF5340 SoC.  The goal is to understand what is needed from a software, hardware, and system level design to minimize the power in your device. This blog is targeted at the nRF5340 SoC, but this information is viable for any nRF52 Series devices except for where we address the use of the dual core in the nRF5340 SoC.  A test project was created using the Nordic UART Service (NUS) on the nRF5340 DK and a current measurement of 8uA was achieved during advertising.  These results should be the target of your design when completed.

Background

Power optimization is an important element of IoT designs, especially if they are battery powered. The use of nRF Connect SDK with Zephyr RTOS is a powerful tool in the implementation of power management.  The interfaces and APIs provided by the power management subsystem in the Zephyr RTOS are designed to be architecture and SoC independent. This enables power management implementations to be easily adapted to different SoCs and architectures. The following sections will describe this subsystem and how you can take advantage of the power saving features when targeting the nRF5340 SoC.

  Note this was done in nRF Connect SDK V1.6.0.  Please review the changes of Power states depreciated from nRF Connect SDK 1.5.1

Software

The power and clock management system in nRF5340 SoC is optimized for ultra-low power applications to ensure maximum power efficiency. The core of the power and clock management system is the power management unit (PMU).  The PMU automatically tracks the power and clock resources required by the different components in the system at any given time. To achieve the lowest power consumption possible, the PMU optimizes the system by evaluating power and clock requests, automatically starting and stopping clock sources, and choosing regulator operation modes.  There are some configuration options that enable you to implement extra power management policies. 

  1. Disabling serial logging with the use of CONFIG_LOG=n; CONFIG_SERIAL=n

In asymmetric multiprocessor systems, the most common way for different cores to cooperate is to use a shared memory-based communication.  This is done in the nRF5340 SoC using HCI_RPMSG transport, which is part of the Zephyr OpenAMP library.   Since the nRF5340 SoC is a dual core device, to minimize power you will need to disable the serial logging in both the application processor and HCI_RPMSG transport.  In the build for the nRF5340 SoC, the HCI_RPMSG protocol will automatically be built as a child image in your multi-image build.  To disable the serial logging, you will need to modify your prj.conf and hci_rpmsg.conf files.

For the prj.conf file make the changes seen below.

CONFIG_LOG=n
CONFIG_SERIAL=n

For the hci_rpmsg.conf, create a folder in your project named child_image.
Your folder structure should look like this:

Copy the prj.conf file from the following folder into the child_image folder and rename it hci_rpmsg.conf.
C:\..\ncs\v1.6.0\zephyr\samples\bluetooth\hci_rpmsg

Then make the modifications to the file as shown below

CONFIG_LOG=n #changed
CONFIG_SERIAL=n #added
 

  1. Enable the System and Device Power management in your prj.conf file.

There are two configurations needed for both system and device power management. 

CONFIG_PM: This option enables the board to implement extra power management
policies whenever the kernel becomes idle. The kernel informs the
power management subsystem of the number of ticks until the next kernel
timer is due to expire.

CONFIG_PM_DEVICE: This option enables the device power management interface.  The
interface consists of hook functions implemented by device drivers
that get called by the power manager application when the system
is going to suspend state or resuming from suspend state. This allows
device drivers to do any necessary power management operations
like turning off device clocks and peripherals. The device drivers
may also save and restore states in these hook functions.
 

To enable these, make the changes to the prj.conf file as seen below.

CONFIG_PM=y
CONFIG_PM_DEVICE=y
 
Make sure you also include pm.h and device.h to your project
#include <pm/pm.h>  
#include <device.h> 

  1. System Power Management

Power Management Subsystem can put an idle system in one of the supported power states, based on the selected power management policy and the duration of the idle time allotted by the kernel.  We can place the entire device into any of the 7 following power states:

PM_STATE_ACTIVE
    Runtime active state.
    The system is fully powered and active.

PM_STATE_RUNTIME_IDLE
    Runtime idle state.
    Runtime idle is a system sleep state in which all of the cores enter deepest possible idle state and wait for interrupts, no requirements for the devices, leaving them at the states where they are.

PM_STATE_SUSPEND_TO_IDLE
    Suspend to idle state.
    The system goes through a normal platform suspend where it puts all of the cores in deepest possible idle state and may puts peripherals into low-power states. No operating state is lost (ie. the cpu core does not lose execution context), so the system can go back to where it left off easily enough.

PM_STATE_STANDBY
    Standby state.
    In addition to putting peripherals into low-power states all non-boot CPUs are powered off. It should allow more energy to be saved relative to suspend to idle, but the resume latency will generally be greater than for that state. But it should be the same state with suspend to idle state on uniprocesser system.

PM_STATE_SUSPEND_TO_RAM
    Suspend to ram state.
    This state offers significant energy savings by powering off as much of the system as possible, where memory should be placed into the self-refresh mode to retain its contents. The state of devices and CPUs is saved and held in memory, and it may require some boot- strapping code in ROM to resume the system from it.

PM_STATE_SUSPEND_TO_DISK
    Suspend to disk state.
    This state offers significant energy savings by powering off as much of the system as possible, including the memory. The contents of memory are written to disk or other non-volatile storage, and on resume it’s read back into memory with the help of boot-strapping code, restores the system to the same point of execution where it went to suspend to disk.

PM_STATE_SOFT_OFF
    Soft off state.
    This state consumes a minimal amount of power and requires a large latency in order to return to runtime active state. The contents of system(CPU and memory) will not be preserved, so the system will be restarted as if from initial power-up and kernel boot.

 By using the pm_power_state_force() call, This function overrides decision made by PM policy forcing usage of given power state immediately.

The lowest power state is PM_STATE_SOFT_OFF.

When using the pm_power_state_force() function you need to set a constraint on the pm system to not enter the PM_STATE_SOFT_OFF state until you are ready.  By setting this constraint the disabled state cannot be selected by the Zephyr power management policies.

/*Prevent deep sleep (system off) from being entered*/
pm_constraint_set(PM_STATE_SOFT_OFF);

Then when you are ready to enable the PM_STATE_SOFT_OFF power state, for example,  you can enable it using the pm_power_state_force()  

pm_power_state_force((struct pm_state_info){PM_STATE_SOFT_OFF,0,0});
 

  1. Device Power Management

When adding peripherals to your project you will create device tree bindings for those peripherals.  Within your C code you will need to retrieve a device structure for a driver by name by getting its binding.  Below is an example of a call to get that device binding.

First start by making a node identifier for the device you are interested in. 
Here are some examples of node identifiers
 

/*Option 1: by node label*/
#define MY_SERIAL DT_NODELABEL(serial0)

/*Option 2: by alias*/
#define MY_SERIAL DT_ALIAS(my_serial)

/*Option 3: by chosen node*/
#define MY_SERIAL DT_CHOSEN(zephyr_console)

/*Option 4: by path*/
#define MY_SERIAL DT_PATH(soc, serial_40002000)

Once you have the node identifier, there are two methods of retrieving a device binding.
First method, you can use the device_get_binding();

const struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));
 
The second method is to use the DEVICE_DT_GET();
const struct device *uart_dev=DEVICE_DT_GET(MY_SERIAL);
 

Either way works: (please note that DEVICE_DT_GET() was added in Zephyr 2.5)
Once you have the binding you can set the device power state by using  pm_device_state_set();

int pm_device_state_set(conststructdevice*dev, uint32_t device_power_state,pm_device_cbcb,void*arg)

there are 5 power states available in the pm_device_state_set()  call

PM_DEVICE_STATE_ACTIVE
    device is in ACTIVE power state
    Normal operation of the device. All device context is retained.

PM_DEVICE_STATE_LOW_POWER
    device is in LOW power state
    Device context is preserved by the HW and need not be restored by the driver.

PM_DEVICE_STATE_SUSPEND
    device is in SUSPEND power state
    Most device context is lost by the hardware. Device drivers must save and restore or reinitialize any context lost by the hardware

PM_DEVCIE_STATE_FORCE_SUSPEND
    device is in force SUSPEND power state
    Driver puts the device in suspended state after completing the ongoing transactions and will not process any queued work or will not take any new requests for processing. Most device context is lost by the hardware. Device drivers must save and restore or reinitialize any context lost by the hardware.

PM_DEVICE_STATE_OFF
    device is in OFF power state
    Power has been fully removed from the device. The device context is lost when this state is entered, so the OS software will reinitialize the device when powering it back on

By following our example from above the code to set the UART to a low power state would look like the following:

rc=pm_device_state_set(uart_dev,PM_DEVICE_STATE_LOW_POWER,NULL,NULL);

note: we do not have a callback function or other arguments to pass, so they are NULL

  1. Disable unused peripherals

Based on the device tree of the board you are using; certain peripherals are enabled by default.  If you are not planning on using those peripherals in your project you can disable them in an overlay file.

For example;

On the nRF5340 DK both UART0 and UART1 are enabled.  If you do not plan on using UART1 in your design write an overlay file to disable UART1 as follows:

Create a file named nrf5340dk_nrf5340.overlay and place it in your project directory.  NOTE: (The name of the overlay file must match the name of the board you are using. So this must match the dts file it is associated with).

The file would need to contain the following
nrf5340dk_nrf5340.overlay

&uart1{

    status="disabled";
};

More information on "Setting Device Tree Overlays"  can be found in the Zephyr documentation.

System

Some configuration options will cause a higher internal voltage to be requested, which will be observed as an increase in power consumption.

  1. By default, the application core frequency is set to 64Mhz and is managed by the PMU as mentioned in the prior section. Keep in mind if you set it to a higher frequency you will see an increase in power. Setting the frequency of the application core's clock to 128 MHz, see Application core frequency scaling. The increased power consumption in this mode will also be observed when the CPU is sleeping, i.e. after executing the WFI (wait for interrupt) or WFE (wait for event) instructions. 
  2.  You can set a device power state for the QSPI when not in use, as described in the prior section on device power management. Under normal use the QSPI interface operates between 6-96Mhz.  The lower the frequency of operation the lower the power consumption of this peripheral
  3. The need to increase the TX power to +3dB you will need to request additional voltage. Requesting additional voltage on the VREGRADIO supply using VREQCTRL — Voltage request control to increase the TX power will add to the overall power consumption of the nRF5340
  4. When using the GPIOTE in a PORT or IN Event the Latency register makes it possible to trade off detection speed versus power. 
  5. The nRF5340 SoC is optimized for running at 3V (although the nRF5340 SoC will accept 5.5V). None of the internal circuitry runs at these voltages except the GPIO, so running at lower voltages will not result in reduced power consumption
  6. By default, the high voltage regulator is configured to also source external components from the VDD pin. To save power this feature should be disabled. For details, see High voltage mode
  7. All regulators operate in LDO mode by default. DC/DC mode can be enabled independently for each regulator.  The advantage of using a regulator in DC/DC mode is that the overall power consumption is reduced.
     

Hardware

  1. Be aware that single-pin GPIOTE interrupts may use more power than Port GPIOTE interrupts depending on the scenario.
  2. The default (from reset) state of all GPIO is as a disconnected input. Unless you’ve previously configured them to something else, you can leave unused pins in their default state and physically disconnected to draw no current.

Debugging

  1. When debugging the nRF5340 SoC you can use thread aware tools like Ozone that will help determine thread status and determine if you are residing in an idle thread. There is a good tutorial on "Thread aware debugging with nRF Connect SDK" that I recommend to review.
  2. Use the right tool to measure your current. Use the Power Profiler Kit II (PPK-2 ) to measure it.  With the PPK-2 you can log for long periods of time.  Please review "Log continuously for up to 500 days with the PPK2" for reference. 
  3. Make sure to power your design with a battery when measuring current, since a power supply may introduce unknown variables. 
  4. Here’s a list of the most common power scenarios and how to recognize them.
    1. You measure the current at about 4mA. The CPU is running either because you’re using it intentionally or that you haven’t set the CONFIG_PM=y in your prj.conf. With the CONFIG_PM set in your prj.conf file you should see the power drop to under 1mA.
    2. You think the part is in PM_STATE_SOFT_OFF mode, but the part is drawing much more than a few uA. The part isn’t actually in PM_STATE_SOFT_OFF mode and something has been left running. The CPU can be active or a peripheral still enabled.  Make sure your shutdown procedure actually turns everything off.  In this state the system will be restarted as if from initial power-up.  Cycle power to make sure the SWD port isn’t inadvertently on. Another common issue is being in debug interface (DIF) mode, either by having the debugger connected or not power cycling after programming.
    3. You think the part is a DEVICE_STATE_LOW_POWER mode, but the part is drawing much more than 10 uA. The part hasn’t set all peripherals in a DEVICE_STATE_LOW_POWER mode and something has been left running.  Check to make sure serial logging is disabled for both the application processor and the HCI_RPMSG interface.  Make sure your low power procedure actually places all peripherals in a low power state. While using the PPK-2 you should be able to see the SOC in a low power state and consuming less than 10uA while advertising.

SUMMARY

In summary, after reading this blog you should be able to optimize the power in your nRF5340 design using nRF Connect SDK. 
We did this by using the following methods:

  1. Disabling the serial Logging
  2. Enabling the Power Management in Zephyr RTOS
  3. Setting the peripherals into a desired power state
  4. Disabling unused peripherals
  5. Checking HW and System settings
  6. Checking your results using a PPK2 or Dynamic Power Analyzer (OTII, Keysight, etc)
blog_lowpower.zip