Skip to content

ESP32-S3: edge DSP drops every STBC CSI frame (192 entries > EDGE_MAX_SUBCARRIERS), which starves the vitals/feature packets #2286

Open

Description

@Skermunkel

Summary

On ESP32-S3, process_frame() in edge_processing.c returns early when
n_subcarriers > EDGE_MAX_SUBCARRIERS (128 on pre-HE parts). With the S3 CSI
config (lltf_en, htltf_en, stbc_htltf2_en all true), every frame that
the AP sends with STBC arrives as info->len == 384, i.e. 192 entries, and is
discarded before Step 13. When the AP uses STBC for most frames, the 1 Hz
vitals (0xC5110002) and feature (0xC5110003) packets almost stop, even
though raw CSI (0xC5110001) keeps streaming at full rate.

Environment

  • Firmware: v0.8.8-esp32 (e04f269), ESP-IDF v5.4, ESP32-S3-WROOM-1 N16R8,
    display-less build (sdkconfig.defaults.devkitc).
  • Still present on main at a1ca023 (edge_processing.c:1065).
  • AP: Cudy WiFi 6 router (MediaTek), 2.4 GHz channel 1, HT20. It uses STBC
    for unicast data to the node.

Evidence (MEASURED)

Host-side capture of the node's UDP stream (30 s and 60 s windows, unicast UDP traffic
to the node at 50 Hz to drive CSI):

Build Raw CSI Frame mix Vitals 0xC5110002 Features 0xC5110003
v0.8.8 (edge path unmodified) 42.4/s 1266/1272 iq_len 384 (STBC) 2 per 30 s 2 per 30 s
patched (below) 40.1/s 2271/2404 iq_len 384 (STBC) 59 per 60 s 59 per 60 s

The STBC flag comes from byte 19 bit 2 of the ADR-018 header
(CONFIG_CSI_FRAME_HE_TAGGING). The few vitals emitted before the patch are
consistent with the rare non-STBC (iq_len 256) frames getting through.

Proposed fix

Analyse the first 128 entries (LLTF + HT-LTF), which line up with plain HT20
frames, and ignore the extra STBC HT-LTF block:

     uint16_t n_subcarriers = slot->iq_len / 2;
-    if (n_subcarriers == 0 || n_subcarriers > EDGE_MAX_SUBCARRIERS) return;
+    if (n_subcarriers == 0) return;
+    /* STBC frames carry LLTF + HT-LTF + a second STBC HT-LTF (192 entries).
+     * The first 128 line up with plain HT20 frames; analyse those. */
+    if (n_subcarriers > EDGE_MAX_SUBCARRIERS) n_subcarriers = EDGE_MAX_SUBCARRIERS;

update_multi_person_vitals() already clamps the same way (line 1386), so
this makes the two paths consistent. Alternatives: set
stbc_htltf2_en = false (loses the STBC block for raw CSI consumers too), or
raise EDGE_MAX_SUBCARRIERS for S3 (costs RAM and changes the DSP's
subcarrier set from frame to frame).

Hardware-verified on one ESP32-S3 node (boot log + host capture as above).

Activity

  1. added a commit that references this issue on Oct 22, 2026
  2. added a commit that references this issue on Oct 22, 2026
  3. danalec commented on Oct 22, 2026

    @danalec
    Contributor

    Fixed upstream. The change is in main and in the firmware release that carries it.

    firmware/esp32-csi-node/main/edge_processing.c now bounds the subcarrier count instead of dropping the frame — the exact diff proposed in this issue:

    uint16_t n_subcarriers = slot->iq_len / 2;
    if (n_subcarriers == 0) return;
    /* S3 STBC frames append a second HT-LTF block (192 entries total).
     * Process the bounded LLTF + HT-LTF prefix instead of starving edge
     * output (#2286). Raw CSI streaming retains the complete payload. */
    if (n_subcarriers > EDGE_MAX_SUBCARRIERS) {
        n_subcarriers = EDGE_MAX_SUBCARRIERS;
    }

    Landed in bf46d113 ("fix(firmware): analyse first 128 entries of STBC CSI frames instead of dropping them (#2286)"), merged through #2289 and #2309 on 2026-10-22, and shipped in firmware 0.8.15 (v0.8.15-esp32).

    The regression test is in the tree and runs in CI: firmware/esp32-csi-node/test/test_edge_stbc.c executes the production DSP with alternating HT20 (256) and STBC (384) shapes and asserts that every complete frame advances s_frame_count, that the bounded prefix is analysed (s_subcarrier_var[bins - 1].count > 0) and that s_pkt_valid is set, ending with PASS: production DSP accepts HT20/STBC and bounds oversized input. It is wired into make host_tests (firmware/esp32-csi-node/test/Makefile:196), which the firmware CI job runs.

    One boundary worth keeping visible: docs/validation/2026-10-20-esp32-csi-input-release-candidate.md states that the C6 check "does not qualify the S3-specific STBC correction". The hardware evidence for the S3 path is the reporter's own capture in this issue, not that validation run.

    The account that ran this check has read-only access to this repository, so it cannot close the issue itself — a maintainer closing it is all that remains.

  4. added and removed on Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfirmwareESP32 firmware

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions