It is crucial to first understand both the source(s) of energy consumption, and the influence software can have on these. A device’s total energy consumption is the sum of the power consumed by
- (i) the CPU,
- (ii) devices connected through peripherals, and
- (iii) various other passive components external to the CPU.
While (iii) is heavily influenced by a board’s hardware design, both (i) and (ii) are influenced by software. On one hand, the power management system in RIOT automatically sets the CPU in the deepest possible sleep mode when idle, which decreases energy consumption due to (i). On the other hand, user applications or other system modules are expected to deal with (ii) by managing the state of peripherals. For example, a network transceiver is managed by a MAC layer protocol module, while a sensor is managed by a sensing application.
At the microcontroller (MCU) level there are several ways to reduce power usage. Some methods for reducing power consumption are:
- Slow down the clocks, especially the main clock for the core (CPU and main logic) of the device.
- Reduce the supply voltage to the core of the device.
- Stop the main clock.
- Turn off power to the core.
- Turn off power to the clock generator(s).
- Turn off clocks to unused peripherals and peripheral bus controllers.
- Turn off power to unused peripherals.
- Put memory into a low-power (RAM data retained) “not instantly accessible” mode.
- Turn off power to unused sections of memory.
- Turn off power to the entire device except an external “wake-up” pin.
Each of these methods has consequences. The most significant consequence is that it can take up to several 10s of microseconds to recover or respond to an IRQ or event. Sometimes this is tolerable, other times it is not.
Most MCUs have some sort of “sleep modes”. These sleep modes invoke some combination of the methods above. Typically these modes progressively turn off clocks/power to sets of major functions of the device (ie. [CPU], [CPU+Main Logic], [CPU+Main Logic+Clock Gen], etc.). These sleep modes don’t generally address Active Mode clock speed and core voltage or turning off clocks and/or power to unused parts of the device. All of this is also passive - the application has to manage power on its own by setting each of these parameters when appropriate. This leads to non-portable application code - power management has to be implemented in the application for each specific MCU.
Understanding how the sleep modes and power reduction methods are implemented for each MCU, how all of the methods interact and affect a specific MCU, and how to architect the power management into the application is an arduous task that the application programmer shouldn’t have to go through.
Therefore the new power management api was born. A specific implementation called
pm_layered is provided to automatically switch to sleep modes. The
pm_layered module will be explained in the next sections.
The pm api provides three functions:
pm_reboot. As the name suggests,
pm_off powers down the node and stops all further operation.
pm_reboot initiates a reboot.
pm_set_lowest is responsible for choosing the minimal applicable power level.
The implementation is not using any weak definitions. Furthermore, we use defines to determine if the cpu provides their own implementations using
When used, the
pm_layered module superseeds the default
pm api, by introducing up to 4 different power modes that can be blocked or unblocked. It is up to the developer to choose the right number of these modes, as there might be more than 4 levels.
The core design element in the CPU’s power management system in RIOT is the so-called idle thread.
This thread is created during system startup and is scheduled when no other thread needs to run, i.e. when the CPU is idle.
pm_layered is built around a single function, which triggers the CPU to go into the lowest possible power state (sleep mode).
Calling this function is the only task the idle thread performs.
This mechanism works transparently for user applications and other system modules, as follows.
pm_layered implements the default mechanism which determines the lowest possible power state.
This module is shared by various CPU implementations.
pm_layered module uses a simple consensus mechanism (code-named Cascade), distributed between CPU peripherals and user threads.
Cascade is based on a strict hierarchy of power mode levels, that are defined on a per-CPU basis.
A lower power level means less power consumption.
The hierarchy is such that, if power level N is blocked, all power levels M ≤ N are implicitly also blocked, thus preventing the CPU to switch to any power level M ≤ N.
When using the pm_layered module, peripheral drivers or application code can each independently (un)block power modes that are (in)appropriate to run on.
For example, a UART driver can block all deep sleep CPU states, as these would prevent the UART peripheral to work correctly.
The UART driver would keep the deep sleep power state blocked while the UART peripheral is enabled, and unblock the power state once the peripheral is disabled.
Independently, when scheduled, the idle thread switches the CPU to the lowest power mode currently unblocked in
The interface consists of three functions defined in
drivers/include/periph/pm.h. An additional module
pm_layered allows for an automatic usage of pre-defined power levels on the CPU level. In this case, an additional function
pm_set is required to be implemented by the CPU.
pm_layered shares the same idea of pm, to function as a fallback and defines in the form of
CPUs and Families can define specific implementations.
A good example is the stm32_common and cortexm_common platform. The stm32_common CPU is providing pm_set and therefore is using pm_layered.