Skip to content

Allow external battery packs to drive battery percentage on RAK boards - #3338

Open
MFornander wants to merge 2 commits into
meshcore-dev:devfrom
MFornander:dev-mf/rak13302-battery-telemetry-ca3ae3
Open

MFornander wants to merge 2 commits into
meshcore-dev:devfrom
MFornander:dev-mf/rak13302-battery-telemetry-ca3ae3

Conversation

@MFornander

@MFornander MFornander commented Sep 1, 2026 •

Copy link
Copy Markdown

Summary

Makes the RAK4631/RAK3401 battery sense overridable from build flags, so nodes powered by an external pack can report the pack's real charge instead of the (meaningless) on-board charger float voltage.

The motivating setup: a Voltaic V25/V50/V75 pack reports its charge level on the USB-C SBU pins as 1/2 of its internal cell voltage (Voltaic write-up). Wire that SBU pin to the RAK19007 J11 header pin 1 (AIN1 = P0.31), build with -D PIN_VBAT_READ=31 -D ADC_MULTIPLIER=7200, and the node reports the pack's true cell voltage (~3.2V empty to ~4.2V full) through every existing path (stats-core, telemetry LPP voltage, companion app).

Incorporates and supersedes #3328 — the variants/rak4631 hunks here are byte-identical to that PR. Credit to @andyshinn for the override mechanism; this PR extends it to the RAK3401 (1W Booster Kit) variant, where it needs one extra fix to work at all (see below), and adds a power-management escape hatch plus a documented example env.

What changed

  • variants/rak4631/RAK4631Board.h, variants/rak3401/RAK3401Board.h — PIN_VBAT_READ / ADC_MULTIPLIER wrapped in #ifndef guards (same pattern as the rak3112 / heltec_v3 variants). Defaults unchanged. ADC_MULTIPLIER semantics: reported millivolts at ADC full scale (3.6V), so 3600 reports the pin voltage as-is and 7200 doubles it for half-scale sources like the SBU pin.
  • variants/rak4631/variant.h, variants/rak3401/variant.h — PWRMGT_VOLTAGE_BOOTLOCK, PWRMGT_LPCOMP_AIN, PWRMGT_LPCOMP_REFSEL wrapped in #ifndef guards (the bootlock guard makes the already-documented "set to 0 to disable" actually settable per-env), plus a comment block decoding the LPCOMP REFSEL register values (0-6 = n/8 VDD, 7 = AREF, 8-15 = odd sixteenths).
    • The PWRMGT_LPCOMP_AIN guard is load-bearing on current dev: NRF52Board::configureVoltageWake() now selects the comparator input from the PWRMGT_LPCOMP_AIN macro directly rather than its ain_channel argument, so without the guard variant.h would silently overwrite a -D PWRMGT_LPCOMP_AIN=7 build flag back to 3, and the wake comparator would keep watching the unused AIN0 divider instead of the pin actually being measured.
  • variants/rak3401/variant.h — removed the unused Meshtastic-leftover battery block (BATTERY_PIN, BATTERY_SENSE_RESOLUTION*, AREF_VOLTAGE, VBAT_AR_INTERNAL, ADC_MULTIPLIER 1.73). This is required, not cosmetic — see the safety analysis below.
  • variants/rak3401/platformio.ini — new documented RAK_3401_repeater_voltaic example env.
  • docs/nrf52_power_management.md — adds the RAK3401 to the Supported Boards table (its power management was already implemented but not listed) and a "Build-Flag Overrides (RAK4631, RAK3401)" section documenting the five overridable defines, the rule that PWRMGT_LPCOMP_AIN must move with PIN_VBAT_READ, and the Voltaic env as a worked example.

Why deleting the rak3401 variant.h battery block is safe (and necessary)

  • Necessary: variant.h is included via Arduino.h before the board header in every translation unit, and its unconditional #define ADC_MULTIPLIER 1.73 therefore wins against both the new #ifndef default (stock battery reads would become raw × 1.73 / 4096 ≈ 2mV) and any -D ADC_MULTIPLIER=... build flag (an in-file #define overrides a command-line define; the redefinition warning is hidden by the project-wide -w). Pre-PR the same shadowing existed harmlessly in the other direction — the board header's unconditional define silently replaced 1.73.
  • The repo copy is the one compiled: a -H include trace on the real build command shows exactly one variant.h opened — variants/rak3401/variant.h (the pinned meshcore-dev Adafruit_nRF52 framework fork contains no RAK variant to shadow it).
  • Zero references in the whole compile closure: grepping all five symbol families across the framework fork's cores/ + libraries/, every library in .pio/libdeps/, and the repo's src/ + examples/ finds nothing. The only occurrences anywhere are other variants' own copies (t1000-e, xiao, ikoka, heltec) in their own directories, never in a rak3401 build's include path.
  • Provenance: the block arrived wholesale in the variant's initial commit (copied from Meshtastic's rak4631 variant, whose arch code consumes those names — MeshCore has no such consumer) and was never touched since. MeshCore's own rak4631/variant.h never had it; removing it restores parity.
  • Binary proof: stock RAK_3401_repeater firmware built before and after this PR differs by exactly 3 bytes — RTClib's embedded __DATE__ __TIME__ compile timestamp, which differs between any two builds of identical source. Section sizes and the full symbol table are identical.

The example env

RAK_3401_repeater_voltaic extends RAK_3401_repeater and keeps nRF52 power management enabled, rerouted to the SBU input: below 3.4V pack cell voltage the node enters SYSTEMOFF instead of boot-flapping on a drained pack (weak morning sun → boot → 1W TX sags the pack → brownout → repeat), and LPCOMP wakes it once the pack recharges past ~3.7V cell (9/16 VDD at the pin; ~half charge, not full — the only usable REFSEL step between 3.3V and 4.1V given the SBU's ÷2 scaling). configureVoltageWake also arms USB-detect, so plugging in wakes it for maintenance. Note the boot-voltage check only engages when isExternalPowered() is false, i.e. when the node is not powered via USB VBUS (true for e.g. the RAK13302 external-5V input).

The env is opt-in only: release builds select environments ending in _repeater (build.sh get-repeater-firmwares-to-build), so RAK_3401_repeater_voltaic is not published as an official firmware.

Testing

  • RAK_3401_repeater, RAK_3401_repeater_voltaic, and RAK_4631_repeater all build clean on dev @ 5d266dcb; pio run -t envdump confirms every override lands in CPPDEFINES.
  • Preprocessing NRF52Board.cpp shows the LPCOMP PSEL channel resolves to 7 (the SBU input) in RAK_3401_repeater_voltaic and 3 (the stock divider) in RAK_3401_repeater.
  • Stock-env binary identity verified as described above (only the RTClib timestamp bytes differ).
  • All 12 RAK4631 envs build. The six that use the base library set (repeater, repeater_bridge_rs232_serial1/2, room_server, sensor, kiss_modem) produce firmware byte-identical to stock dev apart from the compile timestamp. The other six (companion_radio_usb/ble/ethernet, terminal_chat, repeater_ethernet, room_server_ethernet) only add libraries (base64, RAK13800-W5100S), and no build flag or source in src/, examples/ or lib/ defines any of the five guarded names, so every guard takes its default there too.
  • Hardware, on the motivating setup (RAK19007 + RAK3401 + RAK13302 1W on external 5V, Voltaic V25 SBU wired to J11 pin 1): stats-core reports battery_mv ≈ 2× the SBU voltage measured with a meter, i.e. the pack's cell voltage (> 4 V on a charged pack), where the stock build reports the charger float voltage (4321 mV with no LiPo). The SYSTEMOFF / LPCOMP wake cycle has not yet been exercised on hardware.

@MFornander
MFornander force-pushed the dev-mf/rak13302-battery-telemetry-ca3ae3 branch from 34d0618 to 846093e Compare September 1, 2026 20:53
@MFornander
MFornander changed the base branch from main to dev September 1, 2026 20:53
@MFornander

Copy link
Copy Markdown
Author

Doing one more manual bench test before submitting PR:

  1. Meter first: SBU at the breakout should read 1.6–2.1V (a mid-charge pack ≈ 1.85–1.95V). If it reads 0, the cable path isn't carrying SBU (needs full-featured USB-C) or a pull-down is loading it.
  2. Flash: double-tap reset → copy .pio/build/RAK_3401_repeater_voltaic/firmware.uf2 onto the boot drive.
  3. Verify: stats-core on the USB console → battery_mv should be ~2× the metered SBU. Consistently a few percent low usually means SBU source impedance sagging under the ADC — the 100nF cap on J11 pin 1 fixes it, or bump ADC_MULTIPLIER (7300-ish) to calibrate it out.
  4. Confirm in the app: repeater status/telemetry should show the same voltage, and it should track the pack across a charge cycle rather than sitting pinned at ~4.2V like the old charger-float reading.
  5. Optional sleep-path test (safe version): temporarily build with PWRMGT_VOLTAGE_BOOTLOCK=4300 and PWRMGT_LPCOMP_REFSEL=14 — it should boot-protect into SYSTEMOFF immediately and wake only when you plug USB in. Don't test with just the bootlock raised: with a charged pack the LPCOMP threshold would already be exceeded and it'll wake-loop until reflashed.

@MFornander MFornander changed the title Allow overriding battery sense on RAK4631/RAK3401 for external packs Allow external battery packs to drive battery percentage on RAK boards Sep 1, 2026
Wrap PIN_VBAT_READ / ADC_MULTIPLIER and the PWRMGT_* defines in #ifndef
guards so build flags can redirect battery telemetry to another analog
pin, e.g. a Voltaic V25/V50/V75 pack's USB-C SBU output (1/2 cell
voltage) wired to the base board J11 AIN1 pin.

Remove the unused Meshtastic-leftover battery defines from the rak3401
variant.h: nothing in the MeshCore, framework, or library compile
closure references them, and the unconditional ADC_MULTIPLIER there
would defeat both the new #ifndef default and any -D build flag
(variant.h is included via Arduino.h before the board headers).

Add a documented RAK_3401_repeater_voltaic example env that reports the
pack's true cell voltage and keeps nRF52 power management active with
LPCOMP wake rerouted to the SBU pin: sleep below 3.4V cell, wake once
the pack recharges past ~3.7V.

The rak4631 hunks are byte-identical to PR meshcore-dev#3328 by @andyshinn, which
this change incorporates and extends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MFornander
MFornander force-pushed the dev-mf/rak13302-battery-telemetry-ca3ae3 branch from 846093e to d908a3e Compare September 29, 2026 04:45
Add the RAK3401 to the supported boards table and document the #ifndef-guarded battery sense and power management defines on rak4631/rak3401, with the Voltaic SBU env as a worked example.
@MFornander
MFornander marked this pull request as ready for review September 29, 2026 06:17
@MFornander

Copy link
Copy Markdown
Author

TL;DR:
This PR allows using an analog-in pin such as the AIO1 on the RAK baseboard as the battery sensor for telemetry and power management. Many times we may use a battery source that boost or buck the voltage to the required power. We can't use the existing battery or solar headers so a small patch allows the battery sensing to be re-routed. This allows solar repeters with more complex battery setups to still report their telemetry accurately and predict if the panels are under-provisioned.

The env example shows the config that allows a Voltaic battery pack to use the new firmware by providing a 1:2 split (5V down to compatible 2.5V = 100%) and the reouting to the new analog in.

image

This branch has not been deployed

No deployments
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.

1 participant