diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-25 12:40:53 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-25 12:46:51 -0300 |
| commit | f5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch) | |
| tree | 2731a3ed4e51cae09e184e25778eded5fc37d1f5 /captures/RF-REPRODUCTION.md | |
| download | esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.tar.gz esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.zip | |
zig-p4: pure-Zig ESP32-P4 toolchain
build.zig generates the linker script and drives Zig's own LLD; tools/image.zig
turns the ELF into a flashable image and tools/{rom,serial}.zig speak the mask
ROM loader over the UART. No CMake, ninja, idf.py, esptool, or external linker.
src/soc.zig is a comptime register model over ESP-IDF's own *_reg.h headers;
src/hal/ adds peripheral sequences; src/io/ implements std.Io for the chip;
src/oracle/ diffs this HAL against ESP-IDF's on the die.
Diffstat (limited to 'captures/RF-REPRODUCTION.md')
| -rw-r--r-- | captures/RF-REPRODUCTION.md | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/captures/RF-REPRODUCTION.md b/captures/RF-REPRODUCTION.md new file mode 100644 index 0000000..dd58204 --- /dev/null +++ b/captures/RF-REPRODUCTION.md @@ -0,0 +1,98 @@ +# Reproducing the report's RF experiment from this toolchain + +`04-report` section 5 is the only real SDR measurement in that document: a 25 MHz carrier on GPIO20, +OOK-keyed with `0x4200`, detected at **+12.96 dB ± 0.16 dB** and decoded back to `0x4200` from raw IQ. +The emitter was ESP-IDF firmware (`02-esp32p4-m3-radio`). This is the same experiment driven by this +project's own HAL, with the report's analysis tools used unmodified so the comparison is like for +like. + +`examples/rf.zig` is the firmware. `zig build -Dapp=examples/rf.zig flash`, then the report's own +`tools/rfprobe.py` and `tools/decode_tone.py`. + +## What reproduced + +**The emitter, exactly.** LEDC on GPIO20, 1-bit duty resolution, duty 1, hpoint 0 — the +configuration from `02-esp32p4-m3-radio/main/main.c:77-94`. The report derives the carrier from the +divider: 1-bit resolution off the 80 MHz clock can only synthesise `80e6 × 256 / (410 × 2)` = +24,975,609 Hz, never a round 25 MHz. This HAL computes divider **410** and reports +**24,975,610 Hz** — the same number, from IDF's own Q10.8 arithmetic reproduced in Zig. + +**The source clock, measured rather than assumed.** 80,104,000 Hz, timed as 100 LEDC output periods +against the systimer's fixed 16 MHz. Worth checking: this image runs at whatever the bootloader left +(90.0 MHz CPU), not the 360 MHz the ESP-IDF firmware configures, so its APB being 80 MHz was an +assumption until measured. + +**Register-level equivalence to ESP-IDF, on the die.** The differential harness gained a case that +performs the whole 25 MHz / 1-bit bring-up both ways — IDF's `ledc_ll_*` on one side, this HAL on the +other, one image, one boot — and compares the LEDC block: + +``` +MARK DIFF ok ledc.full_rf_config_25MHz_1bit(25) 96 words identical +``` + +**Behavioural equivalence at every rate the instrument can measure.** Same image, same pad, same edge +counter: + +| resolution | frequency | ESP-IDF LL | this HAL | +|---|---|---|---| +| 2-bit | 5 MHz | 112,498 edges | 112,500 | +| 1-bit | 10 MHz | 75,000 | 75,000 | +| 1-bit | 25 MHz | 0 | 0 | + +## What did not reproduce, and why it is not this toolchain's fault + +**The +12.96 dB detection is gone — from the original firmware too.** ESP-IDF's own binary was +reflashed from `02-esp32p4-m3-radio/build/` and driven by the report's own `rfprobe.py chop` with the +report's parameters (25 MHz, gain 19.7 dB, 4 cycles, ±2 kHz): + +| firmware | ON segments (dB) | OFF segments (dB) | ON − OFF | +|---|---|---|---| +| report, section 5.2 | 19.206, 19.169, 19.206, 19.146 | 6.360, 6.252, 6.153, 6.104 | **+12.96 dB** | +| ESP-IDF, re-run today | 6.396, 6.670, 6.401, 6.403 | 6.457, 6.659, 6.523, 6.482 | **−0.063 dB** | +| this toolchain, today | 5.046, 5.298, 5.146, 4.999 | 5.120, 5.128, 5.014, 5.027 | **+0.05 dB** | + +The receive chain is not at fault: the report's own instrument validation was repeated first and an +FM broadcast carrier sits **31.8 dB** above the span median at 97.500625 MHz, against the report's +30.9 dB at 97.497 MHz. The dongle, the antenna and the analysis path all work. + +So the report's detection was conditional on a physical coupling that no longer exists — the report +itself says the emission reached the dongle through whatever wire happened to be on JP1 pin 17. Two +firmwares now give the same null through the same tool. That is a correction to the report, not a +difference between toolchains: **a measurement whose apparatus is "incidental coupling" is not +reproducible, and this one is not.** + +## What is out of scope by construction + +Wi-Fi, BLE and 802.15.4 — sections 4 and 7 of the report. The P4 has no radio; those went out over an +ESP32-C6 across SDIO under `esp_hosted` 2.12.12 + `esp_wifi_remote` 1.6.3, a 19,000-line host stack +plus a prebuilt coprocessor binary. None of that is low-level hardware and none of it exists here. +Reproducing it would mean porting `esp_hosted`, not porting a HAL. + +## The one thing this HAL cannot yet measure, and the fix + +Neither implementation shows edges at 25 MHz, and that is the instrument rather than the pad. The +counter reads `GPIO_IN` in a loop, and both the loop's cadence and the LEDC output descend from the +same clock: 75,000 edges in 300,000 reads at 10 MHz is exactly 300,000/4, i.e. perfectly +phase-locked. At 25 MHz the lock lands on one phase and the count is zero. Adding jitter to the loop +did not break it. + +This is exactly why the report used the ADC (§5.1) and read its **min and max** rather than its mean: +"the ADC cannot track 25 MHz, so its sampling phase is uncorrelated with the pad". Running that same +witness against the ESP-IDF firmware today still shows both rails while the carrier runs +(`min=0 max=3228` against `driven_high min=3362`), so the pad does toggle — under IDF, measured with +IDF's instrument. + +To close it from this side, the HAL needs an instrument whose sampling is uncorrelated with the +signal: a minimal ADC one-shot read, or PCNT. ADC is the harder of the two — a prior review found it +among the peripherals whose LL needs `regi2c_ctrl.c` and the analog I²C master — so PCNT is the +cheaper route to a trustworthy pulse count. Until one of them exists, "the pad toggles at 25 MHz" is +a claim this toolchain can make about its registers and not about its output. + +## Incidental finding + +ESP-IDF's LEDC **LL** silently programs an out-of-range divider. A 1-bit 25 MHz target from the +40 MHz XTAL needs divider 205, below the hardware minimum of 256 (`LEDC_IS_DIV_INVALID`, +`ledc.c:114`). `ledc_ll_set_clock_divider` writes it anyway, because the range check lives one layer +up in `ledc.c` rather than in the LL. This HAL returns `DividerOutOfRange`. The first version of the +differential case above hit exactly this and reported four differing registers; the divergence is +real, and this HAL is on the right side of it. |
