@@ -98,15 +98,29 @@ const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
9898bool NRF52Board::checkBootVoltage (const PowerMgtConfig* config) {
9999 initPowerMgr ();
100100
101+ // Store config for runtime use (voltage monitoring, WDT)
102+ _power_config = config;
103+ _last_voltage_check_ms = 0 ;
104+ _low_voltage_count = 0 ;
105+
101106 // Read boot voltage
102107 boot_voltage_mv = getBattMilliVolts ();
103-
104- if (config->voltage_bootlock == 0 ) return true ; // Protection disabled
108+
109+ if (config->voltage_bootlock == 0 ) {
110+ // Boot protection disabled, but still init WDT if configured
111+ if (config->wdt_timeout_ms > 0 ) {
112+ initWatchdog (config->wdt_timeout_ms );
113+ }
114+ return true ;
115+ }
105116
106117 // Skip check if externally powered
107118 if (isExternalPowered ()) {
108119 MESH_DEBUG_PRINTLN (" PWRMGT: Boot check skipped (external power)" );
109120 boot_voltage_mv = getBattMilliVolts ();
121+ if (config->wdt_timeout_ms > 0 ) {
122+ initWatchdog (config->wdt_timeout_ms );
123+ }
110124 return true ;
111125 }
112126
@@ -122,6 +136,11 @@ bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
122136 return false ; // Should never reach this
123137 }
124138
139+ // Boot voltage OK — start WDT if configured
140+ if (config->wdt_timeout_ms > 0 ) {
141+ initWatchdog (config->wdt_timeout_ms );
142+ }
143+
125144 return true ;
126145}
127146
@@ -222,8 +241,67 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
222241
223242 MESH_DEBUG_PRINTLN (" PWRMGT: VBUS wake configured" );
224243}
244+
245+ #define VOLTAGE_CHECK_INTERVAL_MS 30000
246+ #define LOW_VOLTAGE_COUNT_THRESHOLD 3
247+
248+ void NRF52Board::initWatchdog (uint32_t timeout_ms) {
249+ // Configure WDT via direct register access (same pattern as LPCOMP/POWER registers)
250+ NRF_WDT ->CONFIG = WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos; // Keep running during WFE sleep
251+ NRF_WDT ->CRV = (uint32_t )((uint64_t )timeout_ms * 32768 / 1000 ); // Timeout in 32.768kHz ticks
252+ NRF_WDT ->RREN = WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos; // Enable reload register 0
253+ NRF_WDT ->TASKS_START = 1 ; // Start — cannot be stopped once started
254+
255+ // Initial feed
256+ NRF_WDT ->RR [0 ] = WDT_RR_RR_Reload;
257+
258+ MESH_DEBUG_PRINTLN (" PWRMGT: WDT started (%lu ms)" , (unsigned long )timeout_ms);
259+ }
260+
261+ void NRF52Board::feedWatchdog () {
262+ if (NRF_WDT ->RUNSTATUS ) {
263+ NRF_WDT ->RR [0 ] = WDT_RR_RR_Reload;
264+ }
265+ }
266+
267+ void NRF52Board::checkRuntimeVoltage () {
268+ if (_power_config == nullptr || _power_config->voltage_runtime == 0 ) return ;
269+
270+ uint32_t now = millis ();
271+ if (now - _last_voltage_check_ms < VOLTAGE_CHECK_INTERVAL_MS ) return ;
272+ _last_voltage_check_ms = now;
273+
274+ if (isExternalPowered ()) {
275+ _low_voltage_count = 0 ;
276+ return ;
277+ }
278+
279+ uint16_t mv = getBattMilliVolts ();
280+
281+ // Ignore ADC glitch readings
282+ if (mv < 1000 ) return ;
283+
284+ if (mv < _power_config->voltage_runtime ) {
285+ _low_voltage_count++;
286+ MESH_DEBUG_PRINTLN (" PWRMGT: Low voltage %u mV (%u/%u)" ,
287+ mv, _low_voltage_count, LOW_VOLTAGE_COUNT_THRESHOLD );
288+ if (_low_voltage_count >= LOW_VOLTAGE_COUNT_THRESHOLD ) {
289+ MESH_DEBUG_PRINTLN (" PWRMGT: Runtime voltage too low - shutting down" );
290+ initiateShutdown (SHUTDOWN_REASON_LOW_VOLTAGE );
291+ }
292+ } else {
293+ _low_voltage_count = 0 ;
294+ }
295+ }
225296#endif
226297
298+ void NRF52Board::loop () {
299+ #ifdef NRF52_POWER_MANAGEMENT
300+ feedWatchdog ();
301+ checkRuntimeVoltage ();
302+ #endif
303+ }
304+
227305void NRF52BoardDCDC::begin () {
228306 NRF52Board::begin ();
229307
@@ -252,10 +330,14 @@ bool NRF52Board::isExternalPowered() {
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