diff --git a/docs/hardware-porting-plan.md b/docs/hardware-porting-plan.md index 6ad6e6e..0844a98 100644 --- a/docs/hardware-porting-plan.md +++ b/docs/hardware-porting-plan.md @@ -297,7 +297,7 @@ commit `aab1925`. |---|---|---| | 0 | Remote-flash question researched — two viable paths, design rule derived (§2) | **done, and now measured on the board: neither works** (§2). First flash needs the MicroPython REPL or BOOTSEL; remote *update* is unsolved pending a `BOOTSEL` command | | 2 | **Phase 2 gate: RP2040 + TMC5160-BOB at 3.3 V** | **PASSED** on tower 121, 2026-09-15: `IOIN Y=0x3000001E version=0x30`, encoder reading (`X_ENC`), one axis, `speed=3000` | -| 3 | Yaw homing on the real drivetrain | **BLOCKED — no endstop found.** See below | +| 3 | Yaw homing on the real drivetrain | **done** — the endstop was being sheared through at full torque; homing at 189 mA holds. See below | ### Phase 3, first attempt — 2026-09-16 @@ -314,17 +314,49 @@ direction without the encoder deviation ever exceeding ~100 counts** against the 1500 that marks a jam. The motor met no resistance at any speed. Homing detects an endstop by the shaft falling behind the commanded position, so -it cannot work if nothing stops the shaft. Two candidates, indistinguishable -over the wire because the encoder sits on the motor side: +it cannot work if nothing stops the shaft. Two candidates were considered, +indistinguishable over the wire because the encoder sits on the motor side: no +hard stop at all, or an output not following the motor. -1. **No hard stop on this yaw** — it rotates continuously, and stall-based homing - is the wrong strategy for this rig. -2. **The output is not following the motor** — a slipping coupling or a loose - grub screw. The encoder would track the motor perfectly either way. +### RESOLVED, 2026-09-17 — the stop was there, and losing + +Neither. **The endstop is a screw head meeting another screw head, and the raised +gear ratio gave the motor enough torque to shear one past the other.** The stop +was resisting; it simply lost, every time, and a stop that yields is invisible to +a detector watching for the shaft to fall behind. + +The fix is to make the search weak enough that the screw wins: + +| | | +|---|---| +| Homing current | **189 mA** (IRUN 7 at GLOBALSCALER 80, 75 mΩ sense resistor) | +| Run current | 756 mA — unchanged, because the gear ratio exists for wind stability | +| Measured travel | **1,210,778 counts**, repeatable to 0.27 % across three homes | +| Free-running encoder gap | ~200 counts against the 1500 latch, and it does **not** scale with speed | +| Full homing | ~2 min at VMAX 30000 | + +Declared per tower in `hardware.ini` as `[Motor.yaw] homing_current_ma`, converted +to registers by the host and sent as `CURRENT` before every home — so retuning it +is a restart, not a reflash. See [configuration.md](configuration.md) for the keys +and `firmware/docs/hardware-notes.md` for the mechanism. + +Three things this exposed, all since fixed: + +- **`steps_per_rev` was inert**, still carrying the Delta's 177,000 against a real + turn of ~1.2 M, so it passed at 14 % of a turn. It is the only guard against a + *phantom* endstop, which reducing the current makes reachable. +- **`STOP` did not end a homing** — it halted the motor but left the state machine + waiting for a stall that could no longer come, until the timeout. +- **The host abandoned homing after a hard-coded 65 s**, which this drivetrain + needs more than. Now `[Motor] homing_timeout_s`. + +
What the investigation looked like before it was resolved **Resolving it needs a physical observation**: did the gimbal itself rotate ~1.8 turns, or did the motor spin while the platform stayed put? Check any cabling through that joint before moving the axis further. + +
| 2 | `src/platform.h` — reset cause and free RAM behind one interface per MCU family | **done** | | 2 | `Serial.begin()` moved ahead of all hardware init, so a wedged image stays flashable | **done** | | 2 | `[env:iota_rp2040]` PlatformIO target (arduino-pico core, 8 MB flash) | **done and built** on tower 121, 2026-09-15: 49452 B flash (0.6 % of 8 MB), 9068 B RAM. `leonardo` still builds (19254 B) and the 22 native tests still pass | diff --git a/docs/modules-reference.md b/docs/modules-reference.md index d555cd2..3b764ad 100644 --- a/docs/modules-reference.md +++ b/docs/modules-reference.md @@ -14,6 +14,7 @@ Per-file reference for the refactored tree, plus the shared data structures. | [include/fgc/TelemetryParser.h](../include/fgc/TelemetryParser.h), [src/core/TelemetryParser.cpp](../src/core/TelemetryParser.cpp) | `parseTelemetryLine` (firmware `ST` line) → `std::optional` | | [include/fgc/CommandParser.h](../include/fgc/CommandParser.h), [src/core/CommandParser.cpp](../src/core/CommandParser.cpp) | `parseCommand` whitespace tokenizer → `Command` | | [include/fgc/HelpText.h](../include/fgc/HelpText.h), [src/core/HelpText.cpp](../src/core/HelpText.cpp) | Operator command catalog (`helpCatalog`) + `renderHelp` (console & TUI help) | +| [include/fgc/TmcCurrent.h](../include/fgc/TmcCurrent.h), [src/core/TmcCurrent.cpp](../src/core/TmcCurrent.cpp) | Milliamps → TMC5160 `GLOBALSCALER`/`IRUN` register fields, using the sense resistor from `[Motor.]`. Pure arithmetic, so it is unit-tested; the host owns the conversion because only it knows the resistor | | [include/fgc/DumpParser.h](../include/fgc/DumpParser.h), [src/core/DumpParser.cpp](../src/core/DumpParser.cpp) | `parseDump` firmware `DUMP` block → `DumpData` (per-axis state + decoded TMC registers/flags); `formatDump` | | [include/fgc/DiagParser.h](../include/fgc/DiagParser.h), [src/core/DiagParser.cpp](../src/core/DiagParser.cpp) | `parseDiag` firmware `DG` self-test stream → `DiagResult` (per-axis tests, PASS/FAIL); `formatDiag` | | [include/fgc/Calibration.h](../include/fgc/Calibration.h), [src/core/Calibration.cpp](../src/core/Calibration.cpp) | `linearFit` (least-squares, R²) + `circularMeanDeg` for the IMU-referenced calibration | diff --git a/docs/tower-installation.md b/docs/tower-installation.md index 930a50c..cf721c8 100644 --- a/docs/tower-installation.md +++ b/docs/tower-installation.md @@ -228,6 +228,53 @@ Then prove the program finds it **with nothing in the environment** - no > required` - a message that points at the camera rather than at the file that > disappeared. Verified on tower 121 by rebooting it. +### The drivetrain block + +`install.sh` does not write this one - no probe can read a sense resistor off a +board - so add it by hand, one `[Motor.]` block per axis: + +```ini +[Motor.yaw] +r_sense_ohm = 0.075 ; the resistor fitted to THIS driver board +run_current_ma = 756 ; normal motion +homing_current_ma = 200 ; the endstop search only +travel_deg = 340 ; mechanical angle between the hard stops +``` + +**`homing_current_ma` is a safety setting, not a tuning one.** Homing finds a +stop by driving into it until the shaft falls behind; that only works while the +stop is stronger than the motor. A high gear ratio can shear a screw-head stop +straight past its mate - on tower 121 the axis ran 1.8 turns past the stop with +the encoder never falling behind, at full torque. Declare a reduced search +current wherever the stop cannot survive one. + +Getting it wrong in either direction is visible: too strong and the stop yields +again; too weak and the motor stalls on ordinary friction mid-sweep, trips the +same latch, and records a phantom endstop. The firmware's `steps_per_rev` guard +is what catches the second case, so it has to be set from the real travel. + +**`travel_deg` is a nameplate fact**, and the host divides the measured homing +travel by it to derive `[Motor] _counts_per_deg` into `config.ini`. A rough +angle gives a proportionally rough degree scale - 1 % out here is 1 % out on every +bearing - until `gimbal heading setup` supersedes it with a landmark fit. + +The startup log reports what the registers **actually** deliver, which is not +always what was asked for: + +``` +yaw motor current: run 756 mA (GLOBALSCALER 80, IRUN 31), homing 189 mA (IRUN 7) + -- R_sense 0.075 ohm from hardware.ini [Motor.yaw] +``` + +Omit the block entirely on a tower whose endstops hold against full torque: no +`CURRENT` is sent, the firmware keeps its compile-time defaults, and nothing +changes. + +> **If an axis declares a reduced homing current and the firmware has not adopted +> it, the host refuses to home** rather than driving into a stop at full torque. +> The message names the skew; the fix is to flash the firmware, not to override +> it. + > `[Serial] device` must be a `/dev/serial/by-id/` path, never `/dev/ttyACM0`. > Tower 121 has a 5G modem that claims three `ttyUSB` nodes, and enumeration > order is not stable across reboots. @@ -686,7 +733,9 @@ gimbal pitch setup # needs an IMU; skip on a rig without one | Driver answers over SPI | boot banner shows `IOIN Y=0x… version=0x30` | ✅ | | Remote re-flash | `BOOTSEL`, then `./deploy.sh` - no hands on the board | ✅ | | Declared hardware matches | startup logs no presence warnings | ✅ no shadowed-key warnings | -| Homing repeatable | `gimbal home` several times, endstop counts within `home_spread_max_counts` | ❌ **not attempted** - unknown drivetrain | +| Homing repeatable | `gimbal home` several times, endstop counts within `home_spread_max_counts` | ✅ three homes within 0.27 % (1,210,778 / 1,208,358 / 1,207,514 counts) | +| Endstop survives homing | inspect the stop after a home; the axis must stall against it, not push through | ✅ at 189 mA, after failing at full torque | +| Degree scale derived | startup logs `counts_per_deg` agreeing with the homed travel | ✅ 3552.87, saved with `;prev` history | | Images land | `.jxl` files appear under the configured output dir | ❌ needs a capture run | | Survives a disconnect | `scripts/fgc start`, close ssh, reconnect, `scripts/fgc status` | ❌ not attempted | | Reports to a broker | topics arrive at the configured broker | ❌ no broker on this tower yet |