@@ -112,15 +112,29 @@ const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
112112bool NRF52Board::checkBootVoltage (const PowerMgtConfig* config) {
113113 initPowerMgr ();
114114
115+ // Store config for runtime use (voltage monitoring, WDT)
116+ _power_config = config;
117+ _last_voltage_check_ms = 0 ;
118+ _low_voltage_count = 0 ;
119+
115120 // Read boot voltage
116121 boot_voltage_mv = getBattMilliVolts ();
117-
118- if (config->voltage_bootlock == 0 ) return true ; // Protection disabled
122+
123+ if (config->voltage_bootlock == 0 ) {
124+ // Boot protection disabled, but still init WDT if configured
125+ if (config->wdt_timeout_ms > 0 ) {
126+ initWatchdog (config->wdt_timeout_ms );
127+ }
128+ return true ;
129+ }
119130
120131 // Skip check if externally powered
121132 if (isExternalPowered ()) {
122133 MESH_DEBUG_PRINTLN (" PWRMGT: Boot check skipped (external power)" );
123134 boot_voltage_mv = getBattMilliVolts ();
135+ if (config->wdt_timeout_ms > 0 ) {
136+ initWatchdog (config->wdt_timeout_ms );
137+ }
124138 return true ;
125139 }
126140
@@ -136,6 +150,11 @@ bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
136150 return false ; // Should never reach this
137151 }
138152
153+ // Boot voltage OK — start WDT if configured
154+ if (config->wdt_timeout_ms > 0 ) {
155+ initWatchdog (config->wdt_timeout_ms );
156+ }
157+
139158 return true ;
140159}
141160
@@ -236,8 +255,67 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
236255
237256 MESH_DEBUG_PRINTLN (" PWRMGT: VBUS wake configured" );
238257}
258+
259+ #define VOLTAGE_CHECK_INTERVAL_MS 30000
260+ #define LOW_VOLTAGE_COUNT_THRESHOLD 3
261+
262+ void NRF52Board::initWatchdog (uint32_t timeout_ms) {
263+ // Configure WDT via direct register access (same pattern as LPCOMP/POWER registers)
264+ NRF_WDT ->CONFIG = WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos; // Keep running during WFE sleep
265+ NRF_WDT ->CRV = (uint32_t )((uint64_t )timeout_ms * 32768 / 1000 ); // Timeout in 32.768kHz ticks
266+ NRF_WDT ->RREN = WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos; // Enable reload register 0
267+ NRF_WDT ->TASKS_START = 1 ; // Start — cannot be stopped once started
268+
269+ // Initial feed
270+ NRF_WDT ->RR [0 ] = WDT_RR_RR_Reload;
271+
272+ MESH_DEBUG_PRINTLN (" PWRMGT: WDT started (%lu ms)" , (unsigned long )timeout_ms);
273+ }
274+
275+ void NRF52Board::feedWatchdog () {
276+ if (NRF_WDT ->RUNSTATUS ) {
277+ NRF_WDT ->RR [0 ] = WDT_RR_RR_Reload;
278+ }
279+ }
280+
281+ void NRF52Board::checkRuntimeVoltage () {
282+ if (_power_config == nullptr || _power_config->voltage_runtime == 0 ) return ;
283+
284+ uint32_t now = millis ();
285+ if (now - _last_voltage_check_ms < VOLTAGE_CHECK_INTERVAL_MS ) return ;
286+ _last_voltage_check_ms = now;
287+
288+ if (isExternalPowered ()) {
289+ _low_voltage_count = 0 ;
290+ return ;
291+ }
292+
293+ uint16_t mv = getBattMilliVolts ();
294+
295+ // Ignore ADC glitch readings
296+ if (mv < 1000 ) return ;
297+
298+ if (mv < _power_config->voltage_runtime ) {
299+ _low_voltage_count++;
300+ MESH_DEBUG_PRINTLN (" PWRMGT: Low voltage %u mV (%u/%u)" ,
301+ mv, _low_voltage_count, LOW_VOLTAGE_COUNT_THRESHOLD );
302+ if (_low_voltage_count >= LOW_VOLTAGE_COUNT_THRESHOLD ) {
303+ MESH_DEBUG_PRINTLN (" PWRMGT: Runtime voltage too low - shutting down" );
304+ initiateShutdown (SHUTDOWN_REASON_LOW_VOLTAGE );
305+ }
306+ } else {
307+ _low_voltage_count = 0 ;
308+ }
309+ }
239310#endif
240311
312+ void NRF52Board::loop () {
313+ #ifdef NRF52_POWER_MANAGEMENT
314+ feedWatchdog ();
315+ checkRuntimeVoltage ();
316+ #endif
317+ }
318+
241319void NRF52BoardDCDC::begin () {
242320 NRF52Board::begin ();
243321
@@ -252,10 +330,14 @@ void NRF52BoardDCDC::begin() {
252330}
253331
254332void NRF52Board::sleep (uint32_t secs) {
333+ #ifdef NRF52_POWER_MANAGEMENT
334+ feedWatchdog ();
335+ #endif
336+
255337 // Clear FPU interrupt flags to avoid insomnia
256338 // see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html
257339 #if (__FPU_USED == 1)
258- __set_FPSCR (__get_FPSCR () & ~(0x0000009F ));
340+ __set_FPSCR (__get_FPSCR () & ~(0x0000009F ));
259341 (void ) __get_FPSCR ();
260342 NVIC_ClearPendingIRQ (FPU_IRQn);
261343 #endif
0 commit comments