Harden nRF52 shutdown and radio recovery

This commit is contained in:
mikecarper
2026-07-31 16:01:02 -07:00
parent 86160e9323
commit 308589afa3
20 changed files with 107 additions and 43 deletions
+27 -23
View File
@@ -16,6 +16,9 @@ The nRF52 Power Management module provides battery protection features to preven
- Configures the nRF52's Low Power Comparator (LPCOMP) before entering SYSTEMOFF
- Enables USB VBUS detection so external power can wake the device
- Device automatically wakes when battery voltage rises above recovery threshold or when VBUS is detected
- Uses no LPCOMP hysteresis; on divided battery inputs the additional 50 mV
comparator hysteresis can move the effective battery wake point by several
hundred millivolts and strand an otherwise charged node
### Early Boot Register Capture
- Captures RESETREAS (reset reason) and GPREGRET2 (shutdown reason) before SystemInit() clears them
@@ -34,31 +37,32 @@ Shutdown reason codes (stored in GPREGRET2):
## Supported Boards
| 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 |
| Board family | Implemented | LPCOMP wake | VBUS wake |
|---------------------------------------------------|-------------|-------------|-----------|
| Seeed Studio XIAO nRF52840 (`xiao_nrf52`) | Yes | Yes | Yes |
| SenseCAP Solar | Yes | Yes | Yes |
| RAK4631 / RAK3401 | Yes | Yes | Yes |
| GAT562 30S / EVB Pro / Tracker Pro / Watch13 | Yes | Yes | Yes |
| Heltec T096 / T114 / T1 / Tower V2 | Yes | Yes | Yes |
| Muzi Works R1 Neo | Partial | Inactive | Inactive |
| Promicro nRF52840 | No | No | No |
| RAK WisMesh Tag | No | No | No |
| Heltec Mesh Solar | No | No | No |
| LilyGo T-Echo / T-Echo Lite / T-Impulse Plus | No | No | No |
| WIO Tracker L1 / L1 E-Ink / WM1110 | No | No | No |
| Mesh Pocket / Meshtiny / 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 / Minewsemi ME25LS01 | No | No | No |
Notes:
- "Implemented" reflects Phase 1 (boot lockout + shutdown reason capture).
- User power-off on Heltec T114 does not enable LPCOMP wake.
- R1 Neo has the integration compiled, but its board configuration deliberately
sets `PWRMGT_VOLTAGE_BOOTLOCK` to `0`; automatic protective shutdown and its
voltage/VBUS recovery wake are therefore inactive.
- User power-off does not enable LPCOMP wake; voltage recovery is armed only
for boot protection and automated low-voltage shutdown.
- VBUS detection is used to skip boot lockout on external power, and VBUS wake is configured alongside LPCOMP when supported hardware exposes VBUS to the nRF52.
## Technical Details
@@ -138,7 +142,7 @@ 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-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16)
- Detect UP events (voltage rising above threshold)
- Use 50mV hysteresis for noise immunity
- Use no hysteresis so the configured recovery threshold is not widened by the battery divider
- Wake the device from SYSTEMOFF when triggered
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).
+10 -1
View File
@@ -254,7 +254,16 @@ void setup() {
}
#endif
if (!radio_init()) { halt(); }
int radioinit_attempts = 0;
while (!radio_init()) {
++radioinit_attempts;
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
if (radioinit_attempts >= 3) {
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
board.reboot();
}
delay(500);
}
fast_rng.begin(radio_driver.getRngSeed());
+9 -2
View File
@@ -78,8 +78,15 @@ void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) {
void setup() {
board.begin();
if (!radio_init()) {
halt();
int radioinit_attempts = 0;
while (!radio_init()) {
++radioinit_attempts;
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
if (radioinit_attempts >= 3) {
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
board.reboot();
}
delay(500);
}
radio_driver.begin();
+9 -3
View File
@@ -80,9 +80,15 @@ void setup() {
}
#endif
if (!radio_init()) {
MESH_DEBUG_PRINTLN("Radio init failed!");
halt();
int radioinit_attempts = 0;
while (!radio_init()) {
++radioinit_attempts;
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
if (radioinit_attempts >= 3) {
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
board.reboot();
}
delay(500);
}
fast_rng.begin(radio_driver.getRngSeed());
+10 -1
View File
@@ -48,7 +48,16 @@ void setup() {
}
#endif
if (!radio_init()) { halt(); }
int radioinit_attempts = 0;
while (!radio_init()) {
++radioinit_attempts;
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
if (radioinit_attempts >= 3) {
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
board.reboot();
}
delay(500);
}
fast_rng.begin(radio_driver.getRngSeed());
+10 -1
View File
@@ -568,7 +568,16 @@ void setup() {
external_watchdog.begin();
#endif
if (!radio_init()) { halt(); }
int radioinit_attempts = 0;
while (!radio_init()) {
++radioinit_attempts;
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
if (radioinit_attempts >= 3) {
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
board.reboot();
}
delay(500);
}
fast_rng.begin(radio_driver.getRngSeed());
+10 -1
View File
@@ -70,7 +70,16 @@ void setup() {
}
#endif
if (!radio_init()) { halt(); }
int radioinit_attempts = 0;
while (!radio_init()) {
++radioinit_attempts;
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
if (radioinit_attempts >= 3) {
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
board.reboot();
}
delay(500);
}
fast_rng.begin(radio_driver.getRngSeed());
+10
View File
@@ -467,6 +467,16 @@ void NRF52Board::shutdownPeripherals() {
// Power off LoRa
radio_driver.powerOff();
// RadioLib's IRQ setup can leave DIO1 as a GPIO wake source. Once the radio
// is asleep that line may float or remain asserted, immediately waking an
// nRF52 that is trying to enter SYSTEMOFF. Release the interrupt channel and
// explicitly disable pin sensing; board-specific code can then arm only its
// intended wake source (for example, the SenseCAP user button or LPCOMP).
#ifdef P_LORA_DIO_1
detachInterrupt(P_LORA_DIO_1);
pinMode(P_LORA_DIO_1, INPUT);
#endif
// Keep LoRa inactive during deepsleep
#ifdef P_LORA_NSS
digitalWrite(P_LORA_NSS, HIGH);
@@ -50,6 +50,7 @@ public:
}
uint8_t getSpreadingFactor() const override { return ((CustomSX1262 *)_radio)->spreadingFactor; }
virtual void powerOff() override {
((CustomSX1262 *)_radio)->standby();
((CustomSX1262 *)_radio)->sleep(false);
}
+1 -1
View File
@@ -120,7 +120,7 @@ public:
}
void begin() override;
virtual void powerOff() { _radio->sleep(); }
virtual void powerOff() { _radio->standby(); _radio->sleep(); }
int recvRaw(uint8_t* bytes, int sz) override;
uint32_t getEstAirtimeFor(int len_bytes) override;
bool startSendRaw(const uint8_t* bytes, int len) override;
+1 -1
View File
@@ -8,7 +8,7 @@ build_flags = ${rak4631_hw.build_flags}
-I variants/gat562_30s_mesh_kit
-D RAK_4631
-D RAK_BOARD
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D PIN_OLED_RESET=-1
+1 -1
View File
@@ -5,7 +5,7 @@ board_check = true
build_flags = ${rak4631_hw.build_flags}
${sensor_base.build_flags}
-I variants/gat562_mesh_evb_pro
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D RADIO_CLASS=CustomSX1262
@@ -6,7 +6,7 @@ board_build.ldscript = boards/nrf52840_s140_v6.ld
build_flags = ${rak4631_hw.build_flags}
${sensor_base.build_flags}
-I variants/gat562_mesh_tracker_pro
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D PIN_OLED_RESET=-1
+1 -1
View File
@@ -8,7 +8,7 @@ build_flags = ${rak4631_hw.build_flags}
-I variants/gat562_mesh_watch13
-D RAK_4631
-D RAK_BOARD
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D PIN_OLED_RESET=-1
+1 -1
View File
@@ -14,7 +14,7 @@ build_flags = ${rak4631_hw.build_flags}
-I variants/heltec_t096
-I src/helpers/ui
-D HELTEC_T096
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D P_LORA_DIO_1=21
-D P_LORA_NSS=5
-D P_LORA_RESET=16
+1 -1
View File
@@ -13,7 +13,7 @@ build_flags = ${rak4631_hw.build_flags}
-I variants/heltec_t114
-I src/helpers/ui
-D HELTEC_T114
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D P_LORA_DIO_1=20
-D P_LORA_NSS=24
-D P_LORA_RESET=25
+1 -1
View File
@@ -7,7 +7,7 @@ build_flags = ${nrf52_base.build_flags}
${sensor_base.build_flags}
-I variants/rak3401
-D RAK_3401
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
+1 -1
View File
@@ -11,7 +11,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/rak4631
-D RAK_4631
-D RAK_BOARD
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D MOTA_HW_ID='"RAK4631"' ; OTA hardware tag (apply refuses a .mota for different hw)
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
+1 -1
View File
@@ -10,7 +10,7 @@ build_flags = ${nrf52_base.build_flags}
-I src/helpers/nrf52
-D NRF52_PLATFORM=1
-D USE_SX1262
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D P_LORA_TX_LED=12
+1 -1
View File
@@ -9,7 +9,7 @@ build_flags = ${rak4631_hw.build_flags}
-I variants/xiao_nrf52
-UENV_INCLUDE_GPS
-D NRF52_PLATFORM
; -D NRF52_POWER_MANAGEMENT
-D NRF52_POWER_MANAGEMENT
-D XIAO_NRF52
-D USE_SX1262
-D RADIO_CLASS=CustomSX1262