HomeAbout UsContact Us

Zephyr BLE Power Optimization for Coin Cell Devices

By Jithin Tom
Published in Embedded Concepts
August 11, 2026
2 min read
Zephyr BLE Power Optimization for Coin Cell Devices

Table Of Contents

01
Core Power Management Strategy
02
Critical Zephyr Configuration
03
BLE Advertising Timing
04
Fixing CPU2 Power Draw on STM32WB
05
Device Runtime Power Management
06
Dynamic TX Power Control
07
Measurement and Validation
08
References
09
Frequently Asked Questions

Zephyr RTOS provides powerful power management capabilities that enable Bluetooth Low Energy applications to run for years on coin cell batteries — but only when configured correctly. Default Zephyr BLE samples often show sleep currents in the 10-20µA range between advertising peaks, far too high for multi-year coin cell operation. This article details the specific kernel, driver, and BLE stack configurations needed to approach the datasheet-typical STOP2 floor of ~1.8µA on the STM32WB55 while maintaining reliable connectivity.

Core Power Management Strategy

The fundamental approach uses Zephyr’s power management system to:

  1. Schedule brief advertising events using native BLE controller capabilities
  2. Enter deep sleep states (STOP2/STANDBY) between events
  3. Dynamically adjust BLE TX power based on link budget
  4. Ensure all peripherals and CPU cores fully suspend during sleep
+-------------------+ wake up & advertise +-------------------+
| DEEP SLEEP | --------------------------> | BLE ADVERTISING |
| (STOP2, ~1.8uA) | | (~2-5mA peak) |
+-------------------+ +-------------------+
^ |
| 1-10s interval |
+-------------------------------------------------+
return to sleep

Critical Zephyr Configuration

First, enable the power management subsystem in your prj.conf:

  • CONFIG_PM=y - Core system power management
  • CONFIG_PM_DEVICE=y - Device power management
  • CONFIG_PM_DEVICE_RUNTIME=y - Device runtime PM API

In modern Zephyr (v3.0+), system power states are defined in the Devicetree rather than Kconfig. For the STM32WB series, ensure your board’s Devicetree configures the CPU to use the STOP2 state (which provides full RAM retention at ~1.8µA typical per the STM32WB55 datasheet):

&cpu0 {
cpu-power-states = <&stop0 &stop1 &stop2>;
};

To auto-enable runtime PM for specific peripherals (like I2C or SPI), add this property to their devicetree nodes:

&i2c1 {
zephyr,pm-device-runtime-auto;
};

BLE Advertising Timing

A common anti-pattern in power optimization is attempting to manually start and stop advertising using a host workqueue. This actually increases power consumption by waking up the main CPU (CPU1) for every advertising event. Instead, configure the native advertising parameters and allow the BLE controller to manage the events autonomously while the host remains in deep sleep.

#include <zephyr/bluetooth/bluetooth.h>
/*
* BT_GAP_ADV_FAST_INT_MIN_2 = 0x00a0 (160 * 0.625ms = 100ms)
* BT_GAP_ADV_FAST_INT_MAX_2 = 0x00f0 (240 * 0.625ms = 150ms)
* For longer intervals to save power, use custom values in
* units of 0.625ms, e.g., 0x0640 = 1000ms.
*/
#define ADV_INTERVAL_MIN BT_GAP_ADV_FAST_INT_MIN_2
#define ADV_INTERVAL_MAX BT_GAP_ADV_FAST_INT_MAX_2
static struct bt_le_adv_param adv_param =
BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_CONNECTABLE |
BT_LE_ADV_OPT_USE_IDENTITY,
ADV_INTERVAL_MIN, ADV_INTERVAL_MAX, NULL);
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0x0a, 0x18),
};
int main(void)
{
int err;
err = bt_enable(NULL);
if (err) {
return 0;
}
/* Start advertising once. The controller handles the interval and sleeping. */
err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
return err;
}
/* Main thread can now sleep or perform other low-power tasks */
return 0;
}

Fixing CPU2 Power Draw on STM32WB

The primary reason Zephyr BLE applications on STM32WB fail to reach the datasheet STOP2 current floor is the CLK48 HSEM issue. Zephyr’s STM32WB clock driver permanently locks the CLK48 hardware semaphore during initialization to prevent CPU2 from disabling the clock needed by CPU1’s RNG/USB peripherals. However, it never releases this lock during CPU1 sleep, preventing CPU2 from entering its lowest power states.

Solution: Modify the clock control driver to release the HSEM when entering sleep and re-acquire upon wake:

// In clock_stm32_ll_common.c
static void clock_control_stm32_set_state(const struct device *dev,
uint32_t subsystem,
uint8_t state_val)
{
// ... existing code ...
if (state_val == LL_PWR_STATE_STOP2) {
// Release CLK48 HSEM to allow CPU2 to stop the clock
LL_HSEM_ReleaseLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID, 0);
} else if (state_val == LL_PWR_STATE_RUN) {
// Re-acquire CLK48 HSEM when waking up
LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID);
}
// ... rest of function ...
}

With this fix, CPU2 can now enter its low-power states between advertising events. The combined system current in STOP2 should approach the datasheet-typical value of ~1.8µA for the STM32WB55.

Device Runtime Power Management

Enable automatic power management for peripherals in their drivers:

// In I2C driver example
static int i2c_xy_init(const struct device *dev)
{
// ... hardware init ...
// Enable runtime PM - driver will manage suspend/resume
pm_device_runtime_enable(dev);
return 0;
}
// In driver operations, use get/put around accesses
static int i2c_xy_transfer(const struct device *dev,
struct i2c_msg *msgs, uint8_t num_msgs,
uint16_t addr)
{
int ret;
// Ensure peripheral is powered
ret = pm_device_runtime_get(dev);
if (ret < 0) {
return ret;
}
// Perform I2C transaction
ret = i2c_xy_core_transfer(dev, msgs, num_msgs, addr);
// Allow peripheral to be powered down asynchronously
pm_device_runtime_put_async(dev, K_NO_WAIT);
return ret;
}

Dynamic TX Power Control

Rather than maintaining a static high TX power, utilize Bluetooth 5.2 LE Power Control (LEPC) to allow the controller to autonomously request power level changes based on link quality, optimizing power without waking the host.

Enable it in your configuration:

CONFIG_BT_TRANSMIT_POWER_CONTROL=y

If LEPC is not supported and manual Vendor Specific (VS) commands are required, first enable dynamic control in your prj.conf:

CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y

Then use the standard Zephyr HCI API:

#include <zephyr/bluetooth/hci_vs.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/sys/byteorder.h>
static int set_tx_power(uint16_t handle, int8_t tx_power_level)
{
struct bt_hci_cp_vs_write_tx_power_level *cp;
struct net_buf *buf;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL, sizeof(*cp));
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle_type = BT_HCI_VS_LL_HANDLE_TYPE_CONN; /* Or BT_HCI_VS_LL_HANDLE_TYPE_ADV */
cp->handle = sys_cpu_to_le16(handle);
cp->tx_power_level = tx_power_level;
err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL, buf, NULL);
if (err) {
return err;
}
return 0;
}

Measurement and Validation

Validate your power optimization with these steps:

  1. Measure sleep current between advertising events using a PPK2 or similar
    • Target: ~1.8µA typical for STM32WB55 in STOP2 (per datasheet)
  2. Verify clock states using debug pins or register dumps
    • Confirm CPU1 enters STOP2
    • Confirm CPU2 enters STOP when advertising stops
  3. Check peripheral suspension via driver runtime PM counters
    • I2C, SPI, UART devices should suspend between uses
  4. Test connection stability at reduced TX power
    • Ensure link remains reliable at edge of range

References

  1. Zephyr Project Documentation, “Power Management”, https://docs.zephyrproject.org/latest/services/pm/index.html
  2. Zephyr Project Documentation, “Bluetooth Low Energy”, https://docs.zephyrproject.org/latest/connectivity/bluetooth/index.html
  3. STMicroelectronics, “STM32WB Series Reference Manual (RM0434)“, https://www.st.com/resource/en/reference_manual/rm0434-multiprotocol-wireless-32bit-mcu-armbased-cortexm4-with-fpu-bluetooth-lowenergy-and-802154-radio-solution-stmicroelectronics.pdf
  4. Zephyr Project Documentation, “Device Runtime Power Management”, https://docs.zephyrproject.org/latest/services/pm/device_runtime.html
  5. STMicroelectronics, “Building wireless applications with STM32WB Series (AN5289)“, https://www.st.com/resource/en/application_note/an5289-building-wireless-applications-with-stm32wb-series-microcontrollers-stmicroelectronics.pdf

Frequently Asked Questions

How can Zephyr achieve low-microamp BLE sleep current on coin cells?

By utilizing the native advertising parameters to allow the BLE controller to schedule brief advertising events autonomously, enabling deep sleep modes (STOP2) between events, and leveraging Bluetooth 5.2 LE Power Control for dynamic TX power. The STM32WB55 datasheet specifies ~1.8µA typical in STOP2.

Why does CPU2 clock management cause high BLE power consumption in Zephyr?

Zephyr permanently acquires the CLK48 HSEM to prevent CPU2 from disabling the clock needed for RNG/USB, but fails to release it during sleep, leaving CPU2's peripherals active and drawing ~10-20µA between advertising peaks instead of approaching the ~1.8µA STOP2 floor.

What device runtime power management settings are critical for coin cell BLE applications?

Enable CONFIG_PM=y and CONFIG_PM_DEVICE_RUNTIME=y. To automatically enable runtime PM for specific devices at boot, add the 'zephyr,pm-device-runtime-auto' property to their devicetree nodes. This allows drivers to autonomously manage peripheral power states.

Tags

zephyrblepower-optimizationcoin-cellstm32wb

Share


Previous Article
Cortex-M FPU Context Switching: Lazy vs Eager
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Zephyr thread stack overflow: debugging with runtime monitoring
Zephyr thread stack overflow: debugging with runtime monitoring
September 01, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media