From 08c749ca06c1d47557c8e01d62930d5809192534 Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Thu, 24 Sep 2026 09:52:14 +0200 Subject: [PATCH] Finish migrating tower 111, and check only the pins hardware.ini declares --- docs/hardware-config-plan.md | 48 +++++++++++++++------------- docs/roadmap.md | 6 ++-- hardware/lattepanda-3-delta/NOTES.md | 20 +++++++----- scripts/install-tower | 29 +++++++++++++++-- 4 files changed, 68 insertions(+), 35 deletions(-) diff --git a/docs/hardware-config-plan.md b/docs/hardware-config-plan.md index 0b5e11d..37a7f8c 100644 --- a/docs/hardware-config-plan.md +++ b/docs/hardware-config-plan.md @@ -402,35 +402,39 @@ Deploy host-before-firmware (A.6). Fix the `Application.cpp:1040` literals in th existing fleet that drops yaw 50000 → 30000, a real behaviour change that wants a deliberate rig check rather than a silent correction. -> ### ⚠️ Tower 111 is half migrated: host yes, firmware no - and must stay so for now +> ### Tower 111 is migrated (2026-09-23 host, 2026-09-24 firmware) > -> 2026-09-23, through `scripts/install-tower` with the Delta kit: 111 now has a -> `hardware.ini` (yaw + pitch, MTi on `/dev/ttyS0`, SHT41 on `/dev/i2c-2`, -> declared and confirmed by `test hardware`), its USB preconditions, the current -> host, and a `config.ini` with the keys `hardware.ini` owns commented out. It -> homed and resumed capturing on the new host. A rollback copy of the previous -> sources, binary and `config.ini` is in `~ggs/rollback-20260923/` on the tower. +> Through `scripts/install-tower` with the Delta kit. 111 has a `hardware.ini` +> (yaw + pitch, MTi on `/dev/ttyS0`, SHT41 on `/dev/i2c-2`), its USB +> preconditions, the current host, and firmware `a0b1e4a`. `test hardware` +> passes every leaf, `hw_motor` included, and `install-tower --verify` is green. +> A rollback copy of the previous sources, binary, firmware source and +> `config.ini` is in `~ggs/rollback-20260923/` on the tower. > -> **Every drivetrain value is still a `; TODO`**, because the Delta firmware held -> register values (GLOBALSCALER / IRUN) and converting them to milliamps needs -> each driver board's sense resistor, which has to be read off the board. So -> **do not flash 111 yet**: the new firmware would sit in `NEEDCFG`, refusing to -> move. The installer blocks the flash for exactly this reason. +> The drivetrain was not invented, and not measured afresh either: it is what +> the firmware it replaced (`af74190-dirty`) was running, converted to +> `hardware.ini`'s units with the sense resistor read off the boards (75 mOhm, +> both axes). Round-tripped through the host's own planners back to identical +> registers: GLOBALSCALER 80 / 50, IRUN 31, homing at the same (the Delta never +> had a reduced search current), `steps_per_rev` 177000 / 500000 (the guard +> unchanged; measured spans are 182,374 / 572,004), SW_MODE 0x8A0 / 0x8A3, +> ENC_DEVIATION 1500, homing and centring speeds equal to the old VMAX figures. +> `travel_deg` 361.7 / 40.5 is the homed span over the landmark-calibrated +> `counts_per_deg`, and the host confirmed it agrees with the calibration after +> the first home on the new firmware. Homing repeated the old firmware's travel +> to within 0.03 %. > > Two corrections to what this section used to say: > > - `test hardware` does **not** pass on the old firmware. An absent echo is > reported as unconfirmable, not as a mismatch - but unconfirmable is still a -> FAIL, by design (`checkDeclaredValues`, `checkPinsBuilt`): a check that could -> not happen must not read as a pass. On 111 `hw_axes`, `hw_imu`, `hw_env` and -> `hw_camera` pass; `hw_motor` fails as "predates the pin echo". Phase 1's gate -> for a Delta is therefore "everything but `hw_motor` green, and `hw_motor` -> failing only on missing echoes" until the firmware is flashed. +> FAIL, by design: a check that could not happen must not read as a pass. So on +> a Delta the gate before flashing is "everything but `hw_motor` green, and +> `hw_motor` failing only on missing echoes". > - Speed: `scan_speed_deg_s = 8.0` applies to **both** axes. On 111 that is VMAX -> 5638 for yaw but **157861 for pitch** (14114 counts/deg), above the 150000 it -> ran at and far above the firmware's tuned-down 60000; pitch logged encoder -> tracking errors of 424 and 473 counts (limit 400) in its first minutes. Set -> `[Motor.pitch] scan_speed_deg_s` lower if that persists. +> 5638 for yaw but 157861 for pitch, above the 150000 it ran at; pitch logs an +> occasional encoder tracking error just over the 400-count limit. Accepted for +> now (2026-09-24); `[Motor.pitch] scan_speed_deg_s` is the knob. ## 14. Firmware changes, collected @@ -475,7 +479,7 @@ after the table. | 6 | Config handshake — **firmware half** | **done, and validated on tower 121.** `NEEDCFG` state (`ST` code `C`), `NEED` emitted with the banner, `SET `, `USE DEFAULTS`, and the init block split so the residual-motion stop still runs at boot. 33 native tests pass under `pio test -e native`. On the rig: the handshake was watched live, `HOME` refused with `ERR need config`, `RESET` did not escape the state, and `USE DEFAULTS` released it | | 6 | Adoption check generalised | **done**, 9 tests. `DUMP` echoes every host-owned value plus a `cfg_rx` bitmask; `test hardware motor` checks all of them, and reports an absent field as unconfirmable rather than as a mismatch against -1. Round trip NEED -> SET -> DUMP -> PASS verified against `mock_needcfg` | | 14 | Drivetrain out of `MOTOR_CONFIG` | **done.** The struct keeps pins and `homing_kind` only; `platform_iota.h` keeps its measurement record but no longer defines this tower's values. `USE DEFAULTS` now loads a shared **limp-home** set (`LIMP_*` in `config.h`) that is deliberately nobody's real values — slow speeds, generous margins, the gentle search current — so a tower still running them fails `test hardware motor` instead of looking configured. `SW_MODE` is derived from `homing_kind` rather than shared, because disabling the hardware stops on an axis that homes on real switches would remove its last protection. Flash cost, not saved: see the porting plan. Found a real bug on the way — `DIAG` read `normal_speed` / `center_speed` from the compile-time table, so it ran at compiled speeds rather than the tower's declared ones | -| 14 | Pins and `NUM_AXES` generated from `hardware.ini` | **done, validated on tower 121** (2026-09-23): built from its `/etc` file, flashed, `DUMP` echoed `spi=2,3,0 cs=1 en=4`, `IOIN version=0x30`, and `test hardware motor` passed `yaw_pins` / `spi_pins`; a build against a copy without `[Pins]` failed with the reason. Leonardo: builds against a generated Delta `hardware.ini`; the pin echo costs **170 B of flash** (22,876 -> 23,046 B, 79.8% -> 80.4%, 5,626 B free) and no RAM - measured 2026-09-23, compile only, not yet flashed to a Delta. New `[Pins]` section; `firmware/tools/gen_wiring.py` generates `board_wiring.h` into the build directory from the tower's own file at build time, and refuses pins the board cannot use (RP2040 SPI0 groups, the Leonardo's fixed ICSP pins, duplicates, a board/env mismatch). No compiled-in fallback: no file or no `[Pins]` fails the build. `DUMP` echoes `cs=`/`en=` per axis and `spi=` on the RP2040; `test hardware motor` compares them with `[Pins]`. The loom tables moved to `hardware//pins.ini`. 18 generator tests (run without PlatformIO), 9 host tests | +| 14 | Pins and `NUM_AXES` generated from `hardware.ini` | **done, validated on tower 121** (2026-09-23): built from its `/etc` file, flashed, `DUMP` echoed `spi=2,3,0 cs=1 en=4`, `IOIN version=0x30`, and `test hardware motor` passed `yaw_pins` / `spi_pins`; a build against a copy without `[Pins]` failed with the reason. Leonardo: builds against a generated Delta `hardware.ini`; the pin echo costs **84 B of flash** (22,876 -> 22,960 B, 79.8% -> 80.1%, 5,712 B free) and no RAM. It first cost 170 B, because the SPI echo was gated on `PIN_SPI_SCK`, which the AVR core also defines (for the fixed ICSP pins); it is gated on the RP2040 now. Flashed to tower 111 on 2026-09-24 (the 170 B build, which also reports `spi=15,16,14` - harmless, and gone at its next flash). New `[Pins]` section; `firmware/tools/gen_wiring.py` generates `board_wiring.h` into the build directory from the tower's own file at build time, and refuses pins the board cannot use (RP2040 SPI0 groups, the Leonardo's fixed ICSP pins, duplicates, a board/env mismatch). No compiled-in fallback: no file or no `[Pins]` fails the build. `DUMP` echoes `cs=`/`en=` per axis and `spi=` on the RP2040; `test hardware motor` compares them with `[Pins]`. The loom tables moved to `hardware//pins.ini`. 18 generator tests (run without PlatformIO), 9 host tests | | 6 | `NEEDCFG` understood host-side | **done**, 5 tests. `AxisState::NeedConfig` parsed from `ST` `C`, named in `DUMP` and the dashboard, and counted as telemetry arriving rather than as a dead link. The firmware enum APPENDS the state (5) rather than inserting it — inserting renumbered every later state and made a READY axis read back as ERROR | **Validated on tower 121.** The handshake was watched live on the rig, not inferred: diff --git a/docs/roadmap.md b/docs/roadmap.md index d249307..6266be9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -19,9 +19,9 @@ Keep entries dated; move shipped items into the reference docs. metrics come off the firmware clock instead of host `ST` sampling; plus a `PING`/`PONG` link-health command for `test system link`. - ⚠️ **This collides with AVR flash headroom.** Three new commands are the - kind of growth the ATmega32U4 no longer has room for — it sits at **80.4 % - of its 28 KB** (5626 B free, measured 2026-09-23 after the pin echo added - 170 B; 79.8 % before it), up from 19254 B at the phase 2 gate. The RP2040 is a non-issue at 0.6 % of 8 MB. So either these + kind of growth the ATmega32U4 no longer has room for — it sits at **80.1 % + of its 28 KB** (5712 B free, measured 2026-09-24 after the pin echo added + 84 B; 79.8 % before it), up from 19254 B at the phase 2 gate. The RP2040 is a non-issue at 0.6 % of 8 MB. So either these land RP2040-only behind the `platform_*.h` split, or the Delta fleet gets a deliberate answer on flash first — `PROGMEM` for the string tables, dropping a command, or accepting that the Delta is feature-frozen. Note §14 of the diff --git a/hardware/lattepanda-3-delta/NOTES.md b/hardware/lattepanda-3-delta/NOTES.md index 9d57cb2..c35580e 100644 --- a/hardware/lattepanda-3-delta/NOTES.md +++ b/hardware/lattepanda-3-delta/NOTES.md @@ -27,6 +27,14 @@ rebuilt on Ubuntu 22.04 (GCC 11) and restarted with `--init --start`. It homed a resumed capturing. `test hardware`: axes, IMU, env and camera PASS; motor FAIL on the old firmware's missing echoes, as expected. Firmware NOT flashed. +## Firmware flashed on tower 111, 2026-09-24 `[verified]` + +With the drivetrain converted from what the replaced firmware ran (see +`docs/hardware-config-plan.md` section 13) and 75 mOhm sense resistors read off +both boards. `tools/flash.sh` flashed it through the Leonardo's 1200-baud touch +unchanged. The new firmware asked, the host answered, `test hardware` passed +every leaf, and it homed to within 0.03 % of the old firmware's travel. + ## What this kit has NOT done yet In particular: @@ -34,14 +42,10 @@ In particular: - **`pins.ini` is not a traced loom.** It carries the numbers the firmware ran on before pins moved into `hardware.ini`. Trace one Delta's wiring and replace it with a table the way the IOTA's was done. -- **The drivetrain is not known in the units `hardware.ini` uses.** The Delta's - firmware carried register values (GLOBALSCALER 80 / 50, IRUN 31, SW_MODE - 0x8A0 / 0x8A3, ENC_DEVIATION 1500). Turning them into milliamps needs each - driver board's sense resistor, which has to be read off the board. Until it - is, the current keys stay `; TODO` - and so the installer will not flash the - new firmware, which would refuse to move without them. -- **Flashing** keeps the Leonardo's own 1200-baud touch, which is how the Delta - fleet has always been flashed; `tools/flash.sh` does nothing extra for it. +- **Other Delta towers' drivetrains are not known.** 111's values are 111's: + converted from the firmware it ran, with its own boards' sense resistors. Do + not copy them to another tower without reading that tower's boards and + firmware revision the same way. ## Migrating an existing Delta tower diff --git a/scripts/install-tower b/scripts/install-tower index 7c829d7..40ba0ae 100755 --- a/scripts/install-tower +++ b/scripts/install-tower @@ -229,6 +229,18 @@ PYEOF echo "build=${build:-?} spi=${spi:--} Y=${y:--} P=${p:--}" } +# Does what the firmware reports match what hardware.ini declares? Only the +# declared fields count, as in `test hardware motor`: a Leonardo declares no SPI +# pins (they are fixed), so whatever an older build reports for them is moot. +pins_match() { + local now="$1" want="$2" f v + [[ -n $now ]] || return 1 + for f in $want; do + v="${f#*=}"; [[ $v == - ]] && continue + [[ " $now " == *" $f "* ]] || return 1 + done +} + # The same, as hardware.ini declares it, for comparison. declared_pins() { local spi="-" y="-" p="-" a b c @@ -364,7 +376,7 @@ verify() { check "hardware.ini complete" "$([[ $todo -eq 0 ]] && echo 0 || echo 1)" "$todo TODO(s)" local now; now="$(firmware_report)" local want_pins; want_pins="$(declared_pins)" - check "firmware pins" "$([[ -n $now && ${now#* } == "$want_pins" ]] && echo 0 || echo 1)" \ + check "firmware pins" "$(pins_match "$now" "$want_pins" && echo 0 || echo 1)" \ "running ${now:-unknown}; declared $want_pins" run_hardware_test check "test hardware" "$TEST_RC" "$([[ $TEST_RC == 0 ]] && echo PASS || echo "exit $TEST_RC")" @@ -648,7 +660,7 @@ fi (( todo_n > 0 )) && FW_BLOCK="hardware.ini still has $todo_n gap(s): the firmware would sit in NEEDCFG, or not build" want_pins="$( (HW="$HW_VIEW"; [[ $HW_APPEND_PINS -eq 1 ]] && { cat "$HW" "$KIT/pins.ini" > "$WORK/hw_pins.ini"; HW="$WORK/hw_pins.ini"; }; declared_pins) )" if [[ -n $FW_BLOCK ]]; then plan firmware blocked "$FW_BLOCK" -elif [[ -n $S_FW_NOW && ${S_FW_NOW%% *} == "build=$S_FW_REV" && ${S_FW_NOW#* } == "$want_pins" ]]; then +elif [[ -n $S_FW_NOW && ${S_FW_NOW%% *} == "build=$S_FW_REV" ]] && pins_match "$S_FW_NOW" "$want_pins"; then plan firmware skip "running $S_FW_REV, built with the declared pins" else plan firmware change "build $S_FW_REV and flash it (running: ${S_FW_NOW:-unknown}; declared $want_pins)" @@ -907,7 +919,20 @@ if app_running; then say "running" elif [[ $WAS_RUNNING -eq 1 ]]; then read -r -a restart_args <<< "${S_ARGS:-}" + before="$(latest_log)" ( cd "$REPO" && "$FGC" start "${restart_args[@]}" ) > /dev/null && say "restarted as it was: fgc start${S_ARGS:+ $S_ARGS}" + # With --init it is homing now, and refuses a test until it is done: wait, + # or the checks below report a busy program as a failed one. + if [[ " ${S_ARGS:-} " == *" --init "* ]]; then + say "waiting for it to home before checking" + for _ in $(seq 1 420); do + log="$(latest_log)" + if [[ -n $log && $log != "$before" ]] && grep -qE 'Gimbal homed|homing (failed|timed out)|ERROR.*hom' "$log"; then break; fi + app_running || break + sleep 1 + done + grep -hE 'Gimbal homed|homed .* travel' "$(latest_log)" 2>/dev/null | tail -3 | sed 's/^/ /' + fi else say "left stopped, as it was" fi