summaryrefslogtreecommitdiff
path: root/captures/RF-REPRODUCTION.md
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 12:40:53 -0300
committerGabriel Schneider <[email protected]>2026-08-25 12:46:51 -0300
commitf5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch)
tree2731a3ed4e51cae09e184e25778eded5fc37d1f5 /captures/RF-REPRODUCTION.md
downloadesp32p4-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.md98
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.