From 57a166c9a4e2731480df22f23d17067f688266be Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Mon, 21 Sep 2026 23:06:37 +0200 Subject: [PATCH] Warn when the homed travel falls below zero, and say why the centre matters --- config/config.example.ini | 5 ++++ docs/configuration.md | 2 +- include/fgc/HardwareChecks.h | 16 ++++++++++++ src/core/Application.cpp | 10 ++++++++ src/core/HardwareChecks.cpp | 24 ++++++++++++++++++ tests/test_hardwarechecks.cpp | 47 +++++++++++++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 1 deletion(-) diff --git a/config/config.example.ini b/config/config.example.ini index 4ff9a68..1ae9627 100644 --- a/config/config.example.ini +++ b/config/config.example.ini @@ -178,6 +178,11 @@ period_ms = 2000 ; ; Yaw starting point: firmware steps_per_rev=177000 over ~180 deg => ~983.33. yaw_counts_per_deg = 983.33 +; MUST exceed half the homed travel: the range is centre +/- half-travel, so a +; smaller centre puts the negative end below zero. 500000 suits the Delta +; (~89k half-travel); the IOTA's higher gear ratio needs 1000000. The centre +; itself is arbitrary - only the span is physical - and a warning names it +; after each home if it is too small. yaw_center_count = 500000 ; True bearing (0 = north) the gimbal points at when parked at the centre. yaw_heading_at_center = 0.0 diff --git a/docs/configuration.md b/docs/configuration.md index b9f60fb..70fa3cd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,7 +67,7 @@ Parsed and validated by `ConfigLoader` ([src/core/Config.cpp](../src/core/Config | `Logging` | `trace` | csv | — | Wire-trace categories, off by default (`--trace` overrides) | | `UI` | `enable_tui` | bool | `false` | Full-screen terminal dashboard (`--tui`/`--no-tui` override; needs `WITH_TUI=ON`) | | `Motor` | `yaw_counts_per_deg` / `pitch_counts_per_deg` | float | `983.33` / — | Encoder counts per degree (**calibrate**; may be negative to flip) | -| `Motor` | `yaw_center_count` / `pitch_center_count` | int | `500000` | Count homing **defines** as the middle of the found travel | +| `Motor` | `yaw_center_count` / `pitch_center_count` | int | `500000` | Count homing **defines** as the middle of the found travel. **Must exceed half the travel**, or the negative end falls below zero — the `500000` default suits the Delta's ~89k half-travel, not a geared-up rig (the IOTA needs 1000000). A warning names it after each home | | `Motor` | `yaw_heading_at_center` | float | `0` | True bearing (0 = north) at that centre (**calibrate**) | | `Motor` | `pitch_angle_at_center` | float | `0` | Elevation above level at that centre (**calibrate**; not 0 on a real rig) | | `Motor` | `yaw_soft_margin_counts` / `pitch_soft_margin_counts` | int | `5000` | Clearance kept from each hard endstop; sent to the firmware, which enforces it | diff --git a/include/fgc/HardwareChecks.h b/include/fgc/HardwareChecks.h index 60ff736..a56ef21 100644 --- a/include/fgc/HardwareChecks.h +++ b/include/fgc/HardwareChecks.h @@ -88,4 +88,20 @@ struct DrivetrainEcho { CheckResult checkDrivetrainAdopted(const std::string& axis, const DrivetrainExpectation& want, const DrivetrainEcho& got); +// Does the homed travel fit entirely in positive counts? +// +// `center_count` defines where the middle of the found travel sits, so the range +// is center +/- half the travel. A center smaller than half the travel puts the +// negative end below zero. The 500000 default was sized for the Delta, whose +// half-travel is ~89k; a drivetrain geared up by 7x has ~605k and crosses zero. +// +// Whether the TMC5160 actually misbehaves there is NOT established - XACTUAL is +// a signed 32-bit register and the firmware round-trips the sign correctly - but +// homing is the only thing that has ever driven that far, so the region is +// untested in normal operation. The cheap fix is to not be there. +// +// `center` and `half_travel` are in counts. A non-positive half_travel means the +// axis has not homed, and nothing is checked. +CheckResult checkTravelFitsPositive(const std::string& axis, long center, long half_travel); + } // namespace fgc diff --git a/src/core/Application.cpp b/src/core/Application.cpp index b403421..165525c 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -11,6 +11,7 @@ #include "fgc/CameraProfile.h" #include "fgc/AxisSpeed.h" #include "fgc/FirmwareConfig.h" +#include "fgc/HardwareChecks.h" #include "fgc/HardwarePresence.h" #include "fgc/TmcCurrent.h" #include "fgc/ICameraSource.h" @@ -1498,6 +1499,15 @@ struct Application::Impl { const long want_margin = is_yaw ? cfg.yaw_soft_margin_counts : cfg.pitch_soft_margin_counts; + // The centre is arbitrary, but it has to be big enough that the whole + // travel stays positive: the range is centre +/- half the travel, so + // a centre below half-travel puts the negative end under zero. The + // 500000 default was sized for the Delta's ~89k half-travel and does + // not survive a gear ratio 7x higher. + const auto fits = checkTravelFitsPositive(name, map.center_count, + (a.lim_pos - a.lim_neg) / 2); + if (fits.ran && !fits.pass) LOG_WARN << fits.detail; + // One encoder quantum of slack: ENC_CONST makes X_ENC advance in 12.8 // microstep steps, so the midpoint can land a count or so off. const long mid = a.lim_neg + (a.lim_pos - a.lim_neg) / 2; diff --git a/src/core/HardwareChecks.cpp b/src/core/HardwareChecks.cpp index cf54c8e..fadf9cf 100644 --- a/src/core/HardwareChecks.cpp +++ b/src/core/HardwareChecks.cpp @@ -184,4 +184,28 @@ CheckResult checkDeclaredValues(const std::string& axis, const DeclaredValue* va return r; } +CheckResult checkTravelFitsPositive(const std::string& axis, long center, long half_travel) { + CheckResult r; + if (half_travel <= 0) { + r.ran = false; + r.detail = axis + ": not homed, so the travel range is not known yet"; + return r; + } + const long lo = center - half_travel; + if (lo >= 0) { + r.detail = axis + ": travel is entirely positive (" + std::to_string(lo) + " .. " + + std::to_string(center + half_travel) + ")"; + return r; + } + r.pass = false; + r.detail = axis + ": homed travel reaches " + std::to_string(lo) + + " -- below zero. [Motor] " + axis + "_center_count is " + + std::to_string(center) + " but half the travel is " + + std::to_string(half_travel) + ", so raise the centre above that (a round " + "number with headroom, e.g. " + + std::to_string(((half_travel / 1000000) + 1) * 1000000) + + "). The centre is arbitrary; only the span is physical"; + return r; +} + } // namespace fgc diff --git a/tests/test_hardwarechecks.cpp b/tests/test_hardwarechecks.cpp index 6f4e90b..ee47533 100644 --- a/tests/test_hardwarechecks.cpp +++ b/tests/test_hardwarechecks.cpp @@ -174,3 +174,50 @@ TEST_CASE("a real mismatch alongside an absent field still names the mismatch") CHECK_FALSE(r.pass); CHECK(r.detail.find("GLOBALSCALER wanted 80, firmware has 255") != std::string::npos); } + +// --- the travel must fit in positive counts ------------------------------- + +TEST_CASE("the Delta's centre comfortably holds its travel") { + // ~89k half-travel against a 500000 centre: 411k .. 589k, all positive. + auto r = checkTravelFitsPositive("yaw", 500000, 89000); + CHECK(r.ran); + CHECK(r.pass); +} + +TEST_CASE("the IOTA's gear ratio pushes the same centre below zero") { + // The real case: half-travel 604749 against the inherited 500000 default + // gives lim_neg -104749, which is what tower 121 actually homed to. + auto r = checkTravelFitsPositive("yaw", 500000, 604749); + CHECK(r.ran); + CHECK_FALSE(r.pass); + CHECK(r.detail.find("-104749") != std::string::npos); + CHECK(r.detail.find("yaw_center_count") != std::string::npos); + // The suggestion is the next round million above half the travel - 1000000 + // here, which is the value actually adopted. Not 2000000: over-suggesting + // wastes range for no reason. + CHECK(r.detail.find("1000000") != std::string::npos); +} + +TEST_CASE("raising the centre fixes it") { + auto r = checkTravelFitsPositive("yaw", 1000000, 604749); + CHECK(r.pass); + CHECK(r.detail.find("395251") != std::string::npos); +} + +TEST_CASE("the suggestion scales with the drivetrain, not a fixed constant") { + // A ratio big enough that 1000000 would not be enough either. + auto r = checkTravelFitsPositive("yaw", 500000, 1400000); + CHECK_FALSE(r.pass); + CHECK(r.detail.find("2000000") != std::string::npos); +} + +TEST_CASE("exactly touching zero is accepted, one count below is not") { + CHECK(checkTravelFitsPositive("yaw", 604749, 604749).pass); + CHECK_FALSE(checkTravelFitsPositive("yaw", 604748, 604749).pass); +} + +TEST_CASE("an un-homed axis is not checked") { + auto r = checkTravelFitsPositive("pitch", 500000, 0); + CHECK_FALSE(r.ran); + CHECK(r.pass); +}