FastSetBandwidth: FHSS-grade 20<->5/10 MHz narrowband toggle (all 3 generations)#223
Merged
Conversation
A bandwidth-switch analogue of FastRetune. A full SetMonitorChannel BW change is ~90 ms (8812AU) / ~197 ms (8814AU) — dominated by the per-path RF read-modify-write (PHY_RF6052SetBandwidth8812, whose RF reads trigger the cut-C ~20 ms sleeps), plus the per-rate TX-power re-fold, thermal pwrtrk, and IQK. But across a same-channel 20<->5/10 toggle the RF stays in 20 MHz mode (RF18[11:10]=3) and the MAC stays 20 MHz, so RF18, 0x668, DATA_SC, and the 8814's phy_SetBwRegAdc/Agc all write identical values, and TX power (narrowband folds to the 20 MHz column) / pwrtrk / IQK are all unnecessary. The ONLY register that changes is the baseband re-clock 0x8ac. So the switch collapses to a single write-only dword, composed onto a cached 20 MHz-state 0x8ac (which also captures phy_FixSpur's per-channel ADC-clock choice for a bit-exact 20 MHz restore). Result on the bench: 8812AU 90 ms -> 0.18 ms (490x), 8814AU 197 ms -> 0.74 ms (265x). Register parity: fast-path 0x8ac is bit-for-bit identical to the full path at 5, 10, and 20 MHz on both dies. IRtlDevice::FastSetBandwidth defaults to a full SetMonitorChannel, so callers may use it on any chip; Jaguar1 overrides with the fast path (declines 40/80 endpoints and non-8812/8814 dies). tests/retune_bench.cpp measures timing + parity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…elta Extends the fast bandwidth toggle to the 8822B. Unlike Jaguar1 (a single 0x8ac write), the 8822B narrowband needs more of its sequence: 0x8ac + 0x8c4[30] + 0x8c8[31], the band-keyed CCK trio, the RF18 re-latch EDGE (the 8822B synth only re-latches on an RF18 value change), and a BB reset to relatch the DAC/DFE. But across a same-channel toggle the RF channel tune, RF18 read, CCA/DFIR tail, TX power, and calibrations are all invariant and skipped. set_channel_bw now caches the channel-keyed state (final rf18, cch, g2/r2t2r, the 20 MHz-state 0x8ac); fast_set_bandwidth replays only the BW delta from it — write-only, no RF read. Bench (Archer T3U, ch36): 20->5 90 ms -> 15 ms (6x), 5->20 78 ms -> 3.2 ms (24x). The 20->NB direction is bounded by the RF18 edge (4 RF writes) + BB reset, not USB op count. Parity: fast-path 0x8ac/0x8c4/0x8c8 are bit-for-bit identical to the full narrowband path at 5 and 10 MHz. Functional (cross-RX, tests/fast_bw_rxcheck.cpp): an 8812AU transmits a 10 MHz narrowband beacon; the 8822B RX decodes it ONLY in the fast-toggled narrowband window (5095 hits) and zero in both 20 MHz windows — proving both fast switches re-clock correctly on-air. 8821C declines to the full path (its narrowband path differs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On Jaguar3 the narrowband re-clock is already factored as a self-contained delta (set_bandwidth_dividers — clock dividers 0x9b4, small-BW 0x9b0, RX DFIR, and on the 8822e the MAC clock + TX-shaping), applied on top of an already-tuned channel and including a 20 MHz-restore default. So the fast toggle is just that delta + a BB reset to relatch the DAC/DFE — no RF channel tune, no calibration. Bench (ch36): 8822C 4.7 ms -> 0.8 ms (6x), 8822E 8 ms -> 4.5 ms. The J3 full path was already far cheaper than J1/J2 (register-window RF writes, not slow control reads), so the win is smaller; the 8822e fast path still runs the MAC clock + TX-shaping restore + BB reset it genuinely needs. Functional (cross-RX, tests/fast_bw_rxcheck.cpp): an 8812AU transmits a 10 MHz narrowband beacon; both the 8822C (9798 hits) and 8822E (10158 hits) decode it ONLY in the fast-toggled narrowband window, zero in both 20 MHz windows — both fast switches re-clock correctly on-air. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stSetBandwidth Wraps the two throwaway harnesses (retune_bench, fast_bw_rxcheck) into one adapter-parameterized validation: full-vs-fast timing, 0x8ac (and J2 0x8c4/ 0x8c8) register parity full-vs-fast, and a cross-RX decode test (a narrowband TX partner; the fast-toggled RX must decode ONLY in the narrowband window, proving the re-clock on-air). retune_bench gains an SDRHOLD=<bw> mode that fast-toggles then transmits the canonical beacon continuously for an SDR occupied-bandwidth read. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Documents the bandwidth analogue of FastRetune in docs/narrowband.md (new "Fast bandwidth switching" section: per-generation cost + method + validation), docs/frequency-hopping.md (a closing section pointing to it), and the CLAUDE.md frequency-hopping bullet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jul 9, 2026
## What A worked, runnable example of the **burst-level bandwidth TDMA** idea that `FastSetBandwidth` (#223) unlocks: a transmitter alternates **bursts** between a robust narrowband width (5/10 MHz — the ~6 dB link-budget win, for critical frames) and a wide width (20/40 MHz — throughput, for bulk frames), flipping bandwidth per-burst in well under a millisecond to a few ms. It is *burst*-level, not per-frame, for two reasons that are physics, not laziness: narrowband is an ADC-clock-domain **state** (not a radiotap field), and a receiver decodes exactly one width at a time. So frames of one protection class are grouped into a burst, both ends switch, then the next burst. Per-frame **rate**-UEP is layered on top (critical also rides a robust rate, bulk a fast MCS — reusing the `svctx` radiotap mechanism). ## The interesting hard part: sync devourer exposes no hardware time primitive (no host TSF read, no PPS/GPS hook — only a per-frame read-only `tsfl`). The example ships **both** receiver sync strategies so experimenters can compare: - **`wallclock`** — both ends compute the phase from `system_clock` (shared Unix-epoch anchor). Trivially aligned same-machine; cross-machine needs GPS/PPS/NTP-disciplined clocks (the caller's to provide). - **`marker`** — self-clocking: the TX emits a marker frame at each narrowband-burst start; the RX camps narrowband until it decodes one, anchors its schedule to it, coasts on `steady_clock`, and re-anchors on each subsequent marker. **No external clock at all.** A lockstep receiver switches `guard_ms` **early** to absorb the switch-latency dead-window (the guard must exceed the chip's switch latency; bursts ≫ it). ## Roles (one binary, one adapter, one role) - `DEVOURER_TDMA_ROLE=tx` — run the schedule, inject class-tagged frames + markers. - `=rx-sync` (`DEVOURER_TDMA_SYNC=wallclock|marker`) — switch in lockstep (the user's **1 TX + 1 RX** mode). - `=rx-camp` (`DEVOURER_TDMA_CAMP=5|10|20`) — camp on one width. A `tx` + two `rx-camp` (narrowband + wide) is the user's **1 TX + 2 RX** mode: an independent, always-listening robust narrowband link + a wide bulk link. Schedule knobs: `DEVOURER_TDMA_NB` / `_WIDE`, `_NB_MS` / `_WIDE_MS`, `_EPOCH_MS`, `_GUARD_MS`, `_CRIT_RATE` / `_BULK_RATE`, `_GAP_US`. Metrics are JSONL on stdout (`{"ev":"tdma.*"}`); each RX prints a per-band × per-class decode table. ## Bench validation (`tests/tdma_demo.sh`) TX 8812AU, RX 8822CU/8822BU, ch 36, 10 MHz narrowband. A correct run shows critical+marker under the narrowband band and bulk under the wide band, near-zero off-diagonal (only frames caught mid-switch): ``` Mode 1 wall-clock lockstep: narrowband 43 marker / 7700 crit / 4 bulk wide 0 / 3 / 7432 Mode 1 marker lockstep: narrowband 42 / 8572 / 1 wide 0 / 5 / 7341 Mode 2 dual-RX (camp NB): narrowband 52 /11678 / 2 (wide row 0) dual-RX (camp wide): wide 0 / 0 / 7509 (narrowband row 0) ``` Both sync modes lock and track; the dual-RX split is clean (each receiver hears only its band's bursts). `ctest` green; **no library changes** — additive `examples/tdma/` + `tests/tdma_demo.sh` + one `docs/narrowband.md` section, so it can't regress the driver. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
IRtlDevice::FastSetBandwidth(bw)— a lean same-channel toggle between 20 MHz and 5/10 MHz narrowband, the bandwidth analogue ofFastRetune. Switching bandwidth through the fullSetMonitorChannelis a complete channel set (RF tune + TX-power re-fold + IQK + thermal tick): ~90 ms on the 8812AU, ~197 ms on the 8814AU, ~85 ms on the 8822B. That is a session-level cost — far too slow to interleave with traffic.But a same-channel 20↔5/10 toggle changes almost nothing: narrowband keeps the RF in 20 MHz mode (RF18[11:10] unchanged) and the MAC at 20 MHz, so the RF bandwidth register, MAC BW bits, sub-channel, RX DFIR/CCA tail, TX power (folds to the 20 MHz column), and IQK are all invariant. The only thing that changes is the baseband ADC/DAC re-clock.
FastSetBandwidthwrites just that delta from a cached channel state and falls back to the fullSetMonitorChannelfor a 40/80 MHz endpoint. Same playbook as #187 (write only what changes, from a cache, with no read-modify-write).Results (bench, ch36)
Jaguar1 is a single
0x8acdword write (composed onto a cached 20 MHz value that also preserves the channel'sphy_FixSpurADC-clock choice). Jaguar2's 8822B needs more of its sequence —0x8ac/0x8c4/0x8c8, the band-keyed CCK trio, the RF18 re-latch edge, and a BB reset — all replayed from a cache with no RF read; the 20→NB direction is bounded by the RF18 edge, not USB op count. Jaguar3's narrowband is already a self-contained delta (set_bandwidth_dividers), so the fast path is that delta + a BB reset; its full path was already cheap (register-window RF writes), so the win is smaller.Validation (
tests/fast_bw_parity.sh)0x8acat 5/10/20 MHz on both dies; Jaguar20x8ac/0x8c4/0x8c8at 5/10 MHz. Since the emission and RX are a pure function of these registers, identical registers = identical behaviour.ctestgreen (13/13); OFF-config builds unaffected (FastSetBandwidthdefaults to a fullSetMonitorChannelinIRtlDevice).Why this matters
Bandwidth-hopping is now FHSS-grade. It also revives burst-level unequal error protection by bandwidth: a receiver (or a coordinated TX scheme) can move between a robust narrowband link and a wide high-throughput one in well under a millisecond to a few ms, instead of ~90 ms.
🤖 Generated with Claude Code