#pragma once #include namespace fgc { // Axis speed as a physical quantity, and its conversion to the TMC5160's VMAX. // // VMAX is a register value in internal units, and what it MEANS depends on the // drivetrain: 30000 is 21.8 deg/s on the Delta fleet and 6.0 deg/s on the IOTA // tower, because their gear ratios differ by 3.6x. Copying the number between // towers therefore produces a silently different machine, which is exactly what // happened - both carry the same literal. // // Declaring degrees per second instead makes the intent portable and checkable: // it is the quantity that determines image smear and panorama period, and it can // be reasoned about without knowing a gear ratio. // // The conversion needs only counts_per_deg, which the host already derives per // tower from the homed travel. That works because the firmware keeps XACTUAL // (step space) and XENC (encoder space) on the SAME scale - see // firmware/docs/hardware-notes.md, "Encoders track 1:1 but must be aligned to // XACTUAL". If that ever stops being true, this conversion breaks and the step // scale becomes a separate calibration. // The TMC5160 ramp generator: v[counts/s] = VMAX * f_CLK / 2^24. // // 12 MHz is the internal oscillator's nominal frequency. Confirmed on the rig // rather than taken on faith: the predicted 0.7153 counts/s per VMAX unit is // within 2% of the 0.73 measured across three homing runs on tower 121. constexpr double kTmcClockHz = 12'000'000.0; constexpr double kTmcVmaxDiv = 16'777'216.0; // 2^24 // VMAX is a 23-bit field. constexpr long kTmcVmaxMax = 0x7FFFFF; // The fleet default, in the unit that means the same thing on every tower. constexpr double kDefaultScanSpeedDegPerSec = 8.0; // Below this, counts_per_deg is not a drivetrain - it is an uncalibrated axis // still carrying AxisMap's default of 1.0. // // The floor is physical, not arbitrary: the AS5047P in ABI mode emits 4000 steps // per revolution (COMPONENTS.md section 7), so even an axis driven with NO gear // reduction reads 11.1 counts/deg. Anything under that cannot have come from a // real encoder on this rig. Converting a speed through such a scale yields a // VMAX near zero - an axis that looks configured and never finishes a sweep - // so it is refused rather than commanded. constexpr double kMinPlausibleCountsPerDeg = 10.0; struct AxisSpeedPlan { bool ok = false; long vmax = 0; double requested_deg_s = 0.0; // What `vmax` actually delivers. VMAX is an integer, so this differs from the // request by the quantisation - negligible at production speeds, but it is // what the startup log should report, because it is what the axis does. double actual_deg_s = 0.0; bool clamped = false; std::string error; // why, when !ok }; // `counts_per_deg` may be negative to flip the direction sense (see Geometry.h); // only its magnitude matters to a speed, so the sign is ignored rather than // producing a negative VMAX. AxisSpeedPlan planAxisSpeed(double deg_per_s, double counts_per_deg, double clock_hz = kTmcClockHz); // The inverse, for reporting what a firmware default or an existing literal // actually amounts to on this drivetrain. double vmaxToDegPerSec(long vmax, double counts_per_deg, double clock_hz = kTmcClockHz); } // namespace fgc