Skip to content

Commit d321087

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

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
@@ -243,6 +243,7 @@ void setup() {
243243
}
244244

245245
void loop() {
246+
board.loop();
246247
the_mesh.loop();
247248
sensors.loop();
248249
#ifdef DISPLAY_CLASS

examples/kiss_modem/main.cpp

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

126126
void loop() {
127+
board.loop();
127128
modem->loop();
128129

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

examples/simple_repeater/main.cpp

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

106106
void loop() {
107+
board.loop();
108+
107109
int len = strlen(command);
108110
while (Serial.available() && len < sizeof(command)-1) {
109111
char c = Serial.read();

examples/simple_room_server/main.cpp

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

8787
void loop() {
88+
board.loop();
89+
8890
int len = strlen(command);
8991
while (Serial.available() && len < sizeof(command)-1) {
9092
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
@@ -67,6 +67,7 @@ class MainBoard {
6767
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
6868
virtual bool canControlLoRaFemLna() const { return false; }
6969
virtual bool isLoRaFemLnaEnabled() const { return false; }
70+
virtual void loop() { /* no op */ }
7071

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

src/helpers/NRF52Board.cpp

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

@@ -252,10 +330,14 @@ bool NRF52Board::isExternalPowered() {
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,14 +43,25 @@ 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;
@@ -54,6 +70,7 @@ class NRF52Board : public mesh::MainBoard {
5470
virtual bool startOTAUpdate(const char *id, char reply[]) override;
5571
virtual void sleep(uint32_t secs) override;
5672
bool isExternalPowered() override;
73+
virtual void loop() override;
5774

5875
#ifdef NRF52_POWER_MANAGEMENT
5976
uint16_t getBootVoltage() override { return boot_voltage_mv; }

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)