From 89921f63e2b6d9caebe8e2d2e9daaba6a4015a59 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:56:43 +1000 Subject: [PATCH] tp: keep the G33 sync origin on the spindle index When the axis reached synchronized velocity the position-sync loop moved the sync origin to wherever the axis had got to, folding the lead-in ramp lag into the thread's phase. That lag grows with the square of the spindle speed, so the same thread cut at a different RPM landed at a different Z (issue #3455). Leave the origin at the index and let the tracking loop close the error instead, and warn when the segment is too short for it to do so. The Technical Info block in the G33 docs described a start point worked out from the spindle speed and the acceleration limits, which the planner never did. It now describes what the planner does, and what lead-in the move needs. Measured on a sim lathe, K0.1 in/rev, three G33 passes at 240/960/240 rpm in one program. Z offset from the ideal thread was 0.039/0.405/0.039 mm before and is 0.005/0.020/0.005 mm after, so the shift between the two speeds drops from 0.366 mm to 0.015 mm. The residual is half a servo cycle of travel and is linear in speed. --- docs/src/gcode/g-code.adoc | 21 +++++++++++++-------- src/emc/tp/tp.c | 30 ++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/docs/src/gcode/g-code.adoc b/docs/src/gcode/g-code.adoc index 6253e57afb2..19e519dc480 100644 --- a/docs/src/gcode/g-code.adoc +++ b/docs/src/gcode/g-code.adoc @@ -1073,14 +1073,19 @@ threads. [[gcode:g33-tech-info]] .Technical Info -At the beginning of each G33 pass, LinuxCNC uses the spindle speed and -the machine acceleration limits to calculate how long it will take Z to -accelerate after the index pulse, and determines how many degrees the -spindle will rotate during that time. It then adds that angle to the -index position and computes the Z position using the corrected spindle -angle. That means that Z will reach the correct position just as it -finishes accelerating to the proper speed, and can immediately begin -cutting a good thread. +A G33 move waits for the spindle index, then accelerates from rest to the +synchronized feed. The commanded position follows the spindle angle measured +from that index, so a given thread is cut in the same place whatever the +spindle speed. + +While the axis is still accelerating it falls behind that position, by roughly +the pitch squared times the spindle speed squared over twice the axis +acceleration limit, and it makes the distance up over the rest of the move. +Allow enough lead-in for it to catch up before the cut begins, or the start of +the thread is cut with the wrong lead. LinuxCNC reports 'lead-in too short to +reach sync' when the move ends before the axis has caught up. A small lag +proportional to speed remains for the rest of the move, which is why the +spindle speed must not change while a thread is being cut. .HAL Connections The pin 'spindle.N.at-speed' must be set or driven true for the motion to diff --git a/src/emc/tp/tp.c b/src/emc/tp/tp.c index b193b76fb83..f7d58f50e47 100644 --- a/src/emc/tp/tp.c +++ b/src/emc/tp/tp.c @@ -3567,6 +3567,29 @@ STATIC void tpSyncVelocityMode(TP_STRUCT * const tp, TC_STRUCT * const tc, TC_ST } +/** + * Warn when the segment is too short to absorb the lead-in lag. + * The tracking loop below closes an error e with v = v_spindle + sqrt(e * a), + * which takes 2 * sqrt(e / a) seconds. + */ +STATIC void tpCheckSyncLeadIn(TC_STRUCT const * const tc, double pos_error) +{ + double accel = tcGetTangentialMaxAccel(tc); + if (accel <= 0.0) { + return; + } + double err = fabs(pos_error); + double catchup = tc->currentvel * 2.0 * pmSqrt(err / accel) + err; + double remaining = tc->target - tc->progress; + if (catchup > remaining) { + rtapi_print_msg(RTAPI_MSG_ERR, + "spindle-synchronized move %d: lead-in too short to reach sync, " + "need %f more travel\n", + tc->id, catchup - remaining); + } +} + + /** * Run position mode synchronization. * Updates requested velocity for a trajectory segment to track the spindle's position. @@ -3603,10 +3626,13 @@ STATIC void tpSyncPositionMode(TP_STRUCT * const tp, TC_STRUCT * const tc, target_vel = spindle_vel * tc->uu_per_rev; if(tc->currentvel >= target_vel) { tc_debug_print("Hit accel target in pos sync\n"); - // move target so as to drive pos_error to 0 next cycle - tp->spindle.offset = tp->spindle.revs - tc->progress / tc->uu_per_rev; + // Leave the sync origin on the spindle index. The lag built up + // while ramping to sync speed is a real position error, so hand it + // to the tracking loop below; folding it into the origin instead + // shifts the thread by an amount that grows with spindle speed. tc->sync_accel = 0; tc->target_vel = target_vel; + tpCheckSyncLeadIn(tc, pos_error); } else { tc_debug_print("accelerating in pos_sync\n"); // beginning of move and we are behind: accel as fast as we can