Record the resolved yaw homing and the hand-written drivetrain block

This commit is contained in:
pgdalmeida 2026-09-18 13:19:32 +02:00
parent f695a4eac6
commit 2191a9d8cb
Signed by: pedro.almeida
GPG Key ID: D4A6C394DF13F1D7
3 changed files with 90 additions and 8 deletions

View File

@ -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`.
<details><summary>What the investigation looked like before it was resolved</summary>
**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.
</details>
| 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 |

View File

@ -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<MotorTelemetry>` |
| [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.<axis>]`. 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 |

View File

@ -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.<axis>]` 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] <axis>_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 |