Add 60s watchdog for nrf52

In order to restore after battery voltage sags too low
This commit is contained in:
Wessel Nieboer 2026-03-12 14:41:20 +01:00 committed by Wessel Nieboer
parent fb726e48c2
commit f46f822bfc
No known key found for this signature in database
GPG key ID: 27BB1C3D63DEEFFF
14 changed files with 128 additions and 9 deletions

View file

@ -223,6 +223,7 @@ void setup() {
}
void loop() {
board.loop();
the_mesh.loop();
sensors.loop();
#ifdef DISPLAY_CLASS

View file

@ -119,6 +119,7 @@ void setup() {
}
void loop() {
board.loop();
modem->loop();
if (!modem->isActuallyTransmitting()) {

View file

@ -106,6 +106,8 @@ void setup() {
}
void loop() {
board.loop();
int len = strlen(command);
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();

View file

@ -83,6 +83,8 @@ void setup() {
}
void loop() {
board.loop();
int len = strlen(command);
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();

View file

@ -589,6 +589,7 @@ void setup() {
}
void loop() {
board.loop();
the_mesh.loop();
rtc_clock.tick();
}

View file

@ -117,6 +117,8 @@ void setup() {
}
void loop() {
board.loop();
int len = strlen(command);
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();

View file

@ -58,6 +58,7 @@ public:
virtual uint8_t getStartupReason() const = 0;
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
virtual void loop() { /* no op */ }
// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }

View file

@ -112,15 +112,29 @@ const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
initPowerMgr();
// Store config for runtime use (voltage monitoring, WDT)
_power_config = config;
_last_voltage_check_ms = 0;
_low_voltage_count = 0;
// Read boot voltage
boot_voltage_mv = getBattMilliVolts();
if (config->voltage_bootlock == 0) return true; // Protection disabled
if (config->voltage_bootlock == 0) {
// Boot protection disabled, but still init WDT if configured
if (config->wdt_timeout_ms > 0) {
initWatchdog(config->wdt_timeout_ms);
}
return true;
}
// Skip check if externally powered
if (isExternalPowered()) {
MESH_DEBUG_PRINTLN("PWRMGT: Boot check skipped (external power)");
boot_voltage_mv = getBattMilliVolts();
if (config->wdt_timeout_ms > 0) {
initWatchdog(config->wdt_timeout_ms);
}
return true;
}
@ -136,6 +150,11 @@ bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
return false; // Should never reach this
}
// Boot voltage OK — start WDT if configured
if (config->wdt_timeout_ms > 0) {
initWatchdog(config->wdt_timeout_ms);
}
return true;
}
@ -236,8 +255,67 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured");
}
#define VOLTAGE_CHECK_INTERVAL_MS 30000
#define LOW_VOLTAGE_COUNT_THRESHOLD 3
void NRF52Board::initWatchdog(uint32_t timeout_ms) {
// Configure WDT via direct register access (same pattern as LPCOMP/POWER registers)
NRF_WDT->CONFIG = WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos; // Keep running during WFE sleep
NRF_WDT->CRV = (uint32_t)((uint64_t)timeout_ms * 32768 / 1000); // Timeout in 32.768kHz ticks
NRF_WDT->RREN = WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos; // Enable reload register 0
NRF_WDT->TASKS_START = 1; // Start — cannot be stopped once started
// Initial feed
NRF_WDT->RR[0] = WDT_RR_RR_Reload;
MESH_DEBUG_PRINTLN("PWRMGT: WDT started (%lu ms)", (unsigned long)timeout_ms);
}
void NRF52Board::feedWatchdog() {
if (NRF_WDT->RUNSTATUS) {
NRF_WDT->RR[0] = WDT_RR_RR_Reload;
}
}
void NRF52Board::checkRuntimeVoltage() {
if (_power_config == nullptr || _power_config->voltage_runtime == 0) return;
uint32_t now = millis();
if (now - _last_voltage_check_ms < VOLTAGE_CHECK_INTERVAL_MS) return;
_last_voltage_check_ms = now;
if (isExternalPowered()) {
_low_voltage_count = 0;
return;
}
uint16_t mv = getBattMilliVolts();
// Ignore ADC glitch readings
if (mv < 1000) return;
if (mv < _power_config->voltage_runtime) {
_low_voltage_count++;
MESH_DEBUG_PRINTLN("PWRMGT: Low voltage %u mV (%u/%u)",
mv, _low_voltage_count, LOW_VOLTAGE_COUNT_THRESHOLD);
if (_low_voltage_count >= LOW_VOLTAGE_COUNT_THRESHOLD) {
MESH_DEBUG_PRINTLN("PWRMGT: Runtime voltage too low - shutting down");
initiateShutdown(SHUTDOWN_REASON_LOW_VOLTAGE);
}
} else {
_low_voltage_count = 0;
}
}
#endif
void NRF52Board::loop() {
#ifdef NRF52_POWER_MANAGEMENT
feedWatchdog();
checkRuntimeVoltage();
#endif
}
void NRF52BoardDCDC::begin() {
NRF52Board::begin();
@ -252,10 +330,14 @@ void NRF52BoardDCDC::begin() {
}
void NRF52Board::sleep(uint32_t secs) {
#ifdef NRF52_POWER_MANAGEMENT
feedWatchdog();
#endif
// Clear FPU interrupt flags to avoid insomnia
// see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html
#if (__FPU_USED == 1)
__set_FPSCR(__get_FPSCR() & ~(0x0000009F));
__set_FPSCR(__get_FPSCR() & ~(0x0000009F));
(void) __get_FPSCR();
NVIC_ClearPendingIRQ(FPU_IRQn);
#endif

View file

@ -21,6 +21,11 @@ struct PowerMgtConfig {
// Boot protection voltage threshold (millivolts)
// Set to 0 to disable boot protection
uint16_t voltage_bootlock;
// Runtime low voltage shutdown threshold (millivolts), 0=disabled
uint16_t voltage_runtime;
// Watchdog timer timeout (ms), 0=disabled
uint32_t wdt_timeout_ms;
};
#endif
@ -38,14 +43,25 @@ protected:
uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF)
uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts)
const PowerMgtConfig* _power_config;
uint32_t _last_voltage_check_ms;
uint8_t _low_voltage_count;
bool checkBootVoltage(const PowerMgtConfig* config);
void enterSystemOff(uint8_t reason);
void configureVoltageWake(uint8_t ain_channel, uint8_t refsel);
virtual void initiateShutdown(uint8_t reason);
void initWatchdog(uint32_t timeout_ms);
void feedWatchdog();
void checkRuntimeVoltage();
#endif
public:
NRF52Board(char *otaname) : ota_name(otaname) {}
NRF52Board(char *otaname) : ota_name(otaname)
#ifdef NRF52_POWER_MANAGEMENT
, _power_config(nullptr), _last_voltage_check_ms(0), _low_voltage_count(0)
#endif
{}
virtual void begin();
virtual uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override;
@ -53,6 +69,7 @@ public:
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual void sleep(uint32_t secs) override;
virtual void loop() override;
#ifdef NRF52_POWER_MANAGEMENT
bool isExternalPowered() override;

View file

@ -10,7 +10,9 @@
const PowerMgtConfig power_config = {
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK,
.voltage_runtime = PWRMGT_VOLTAGE_BOOTLOCK - 200,
.wdt_timeout_ms = 60000
};

View file

@ -9,7 +9,9 @@
const PowerMgtConfig power_config = {
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK,
.voltage_runtime = PWRMGT_VOLTAGE_BOOTLOCK - 200,
.wdt_timeout_ms = 60000
};
void T114Board::initiateShutdown(uint8_t reason) {

View file

@ -9,7 +9,9 @@
const PowerMgtConfig power_config = {
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK,
.voltage_runtime = PWRMGT_VOLTAGE_BOOTLOCK - 200,
.wdt_timeout_ms = 60000
};
void RAK4631Board::initiateShutdown(uint8_t reason) {

View file

@ -7,7 +7,9 @@
const PowerMgtConfig power_config = {
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK,
.voltage_runtime = PWRMGT_VOLTAGE_BOOTLOCK - 200,
.wdt_timeout_ms = 60000
};
void SenseCapSolarBoard::initiateShutdown(uint8_t reason) {

View file

@ -11,7 +11,9 @@
const PowerMgtConfig power_config = {
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK,
.voltage_runtime = PWRMGT_VOLTAGE_BOOTLOCK - 200,
.wdt_timeout_ms = 60000
};
void XiaoNrf52Board::initiateShutdown(uint8_t reason) {