The nRF52 Power Management module provides battery protection features to prevent over-discharge, minimise likelihood of brownout and flash corruption conditions existing, and enable safe voltage-based recovery.
Shutdown reason codes (stored in GPREGRET2): | Code | Name | Description | |------|------|-------------| | 0x00 | NONE | Normal boot / no previous shutdown | | 0x4C | LOW_VOLTAGE | Runtime low voltage threshold reached | | 0x55 | USER | User requested powerOff() | | 0x42 | BOOT_PROTECT | Boot voltage protection triggered |
| Board | Implemented | LPCOMP wake | VBUS wake |
|---|---|---|---|
Seeed Studio XIAO nRF52840 (xiao_nrf52) |
Yes | Yes | Yes |
RAK4631 (rak4631) |
Yes | Yes | Yes |
Heltec T114 (heltec_t114) |
Yes | Yes | Yes |
| GAT562 Mesh Watch13 | Yes | Yes | Yes |
| Promicro nRF52840 | No | No | No |
| RAK WisMesh Tag | No | No | No |
| Heltec Mesh Solar | No | No | No |
| LilyGo T-Echo / T-Echo Lite | No | No | No |
| SenseCAP Solar | Yes | Yes | Yes |
| WIO Tracker L1 / L1 E-Ink | No | No | No |
| WIO WM1110 | No | No | No |
| Mesh Pocket | No | No | No |
| Nano G2 Ultra | No | No | No |
| ThinkNode M1/M3/M6 | No | No | No |
| T1000-E | No | No | No |
| Ikoka Nano/Stick/Handheld (nRF) | No | No | No |
| Keepteen LT1 | No | No | No |
| Minewsemi ME25LS01 | No | No | No |
Notes:
The power management functionality is integrated into the NRF52Board base class in src/helpers/NRF52Board.cpp. Board variants provide hardware-specific configuration via a PowerMgtConfig struct and override initiateShutdown(uint8_t reason) to perform board-specific power-down work and conditionally enable voltage wake (LPCOMP + VBUS).
A static constructor with priority 101 in NRF52Board.cpp captures the RESETREAS and GPREGRET2 registers before:
This ensures we capture the true reset reason before any initialisation code runs.
To enable power management on a board variant:
Enable in platformio.ini:
-D NRF52_POWER_MANAGEMENT
Define configuration in variant.h:
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV)
#define PWRMGT_LPCOMP_AIN 7 // AIN channel for voltage sensing
#define PWRMGT_LPCOMP_REFSEL 2 // REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16)
Implement in board .cpp file:
#ifdef NRF52_POWER_MANAGEMENT
const PowerMgtConfig power_config = {
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
};
void MyBoard::initiateShutdown(uint8_t reason) {
// Board-specific shutdown preparation (e.g., disable peripherals)
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
reason == SHUTDOWN_REASON_BOOT_PROTECT);
if (enable_lpcomp) {
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
}
enterSystemOff(reason);
}
#endif
void MyBoard::begin() {
NRF52Board::begin(); // or NRF52BoardDCDC::begin()
// ... board setup ...
#ifdef NRF52_POWER_MANAGEMENT
checkBootVoltage(&power_config);
#endif
}
For user-initiated shutdowns, powerOff() remains board-specific. Power management only arms LPCOMP for automated shutdown reasons (boot protection/low voltage).
Declare override in board .h file:
#ifdef NRF52_POWER_MANAGEMENT
void initiateShutdown(uint8_t reason) override;
#endif
The LPCOMP (Low Power Comparator) is configured to:
VBUS wake is enabled via the POWER peripheral USBDETECTED event whenever configureVoltageWake() is used. This requires USB VBUS to be routed to the nRF52 (typical on nRF52840 boards with native USB).
LPCOMP Reference Selection (PWRMGT_LPCOMP_REFSEL): | REFSEL | Fraction | VBAT @ 1M/1M divider (VDD=3.0-3.3) | VBAT @ 1.5M/1M divider (VDD=3.0-3.3) | |--------|----------|------------------------------------|--------------------------------------| | 0 | 1/8 | 0.75-0.82 V | 0.94-1.03 V | | 1 | 2/8 | 1.50-1.65 V | 1.88-2.06 V | | 2 | 3/8 | 2.25-2.47 V | 2.81-3.09 V | | 3 | 4/8 | 3.00-3.30 V | 3.75-4.12 V | | 4 | 5/8 | 3.75-4.12 V | 4.69-5.16 V | | 5 | 6/8 | 4.50-4.95 V | 5.62-6.19 V | | 6 | 7/8 | 5.25-5.77 V | 6.56-7.22 V | | 7 | ARef | - | - | | 8 | 1/16 | 0.38-0.41 V | 0.47-0.52 V | | 9 | 3/16 | 1.12-1.24 V | 1.41-1.55 V | | 10 | 5/16 | 1.88-2.06 V | 2.34-2.58 V | | 11 | 7/16 | 2.62-2.89 V | 3.28-3.61 V | | 12 | 9/16 | 3.38-3.71 V | 4.22-4.64 V | | 13 | 11/16 | 4.12-4.54 V | 5.16-5.67 V | | 14 | 13/16 | 4.88-5.36 V | 6.09-6.70 V | | 15 | 15/16 | 5.62-6.19 V | 7.03-7.73 V |
Important: For boards with a voltage divider on the battery sense pin, LPCOMP measures the divided voltage. Use:
VBAT_threshold ≈ (VDD * fraction) * divider_scale, where divider_scale = (Rtop + Rbottom) / Rbottom (e.g., 2.0 for 1M/1M, 2.5 for 1.5M/1M, 3.0 for XIAO).
The power management code checks whether SoftDevice is enabled and uses the appropriate API:
sd_power_* functionsThis ensures compatibility regardless of BLE stack state.
Power management status can be queried via the CLI:
| Command | Description |
|---|---|
get pwrmgt.support |
Returns "supported" or "unsupported" |
get pwrmgt.source |
Returns current power source - "battery" or "external" (5V/USB power) |
get pwrmgt.bootreason |
Returns reset and shutdown reason strings |
get pwrmgt.bootmv |
Returns boot voltage in millivolts |
On boards without power management enabled, all commands except get pwrmgt.support return:
ERROR: Power management not supported
When MESH_DEBUG=1 is enabled, the power management module outputs:
DEBUG: PWRMGT: Reset = Wake from LPCOMP (0x20000); Shutdown = Low Voltage (0x4C)
DEBUG: PWRMGT: Boot voltage = 3450 mV (threshold = 3300 mV)
DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD)