Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions docs/nrf52_power_management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# nRF52 Power Management

## Overview

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.

## Features

### Boot Voltage Protection
- Checks battery voltage immediately after boot and before mesh operations commence
- If voltage is below a configurable threshold (e.g., 3400mV), the device enables LPCOMP wake and enters protective shutdown (SYSTEMOFF)
- Prevents boot loops when battery is critically low
- Skipped when external power (USB VBUS) is detected

### LPCOMP Wake
- Configures the nRF52's Low Power Comparator (LPCOMP) before entering SYSTEMOFF
- Device automatically wakes when battery voltage rises above recovery threshold

### Early Boot Register Capture
- Captures RESETREAS (reset reason) and GPREGRET2 (shutdown reason) before SystemInit() clears them
- Allows firmware to determine why it booted (cold boot, watchdog, LPCOMP wake, etc.)
- Allows firmware to determine why it last shut down (user request, low voltage, boot protection)

### Shutdown Reason Tracking
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 |

## Supported Boards

Currently enabled:
- **Seeed Studio XIAO nRF52840** (`xiao_nrf52`)
- **ProMicro nRF52840** (`promicro`)

Compatible boards (can enable with configuration):
- RAK4631
- RAK WisMesh Tag
- Heltec T114
- Heltec Mesh Solar
- LilyGo T-Echo / T-Echo Lite
- SenseCAP Solar
- WIO Tracker L1 / L1 E-Ink
- WIO WM1110
- Mesh Pocket
- Nano G2 Ultra
- ThinkNode M1/M3/M6
- T1000-E
- Ikoka Nano/Stick/Handheld (nRF)
- Keepteen LT1
- Minewsemi ME25LS01

## Technical Details

### Architecture

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 can override `prepareForShutdown()` for board-specific shutdown preparation.

### Early Boot Capture

A static constructor with priority 101 in `NRF52Board.cpp` captures the RESETREAS and GPREGRET2 registers before:
- SystemInit() (priority 102) - which clears RESETREAS
- Static C++ constructors (default priority 65535)

This ensures we capture the true reset reason before any initialisation code runs.

### Board Implementation

To enable power management on a board variant:

1. **Enable in platformio.ini**:
```ini
-D NRF52_POWER_MANAGEMENT
```

2. **Define configuration in variant.h**:
```c
#define PWRMGT_VOLTAGE_BOOTLOCK 3400 // Won't boot below this voltage (mV)
#define PWRMGT_LPCOMP_AIN 7 // AIN channel for voltage sensing
#define PWRMGT_LPCOMP_REFSEL 2 // VDD fraction (0=1/8, 1=2/8, ..., 6=7/8)
```

3. **Implement in board .cpp file**:
```cpp
#ifdef NRF52_POWER_MANAGEMENT
const PowerMgtConfig power_config = {
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
.lpcomp_ref_eighths = PWRMGT_LPCOMP_REFSEL,
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
};

void MyBoard::prepareForShutdown() {
// Board-specific shutdown preparation (e.g., disable peripherals)
}
#endif

void MyBoard::begin() {
NRF52Board::begin(); // or NRF52BoardDCDC::begin()
// ... board setup ...

#ifdef NRF52_POWER_MANAGEMENT
checkBootVoltage(&power_config);
#endif
}

void MyBoard::powerOff() {
#ifdef NRF52_POWER_MANAGEMENT
prepareForShutdown();
configureLpcompWake(power_config.lpcomp_ain_channel, power_config.lpcomp_ref_eighths);
enterSystemOff(SHUTDOWN_REASON_USER);
#else
sd_power_system_off();
#endif
}
```

4. **Declare override in board .h file**:
```cpp
#ifdef NRF52_POWER_MANAGEMENT
void prepareForShutdown() override;
#endif
```

### LPCOMP Configuration

The LPCOMP (Low Power Comparator) is configured to:
- Monitor the specified AIN channel (0-7 corresponding to P0.02-P0.05, P0.28-P0.31)
- Compare against VDD fraction reference (REFSEL: 0=1/8, 1=2/8, ..., 6=7/8)
- Detect UP events (voltage rising above threshold)
- Use 50mV hysteresis for noise immunity
- Wake the device from SYSTEMOFF when triggered

**LPCOMP Reference Selection (PWRMGT_LPCOMP_REFSEL)**:
| Value | Fraction | At VDD=3.0V | At VDD=3.3V |
|-------|----------|-------------|-------------|
| 0 | 1/8 | 375mV | 412mV |
| 1 | 2/8 | 750mV | 825mV |
| 2 | 3/8 | 1125mV | 1237mV |
| 3 | 4/8 | 1500mV | 1650mV |
| 4 | 5/8 | 1875mV | 2062mV |
| 5 | 6/8 | 2250mV | 2475mV |
| 6 | 7/8 | 2625mV | 2887mV |

**Important**: For boards with a voltage divider on the battery sense pin (e.g., XIAO nRF52840 with 3:1 divider), the LPCOMP measures the divided voltage. The wake threshold in battery millivolts is: `(VDD * fraction) * divider_ratio`.

### SoftDevice Compatibility

The power management code checks whether SoftDevice is enabled and uses the appropriate API:
- When SD enabled: `sd_power_*` functions
- When SD disabled: Direct register access (NRF_POWER->*)

This ensures compatibility regardless of BLE stack state.

## CLI Commands

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
```

## Debug Output

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 = 3400 mV)
DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD)
```

## Phase 2 (Planned)

- Runtime voltage monitoring
- Voltage state machine (Normal -> Warning -> Critical -> Shutdown)
- Configurable thresholds
- Load shedding callbacks for power reduction
- Deep sleep integration
- Scheduled wake-up
- Extended sleep with periodic monitoring

## References

- [nRF52840 Product Specification - POWER](https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html)
- [nRF52840 Product Specification - LPCOMP](https://infocenter.nordicsemi.com/topic/ps_nrf52840/lpcomp.html)
- [SoftDevice S140 API - Power Management](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.1.0/group__nrf__sdm__api.html)
8 changes: 8 additions & 0 deletions src/MeshCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ class MainBoard {
virtual void setGpio(uint32_t values) {}
virtual uint8_t getStartupReason() const = 0;
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported

// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }
virtual uint16_t getBootVoltage() { return 0; }
virtual uint32_t getResetReason() const { return 0; }
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
virtual uint8_t getShutdownReason() const { return 0; }
virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; }
};

/**
Expand Down
27 changes: 27 additions & 0 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,33 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else {
sprintf(reply, "> %.3f", adc_mult);
}
// Power management commands
} else if (memcmp(config, "pwrmgt.support", 14) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
strcpy(reply, "> supported");
#else
strcpy(reply, "> unsupported");
#endif
} else if (memcmp(config, "pwrmgt.source", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery");
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> Reset: %s; Shutdown: %s",
_board->getResetReasonString(_board->getResetReason()),
_board->getShutdownReasonString(_board->getShutdownReason()));
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> %u mV", _board->getBootVoltage());
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else {
sprintf(reply, "??: %s", config);
}
Expand Down
Loading