Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/nrf52_watchdog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Overview

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.

## Parameters

| Parameter | Value |
|-----------|-------|
| Timeout | 30 seconds |
| Sleep behavior | Pauses during sleep mode |
| Halt behavior | Pauses during halt state |
| Control | Compile-time via `NRF52_WATCHDOG` build flag + runtime pref `wdt_enabled` (CLI `set wdt on/off`) |

The implementation uses the nRF52840 WDT peripheral with the following configuration:

| Register | Value | Description |
|----------|-------|-------------|
| `CONFIG.SLEEP` | Pause | WDT pauses when CPU enters sleep |
| `CONFIG.HALT` | Pause | WDT pauses during debug halt |
| `CRV` | 983040 | Counter reload value (30s × 32768 Hz) |
| `RREN` | RR0 enabled | Uses reload request register 0 |
| `RR[0]` | 0x6E524635 | Magic value to feed watchdog |

## Usage

1. Watchdog is **disabled by default** - enable via `set wdt on` (reboot required to turn on/off)
2. Application code checks `prefs.wdt_enabled` after loading prefs and calls `board.initWatchdog()` if enabled
3. The main loop must call `board.tick()` regularly to feed the watchdog
4. If `board.tick()` is not called within 30 seconds, the device resets
5. The watchdog pauses during low-power sleep modes (won't reset during intended sleep)

**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`.

## Enabling Watchdog for a Board Variant

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):

```ini
[env:your_variant]
extends = nrf52_base
build_flags = ${nrf52_base.build_flags}
-D NRF52_WATCHDOG
# ... other flags
```

## Firmware Integration

Currently, Watchdog is only implemented in the Repeater firmware. To add to other firmware types, perform the below steps.

After loading prefs in the `begin()` method, start the watchdog if enabled:

```cpp
_cli.loadPrefs(_fs);

#ifdef NRF52_WATCHDOG
if (_prefs.wdt_enabled) {
board.initWatchdog();
}
#endif
```

Ensure the main `loop()` function calls `board.tick()` to feed the watchdog:

```cpp
void loop() {
the_mesh.loop();
sensors.loop();
#ifdef DISPLAY_CLASS
ui_task.loop();
#endif
rtc_clock.tick();
board.tick(); // Feed the watchdog
}
```

## CLI Commands

When `NRF52_WATCHDOG` is defined, the following CLI commands are available:

| Command | Description |
|---------|-------------|
| `get wdt` | Returns `Enabled/Disabled, running/not running` |
| `set wdt on/off` | Enable/disable watchdog on next reboot |
7 changes: 7 additions & 0 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,13 @@ void MyMesh::begin(FILESYSTEM *fs) {
_fs = fs;
// load persisted prefs
_cli.loadPrefs(_fs);

#ifdef NRF52_WATCHDOG
if (_prefs.wdt_enabled) {
board.initWatchdog();
}
#endif

acl.load(_fs);
// TODO: key_store.begin();
region_map.load(_fs);
Expand Down
1 change: 1 addition & 0 deletions examples/simple_repeater/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();
board.tick();

if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled
the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep
Expand Down
3 changes: 3 additions & 0 deletions src/MeshCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class MainBoard {
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
virtual uint8_t getShutdownReason() const { return 0; }
virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; }

// Watchdog interface (boards with watchdog support override these)
virtual bool isWatchdogRunning() { return false; }
};

/**
Expand Down
21 changes: 19 additions & 2 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
file.read(pad, 3); // 153
file.read((uint8_t *)&_prefs->wdt_enabled, sizeof(_prefs->wdt_enabled)); // 153
file.read(pad, 2); // 154
file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
Expand All @@ -95,6 +96,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
_prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
_prefs->wdt_enabled = constrain(_prefs->wdt_enabled, 0, 1);

// sanitise bad bridge pref values
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
Expand Down Expand Up @@ -158,7 +160,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
file.write(pad, 3); // 153
file.write((uint8_t *)&_prefs->wdt_enabled, sizeof(_prefs->wdt_enabled)); // 153
file.write(pad, 2); // 154
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
Expand Down Expand Up @@ -390,6 +393,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "> %u mV", _board->getBootVoltage());
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
#ifdef NRF52_WATCHDOG
} else if (memcmp(config, "wdt", 3) == 0 && config[3] == 0) {
sprintf(reply, "> %s, %s",
_prefs->wdt_enabled ? "Enabled" : "Disabled",
_board->isWatchdogRunning() ? "running" : "not running");
#endif
} else {
sprintf(reply, "??: %s", config);
Expand Down Expand Up @@ -610,6 +619,14 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_prefs->adc_multiplier = 0.0f;
strcpy(reply, "Error: unsupported by this board");
};
#ifdef NRF52_WATCHDOG
} else if (memcmp(config, "wdt ", 4) == 0) {
const char* value = &config[4];
bool enable = memcmp(value, "on", 2) == 0 || memcmp(value, "1", 1) == 0;
_prefs->wdt_enabled = enable ? 1 : 0;
savePrefs();
strcpy(reply, enable ? "OK - reboot to enable watchdog" : "OK - reboot to disable watchdog");
#endif
} else {
sprintf(reply, "unknown config: %s", config);
}
Expand Down
1 change: 1 addition & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct NodePrefs { // persisted to file
uint32_t discovery_mod_timestamp;
float adc_multiplier;
char owner_info[120];
uint8_t wdt_enabled; // nRF52 watchdog enabled preference (0/1)
};

class CommonCLICallbacks {
Expand Down
47 changes: 47 additions & 0 deletions src/helpers/NRF52Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,47 @@
#include <bluefruit.h>
#include <nrf_soc.h>

#ifdef NRF52_WATCHDOG
#include <nrf.h>

// nRF52 WDT reload register magic value ("nRF5" in ASCII, per Nordic SDK)
#define WDT_RR_VALUE 0x6E524635UL

// 30 second timeout at 32.768kHz clock
#define WDT_TIMEOUT_SECONDS 30
#define WDT_CRV_VALUE (WDT_TIMEOUT_SECONDS * 32768UL)

bool NRF52Board::initWatchdog() {
// Check if already running - WDT cannot be reconfigured once started
if (NRF_WDT->RUNSTATUS) {
return false;
}

// Configure WDT to pause during sleep and halt modes
NRF_WDT->CONFIG = (WDT_CONFIG_SLEEP_Pause << WDT_CONFIG_SLEEP_Pos) |
(WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos);

// Set timeout value (30 seconds)
NRF_WDT->CRV = WDT_CRV_VALUE;

// Enable reload request register 0
NRF_WDT->RREN = WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos;

// Start the watchdog
NRF_WDT->TASKS_START = 1;

return true;
}

void NRF52Board::feedWatchdog() {
NRF_WDT->RR[0] = WDT_RR_VALUE;
}

bool NRF52Board::isWatchdogRunning() {
return NRF_WDT->RUNSTATUS != 0;
}
#endif

static BLEDfu bledfu;

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

void NRF52Board::tick() {
#ifdef NRF52_WATCHDOG
feedWatchdog();
#endif
}

#ifdef NRF52_POWER_MANAGEMENT
#include "nrf.h"

Expand Down
10 changes: 10 additions & 0 deletions src/helpers/NRF52Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ class NRF52Board : public mesh::MainBoard {
virtual void initiateShutdown(uint8_t reason);
#endif

#ifdef NRF52_WATCHDOG
void feedWatchdog();
#endif

public:
virtual void begin();
virtual void tick();
virtual uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override;
virtual void reboot() override { NVIC_SystemReset(); }
Expand All @@ -57,6 +62,11 @@ class NRF52Board : public mesh::MainBoard {
const char* getResetReasonString(uint32_t reason) override;
const char* getShutdownReasonString(uint8_t reason) override;
#endif

#ifdef NRF52_WATCHDOG
bool initWatchdog();
bool isWatchdogRunning() override;
#endif
};

/*
Expand Down
4 changes: 4 additions & 0 deletions variants/heltec_t114/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D NRF52_WATCHDOG
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1

Expand All @@ -72,6 +73,7 @@ build_flags =
-D WITH_RS232_BRIDGE=Serial2
-D WITH_RS232_BRIDGE_RX=9
-D WITH_RS232_BRIDGE_TX=10
-D NRF52_WATCHDOG
; -D BRIDGE_DEBUG=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
Expand Down Expand Up @@ -168,6 +170,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D NRF52_WATCHDOG
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1

Expand All @@ -183,6 +186,7 @@ build_flags =
-D WITH_RS232_BRIDGE=Serial2
-D WITH_RS232_BRIDGE_RX=9
-D WITH_RS232_BRIDGE_TX=10
-D NRF52_WATCHDOG
; -D BRIDGE_DEBUG=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
Expand Down
3 changes: 3 additions & 0 deletions variants/rak4631/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D NRF52_WATCHDOG
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${rak4631.build_src_filter}
Expand All @@ -59,6 +60,7 @@ build_flags =
-D WITH_RS232_BRIDGE=Serial1
-D WITH_RS232_BRIDGE_RX=PIN_SERIAL1_RX
-D WITH_RS232_BRIDGE_TX=PIN_SERIAL1_TX
-D NRF52_WATCHDOG
-UENV_INCLUDE_GPS
; -D BRIDGE_DEBUG=1
; -D MESH_PACKET_LOGGING=1
Expand All @@ -82,6 +84,7 @@ build_flags =
-D WITH_RS232_BRIDGE=Serial2
-D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX
-D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX
-D NRF52_WATCHDOG
-UENV_INCLUDE_GPS
; -D BRIDGE_DEBUG=1
; -D MESH_PACKET_LOGGING=1
Expand Down
1 change: 1 addition & 0 deletions variants/xiao_nrf52/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ build_flags =
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D NRF52_WATCHDOG
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Xiao_nrf52.build_src_filter}
Expand Down