Skip to content
Draft
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
1 change: 1 addition & 0 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ void setup() {
}

void loop() {
board.loop();
the_mesh.loop();
sensors.loop();
#ifdef DISPLAY_CLASS
Expand Down
1 change: 1 addition & 0 deletions examples/kiss_modem/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ void setup() {
}

void loop() {
board.loop();
modem->loop();

if (!modem->isActuallyTransmitting()) {
Expand Down
2 changes: 2 additions & 0 deletions examples/simple_repeater/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions examples/simple_room_server/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions examples/simple_secure_chat/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ void setup() {
}

void loop() {
board.loop();
the_mesh.loop();
rtc_clock.tick();
}
2 changes: 2 additions & 0 deletions examples/simple_sensor/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/MeshCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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 */ }

// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }
Expand Down
88 changes: 85 additions & 3 deletions src/helpers/NRF52Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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();

Expand All @@ -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
Expand Down
19 changes: 18 additions & 1 deletion src/helpers/NRF52Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -38,21 +43,33 @@ class NRF52Board : public mesh::MainBoard {
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;
virtual void reboot() override { NVIC_SystemReset(); }
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
};


Expand Down
4 changes: 3 additions & 1 deletion variants/heltec_t114/T114Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion variants/rak4631/RAK4631Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion variants/sensecap_solar/SenseCapSolarBoard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion variants/xiao_nrf52/XiaoNrf52Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading