Stop addressing an axis this tower does not have, and carry the live calibration

This commit is contained in:
pgdalmeida 2026-09-17 18:14:13 +02:00
parent 5b3662c529
commit 31da8012d7
Signed by: pedro.almeida
GPG Key ID: D4A6C394DF13F1D7
2 changed files with 29 additions and 6 deletions

View File

@ -289,11 +289,22 @@ struct CalibAxisView {
double r2 = 0; double r2 = 0;
int n = 0; int n = 0;
}; };
// The degrees<->counts map an axis is actually using right now: from config.ini
// at startup, replaced in memory by a fit or a homing derivation. Distinct from
// CalibAxisView, which describes the last FIT and only exists once one has run.
struct AxisMapView {
double counts_per_deg = 0;
double deg_at_center = 0;
};
struct CalibResultView { struct CalibResultView {
bool has = false; bool has = false; // has a fit run THIS SESSION?
long long ts_ms = 0; long long ts_ms = 0;
bool pitch_present = false; bool pitch_present = false;
CalibAxisView yaw, pitch; CalibAxisView yaw, pitch;
// Always populated. "No fit this session" is not the same as "uncalibrated":
// the working calibration usually comes from config.ini and predates the run.
AxisMapView live_yaw, live_pitch;
}; };
// Worst-case encoder tracking error from the last DIAG run, per axis, for the // Worst-case encoder tracking error from the last DIAG run, per axis, for the

View File

@ -907,6 +907,9 @@ struct Application::Impl {
// --- Activity strip + last calibration --- // --- Activity strip + last calibration ---
s.calib = last_calib_view; s.calib = last_calib_view;
s.calib.pitch_present = s.gimbal.pitch_present || s.calib.pitch_present; s.calib.pitch_present = s.gimbal.pitch_present || s.calib.pitch_present;
// The map in force right now, whether or not anything was fitted today.
s.calib.live_yaw = {cfg.geometry.yaw.counts_per_deg, cfg.geometry.yaw.deg_at_center};
s.calib.live_pitch = {cfg.geometry.pitch.counts_per_deg, cfg.geometry.pitch.deg_at_center};
s.diag = last_diag_view; s.diag = last_diag_view;
s.test = last_test_view; s.test = last_test_view;
fillActivity(s); fillActivity(s);
@ -1040,7 +1043,11 @@ struct Application::Impl {
} clear_homing{homing_active_}; } clear_homing{homing_active_};
motor->sendCommand("ENABLE Y"); motor->sendCommand("ENABLE Y");
motor->sendCommand("ENABLE P"); // Only address axes this tower actually has. A yaw-only rig answers
// "ERR axis not present" to every pitch command, which arrives in the log
// as a row of warnings at every startup - noise that trains an operator to
// ignore exactly the channel that reports real faults.
if (cfg.hardware.hasAxis("pitch")) motor->sendCommand("ENABLE P");
// Tell the firmware where to anchor the homed midpoint and how much // Tell the firmware where to anchor the homed midpoint and how much
// clearance to keep from each endstop, BEFORE homing: finishHoming() uses // clearance to keep from each endstop, BEFORE homing: finishHoming() uses
// the centre count to re-origin, so a late send would apply to the next // the centre count to re-origin, so a late send would apply to the next
@ -1050,7 +1057,10 @@ struct Application::Impl {
// Confirm the current landed BEFORE driving into anything. This is the // Confirm the current landed BEFORE driving into anything. This is the
// only check standing between a stale firmware and an endstop that cannot // only check standing between a stale firmware and an endstop that cannot
// survive full torque. // survive full torque.
if (yaw_current_.ok || pitch_current_.ok) { // Only when a reduction is actually depended on. An axis running at one
// current for everything has nothing to lose if the command went astray,
// and the check costs a DUMP round trip whose output floods the log.
if (cfg.drivetrain.yaw.reducesForHoming() || cfg.drivetrain.pitch.reducesForHoming()) {
if (auto dd = freshDump(std::chrono::seconds(3))) { if (auto dd = freshDump(std::chrono::seconds(3))) {
if (!verifyCurrentAdoption(*dd)) return; if (!verifyCurrentAdoption(*dd)) return;
} else { } else {
@ -1122,7 +1132,7 @@ struct Application::Impl {
} }
// Production move speeds for subsequent MOVE commands. // Production move speeds for subsequent MOVE commands.
motor->sendCommand("SPEED Y 50000"); motor->sendCommand("SPEED Y 50000");
motor->sendCommand("SPEED P 150000"); if (cfg.hardware.hasAxis("pitch")) motor->sendCommand("SPEED P 150000");
LOG_INFO << "Gimbal init finished"; LOG_INFO << "Gimbal init finished";
} }
@ -1208,9 +1218,11 @@ struct Application::Impl {
// clampTarget() keeps clear of each hard limit. // clampTarget() keeps clear of each hard limit.
void sendAnchorConfig() { void sendAnchorConfig() {
motor->sendCommand("CENTER Y " + std::to_string(cfg.geometry.yaw.center_count)); motor->sendCommand("CENTER Y " + std::to_string(cfg.geometry.yaw.center_count));
motor->sendCommand("CENTER P " + std::to_string(cfg.geometry.pitch.center_count));
motor->sendCommand("MARGIN Y " + std::to_string(cfg.yaw_soft_margin_counts)); motor->sendCommand("MARGIN Y " + std::to_string(cfg.yaw_soft_margin_counts));
motor->sendCommand("MARGIN P " + std::to_string(cfg.pitch_soft_margin_counts)); if (cfg.hardware.hasAxis("pitch")) {
motor->sendCommand("CENTER P " + std::to_string(cfg.geometry.pitch.center_count));
motor->sendCommand("MARGIN P " + std::to_string(cfg.pitch_soft_margin_counts));
}
// Motor current belongs here too: it governs homing torque, so it has to // Motor current belongs here too: it governs homing torque, so it has to
// land before the search, not after it. // land before the search, not after it.
auto sendCurrent = [&](char axis, const TmcCurrentPlan& p) { auto sendCurrent = [&](char axis, const TmcCurrentPlan& p) {