Skip to content

Commit f20e5af

Browse files
committed
Add 60s watchdog for nrf52
In order to restore after battery voltage sags too low
1 parent bb8dc5b commit f20e5af

14 files changed

Lines changed: 128 additions & 9 deletions

File tree

examples/companion_radio/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ void setup() {
219219
}
220220

221221
void loop() {
222+
board.loop();
222223
the_mesh.loop();
223224
sensors.loop();
224225
#ifdef DISPLAY_CLASS

examples/kiss_modem/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ void setup() {
119119
}
120120

121121
void loop() {
122+
board.loop();
122123
modem->loop();
123124

124125
if (!modem->isActuallyTransmitting()) {

examples/simple_repeater/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ void setup() {
106106
}
107107

108108
void loop() {
109+
board.loop();
110+
109111
int len = strlen(command);
110112
while (Serial.available() && len < sizeof(command)-1) {
111113
char c = Serial.read();

examples/simple_room_server/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ void setup() {
8383
}
8484

8585
void loop() {
86+
board.loop();
87+
8688
int len = strlen(command);
8789
while (Serial.available() && len < sizeof(command)-1) {
8890
char c = Serial.read();

examples/simple_secure_chat/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ void setup() {
589589
}
590590

591591
void loop() {
592+
board.loop();
592593
the_mesh.loop();
593594
rtc_clock.tick();
594595
}

examples/simple_sensor/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ void setup() {
117117
}
118118

119119
void loop() {
120+
board.loop();
121+
120122
int len = strlen(command);
121123
while (Serial.available() && len < sizeof(command)-1) {
122124
char c = Serial.read();

src/MeshCore.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class MainBoard {
5757
virtual uint8_t getStartupReason() const = 0;
5858
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
5959
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
60+
virtual void loop() { /* no op */ }
6061

6162
// Power management interface (boards with power management override these)
6263
virtual bool isExternalPowered() { return false; }

src/helpers/NRF52Board.cpp

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,29 @@ const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
112112
bool 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+
241319
void NRF52BoardDCDC::begin() {
242320
NRF52Board::begin();
243321

@@ -252,10 +330,14 @@ void NRF52BoardDCDC::begin() {
252330
}
253331

254332
void 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

src/helpers/NRF52Board.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ struct PowerMgtConfig {
2121
// Boot protection voltage threshold (millivolts)
2222
// Set to 0 to disable boot protection
2323
uint16_t voltage_bootlock;
24+
25+
// Runtime low voltage shutdown threshold (millivolts), 0=disabled
26+
uint16_t voltage_runtime;
27+
// Watchdog timer timeout (ms), 0=disabled
28+
uint32_t wdt_timeout_ms;
2429
};
2530
#endif
2631

@@ -38,21 +43,33 @@ class NRF52Board : public mesh::MainBoard {
3843
uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF)
3944
uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts)
4045

46+
const PowerMgtConfig* _power_config;
47+
uint32_t _last_voltage_check_ms;
48+
uint8_t _low_voltage_count;
49+
4150
bool checkBootVoltage(const PowerMgtConfig* config);
4251
void enterSystemOff(uint8_t reason);
4352
void configureVoltageWake(uint8_t ain_channel, uint8_t refsel);
4453
virtual void initiateShutdown(uint8_t reason);
54+
void initWatchdog(uint32_t timeout_ms);
55+
void feedWatchdog();
56+
void checkRuntimeVoltage();
4557
#endif
4658

4759
public:
48-
NRF52Board(char *otaname) : ota_name(otaname) {}
60+
NRF52Board(char *otaname) : ota_name(otaname)
61+
#ifdef NRF52_POWER_MANAGEMENT
62+
, _power_config(nullptr), _last_voltage_check_ms(0), _low_voltage_count(0)
63+
#endif
64+
{}
4965
virtual void begin();
5066
virtual uint8_t getStartupReason() const override { return startup_reason; }
5167
virtual float getMCUTemperature() override;
5268
virtual void reboot() override { NVIC_SystemReset(); }
5369
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
5470
virtual bool startOTAUpdate(const char *id, char reply[]) override;
5571
virtual void sleep(uint32_t secs) override;
72+
virtual void loop() override;
5673

5774
#ifdef NRF52_POWER_MANAGEMENT
5875
bool isExternalPowered() override;

variants/gat562_mesh_tracker_pro/GAT562MeshTrackerProBoard.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
const PowerMgtConfig power_config = {
1111
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
1212
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
13-
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
13+
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK,
14+
.voltage_runtime = PWRMGT_VOLTAGE_BOOTLOCK - 200,
15+
.wdt_timeout_ms = 60000
1416
};
1517

1618

0 commit comments

Comments
 (0)