Skip to content

Commit 9fa23ea

Browse files
committed
Watchdog Reset Timer for nRF52840
- NRF52Board: Add initWatchdog() to initialise WDT, set timer to 30 seconds and pause WDT if board enters sleep state - NRF52Board: Add feedWatchdog() to bump the watchdog timer during normal operations - NRF52Board: Add isWatchdogRunning() to query current run state of WDT - NRF52Board: add tick() to be called from each example firmware to bump timer and allow a place for future tasks to be added centrally - CommonCLI: Add "set wdt (on/off)" to enable or disable WDT (requires reboot) - CommonCLI: Add "get wdt" to query enabled status and if wdt is running - Repeater firmware: Add init and tick calls for WDT - Added Heltec T114, RAK4631, Xiao nRF52840 build flag to include WDT
1 parent f46f0d0 commit 9fa23ea

11 files changed

Lines changed: 179 additions & 2 deletions

File tree

docs/nrf52_watchdog.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Overview
2+
3+
The nRF52 hardware watchdog timer (WDT) provides automatic recovery from firmware hangs or crashes. When enabled, if the firmware fails to "feed" the watchdog within the timeout period, the device will automatically reset.
4+
5+
## Parameters
6+
7+
| Parameter | Value |
8+
|-----------|-------|
9+
| Timeout | 30 seconds |
10+
| Sleep behavior | Pauses during sleep mode |
11+
| Halt behavior | Pauses during halt state |
12+
| Control | Compile-time via `NRF52_WATCHDOG` build flag + runtime pref `wdt_enabled` (CLI `set wdt on/off`) |
13+
14+
The implementation uses the nRF52840 WDT peripheral with the following configuration:
15+
16+
| Register | Value | Description |
17+
|----------|-------|-------------|
18+
| `CONFIG.SLEEP` | Pause | WDT pauses when CPU enters sleep |
19+
| `CONFIG.HALT` | Pause | WDT pauses during debug halt |
20+
| `CRV` | 983040 | Counter reload value (30s × 32768 Hz) |
21+
| `RREN` | RR0 enabled | Uses reload request register 0 |
22+
| `RR[0]` | 0x6E524635 | Magic value to feed watchdog |
23+
24+
## Usage
25+
26+
1. Watchdog is **disabled by default** - enable via `set wdt on` (reboot required to turn on/off)
27+
2. Application code checks `prefs.wdt_enabled` after loading prefs and calls `board.initWatchdog()` if enabled
28+
3. The main loop must call `board.tick()` regularly to feed the watchdog
29+
4. If `board.tick()` is not called within 30 seconds, the device resets
30+
5. The watchdog pauses during low-power sleep modes (won't reset during intended sleep)
31+
32+
**Important**: Once the watchdog is started, it cannot be stopped during runtime. A power cycle is required to apply changes made via `set wdt on/off`.
33+
34+
## Enabling Watchdog for a Board Variant
35+
36+
Watchdog is not compiled by default. To enable it for a board variant, add the `NRF52_WATCHDOG` flag to the variant's `platformio.ini`. The `wdt_enabled` preference (set via `set wdt on/off`) controls whether it starts on boot (disabled by default):
37+
38+
```ini
39+
[env:your_variant]
40+
extends = nrf52_base
41+
build_flags = ${nrf52_base.build_flags}
42+
-D NRF52_WATCHDOG
43+
# ... other flags
44+
```
45+
46+
## Firmware Integration
47+
48+
Currently, Watchdog is only implemented in the Repeater firmware. To add to other firmware types, perform the below steps.
49+
50+
After loading prefs in the `begin()` method, start the watchdog if enabled:
51+
52+
```cpp
53+
_cli.loadPrefs(_fs);
54+
55+
#ifdef NRF52_WATCHDOG
56+
if (_prefs.wdt_enabled) {
57+
board.initWatchdog();
58+
}
59+
#endif
60+
```
61+
62+
Ensure the main `loop()` function calls `board.tick()` to feed the watchdog:
63+
64+
```cpp
65+
void loop() {
66+
the_mesh.loop();
67+
sensors.loop();
68+
#ifdef DISPLAY_CLASS
69+
ui_task.loop();
70+
#endif
71+
rtc_clock.tick();
72+
board.tick(); // Feed the watchdog
73+
}
74+
```
75+
76+
## CLI Commands
77+
78+
When `NRF52_WATCHDOG` is defined, the following CLI commands are available:
79+
80+
| Command | Description |
81+
|---------|-------------|
82+
| `get wdt` | Returns `Enabled/Disabled, running/not running` |
83+
| `set wdt on/off` | Enable/disable watchdog on next reboot |

examples/simple_repeater/MyMesh.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,13 @@ void MyMesh::begin(FILESYSTEM *fs) {
808808
_fs = fs;
809809
// load persisted prefs
810810
_cli.loadPrefs(_fs);
811+
812+
#ifdef NRF52_WATCHDOG
813+
if (_prefs.wdt_enabled) {
814+
board.initWatchdog();
815+
}
816+
#endif
817+
811818
acl.load(_fs);
812819
// TODO: key_store.begin();
813820
region_map.load(_fs);

examples/simple_repeater/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ void loop() {
124124
ui_task.loop();
125125
#endif
126126
rtc_clock.tick();
127+
board.tick();
127128

128129
if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled
129130
the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep

src/MeshCore.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class MainBoard {
6464
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
6565
virtual uint8_t getShutdownReason() const { return 0; }
6666
virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; }
67+
68+
// Watchdog interface (boards with watchdog support override these)
69+
virtual bool isWatchdogRunning() { return false; }
6770
};
6871

6972
/**

src/helpers/CommonCLI.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
7474
file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
7575
file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
7676
file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
77-
file.read(pad, 3); // 153
77+
file.read((uint8_t *)&_prefs->wdt_enabled, sizeof(_prefs->wdt_enabled)); // 153
78+
file.read(pad, 2); // 154
7879
file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
7980
file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
8081
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
@@ -95,6 +96,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
9596
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
9697
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
9798
_prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
99+
_prefs->wdt_enabled = constrain(_prefs->wdt_enabled, 0, 1);
98100

99101
// sanitise bad bridge pref values
100102
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
@@ -158,7 +160,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
158160
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
159161
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
160162
file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
161-
file.write(pad, 3); // 153
163+
file.write((uint8_t *)&_prefs->wdt_enabled, sizeof(_prefs->wdt_enabled)); // 153
164+
file.write(pad, 2); // 154
162165
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
163166
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
164167
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
@@ -390,6 +393,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
390393
sprintf(reply, "> %u mV", _board->getBootVoltage());
391394
#else
392395
strcpy(reply, "ERROR: Power management not supported");
396+
#endif
397+
#ifdef NRF52_WATCHDOG
398+
} else if (memcmp(config, "wdt", 3) == 0 && config[3] == 0) {
399+
sprintf(reply, "> %s, %s",
400+
_prefs->wdt_enabled ? "Enabled" : "Disabled",
401+
_board->isWatchdogRunning() ? "running" : "not running");
393402
#endif
394403
} else {
395404
sprintf(reply, "??: %s", config);
@@ -610,6 +619,14 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
610619
_prefs->adc_multiplier = 0.0f;
611620
strcpy(reply, "Error: unsupported by this board");
612621
};
622+
#ifdef NRF52_WATCHDOG
623+
} else if (memcmp(config, "wdt ", 4) == 0) {
624+
const char* value = &config[4];
625+
bool enable = memcmp(value, "on", 2) == 0 || memcmp(value, "1", 1) == 0;
626+
_prefs->wdt_enabled = enable ? 1 : 0;
627+
savePrefs();
628+
strcpy(reply, enable ? "OK - reboot to enable watchdog" : "OK - reboot to disable watchdog");
629+
#endif
613630
} else {
614631
sprintf(reply, "unknown config: %s", config);
615632
}

src/helpers/CommonCLI.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ struct NodePrefs { // persisted to file
5151
uint32_t discovery_mod_timestamp;
5252
float adc_multiplier;
5353
char owner_info[120];
54+
uint8_t wdt_enabled; // nRF52 watchdog enabled preference (0/1)
5455
};
5556

5657
class CommonCLICallbacks {

src/helpers/NRF52Board.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,47 @@
44
#include <bluefruit.h>
55
#include <nrf_soc.h>
66

7+
#ifdef NRF52_WATCHDOG
8+
#include <nrf.h>
9+
10+
// nRF52 WDT reload register magic value ("nRF5" in ASCII, per Nordic SDK)
11+
#define WDT_RR_VALUE 0x6E524635UL
12+
13+
// 30 second timeout at 32.768kHz clock
14+
#define WDT_TIMEOUT_SECONDS 30
15+
#define WDT_CRV_VALUE (WDT_TIMEOUT_SECONDS * 32768UL)
16+
17+
bool NRF52Board::initWatchdog() {
18+
// Check if already running - WDT cannot be reconfigured once started
19+
if (NRF_WDT->RUNSTATUS) {
20+
return false;
21+
}
22+
23+
// Configure WDT to pause during sleep and halt modes
24+
NRF_WDT->CONFIG = (WDT_CONFIG_SLEEP_Pause << WDT_CONFIG_SLEEP_Pos) |
25+
(WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos);
26+
27+
// Set timeout value (30 seconds)
28+
NRF_WDT->CRV = WDT_CRV_VALUE;
29+
30+
// Enable reload request register 0
31+
NRF_WDT->RREN = WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos;
32+
33+
// Start the watchdog
34+
NRF_WDT->TASKS_START = 1;
35+
36+
return true;
37+
}
38+
39+
void NRF52Board::feedWatchdog() {
40+
NRF_WDT->RR[0] = WDT_RR_VALUE;
41+
}
42+
43+
bool NRF52Board::isWatchdogRunning() {
44+
return NRF_WDT->RUNSTATUS != 0;
45+
}
46+
#endif
47+
748
static BLEDfu bledfu;
849

950
static void connect_callback(uint16_t conn_handle) {
@@ -22,6 +63,12 @@ void NRF52Board::begin() {
2263
startup_reason = BD_STARTUP_NORMAL;
2364
}
2465

66+
void NRF52Board::tick() {
67+
#ifdef NRF52_WATCHDOG
68+
feedWatchdog();
69+
#endif
70+
}
71+
2572
#ifdef NRF52_POWER_MANAGEMENT
2673
#include "nrf.h"
2774

src/helpers/NRF52Board.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ class NRF52Board : public mesh::MainBoard {
4343
virtual void initiateShutdown(uint8_t reason);
4444
#endif
4545

46+
#ifdef NRF52_WATCHDOG
47+
void feedWatchdog();
48+
#endif
49+
4650
public:
4751
virtual void begin();
52+
virtual void tick();
4853
virtual uint8_t getStartupReason() const override { return startup_reason; }
4954
virtual float getMCUTemperature() override;
5055
virtual void reboot() override { NVIC_SystemReset(); }
@@ -57,6 +62,11 @@ class NRF52Board : public mesh::MainBoard {
5762
const char* getResetReasonString(uint32_t reason) override;
5863
const char* getShutdownReasonString(uint8_t reason) override;
5964
#endif
65+
66+
#ifdef NRF52_WATCHDOG
67+
bool initWatchdog();
68+
bool isWatchdogRunning() override;
69+
#endif
6070
};
6171

6272
/*

variants/heltec_t114/platformio.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ build_flags =
5757
-D ADVERT_LON=0.0
5858
-D ADMIN_PASSWORD='"password"'
5959
-D MAX_NEIGHBOURS=50
60+
-D NRF52_WATCHDOG
6061
; -D MESH_PACKET_LOGGING=1
6162
; -D MESH_DEBUG=1
6263

@@ -72,6 +73,7 @@ build_flags =
7273
-D WITH_RS232_BRIDGE=Serial2
7374
-D WITH_RS232_BRIDGE_RX=9
7475
-D WITH_RS232_BRIDGE_TX=10
76+
-D NRF52_WATCHDOG
7577
; -D BRIDGE_DEBUG=1
7678
; -D MESH_PACKET_LOGGING=1
7779
; -D MESH_DEBUG=1
@@ -168,6 +170,7 @@ build_flags =
168170
-D ADVERT_LON=0.0
169171
-D ADMIN_PASSWORD='"password"'
170172
-D MAX_NEIGHBOURS=50
173+
-D NRF52_WATCHDOG
171174
; -D MESH_PACKET_LOGGING=1
172175
; -D MESH_DEBUG=1
173176

@@ -183,6 +186,7 @@ build_flags =
183186
-D WITH_RS232_BRIDGE=Serial2
184187
-D WITH_RS232_BRIDGE_RX=9
185188
-D WITH_RS232_BRIDGE_TX=10
189+
-D NRF52_WATCHDOG
186190
; -D BRIDGE_DEBUG=1
187191
; -D MESH_PACKET_LOGGING=1
188192
; -D MESH_DEBUG=1

variants/rak4631/platformio.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ build_flags =
4040
-D ADVERT_LON=0.0
4141
-D ADMIN_PASSWORD='"password"'
4242
-D MAX_NEIGHBOURS=50
43+
-D NRF52_WATCHDOG
4344
; -D MESH_PACKET_LOGGING=1
4445
; -D MESH_DEBUG=1
4546
build_src_filter = ${rak4631.build_src_filter}
@@ -59,6 +60,7 @@ build_flags =
5960
-D WITH_RS232_BRIDGE=Serial1
6061
-D WITH_RS232_BRIDGE_RX=PIN_SERIAL1_RX
6162
-D WITH_RS232_BRIDGE_TX=PIN_SERIAL1_TX
63+
-D NRF52_WATCHDOG
6264
-UENV_INCLUDE_GPS
6365
; -D BRIDGE_DEBUG=1
6466
; -D MESH_PACKET_LOGGING=1
@@ -82,6 +84,7 @@ build_flags =
8284
-D WITH_RS232_BRIDGE=Serial2
8385
-D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX
8486
-D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX
87+
-D NRF52_WATCHDOG
8588
-UENV_INCLUDE_GPS
8689
; -D BRIDGE_DEBUG=1
8790
; -D MESH_PACKET_LOGGING=1

0 commit comments

Comments
 (0)