mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-27 07:18:11 +00:00
feat(power): warm sleep between CAD scans + TCXO stabilisation delay
cadSleep() now puts the SX1262 into warm sleep (~1.5 µA, config retained) instead of standby-RC (~600 µA). cadWake() calls standby() then waits 5 ms for the TCXO to re-stabilise before the next CAD scan — the missing delay was the root cause of long-packet corruption in the earlier attempt (freq drift over a long payload before the oscillator settled). CAD interval bumped 45 → 65 ms: with the 32-symbol preamble now in place (≈ 131 ms at SF8/BW62.5) two scan windows still fit comfortably, and the longer standby window reduces average RX current further. Estimated idle current: ~200 µA (vs ~750 µA standby-between-scans, ~5.3 mA continuous RX). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
65
FEATURES.md
65
FEATURES.md
@@ -339,7 +339,61 @@ A fuller position-centred navigator (you fixed at screen centre, fixed/preset
|
||||
zoom, pan) is a larger separate feature; start with the rotate-the-fit version
|
||||
if pursued.
|
||||
|
||||
### 📋 Battery / power optimization (CAD RX windowing + APC) — after trail polish
|
||||
### 🚧 Battery / power optimization (CAD RX windowing + APC)
|
||||
|
||||
**Phase 1 shipped on branch `feat/power-saving`** (CAD-windowed receive). Not yet
|
||||
merged — under field testing. Where things stand and what to return to:
|
||||
|
||||
**✅ Done (phase 1) — CAD-windowed RX, behind a setting**
|
||||
- `RadioLibWrapper` gained a CAD state machine (scan → standby window → on
|
||||
detect, full RX), driven from `loop()` (runs before `recvRaw`). `state` stays
|
||||
`STATE_RX` through all phases so the dispatcher's not-in-RX watchdog never
|
||||
trips; re-arm routes through `armRecv()`; falls back to continuous RX if CAD
|
||||
is unsupported. `setPowerSaving()`/`getPowerSaving()`.
|
||||
- Companion: `rx_powersave` pref (schema `0xC0DE0008`), **Settings › Radio ›
|
||||
"Pwr save"** toggle, applied at boot (MyMesh) and on change (UITask).
|
||||
- CSMA unaffected: this firmware's interference threshold is already 0, so
|
||||
listen-before-talk was a no-op; airtime budget + `isReceivingPacket()` still
|
||||
gate TX. CAD window = 45 ms (< the ~66 ms 16-symbol preamble at SF8/BW62.5),
|
||||
RadioLib default CAD params.
|
||||
- **Field test:** the **base (Pwr save OFF) is healthy** — no regression vs
|
||||
stock; an earlier "drops vs stock" scare turned out to be weak repeater
|
||||
coverage at the test site (direct device-to-device was fine). With **Pwr save
|
||||
ON, occasional message drops** were observed (CAD misses some), so it stays
|
||||
**default OFF** and is experimental until detection is made reliable.
|
||||
|
||||
**⏸ To return to (not done)**
|
||||
- **WAITING ON: upstream preamble 16 → 32.** Upstream `dev` raised the LoRa
|
||||
preamble from 16 to 32 symbols; we'll pick it up via the next upstream
|
||||
release/merge (our radio init still hardcodes 16 in `CustomSX1262.h`
|
||||
`begin(..., 16, tcxo)`). This is the natural fix for the CAD drops: at
|
||||
SF8/BW62.5 the preamble goes ~66 ms → ~131 ms, so the conservative 45 ms scan
|
||||
window gains a huge margin (~21 ms → ~86 ms) and the occasional misses should
|
||||
disappear — **revisit power-save right after that lands.** It also un-no-ops
|
||||
`startReceiveDutyCycleAuto(32)` (32 ≥ 2·minSymbols+1), enabling the SX126x
|
||||
hardware RX duty cycle as an alternative to the software CAD loop. Caveat: the
|
||||
scan window must be sized for the *shortest* preamble on the mesh, so a longer
|
||||
window / `startReceiveDutyCycleAuto(32)` only pays off once senders are
|
||||
broadly on 32 — keep 45 ms while the mesh is mixed.
|
||||
- **CAD detection reliability** — the open blocker (see above; the preamble
|
||||
bump is expected to resolve most of it). If misses persist after preamble 32,
|
||||
tune the CAD parameters (`setCad` symbolNum / detPeak / detMin via a
|
||||
`ChannelScanConfig_t`). Needs the current measurement below to judge the
|
||||
reliability-vs-savings trade.
|
||||
- **Current measurement** — never taken (no PPK2/meter to hand). The actual mA
|
||||
win is unquantified. Do this alongside the detection-reliability tuning.
|
||||
- **Warm sleep between scans** — tried (`cadSleep()`/`cadWake()` hooks exist) but
|
||||
**reverted**: warm sleep dropped longer packets because the **TCXO** hadn't
|
||||
re-stabilised before the post-CAD receive → frequency drift → symbol errors
|
||||
over a long payload. To revisit: configure a proper SX126x TCXO startup delay
|
||||
and ensure RX waits for "TCXO ready", then re-test long-packet reception. Worth
|
||||
~another 2× on idle-window current (standby ~mA → sleep ~µA).
|
||||
- **CAD window tuning** — 45 ms is conservative; could push toward the preamble
|
||||
limit (~55 ms) for fewer scans / lower average current, once measured.
|
||||
- **Adaptive Power Control (APC)** — drop `tx_power_dbm` dynamically on good
|
||||
links. Small win for a mostly-listening node; lower priority.
|
||||
|
||||
**Original analysis (kept for context):**
|
||||
|
||||
Goal: multiply battery life **without losing functionality**. The dominant
|
||||
draw on this node is the radio in **continuous RX** (always-on `startReceive()`
|
||||
@@ -379,12 +433,9 @@ at preamble 16). Prefer adopting/extending upstream work over a parallel
|
||||
implementation. Note ZephCore's licence before copying any code verbatim
|
||||
(architecture inspiration is fine).
|
||||
|
||||
Sequencing: **deferred until the trail/waypoints/nav polishing is finished.**
|
||||
The radio-side CAD windowing lives on the `feat/power-saving` branch and is now
|
||||
**unblocked** by the preamble fix. First step when picked up: rebase it onto the
|
||||
v1.16 merge (it will hit the removed `radio_set_tx_power()`/`radio_set_params()`
|
||||
globals — replace with `radio_driver.*` like the merge did), then re-test whether
|
||||
long messages still drop at preamble 32, ideally profiling with a PPK2/meter.
|
||||
Sequencing: phase 1 (CAD windowing) is done and in field test on
|
||||
`feat/power-saving`; see the status block at the top of this entry for what's
|
||||
left (measurement, warm sleep, window tuning, APC).
|
||||
|
||||
### SOS broadcast
|
||||
|
||||
|
||||
@@ -40,6 +40,17 @@ public:
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
// Warm sleep between CAD scans: ~1.5 µA vs ~600 µA in standby-RC.
|
||||
// cadWake() delays 5 ms so the TCXO fully re-stabilises before the next scan
|
||||
// (previous attempt without the delay corrupted long packets via freq drift).
|
||||
void cadSleep() override {
|
||||
((CustomSX1262 *)_radio)->sleep(true); // warm sleep — config retained
|
||||
}
|
||||
void cadWake() override {
|
||||
_radio->standby();
|
||||
delayMicroseconds(5000); // TCXO stabilisation after warm sleep
|
||||
}
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomSX1262 *)_radio)->setRxBoostedGainMode(en);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
#define PS_SLEEPING 2 // standby between scans, waiting for _ps_timer
|
||||
#define PS_RXING 3 // CAD hit → full receive armed, waiting for the packet
|
||||
|
||||
// Time between CAD scans. Must be shorter than the on-air preamble so a scan
|
||||
// always lands inside it (16-symbol preamble ≈ 66 ms at SF8/BW62.5). Tuned for
|
||||
// this firmware's fixed LoRa params — revisit if SF/BW change.
|
||||
#define PS_CAD_INTERVAL_MS 45
|
||||
// Time between CAD scans. Must be shorter than half the on-air preamble so at
|
||||
// least two scans land inside it. 32-symbol preamble ≈ 131 ms at SF8/BW62.5
|
||||
// → 65 ms gives 2 guaranteed windows with margin. Revisit if SF/BW change.
|
||||
#define PS_CAD_INTERVAL_MS 65
|
||||
// If CAD detects activity but no packet actually lands, fall back to scanning
|
||||
// instead of sitting in continuous RX forever.
|
||||
#define PS_RX_TIMEOUT_MS 1500
|
||||
@@ -144,6 +144,7 @@ void RadioLibWrapper::startRecv() {
|
||||
// If CAD isn't supported by the module, permanently fall back to continuous RX.
|
||||
void RadioLibWrapper::armRecv() {
|
||||
if (_power_save) {
|
||||
cadWake(); // bring the radio out of warm sleep first
|
||||
int16_t e = _radio->startChannelScan();
|
||||
if (e == RADIOLIB_ERR_NONE) { _ps_phase = PS_SCANNING; state = STATE_RX; return; }
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD unsupported (%d) — power-save off", (int)e);
|
||||
@@ -159,7 +160,7 @@ void RadioLibWrapper::armRecv() {
|
||||
}
|
||||
|
||||
void RadioLibWrapper::enterCadSleep() {
|
||||
_radio->standby(); // low-power between scans (TODO: warm sleep for more savings)
|
||||
cadSleep(); // warm sleep (SX126x) or standby fallback between scans
|
||||
_ps_phase = PS_SLEEPING;
|
||||
_ps_timer = millis() + PS_CAD_INTERVAL_MS;
|
||||
state = STATE_RX; // keep isInRecvMode() true so the dispatcher doesn't flag a stuck radio
|
||||
|
||||
@@ -30,6 +30,11 @@ protected:
|
||||
void armRecv(); // arm RX honouring power-save (CAD) or continuous
|
||||
void powerSaveLoop();
|
||||
void enterCadSleep();
|
||||
// Low-power state between CAD scans. Default = standby; SX126x overrides with
|
||||
// a warm (config-retained) sleep for ~µA idle. cadWake() brings it back to a
|
||||
// responsive standby before the next scan.
|
||||
virtual void cadSleep() { _radio->standby(); }
|
||||
virtual void cadWake() { _radio->standby(); }
|
||||
|
||||
public:
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }
|
||||
|
||||
Reference in New Issue
Block a user