Compare commits
No commits in common. "2191a9d8cb11d29467f9b7e697499ebb05dae51f" and "f9e209bf2ae3525010e5c18b67d984b6691606c3" have entirely different histories.
2191a9d8cb
...
f9e209bf2a
|
|
@ -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 |
|
| 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` |
|
| 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 | **done** — the endstop was being sheared through at full torque; homing at 189 mA holds. See below |
|
| 3 | Yaw homing on the real drivetrain | **BLOCKED — no endstop found.** See below |
|
||||||
|
|
||||||
### Phase 3, first attempt — 2026-09-16
|
### Phase 3, first attempt — 2026-09-16
|
||||||
|
|
||||||
|
|
@ -314,49 +314,17 @@ direction without the encoder deviation ever exceeding ~100 counts** against the
|
||||||
1500 that marks a jam. The motor met no resistance at any speed.
|
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
|
Homing detects an endstop by the shaft falling behind the commanded position, so
|
||||||
it cannot work if nothing stops the shaft. Two candidates were considered,
|
it cannot work if nothing stops the shaft. Two candidates, indistinguishable
|
||||||
indistinguishable over the wire because the encoder sits on the motor side: no
|
over the wire because the encoder sits on the motor side:
|
||||||
hard stop at all, or an output not following the motor.
|
|
||||||
|
|
||||||
### RESOLVED, 2026-09-17 — the stop was there, and losing
|
1. **No hard stop on this yaw** — it rotates continuously, and stall-based homing
|
||||||
|
is the wrong strategy for this rig.
|
||||||
Neither. **The endstop is a screw head meeting another screw head, and the raised
|
2. **The output is not following the motor** — a slipping coupling or a loose
|
||||||
gear ratio gave the motor enough torque to shear one past the other.** The stop
|
grub screw. The encoder would track the motor perfectly either way.
|
||||||
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
|
**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
|
turns, or did the motor spin while the platform stayed put? Check any cabling
|
||||||
through that joint before moving the axis further.
|
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 | `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 | `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 |
|
| 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 |
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ 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/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/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/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/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/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 |
|
| [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 |
|
||||||
|
|
|
||||||
|
|
@ -228,53 +228,6 @@ 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
|
> required` - a message that points at the camera rather than at the file that
|
||||||
> disappeared. Verified on tower 121 by rebooting it.
|
> 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`.
|
> `[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
|
> Tower 121 has a 5G modem that claims three `ttyUSB` nodes, and enumeration
|
||||||
> order is not stable across reboots.
|
> order is not stable across reboots.
|
||||||
|
|
@ -733,9 +686,7 @@ gimbal pitch setup # needs an IMU; skip on a rig without one
|
||||||
| Driver answers over SPI | boot banner shows `IOIN Y=0x… version=0x30` | ✅ |
|
| Driver answers over SPI | boot banner shows `IOIN Y=0x… version=0x30` | ✅ |
|
||||||
| Remote re-flash | `BOOTSEL`, then `./deploy.sh` - no hands on the board | ✅ |
|
| Remote re-flash | `BOOTSEL`, then `./deploy.sh` - no hands on the board | ✅ |
|
||||||
| Declared hardware matches | startup logs no presence warnings | ✅ no shadowed-key warnings |
|
| 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` | ✅ three homes within 0.27 % (1,210,778 / 1,208,358 / 1,207,514 counts) |
|
| Homing repeatable | `gimbal home` several times, endstop counts within `home_spread_max_counts` | ❌ **not attempted** - unknown drivetrain |
|
||||||
| 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 |
|
| 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 |
|
| 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 |
|
| Reports to a broker | topics arrive at the configured broker | ❌ no broker on this tower yet |
|
||||||
|
|
|
||||||
|
|
@ -95,13 +95,7 @@ std::string utcStampNow() {
|
||||||
#else
|
#else
|
||||||
gmtime_r(&t, &tm);
|
gmtime_r(&t, &tm);
|
||||||
#endif
|
#endif
|
||||||
// 64, not 32, purely to keep the compiler quiet. The real output is always 17
|
char b[32];
|
||||||
// characters, but the tm fields are plain ints, so GCC cannot rule out a
|
|
||||||
// nonsense clock and computes a worst case of 61 bytes - warning on every
|
|
||||||
// build. snprintf truncates rather than overflows either way, so this was
|
|
||||||
// never a safety problem; a recurring warning that everyone learns to ignore
|
|
||||||
// is the actual cost.
|
|
||||||
char b[64];
|
|
||||||
std::snprintf(b, sizeof(b), "%04d-%02d-%02dT%02d:%02dZ", tm.tm_year + 1900, tm.tm_mon + 1,
|
std::snprintf(b, sizeof(b), "%04d-%02d-%02dT%02d:%02dZ", tm.tm_year + 1900, tm.tm_mon + 1,
|
||||||
tm.tm_mday, tm.tm_hour, tm.tm_min);
|
tm.tm_mday, tm.tm_hour, tm.tm_min);
|
||||||
return std::string(b);
|
return std::string(b);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue