Files
firmware/TODO.md
T
swaits 23ddc15557 fix(radio): consume upstream lora-phy pre-command BUSY check
Pick up the proper fix from our fork at swaits/lora-rs@fix/sx126x-busy-race:
move the ready check from *after* to *before* each SPI op per the
Semtech manuals (SX1261/2 §8.3.1, LR1121 UM §3 / §3.5, LR1110 DS
§1.2.5) and rename InterfaceVariant::wait_on_busy to wait_until_ready.

Firmware side is surgical: since the upstream fix keeps the
GenericSx126xInterfaceVariant::new signature at 5 args (no DelayNs
threading), there is nothing to change in the four nRF / ESP / RP2040
board files beyond a one-line comment rename (wait_on_busy ->
wait_until_ready) in the BUSY-pullup comment on the two nRF boards.

Wire in the [patch.crates-io] override pointing at ../../lora-rs/lora-phy,
bump the dep spec to the pre-release 3.0.2-alpha that matches the fork,
and enable the defmt-03 feature that upstream moved behind a gate
between 3.0.1 and the fix branch.

TODO.md rewritten to track the new approach: PR body template with
verbatim citations from all three Semtech chip manuals (SX1261/2,
LR1121 UM, LR1110 DS) plus the SX1276 pin table confirming the family
has no BUSY line.

No change to firmware behavior beyond picking up the upstream fix,
which takes first-TX-after-boot success from ~50% to 20/20 on the
Seeed Wio Tracker L1 repro board.
2026-04-24 12:44:01 -06:00

197 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Firmware TODO List
## `lora-phy` fix shipped locally; upstream PR pending submission
Branch: [`swaits/lora-rs @ fix/sx126x-busy-race`](https://github.com/swaits/lora-rs/tree/fix/sx126x-busy-race).
PR not yet opened. Firmware consumes the fix via `[patch.crates-io]`
pointing at `../../lora-rs/lora-phy`; drop the patch line + pre-release
selector once upstream releases.
Hardware-verified on Wio Tracker L1: 20/20 clean-boot TX after the fix
(vs ~50% before). Additive fix: adds pre-command BUSY check, keeps
existing post-command wait in place.
### PR body
```
Title: phy: pre-command BUSY check per Semtech datasheets
## Problem
While bringing up a downstream project (donglora), I hit a race where TX commands would occasionally fail. Tracked it down to lora-phy not following the BUSY-handshake algorithm Semtech recommends: the host should wait on BUSY *before* issuing each command, not after.
Details: `SpiInterface::{write, write_with_payload, read,
read_with_status}` polls BUSY *after* each SPI op.
On fast MCUs (nRF52840 at 64 MHz, release build) this is racy — `embedded-hal-async`'s `wait_for_low` returns immediately if the pin is already low, and the chip takes up to T_SW ns (SX1261/2 §8.3, Fig. 8-3) to assert BUSY after NSS rises. The host polls inside that pre-assertion window, sees a stale LOW, fires the next command while the chip is still busy, and the chip silently drops it. First `lora.tx()` after a clean boot on Seeed Wio Tracker L1 hangs ~50% of the time.
## Authority
- [SX1261/2 datasheet, DS.SX1261-2.W.APP Rev. 1.2](https://cdn.sparkfun.com/assets/6/b/5/1/4/SX1262_datasheet.pdf) §8.3.1
"BUSY Control Line": *"it is essential to wait for the BUSY line to
go low before sending an SPI command."*
- [LR1121 User Manual, UM.LR1121.W.APP Rev. 1.2](https://files.waveshare.com/wiki/Core1121/UserManual_LR1121_v1_2.pdf) §3
"Host-Controller Interface": *"it is necessary to check the status
of BUSY prior to sending a command."* (Applies to the LR11xx family
— see also [LR1110 DS Rev. 1.1](https://download.mikroe.com/documents/datasheets/LR1110_datasheet.pdf) §1.2.5.)
- [RadioLib](https://github.com/jgromes/RadioLib/blob/cffd5af/src/Module.cpp)
`Module::SPIwriteStream` / `SPIreadStream` call `waitForBusy()` at
the start of every transaction.
- The [SX1276/77/78/79 datasheet](https://cdn.sparkfun.com/assets/7/7/3/2/2/SX1276_Datasheet.pdf)
Table 2 exposes no BUSY pin, so
`GenericSx127xInterfaceVariant::wait_on_busy` stays a no-op and the
change is behaviorally identical for SX127x.
## Fix
Replace the post-command BUSY wait with a pre-command BUSY check at the start of each `SpiInterface` / `Lr1110SpiInterface` read/write method. Drop the redundant explicit `wait_on_busy()` calls in `sx126x` (after Calibrate, `ensure_ready` non-sleep arm) and `lr1110::mod` (4 regmem/bootloader sites) — all now covered by the interface pre-wait.
The `is_sleep_command` flag keeps its name but now gates the pre-wait; set `true` only when BUSY is held HIGH by design (the wake-from-sleep GetStatus pulse in `sx126x::ensure_ready`). Two callsite flips as a result: `ensure_ready` Sleep arm `false`→`true`; `set_sleep` `true`→`false`.
No public API signature changes.
## Test
- `cargo clippy -p lora-phy --features defmt-03 -- -D warnings` clean.
- Hardware: Seeed Wio Tracker L1 (nRF52840 + SX1262), 20 consecutive
clean-boot TX cycles → 20/20 (vs ~50% before).
```
---
## Drop `[patch.crates-io]` + upgrade esp-hal / esp-rtos + add `esp_app_desc!()` + bump espflash to v4
Single coordinated change that clears our last remaining dep-graph debt
on the ESP boards. All four pieces are coupled, so they land together.
### Why
Today `Cargo.toml` overrides `esp-hal`, `esp-rtos`, `esp-backtrace`, and
`esp-println` with a git pin to `esp-rs/esp-hal#main`. The note in that
block explains the historical reason:
> REVISIT: esp-rtos 0.2.0 on crates.io pins embassy-executor ^0.9. The
> esp-rs/esp-hal main branch already supports 0.10. Remove these
> patches once esp-rtos publishes a release with embassy-executor 0.10
> support.
**That condition is now met.** `esp-rtos 0.3.0` is published on crates.io
and depends on `embassy-executor ^0.10` and `esp-hal ~1.1.0-rc.0` — exactly
what our patches were pulling unofficially. Keeping the patches is no
longer free: it blocks `espflash` v4, blocks `esp-bootloader-esp-idf`, and
ties reproducible builds to whatever commit happens to be on
`esp-rs/esp-hal#main`.
The immediate trigger is `espflash` v4's required
`esp_bootloader_esp_idf::esp_app_desc!()` check. `save-image` refuses any
ESP binary that lacks the IDF app descriptor. The crate that provides the
macro (`esp-bootloader-esp-idf`) uses the same `esp-rom-sys` native
library as `esp-hal` — two copies of the same `links = "esp_rom_sys"` is
a hard cargo error, so `esp-bootloader-esp-idf` must come from the _same
source_ as `esp-hal`. Easiest way to guarantee that: both from crates.io,
no patches.
### What changes
1. **`Cargo.toml`** — delete the entire `[patch.crates-io]` block and the
preceding `REVISIT:` comment. Bump the four esp-rs deps to published
versions:
- `esp-hal = "1.0"` → `"1.1.0-rc.0"` (pre-release selector — see Gotchas)
- `esp-rtos = "0.2"` → `"0.3"`
- `esp-backtrace = "0.18"` → whatever matches esp-rtos 0.3 (check its deps)
- `esp-println = "0.16"` → same
Add a new optional dep + feature wiring for
`esp-bootloader-esp-idf = "0.5"` with `defmt` feature, plus per-board
chip feature (`esp-bootloader-esp-idf/esp32s3` for the S3 boards,
`esp-bootloader-esp-idf/esp32` for LilyGo T-Beam classic).
2. **`src/main.rs`** — inside the ESP-family branch of the existing
`cfg_if!` block (around line 51), invoke the macro at module scope:
```rust
esp_bootloader_esp_idf::esp_app_desc!();
```
Zero runtime cost. Emits ~256 bytes in `.rodata_desc` with app
metadata (name, version, build date) that the ESP-IDF 2nd-stage
bootloader + `espflash save-image` look for.
3. **`mise.toml`** — `"cargo:espflash" = "3"` → `"4"`. Drop the deferral
comment on that line.
4. **Possible API drift fixes** — esp-hal's pre-release versions
occasionally rename or move items. Most likely touch points:
- `src/board/esp32.rs`, `src/board/esp32s3.rs`
- Any board file in `src/board/heltec_*.rs`, `lilygo_*.rs`,
`elecrow_*.rs` that calls into esp-hal / esp-rtos init
Run `just check-all` after steps 1–3 and patch up whatever surfaces.
Don't guess — read the actual compiler errors.
### How (step by step)
1. `jj new -m "chore(esp): drop esp-hal patches, bump to published versions, add esp_app_desc!()"`
2. Edit `Cargo.toml` — delete `[patch.crates-io]` + `REVISIT:` comment;
bump the four versions; add `esp-bootloader-esp-idf` dep + feature
wiring for each ESP board feature block.
3. Edit `src/main.rs` — add `esp_bootloader_esp_idf::esp_app_desc!();`
to the ESP branch of the top-level `cfg_if!`.
4. Edit `mise.toml` — espflash `"3"` → `"4"`.
5. `mise install` to pull espflash 4.x.
6. `cargo update` to let the resolver pick a consistent tree with the
new requirements.
7. `just check-all` — expect some compilation errors from API drift on
ESP boards. Fix them one at a time; consult the official examples
in `esp-rs/esp-hal` if in doubt.
8. `just build heltec_v3` (or any ESP board you have) — verify
`espflash save-image` now succeeds end-to-end producing a valid
`.bin`.
9. Flash to real hardware if available. **The RAK4631 and Wio Tracker
L1 (nRF52) boards should be untouched.**
### Verification
- `just check-all` clean on all 9 boards.
- `just test` (host-side protocol tests) still green.
- For each ESP board that has been physically validated before
(Heltec V3, LilyGo T-Beam units): flash + confirm radio TX works
end-to-end via ai-bot or a known peer.
- RAK4631 + Wio Tracker L1 unchanged; re-check via `just check` on
both, but no re-flash needed (they don't go through the ESP code
path).
### Gotchas
- **Pre-release version selector**: `esp-hal = "1.1.0-rc.0"` by itself
does NOT match pre-release versions in cargo's default semver rules.
Use an exact pre-release like `"1.1.0-rc.0"`, `"~1.1.0-rc.0"` (only
`1.1.0-rc.0`, `1.1.0-rc.1`, … with any patch), or `">=1.1.0-rc.0"`.
`"1.1"` will NOT resolve to a pre-release.
- **Two copies of `esp-rom-sys`**: the root cause of the first failed
attempt at this task. If cargo errors with _"Only one package in the
dependency graph may specify the same links value"_ for
`esp_rom_sys`, it means `esp-hal` and `esp-bootloader-esp-idf` are
being pulled from different sources (one crates.io, one git). Either
both from crates.io (this task's plan) or both from the same git
commit.
- **`esp-bootloader-esp-idf` chip features**: the crate needs exactly
one of `esp32 / esp32s3 / esp32c3 / esp32c6 / …` to be enabled.
Without it, the build panics at its `build.rs`: _"Expected exactly
one of the following features to be enabled: esp32, esp32c2, ..."_.
Wire per-board the same way we wire `esp-hal/esp32s3` etc.
- **Probe-less repro**: `mise install` after bumping espflash to "4"
only installs mise's copy. If the user's global
`~/.cargo/bin/espflash` is stale/different, it still wins on PATH.
The justfile `set shell := ["mise", "exec", "--", "sh", "-c"]`
prepends mise's paths but does NOT reorder `~/.cargo/bin` ahead of
them. Either `cargo uninstall espflash` globally, or change the
justfile recipes to call `$(mise which espflash)` explicitly.
### Why not now
Tried it alongside the RAK4631 bring-up in April 2026 and bounced off
the `esp-rom-sys` two-copies problem. Backed out the attempt and left
espflash pinned at v3 + the patches in place so the bring-up commit
stayed atomic and shippable. This task is what's needed to unblock v4
properly, but it deserves its own focused session — not as
"while-I'm-here" scope on a board bring-up.