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
10 changes: 6 additions & 4 deletions src/helpers/NRF52Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ 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 refsel, bool detect_down) {
// LPCOMP is not managed by SoftDevice - direct register access required
// Halt and disable before reconfiguration
NRF_LPCOMP->TASKS_STOP = 1;
Expand All @@ -189,8 +189,10 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
// 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;

// Detect UP events (voltage rises above threshold for battery recovery)
NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up;
// Crossing direction: UP for voltage recovery (rises above threshold),
// DOWN for an analog button press (pin pulled below threshold).
NRF_LPCOMP->ANADETECT = detect_down ? LPCOMP_ANADETECT_ANADETECT_Down
: LPCOMP_ANADETECT_ANADETECT_Up;

// Enable 50mV hysteresis for noise immunity
NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV;
Expand All @@ -202,7 +204,7 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
NRF_LPCOMP->EVENTS_CROSS = 0;

NRF_LPCOMP->INTENCLR = 0xFFFFFFFF;
NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk;
NRF_LPCOMP->INTENSET = detect_down ? LPCOMP_INTENSET_DOWN_Msk : LPCOMP_INTENSET_UP_Msk;

// Enable LPCOMP
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled;
Expand Down
5 changes: 4 additions & 1 deletion src/helpers/NRF52Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ class NRF52Board : public mesh::MainBoard {

bool checkBootVoltage(const PowerMgtConfig* config);
void enterSystemOff(uint8_t reason);
void configureVoltageWake(uint8_t ain_channel, uint8_t refsel);
// Arm LPCOMP as a SYSTEMOFF wake source on the given analog channel.
// detect_down=false wakes on an upward crossing (voltage recovery);
// detect_down=true wakes on a downward crossing (e.g. an analog button press).
void configureVoltageWake(uint8_t ain_channel, uint8_t refsel, bool detect_down = false);
virtual void initiateShutdown(uint8_t reason);
#endif

Expand Down
42 changes: 42 additions & 0 deletions variants/rak3401/RAK3401Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
#include "RAK3401Board.h"

#ifdef NRF52_POWER_MANAGEMENT
#ifdef PIN_USER_BTN_ANA
// LPCOMP wake config for the AIN user button. Defaults assume PIN_USER_BTN_ANA
// is pin 31 (P0.31 = AIN7); override via build flags if the button moves.
#ifndef PWRMGT_BTN_LPCOMP_AIN
#define PWRMGT_BTN_LPCOMP_AIN 7
#endif
#ifndef PWRMGT_BTN_LPCOMP_REFSEL
#define PWRMGT_BTN_LPCOMP_REFSEL 3 // 4/8 VDD (~1.5V) threshold
#endif
#endif

// Static configuration for power management
// Values set in variant.h defines
const PowerMgtConfig power_config = {
Expand All @@ -24,6 +35,37 @@ void RAK3401Board::initiateShutdown(uint8_t reason) {
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
}

#ifdef PIN_USER_BTN_ANA
// Wake-from-SYSTEMOFF on the AIN user button (P0.31 = AIN7).
//
// This pin is wired as an *analog* button (see MomentaryButton in target.cpp:
// pressed == analogRead() < threshold). GPIO SENSE can't be used as the wake
// source: the digital input buffer reads this line as LOW even at the released
// idle level (verified on hardware β€” analogRead reports ~VDD while NRF_GPIO->IN
// reads 0 and SENSE_Low latches immediately), so a GPIO SENSE arm wakes the
// chip the instant we enter SYSTEMOFF and it can never stay off.
//
// LPCOMP works in the analog domain, so it sees the idle level correctly. Arm
// it for a DOWN crossing at ~1/2 VDD: released idles near VDD (above), a press
// pulls the pin toward 0V (below) -> downward crossing -> wake. The LPCOMP is
// otherwise unused for a USER shutdown (voltage wake is only armed for the
// low-voltage / boot-protect reasons handled above), so there is no conflict.
//
// Wait for release first so LPCOMP is armed while the level is above the
// threshold β€” otherwise the initial press generates no new downward crossing.
// Bounded by a timeout so a stuck/low reading can never wedge shutdown.
const int BTN_RELEASED_ADC = 1024; // well above the press threshold
uint32_t t0 = millis();
int released_streak = 0;
while (released_streak < 5 && (millis() - t0) < 5000) {
if (analogRead(PIN_USER_BTN_ANA) > BTN_RELEASED_ADC) released_streak++;
else released_streak = 0;
delay(10);
}

configureVoltageWake(PWRMGT_BTN_LPCOMP_AIN, PWRMGT_BTN_LPCOMP_REFSEL, /*detect_down=*/true);
#endif

enterSystemOff(reason);
}
#endif
Expand Down
4 changes: 4 additions & 0 deletions variants/rak3401/RAK3401Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class RAK3401Board : public NRF52BoardDCDC {
RAK3401Board() : NRF52Board("RAK3401_OTA") {}
void begin();

#ifdef NRF52_POWER_MANAGEMENT
void powerOff() override { initiateShutdown(SHUTDOWN_REASON_USER); }
#endif

#define BATTERY_SAMPLES 8

uint16_t getBattMilliVolts() override {
Expand Down