Compare commits

...

2 Commits

8 changed files with 139 additions and 1 deletions

View File

@ -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

View File

@ -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 |

View File

@ -271,6 +271,23 @@ 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.
> **The centre lives in `config.ini`, not here.** `install.sh` cannot write it —
> `hardware.ini` is merged *over* `config.ini` and wins, so a shared key would
> silently discard whatever `gimbal heading setup` had saved. What it does instead
> is work the requirement out from `steps_per_rev` and print it as a `NOTE` in the
> block, because the default is wrong for any geared-up rig:
>
> ```
> ; NOTE: half this travel is 605389 counts, so config.ini needs
> ; [Motor] yaw_center_count = 1000000 (or anything above 605389)
> ; The 500000 default would put the negative end at -105389.
> ```
>
> The range is centre ± half the travel, so a centre below half-travel puts the
> negative end under zero. On tower 121 the inherited `500000` homed to
> `-104749..1104749`; at `1000000` it homes to `395194..1604807`. A warning names
> it after every home if it is still too small.
**`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

View File

@ -299,6 +299,24 @@ do_vimba() {
# and a non-numeric value makes the program refuse to start with a message about
# a config key. Absent, it starts and reports the axis as undeclared - naming the
# missing keys and which axis will not move, which is the useful failure.
# The centre lives in config.ini, not here - it is calibration, and hardware.ini
# is merged OVER config.ini, so a shared key would silently discard whatever
# `gimbal heading setup` had saved. All this file can do is say what the value
# has to be, which it can work out from the travel.
#
# The range is centre +/- half the travel, so a centre below half-travel puts the
# negative end under zero. The long-standing 500000 default suits the Delta's
# ~89k half-travel and does not survive a gear ratio several times higher.
center_note() {
local steps="$1" ax="$2"
[[ -z $steps ]] && return 0
local half=$(( steps / 2 ))
local want=$(( (half / 1000000 + 1) * 1000000 ))
echo "; NOTE: half this travel is $half counts, so config.ini needs"
echo "; [Motor] ${ax}_center_count = $want (or anything above $half)"
echo "; The 500000 default would put the negative end at $(( 500000 - half ))."
}
kv_or_todo() {
local key="$1" value="$2" why="$3"
if [[ -n $value ]]; then
@ -400,6 +418,7 @@ EOF
"mechanical angle between the hard stops. Rough is fine to start; every bearing inherits its error proportionally until 'gimbal heading setup' supersedes it"
kv_or_todo "steps_per_rev" "$STEPS_PER_REV" \
"measured full travel IN COUNTS - read it off a homing run. The only guard against a PHANTOM endstop"
center_note "$STEPS_PER_REV" "$ax"
kv_or_todo "sw_mode" "$SW_MODE" \
"TMC SW_MODE. 0 = no physical switches (StallGuard homing); set the bits if this axis has REFL/REFR wired"
else

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
}