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
12 changes: 12 additions & 0 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "MyMesh.h"
#include <algorithm>
#if defined(NRF52_PLATFORM)
#include "helpers/NRF52Board.h"
#endif

/* ------------------------------ Config -------------------------------- */

Expand Down Expand Up @@ -946,6 +949,15 @@ void MyMesh::begin(FILESYSTEM *fs) {
// load persisted prefs
_cli.loadPrefs(_fs);
acl.load(_fs, self_id);
#if defined(NRF52_PLATFORM)
{
uint8_t wdt_to = 0;
if (_prefs.wdt_enabled) {
wdt_to = constrain(_prefs.wdt_timeout_secs, 1, 255);
}
static_cast<NRF52Board&>(board).initWatchdog(wdt_to);
}
#endif
// TODO: key_store.begin();
region_map.load(_fs);

Expand Down
3 changes: 3 additions & 0 deletions examples/simple_repeater/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ void loop() {

the_mesh.loop();
sensors.loop();
#if defined(NRF52_PLATFORM)
board.loop();
#endif
#ifdef DISPLAY_CLASS
ui_task.loop();
#endif
Expand Down
1 change: 1 addition & 0 deletions src/MeshCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class MainBoard {
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 */ }
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemLna() const { return false; }
virtual bool isLoRaFemLnaEnabled() const { return false; }
Expand Down
90 changes: 90 additions & 0 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#include "TxtDataHelpers.h"
#include <RTClib.h>

#if defined(NRF52_PLATFORM)
#include "helpers/NRF52Board.h"
#endif

#ifndef BRIDGE_MAX_BAUD
#define BRIDGE_MAX_BAUD 115200
#endif
Expand Down Expand Up @@ -126,6 +130,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy
_prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);

_prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1);
_prefs->wdt_enabled = constrain(_prefs->wdt_enabled, 0, 1);
if (_prefs->wdt_timeout_secs == 0) _prefs->wdt_timeout_secs = 30;
_prefs->wdt_timeout_secs = constrain(_prefs->wdt_timeout_secs, 1, 255);

_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
Expand Down Expand Up @@ -397,6 +404,89 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
} else {
strcpy(reply, "Can't find GPS");
}
#endif
} else if (memcmp(command, "watchdog lockup", 15) == 0) {
#if defined(NRF52_PLATFORM)
NRF52Board* nb = static_cast<NRF52Board*>(_board);
if (!nb->isWatchdogRunning()) {
strcpy(reply, "error - watchdog not running");
} else {
strcpy(reply, "OK - locking loop");
nb->requestLockup();
}
#else
strcpy(reply, "not supported on this platform");
#endif
} else if (memcmp(command, "watchdog timeout ", 17) == 0) {
#if defined(NRF52_PLATFORM)
uint32_t n = _atoi(&command[17]);
if (n < 1 || n > 255) {
strcpy(reply, "error - timeout must be 1..255 (use watchdog off to disable)");
} else {
_prefs->wdt_timeout_secs = (uint8_t)n;
savePrefs();
NRF52Board* nb = static_cast<NRF52Board*>(_board);
if (nb->isWatchdogRunning() && nb->getWatchdogRunningTimeoutSecs() != n) {
strcpy(reply, "OK - reboot to apply");
} else {
sprintf(reply, "OK - timeout %us", (unsigned)n);
}
}
#else
strcpy(reply, "not supported on this platform");
#endif
} else if (memcmp(command, "watchdog timeout", 16) == 0 && (command[16] == 0 || command[16] == ' ')) {
#if defined(NRF52_PLATFORM)
NRF52Board* nb = static_cast<NRF52Board*>(_board);
uint8_t run_to = nb->getWatchdogRunningTimeoutSecs();
if (run_to) {
sprintf(reply, "pref %us, running %us", (unsigned)_prefs->wdt_timeout_secs, (unsigned)run_to);
} else {
sprintf(reply, "pref %us, not running", (unsigned)_prefs->wdt_timeout_secs);
}
#else
strcpy(reply, "not supported on this platform");
#endif
} else if (memcmp(command, "watchdog on", 11) == 0) {
#if defined(NRF52_PLATFORM)
_prefs->wdt_enabled = 1;
savePrefs();
NRF52Board* nb = static_cast<NRF52Board*>(_board);
if (nb->isWatchdogRunning()) {
strcpy(reply, "OK - already running");
} else {
strcpy(reply, "OK - reboot to start");
}
#else
strcpy(reply, "not supported on this platform");
#endif
} else if (memcmp(command, "watchdog off", 12) == 0) {
#if defined(NRF52_PLATFORM)
_prefs->wdt_enabled = 0;
savePrefs();
strcpy(reply, "OK - reboot to stop");
#else
strcpy(reply, "not supported on this platform");
#endif
} else if (memcmp(command, "watchdog status", 15) == 0 || memcmp(command, "watchdog", 8) == 0) {
#if defined(NRF52_PLATFORM)
NRF52Board* nb = static_cast<NRF52Board*>(_board);
const char* en = _prefs->wdt_enabled ? "enabled" : "disabled";
const char* run = nb->isWatchdogRunning() ? "running" : "not running";
const char* last = nb->getResetReasonString(nb->getResetReason());
uint8_t run_to = nb->getWatchdogRunningTimeoutSecs();
if (_prefs->wdt_enabled && !nb->isWatchdogRunning()) {
sprintf(reply, "%s, %s, timeout %us (reboot to start), last reset: %s",
en, run, (unsigned)_prefs->wdt_timeout_secs, last);
} else if (!_prefs->wdt_enabled && nb->isWatchdogRunning()) {
sprintf(reply, "%s, %s, timeout %us (reboot to stop), last reset: %s",
en, run, (unsigned)(run_to ? run_to : _prefs->wdt_timeout_secs), last);
} else {
sprintf(reply, "%s, %s, timeout %us, last reset: %s",
en, run, (unsigned)(run_to ? run_to : _prefs->wdt_timeout_secs), last);
}
#else
strcpy(reply, "not supported on this platform");
#endif
} else if (memcmp(command, "powersaving on", 14) == 0) {
#if defined(NRF52_PLATFORM)
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class NodePrefs : public ConfigSerializer {
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
// Power setting
uint8_t powersaving_enabled = 0; // boolean
uint8_t wdt_enabled = 1; // nRF52 hardware watchdog
uint8_t wdt_timeout_secs = 30; // seconds (1..255)
// Gps settings
uint8_t gps_enabled = 0;
uint32_t gps_interval = 0; // in seconds
Expand Down Expand Up @@ -134,6 +136,8 @@ class NodePrefs : public ConfigSerializer {
void structure() override {
def("adc_mult", _parent->adc_multiplier);
def("pwr_sav_en", _parent->powersaving_enabled);
def("wdt_en", _parent->wdt_enabled);
def("wdt_timeout", _parent->wdt_timeout_secs);
}
public:
PowerPrefs(NodePrefs* parent) : _parent(parent) { }
Expand Down
106 changes: 74 additions & 32 deletions src/helpers/NRF52Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,92 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
MESH_DEBUG_PRINTLN("BLE client disconnected");
}

#include "nrf.h"

// Reset/shutdown capture — always compiled so watchdog status works without NRF52_POWER_MANAGEMENT
uint32_t g_nrf52_reset_reason = 0;
uint8_t g_nrf52_shutdown_reason = 0;

static void __attribute__((constructor(101))) nrf52_early_reset_capture() {
g_nrf52_reset_reason = NRF_POWER->RESETREAS;
g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2;
}

void NRF52Board::begin() {
startup_reason = BD_STARTUP_NORMAL;
reset_reason = g_nrf52_reset_reason;
#ifndef NRF52_POWER_MANAGEMENT
NRF_POWER->RESETREAS = 0xFFFFFFFF;
#endif

#ifdef USE_CC310_HW_CRYPTO
// CC310 TRNG is higher quality and environment-independent vs radio RSSI noise.
nRFCrypto.begin();
#endif
}

#ifdef NRF52_POWER_MANAGEMENT
#include "nrf.h"
static const uint32_t WDT_FEED_MAGIC = 0x6E524635u;

// Power Management global variables
uint32_t g_nrf52_reset_reason = 0; // Reset/Startup reason
uint8_t g_nrf52_shutdown_reason = 0; // Shutdown reason
void NRF52Board::feedWatchdogIfRunning() {
if (NRF_WDT->RUNSTATUS) NRF_WDT->RR[0] = WDT_FEED_MAGIC;
}

// Early constructor - runs before SystemInit() clears the registers
// Priority 101 ensures this runs before SystemInit (102) and before
// any C++ static constructors (default 65535)
static void __attribute__((constructor(101))) nrf52_early_reset_capture() {
g_nrf52_reset_reason = NRF_POWER->RESETREAS;
g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2;
bool NRF52Board::isWatchdogRunning() const {
return NRF_WDT->RUNSTATUS != 0;
}

uint8_t NRF52Board::getWatchdogRunningTimeoutSecs() const {
if (!isWatchdogRunning()) return 0;
uint32_t crv = NRF_WDT->CRV;
if (crv == 0) return 0;
uint32_t secs = (crv + 32767u) / 32768u;
return secs > 255u ? 255u : (uint8_t)secs;
}

void NRF52Board::initWatchdog(uint8_t timeout_secs) {
if (timeout_secs == 0) return;
_wdt_timeout_secs = timeout_secs;
if (NRF_WDT->RUNSTATUS) return; // already counting (e.g. carried over from prior boot)
NRF_WDT->CRV = (uint32_t)timeout_secs * 32768u;
NRF_WDT->RREN = WDT_RREN_RR0_Enabled;
NRF_WDT->CONFIG = (WDT_CONFIG_SLEEP_Pause << WDT_CONFIG_SLEEP_Pos)
| (WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos);
NRF_WDT->TASKS_START = 1;
}

void NRF52Board::requestLockup() {
_lockup_at = millis() + 500;
}

void NRF52Board::loop() {
feedWatchdogIfRunning();
if (_lockup_at != 0 && (int32_t)(millis() - _lockup_at) >= 0) {
Serial.flush();
while (1) {}
}
}

const char* NRF52Board::getResetReasonString(uint32_t reason) {
if (reason & POWER_RESETREAS_RESETPIN_Msk) return "Reset Pin";
if (reason & POWER_RESETREAS_DOG_Msk) return "Watchdog";
if (reason & POWER_RESETREAS_SREQ_Msk) return "Soft Reset";
if (reason & POWER_RESETREAS_LOCKUP_Msk) return "CPU Lockup";
#ifdef POWER_RESETREAS_LPCOMP_Msk
if (reason & POWER_RESETREAS_LPCOMP_Msk) return "Wake from LPCOMP";
#endif
#ifdef POWER_RESETREAS_VBUS_Msk
if (reason & POWER_RESETREAS_VBUS_Msk) return "Wake from VBUS";
#endif
#ifdef POWER_RESETREAS_OFF_Msk
if (reason & POWER_RESETREAS_OFF_Msk) return "Wake from GPIO";
#endif
#ifdef POWER_RESETREAS_DIF_Msk
if (reason & POWER_RESETREAS_DIF_Msk) return "Debug Interface";
#endif
return "Cold Boot";
}

#ifdef NRF52_POWER_MANAGEMENT

void NRF52Board::initPowerMgr() {
// Copy early-captured register values
reset_reason = g_nrf52_reset_reason;
Expand Down Expand Up @@ -76,26 +138,6 @@ void NRF52Board::initPowerMgr() {
}
}

const char* NRF52Board::getResetReasonString(uint32_t reason) {
if (reason & POWER_RESETREAS_RESETPIN_Msk) return "Reset Pin";
if (reason & POWER_RESETREAS_DOG_Msk) return "Watchdog";
if (reason & POWER_RESETREAS_SREQ_Msk) return "Soft Reset";
if (reason & POWER_RESETREAS_LOCKUP_Msk) return "CPU Lockup";
#ifdef POWER_RESETREAS_LPCOMP_Msk
if (reason & POWER_RESETREAS_LPCOMP_Msk) return "Wake from LPCOMP";
#endif
#ifdef POWER_RESETREAS_VBUS_Msk
if (reason & POWER_RESETREAS_VBUS_Msk) return "Wake from VBUS";
#endif
#ifdef POWER_RESETREAS_OFF_Msk
if (reason & POWER_RESETREAS_OFF_Msk) return "Wake from GPIO";
#endif
#ifdef POWER_RESETREAS_DIF_Msk
if (reason & POWER_RESETREAS_DIF_Msk) return "Debug Interface";
#endif
return "Cold Boot";
}

const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
switch (reason) {
case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage";
Expand Down
19 changes: 15 additions & 4 deletions src/helpers/NRF52Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ class NRF52Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
char *ota_name;
uint32_t reset_reason; // RESETREAS captured at boot (before clear)
uint32_t _lockup_at = 0; // millis deadline for deferred watchdog lockup test
uint8_t _wdt_timeout_secs = 0; // configured/pref timeout (CRV frozen after start)

#ifdef NRF52_POWER_MANAGEMENT
uint32_t reset_reason; // RESETREAS register value
uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF)
uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts)

Expand All @@ -45,7 +47,7 @@ class NRF52Board : public mesh::MainBoard {
#endif

public:
NRF52Board(char *otaname) : ota_name(otaname) {}
NRF52Board(char *otaname) : ota_name(otaname), reset_reason(0) {}
virtual void begin();
virtual uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override;
Expand All @@ -55,13 +57,22 @@ class NRF52Board : public mesh::MainBoard {
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;
bool isExternalPowered() override;

void initWatchdog(uint8_t timeout_secs);
void requestLockup();
bool isWatchdogRunning() const;
uint8_t getWatchdogTimeoutSecs() const { return _wdt_timeout_secs; }
uint8_t getWatchdogRunningTimeoutSecs() const;
static void feedWatchdogIfRunning();

virtual uint32_t getResetReason() const override { return reset_reason; }
const char* getResetReasonString(uint32_t reason) override;

#ifdef NRF52_POWER_MANAGEMENT
uint16_t getBootVoltage() override { return boot_voltage_mv; }
virtual uint32_t getResetReason() const override { return reset_reason; }
uint8_t getShutdownReason() const override { return shutdown_reason; }
const char* getResetReasonString(uint32_t reason) override;
const char* getShutdownReasonString(uint8_t reason) override;
#endif
};
Expand Down