diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 2c8753d464..66823b0eb2 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -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; @@ -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; @@ -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; diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index c9f1e071b8..65fe897266 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -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 diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index cbf7c1087d..07e28fcbe5 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -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 = { @@ -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 diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 3a080d5e2c..6b1d398735 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -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 {