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
2 changes: 2 additions & 0 deletions src/MeshCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ class MainBoard {
virtual bool isLoRaFemLnaEnabled() const { return false; }

// Power management interface (boards with power management override these)
virtual bool isPwrMgtInitialised() const { return false; }
virtual bool isExternalPowered() { return false; }
virtual uint16_t getBootVoltage() { return 0; }
virtual bool getWakeLpcompSupported() const { return false; }
virtual uint32_t getResetReason() const { return 0; }
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
virtual uint8_t getShutdownReason() const { return 0; }
Expand Down
47 changes: 27 additions & 20 deletions src/helpers/NRF52Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ static void __attribute__((constructor(101))) nrf52_early_reset_capture() {
g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2;
}

void NRF52Board::initPowerMgr() {
void NRF52Board::pwrmgtInit() {
if (pwrmgt_initialised) return;
// Copy early-captured register values
reset_reason = g_nrf52_reset_reason;
shutdown_reason = g_nrf52_shutdown_reason;
Expand All @@ -65,6 +66,7 @@ void NRF52Board::initPowerMgr() {
MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX)",
getResetReasonString(reset_reason), (unsigned long)reset_reason);
}
pwrmgt_initialised = true;
}

const char* NRF52Board::getResetReasonString(uint32_t reason) {
Expand All @@ -89,6 +91,7 @@ const char* NRF52Board::getResetReasonString(uint32_t reason) {

const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
switch (reason) {
case SHUTDOWN_REASON_NONE: return "None";
case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage";
case SHUTDOWN_REASON_USER: return "User Request";
case SHUTDOWN_REASON_BOOT_PROTECT: return "Boot Protection";
Expand All @@ -97,7 +100,7 @@ const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
}

bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
initPowerMgr();
pwrmgtInit();

// Read boot voltage
boot_voltage_mv = getBattMilliVolts();
Expand Down Expand Up @@ -164,24 +167,30 @@ void NRF52Board::enterSystemOff(uint8_t reason) {
NVIC_SystemReset();
}

void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t lpcomp_refsel) {
pwrmgtWakeArmVbus();
if (!isPwrMgtInitialised()) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake not armed. Reason: Power Management not initialised");
return;
}
if (!getWakeLpcompSupported()) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake not armed. Reason: LPCOMP unsupported on variant");
return;
}
// LPCOMP is not managed by SoftDevice - direct register access required
// Halt and disable before reconfiguration
NRF_LPCOMP->TASKS_STOP = 1;
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled;

// Select analog input (AIN0-7 maps to PSEL 0-7)
NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk;
NRF_LPCOMP->PSEL = ((uint32_t)PWRMGT_LPCOMP_AIN << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk;

// Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16)
NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk;
NRF_LPCOMP->REFSEL = ((uint32_t)lpcomp_refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk;

// Detect UP events (voltage rises above threshold for battery recovery)
NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up;

// Enable 50mV hysteresis for noise immunity
NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV;

// Clear stale events/interrupts before enabling wake
NRF_LPCOMP->EVENTS_READY = 0;
NRF_LPCOMP->EVENTS_DOWN = 0;
Expand All @@ -195,23 +204,22 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled;
NRF_LPCOMP->TASKS_START = 1;

// Wait for comparator to settle before entering SYSTEMOFF
// Wait for comparator to settle
for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) {
delayMicroseconds(50);
}

if (refsel == 7) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel);
} else if (refsel <= 6) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)",
ain_channel, refsel + 1);
if (lpcomp_refsel == 7) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake armed (AIN%d, ref=ARef)", PWRMGT_LPCOMP_AIN);
} else if (lpcomp_refsel <= 6) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake armed (AIN%d, ref=%d/8 VDD)", PWRMGT_LPCOMP_AIN, lpcomp_refsel + 1);
} else {
uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1);
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)",
ain_channel, ref_num);
uint8_t ref_num = (uint8_t)((lpcomp_refsel - 8) * 2 + 1);
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake armed (AIN%d, ref=%d/16 VDD)", PWRMGT_LPCOMP_AIN, ref_num);
}
}

// Configure VBUS (USB power) wake alongside LPCOMP
void NRF52Board::pwrmgtWakeArmVbus() {
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
Expand All @@ -220,8 +228,7 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
NRF_POWER->EVENTS_USBDETECTED = 0;
NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk;
}

MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured");
MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake armed");
}
#endif

Expand Down
11 changes: 8 additions & 3 deletions src/helpers/NRF52Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,23 @@ struct PowerMgtConfig {
#endif

class NRF52Board : public mesh::MainBoard {
#ifdef NRF52_POWER_MANAGEMENT
void initPowerMgr();
#endif
private:
bool pwrmgt_initialised = false;

protected:
uint8_t startup_reason;
char *ota_name;

#ifdef NRF52_POWER_MANAGEMENT
void pwrmgtInit();
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)

bool checkBootVoltage(const PowerMgtConfig* config);
void enterSystemOff(uint8_t reason);
void configureVoltageWake(uint8_t ain_channel, uint8_t refsel);
void pwrmgtWakeArmVbus();
virtual void initiateShutdown(uint8_t reason);
#endif

Expand All @@ -63,6 +64,10 @@ class NRF52Board : public mesh::MainBoard {
uint8_t getShutdownReason() const override { return shutdown_reason; }
const char* getResetReasonString(uint32_t reason) override;
const char* getShutdownReasonString(uint8_t reason) override;
bool isPwrMgtInitialised() const override { return pwrmgt_initialised; }
#ifdef PWRMGT_LPCOMP_AIN
bool getWakeLpcompSupported() const override { return true; }
#endif
#endif
};

Expand Down
Loading