Skip to content

RAK3401: powerOff() override + AIN button LPCOMP wake from SYSTEMOFF 🤖🤖#2642

Open
disq wants to merge 2 commits into
meshcore-dev:devfrom
disq:rak3401-ain1-wake
Open

RAK3401: powerOff() override + AIN button LPCOMP wake from SYSTEMOFF 🤖🤖#2642
disq wants to merge 2 commits into
meshcore-dev:devfrom
disq:rak3401-ain1-wake

Conversation

@disq
Copy link
Copy Markdown

@disq disq commented May 28, 2026

Two board-level pieces from @dorfman2's #2414 that are useful for any RAK3401 firmware, not just the repeater variant that PR targets.

What this changes

  1. RAK3401Board::powerOff() override (variants/rak3401/RAK3401Board.h) — routes _board->powerOff() through initiateShutdown(SHUTDOWN_REASON_USER) so the shutdown reason is properly tagged in GPREGRET2 instead of falling through to the base class default.

  2. AIN button wake-from-SYSTEMOFF via LPCOMP (variants/rak3401/RAK3401Board.cpp) — when an env defines PIN_USER_BTN_ANA, arm a wake source before entering SYSTEMOFF so pressing the button powers the board back on.

  3. NRF52Board::configureVoltageWake() gains a detect_down flag (src/helpers/NRF52Board.{h,cpp}) — defaults to false (the existing upward-crossing voltage-recovery behavior, so no other NRF52 board is affected), and true selects a downward crossing for the button wake.

Why LPCOMP and not GPIO SENSE

#2414's original code armed the pin with SENSE_Low. That works for the repeater, where the button is a digital normally-open switch to GND. It does not work on the companion_radio build, where the same pin (PIN_USER_BTN_ANA=31, P0.31 = AIN7) is wired as an analog button — MomentaryButton reads it as analogRead() < threshold.

Two failures, both reproduced and verified on hardware (companion_radio_ble, on USB and battery):

  • Shutdown hung. The release-wait used digitalRead() on this pin, but the SAADC leaves the digital input buffer disconnected, so digitalRead() always returns LOW. The loop never exited, SYSTEMOFF was never reached — screen off, but BLE/serial still alive.
  • Immediate self-wake. Even past that, GPIO SENSE_Low reads this line as LOW at the released idle level (analogRead reports ~VDD while NRF_GPIO->IN reads 0 and the SENSE latch re-asserts the instant it's cleared), so the chip woke the moment it entered SYSTEMOFF. RESETREAS = Wake from GPIO (0x10000), identical on USB and battery — ruling out VBUS.

The fix arms LPCOMP instead. It operates in the analog domain, so it sees the idle level correctly: a DOWN crossing at ~½ VDD wakes on a press (released idles near VDD = above threshold; a press pulls the pin to ~0V = below). The release-wait now uses analogRead and is bounded by a 5s timeout so a stuck reading can never wedge shutdown. LPCOMP is otherwise unused for a USER shutdown (voltage wake is only armed for the low-voltage / boot-protect reasons), so there is no conflict. The AIN channel and threshold are overridable build defines.

What this does NOT include

The repeater-specific UI redesign, status screen, and simple_repeater/main.cpp button handler from #2414 are out of scope here — the companion_radio UI tree already has its own button handling in examples/companion_radio/ui-new/UITask.cpp. This PR cleanly lifts only the universally-beneficial board-level pieces; #2414 can land independently with the repeater UI work on top.

Testing

Verified on RAK3401 + RAK19007 + companion_radio_ble build, on both USB and battery:

  • _board->powerOff() from the companion UI menu enters SYSTEMOFF and the board stays off (USB/serial drop, BLE LED dark).
  • Pressing the AIN button wakes the board; RESETREAS = Wake from LPCOMP and the subsequent boot is normal.
  • RAK_3401_repeater (no PIN_USER_BTN_ANA) and RAK_3401_companion_radio_ble both build clean; the configureVoltageWake() signature change is backward-compatible via the defaulted parameter.

🤖 Generated with Claude Code

Two board-level pieces from dorfman2's PR meshcore-dev#2414 that are useful for any
RAK3401 firmware, not just the repeater variant the PR targets:

1. RAK3401Board::powerOff() override — routes _board->powerOff() through
   initiateShutdown(SHUTDOWN_REASON_USER) so the shutdown reason is
   properly tagged in GPREGRET2 instead of falling through to the base
   class default.

2. AIN1 GPIO SENSE LOW config in initiateShutdown() — when an env defines
   PIN_USER_BTN_ANA, configure that pin with pull-up + SENSE LOW before
   entering SYSTEMOFF, so pressing the AIN1 button wakes the board from
   the off state via the GPIO LATCH/SENSE mechanism. Waits for button
   release first (level-triggered SENSE would otherwise wake immediately
   if the user is still holding the button when we arm it).

The repeater-specific UI redesign, status screen, and simple_repeater
main.cpp button handler from the same PR are out of scope here; the
companion radio UI tree already has its own equivalent button handling
in examples/companion_radio/ui-new/UITask.cpp.

Co-Authored-By: dorfman2 <noreply@github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@disq disq changed the title RAK3401: powerOff() override + AIN1 SENSE LOW wake from SYSTEMOFF RAK3401: powerOff() override + AIN1 SENSE LOW wake from SYSTEMOFF 🤖🤖 May 28, 2026
@dorfman2
Copy link
Copy Markdown

Ah @disq I see what you meant now. I was too focused on the UI portion of the code. Would love to see this implemented!

@disq
Copy link
Copy Markdown
Author

disq commented Jun 1, 2026

The other PR wakes immediately and I put it to draft while I investigate, so I need to test this again first.
Now tested successfully.

…tton

The AIN user button on the companion_radio build is an *analog* button
(MomentaryButton reads it as analogRead() < threshold), not a digital
switch. GPIO SENSE cannot wake from this pin: the digital input buffer
reads the line as LOW even at the released idle level — verified on
hardware, analogRead reports ~VDD while NRF_GPIO->IN reads 0 and the
SENSE_Low condition latches immediately (LATCH bit re-asserts right after
being cleared). So a GPIO SENSE_Low arm wakes the chip the instant it
enters SYSTEMOFF and it can never stay off. This reproduced identically
on USB and battery (RESETREAS = Wake from GPIO, 0x10000), ruling out VBUS.

This also fixes an earlier hang: the original release-wait used
digitalRead() on this pin, which always returns LOW (SAADC leaves the
digital buffer disconnected), so shutdown never reached SYSTEMOFF —
screen off, BLE/serial still alive.

Fix: arm LPCOMP instead. It works in the analog domain, so it sees the
idle level correctly. Configure a DOWN crossing at ~1/2 VDD (released
idles near VDD = above, a press pulls to ~0V = below -> wake). LPCOMP is
otherwise unused for a USER shutdown (voltage wake is only armed for the
low-voltage / boot-protect reasons), so there is no conflict.

- NRF52Board::configureVoltageWake() gains a detect_down flag (default
  false = existing upward voltage-recovery behavior, backward compatible)
  to also support downward-crossing wake.
- RAK3401 initiateShutdown() waits for release via analogRead (bounded by
  a 5s timeout so a stuck reading can never wedge shutdown), then arms
  LPCOMP on AIN7. AIN channel / threshold are overridable build defines.

Verified on hardware: device reaches SYSTEMOFF and stays off; a button
press wakes it (RESETREAS = Wake from LPCOMP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@disq disq changed the title RAK3401: powerOff() override + AIN1 SENSE LOW wake from SYSTEMOFF 🤖🤖 RAK3401: powerOff() override + AIN button LPCOMP wake from SYSTEMOFF Jun 1, 2026
@disq disq changed the title RAK3401: powerOff() override + AIN button LPCOMP wake from SYSTEMOFF RAK3401: powerOff() override + AIN button LPCOMP wake from SYSTEMOFF 🤖🤖 Jun 1, 2026
@disq
Copy link
Copy Markdown
Author

disq commented Jun 1, 2026

@dorfman2 It wasn't working for me on the companion so had to burn more tokens and do the "right thing".

disq added a commit to disq/MeshCore that referenced this pull request Jun 1, 2026
Brings meshcore-dev#2642's RAK3401 board pieces into this branch so the AIN1 button story is
self-contained, using LPCOMP (not GPIO SENSE) as the hibernate wake source.

AIN1 (P0.31) is wired as an analog button (pressed == analogRead() < threshold).
GPIO SENSE can't wake on it: the digital input buffer reads the *released* idle
level as LOW even though analogRead reports ~VDD, so arming SENSE_Low latches DETECT
the instant we enter SYSTEMOFF — sd_power_system_off() returns immediately and the
fall-through reset reboots ("instant wake"). LATCH/EVENTS_PORT clearing and release
debounce don't help, because the released level itself reads LOW digitally.

LPCOMP operates in the analog domain and sees the idle level correctly. Arm a DOWN
crossing at ~1/2 VDD: released idles above threshold, a press pulls the pin below ->
downward crossing -> wake. Wait for a confirmed release (analogRead above threshold)
before arming, bounded by a 5s timeout so a stuck/low reading can't wedge shutdown.

- NRF52Board::configureVoltageWake() gains a detect_down param (default false = UP
  crossing for voltage recovery; true = DOWN crossing for a button press), selecting
  ANADETECT Down/Up and INTENSET DOWN/UP accordingly.
- RAK3401Board.h: powerOff() override routing through initiateShutdown(USER).
- RAK3401Board::initiateShutdown() arms LPCOMP on AIN7 at REFSEL 3 (~1/2 VDD) for a
  USER shutdown when PIN_USER_BTN_ANA is defined. Channel/threshold overridable via
  PWRMGT_BTN_LPCOMP_AIN / PWRMGT_BTN_LPCOMP_REFSEL.

Ports the fix from meshcore-dev#2642. Built: RAK_3401_companion_radio_ble.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants