Beamforming self-sounding: MU report → per-subcarrier SNR by josephnef · Pull Request #155 · OpenIPC/devourer · GitHub
Skip to content

Beamforming self-sounding: MU report → per-subcarrier SNR#155

Merged
josephnef merged 4 commits into
masterfrom
bf-mu-sounding
Jul 3, 2026
Merged

Beamforming self-sounding: MU report → per-subcarrier SNR#155
josephnef merged 4 commits into
masterfrom
bf-mu-sounding

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

What

Follow-up to #154 (SU self-sounding). An SU beamforming report carries only the per-tone steering direction plus a per-stream scalar SNR. An MU report additionally appends the MU Exclusive Beamforming Report — genuine per-subcarrier SNR — which is the per-tone channel quality that maps to a per-tone modulation order (the textbook "256-QAM on clean tones, QPSK on faded tones" picture).

How

  • arm_beamformee_mu() (src/BeamformingSounder.h) layers the MU group-table registers (0x14C0 MU-TX-CTL, 0x14C4/0x14C8/0x14CC group/user-position, entry 0x1684) on top of the SU responder base. For self-sounding we program the group/user-position directly, so no over-the-air VHT Group ID Management handshake is needed. Recipe from the vendor hal_txbf_8822b_enter() MU branch (identical across 8822B/C/E).
  • Wired on Jaguar-3 via DEVOURER_BF_ARM_BFEE_MU=1; the NDPA MU feedback bit (STA-info bit 12) via DEVOURER_TX_NDPA_MU=1 in WiFiDriverTxDemo.
  • tools/bf_report_decode.py parses the MU report. Realtek packs the SNR not as the spec's 4-bit deltas but as 8-bit values in pairs — series A (even bytes) is the per-tone SNR that swings with the channel. The decoder extracts it, maps to dB (22 + 0.25·int8) and to a modulation per tone, and trims devourer's trailing chip-FCS/RX bytes where the smooth series collapses. --operating-snr N re-centres the measured per-tone shape to a stated link budget, turning the real frequency-selectivity into the textbook QAM ladder even on a strong bench link.

Result (8822CU MU-beamformee, ch100, 20 MHz)

Reports grow 99 → 153 bytes; the per-tone SNR series A is a smooth 45–53 dB curve with ~8 dB of real frequency-selective swing:

as measured:   SNR 53..45 dB  →  all 256-QAM (link is uniformly strong)
--operating-snr 26 (same shape, weaker link):
   SNR:  30 29 29 28 28 28 27 27 27 26 26 26 25 24 24 24 24 23 23 22  dB
   QAM:  +  +  +  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :  :     (+ 16-QAM / : QPSK)

Note: DEVOURER_TX_PWR does not attenuate the link — the NDP is hardware-generated by the sounding engine, not sent through send_packet — hence the operating-point model rather than a physical power sweep.

ctest green; all new code is env-gated and no-op by default.

🤖 Generated with Claude Code

josephnef and others added 2 commits July 2, 2026 22:43
An SU report gives only per-tone steering direction + a per-stream scalar SNR.
An MU report additionally appends the MU Exclusive Beamforming Report — real
per-subcarrier SNR. Adds arm_beamformee_mu() (layers the MU group-table
registers 0x14C0/C4/C8/CC + entry 0x1684 on the SU responder base, programming
the group/user-position directly so self-sounding needs no over-the-air Group
ID Management handshake; recipe from hal_txbf_8822b_enter), wired on Jaguar-3
via DEVOURER_BF_ARM_BFEE_MU=1, plus the NDPA MU feedback bit via
DEVOURER_TX_NDPA_MU=1.

bf_report_decode.py parses the MU report: Realtek packs the SNR as 8-bit pairs
(series A = the per-tone SNR that swings with the channel), which the decoder
extracts, maps to dB and to a modulation per tone, and trims of devourer's
trailing chip-FCS/RX bytes. --operating-snr re-centres the measured per-tone
shape to a stated link budget to show the textbook per-tone QAM ladder.

Validated: 8822CU MU-beamformee reports run 153 B (vs 99 SU); series A is a
smooth 45-53 dB per-tone curve with ~8 dB frequency-selective swing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a live scrolling truecolor per-subcarrier SNR waterfall — pipe
WiFiDriverDemo's raw report stream into tools/bf_waterfall.py (or run the
whole rig via tests/bf_waterfall.sh) to watch the per-tone channel across
frequency (X) and time (Y), coloured by the modulation a rate-adaptive link
would pick per subcarrier. tools/bf_waterfall_svg.py renders the same as an
SVG for docs/img/bf_waterfall.svg (embedded in the doc).

Also corrects the MU per-tone SNR mapping: the values cross 128 (a stronger
tone reads e.g. 131 > 122), so the field is UNSIGNED (dB = -10 + 0.25*v); the
prior signed int8 reading wrapped the strongest tones to negative (spurious
navy cells). QAM thresholds recalibrated to the ~1e-3 uncoded-BER ballpark. A
measured bench capture now reads a realistic ~13-21 dB per-tone SNR (was a
too-high ~45-53) — 16-QAM on the stronger tones, QPSK on the weaker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Collaborator Author

josephnef and others added 2 commits July 2, 2026 23:24
tools/bf_waterfall_gif.py renders the per-subcarrier SNR waterfall from a real
capture as an animated GIF styled as a live UI — DEVOURER-branded header, a
blinking LIVE indicator, a scrolling frequency×time heatmap that fades into the
past, a per-frame readout (peak/min SNR, best/worst modulation, a current-tone
profile bar), and the SNR→QAM legend. Embedded at the top of the doc in place
of the static SVG (bf_waterfall_svg.py stays for a diff-able static export).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each frame was quantised with its own ADAPTIVE palette, so identical static
pixels (title, labels, legend) mapped to slightly different colours every frame
— the visible "trembling". Build one global palette from a montage of sampled
frames and quantise every frame to it with dithering off, so unchanged pixels
are bit-identical across frames (verified: title/legend inter-frame diff = 0).
Smaller too (~970 KB), and the waterfall/readout still animate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit 9d97842 into master Jul 3, 2026
12 checks passed
@josephnef josephnef deleted the bf-mu-sounding branch July 3, 2026 04:37
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