Finish migrating tower 111, and check only the pins hardware.ini declares

This commit is contained in:
pgdalmeida 2026-09-24 09:52:14 +02:00
parent 6824a0d580
commit 08c749ca06
Signed by: pedro.almeida
GPG Key ID: D4A6C394DF13F1D7
4 changed files with 68 additions and 35 deletions

View File

@ -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 existing fleet that drops yaw 50000 → 30000, a real behaviour change that wants a deliberate rig check
rather than a silent correction. 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 > Through `scripts/install-tower` with the Delta kit. 111 has a `hardware.ini`
> `hardware.ini` (yaw + pitch, MTi on `/dev/ttyS0`, SHT41 on `/dev/i2c-2`, > (yaw + pitch, MTi on `/dev/ttyS0`, SHT41 on `/dev/i2c-2`), its USB
> declared and confirmed by `test hardware`), its USB preconditions, the current > preconditions, the current host, and firmware `a0b1e4a`. `test hardware`
> host, and a `config.ini` with the keys `hardware.ini` owns commented out. It > passes every leaf, `hw_motor` included, and `install-tower --verify` is green.
> homed and resumed capturing on the new host. A rollback copy of the previous > A rollback copy of the previous sources, binary, firmware source and
> sources, binary and `config.ini` is in `~ggs/rollback-20260923/` on the tower. > `config.ini` is in `~ggs/rollback-20260923/` on the tower.
> >
> **Every drivetrain value is still a `; TODO`**, because the Delta firmware held > The drivetrain was not invented, and not measured afresh either: it is what
> register values (GLOBALSCALER / IRUN) and converting them to milliamps needs > the firmware it replaced (`af74190-dirty`) was running, converted to
> each driver board's sense resistor, which has to be read off the board. So > `hardware.ini`'s units with the sense resistor read off the boards (75 mOhm,
> **do not flash 111 yet**: the new firmware would sit in `NEEDCFG`, refusing to > both axes). Round-tripped through the host's own planners back to identical
> move. The installer blocks the flash for exactly this reason. > 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: > Two corrections to what this section used to say:
> >
> - `test hardware` does **not** pass on the old firmware. An absent echo is > - `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 > reported as unconfirmable, not as a mismatch - but unconfirmable is still a
> FAIL, by design (`checkDeclaredValues`, `checkPinsBuilt`): a check that could > FAIL, by design: a check that could not happen must not read as a pass. So on
> not happen must not read as a pass. On 111 `hw_axes`, `hw_imu`, `hw_env` and > a Delta the gate before flashing is "everything but `hw_motor` green, and
> `hw_camera` pass; `hw_motor` fails as "predates the pin echo". Phase 1's gate > `hw_motor` failing only on missing echoes".
> for a Delta is therefore "everything but `hw_motor` green, and `hw_motor`
> failing only on missing echoes" until the firmware is flashed.
> - Speed: `scan_speed_deg_s = 8.0` applies to **both** axes. On 111 that is VMAX > - 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 > 5638 for yaw but 157861 for pitch, above the 150000 it ran at; pitch logs an
> ran at and far above the firmware's tuned-down 60000; pitch logged encoder > occasional encoder tracking error just over the 400-count limit. Accepted for
> tracking errors of 424 and 473 counts (limit 400) in its first minutes. Set > now (2026-09-24); `[Motor.pitch] scan_speed_deg_s` is the knob.
> `[Motor.pitch] scan_speed_deg_s` lower if that persists.
## 14. Firmware changes, collected ## 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 <axis> <key> <value>`, `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 | Config handshake — **firmware half** | **done, and validated on tower 121.** `NEEDCFG` state (`ST` code `C`), `NEED` emitted with the banner, `SET <axis> <key> <value>`, `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` | | 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 | 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/<board>/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/<board>/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 | | 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: **Validated on tower 121.** The handshake was watched live on the rig, not inferred:

View File

@ -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 metrics come off the firmware clock instead of host `ST` sampling; plus a
`PING`/`PONG` link-health command for `test system link`. `PING`/`PONG` link-health command for `test system link`.
- ⚠️ **This collides with AVR flash headroom.** Three new commands are the - ⚠️ **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 % kind of growth the ATmega32U4 no longer has room for — it sits at **80.1 %
of its 28 KB** (5626 B free, measured 2026-09-23 after the pin echo added of its 28 KB** (5712 B free, measured 2026-09-24 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 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 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 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 a command, or accepting that the Delta is feature-frozen. Note §14 of the

View File

@ -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 resumed capturing. `test hardware`: axes, IMU, env and camera PASS; motor FAIL on
the old firmware's missing echoes, as expected. Firmware NOT flashed. 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 ## What this kit has NOT done yet
In particular: In particular:
@ -34,14 +42,10 @@ In particular:
- **`pins.ini` is not a traced loom.** It carries the numbers the firmware ran - **`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 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. 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 - **Other Delta towers' drivetrains are not known.** 111's values are 111's:
firmware carried register values (GLOBALSCALER 80 / 50, IRUN 31, SW_MODE converted from the firmware it ran, with its own boards' sense resistors. Do
0x8A0 / 0x8A3, ENC_DEVIATION 1500). Turning them into milliamps needs each not copy them to another tower without reading that tower's boards and
driver board's sense resistor, which has to be read off the board. Until it firmware revision the same way.
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.
## Migrating an existing Delta tower ## Migrating an existing Delta tower

View File

@ -229,6 +229,18 @@ PYEOF
echo "build=${build:-?} spi=${spi:--} Y=${y:--} P=${p:--}" 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. # The same, as hardware.ini declares it, for comparison.
declared_pins() { declared_pins() {
local spi="-" y="-" p="-" a b c 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)" check "hardware.ini complete" "$([[ $todo -eq 0 ]] && echo 0 || echo 1)" "$todo TODO(s)"
local now; now="$(firmware_report)" local now; now="$(firmware_report)"
local want_pins; want_pins="$(declared_pins)" 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" "running ${now:-unknown}; declared $want_pins"
run_hardware_test run_hardware_test
check "test hardware" "$TEST_RC" "$([[ $TEST_RC == 0 ]] && echo PASS || echo "exit $TEST_RC")" 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" (( 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) )" 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" 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" plan firmware skip "running $S_FW_REV, built with the declared pins"
else else
plan firmware change "build $S_FW_REV and flash it (running: ${S_FW_NOW:-unknown}; declared $want_pins)" 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" say "running"
elif [[ $WAS_RUNNING -eq 1 ]]; then elif [[ $WAS_RUNNING -eq 1 ]]; then
read -r -a restart_args <<< "${S_ARGS:-}" 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}" ( 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 else
say "left stopped, as it was" say "left stopped, as it was"
fi fi