Skip to content

Commit 014fd81

Browse files
author
Luca Toniolo
committed
chain-split: fix short-segment lost-time jerk spikes
When a short segment (e.g. 0.030mm arc) completes within a split cycle's remaining time, the leftover time produced zero displacement, causing a 1-sample velocity dip visible as ~1.9M mm/s³ jerk spikes. Chain-split forwards leftover time to the next segment instead of losing it. After tpUpdateCycle detects nexttc completed within the split budget (profile duration <= remain_time), the loop marks it for removal and activates the next-next segment with the leftover time. Multi-pop removal in tpRunCycle ensures all chained segments are dequeued in the same servo cycle, preventing dead-cycle velocity dips. Also removes all investigation debug probes (JERK_DBG, SPLIT_DBG, JUNCTION_DBG, GAP_DBG, RESET_DBG, CHAIN_DBG, PATH_DBG, FWD_V0_DBG, FWD_SKIP_DBG).
1 parent f73f642 commit 014fd81

File tree

1 file changed

+131
-8
lines changed

1 file changed

+131
-8
lines changed

src/emc/tp/tp.c

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4170,7 +4170,6 @@ STATIC int tpUpdateCycle(TP_STRUCT * const tp,
41704170
// This provides time-optimal jerk-limited deceleration
41714171
use_ruckig = 1;
41724172
}
4173-
41744173
if (use_ruckig_stopping) {
41754174
// LEGACY: Cycle-by-cycle jerk-limited stopping (dead code, kept for rollback)
41764175
// Now pause/abort uses the same Ruckig branch path as feed hold for
@@ -4786,7 +4785,12 @@ STATIC int tpCheckEndCondition(TP_STRUCT const * const tp, TC_STRUCT * const tc,
47864785
}
47874786

47884787

4789-
// DEBUG: track v0 correction for next-cycle velocity logging
4788+
// Maximum chain-split depth: how many consecutive short segments can
4789+
// complete within a single split cycle's remaining time and be chained
4790+
// through in one servo cycle. 10 is very conservative — in practice
4791+
// at 1 kHz with ~0.03 mm arcs at 30 mm/s, at most 1-2 iterations.
4792+
#define MAX_CHAIN_DEPTH 10
4793+
47904794
// Previous-cycle commanded velocity for junction_vel clamping.
47914795
// Saved before tpHandleSplitCycle so the crossing-point velocity
47924796
// (from a potentially stale/swapped profile) can be clamped to
@@ -5018,6 +5022,112 @@ STATIC int tpHandleSplitCycle(TP_STRUCT * const tp, TC_STRUCT * const tc,
50185022
int mode = 0;
50195023
tpUpdateCycle(tp, nexttc, next2tc, &mode);
50205024

5025+
// === CHAIN-SPLIT: Forward leftover time through short segments ===
5026+
// When nexttc completes within remain_time (its Ruckig profile duration
5027+
// fits inside the split budget), tpCheckEndCondition inside tpUpdateCycle
5028+
// sets nexttc->splitting=1. Instead of losing the leftover time as zero
5029+
// displacement, chain into the next segment(s) to recover it.
5030+
// This eliminates the 1-sample velocity dip / jerk spike caused by
5031+
// short arcs completing within the split cycle.
5032+
int chain_depth = 0;
5033+
TC_STRUCT *chain_last = nexttc; // last segment in chain (for DIO/status)
5034+
if (GET_TRAJ_PLANNER_TYPE() == 2) {
5035+
double chain_remain = nexttc_remain_time;
5036+
TC_STRUCT *chain_prev = nexttc;
5037+
int chain_queue_idx = queue_dir_step * 2; // next after nexttc
5038+
5039+
while (chain_depth < MAX_CHAIN_DEPTH &&
5040+
chain_prev->splitting &&
5041+
chain_prev->term_cond == TC_TERM_COND_TANGENT &&
5042+
!chain_prev->remove)
5043+
{
5044+
// Check that chain_prev has a valid profile so we can read duration
5045+
int cpv = __atomic_load_n(&chain_prev->shared_9d.profile.valid,
5046+
__ATOMIC_ACQUIRE);
5047+
if (!cpv) break;
5048+
5049+
double chain_dur = chain_prev->shared_9d.profile.duration;
5050+
5051+
// Duration check: if chain_dur > chain_remain, the segment did NOT
5052+
// complete within the current budget — tpCheckEndCondition flagged
5053+
// splitting for the NEXT regular cycle. Don't chain.
5054+
if (chain_dur > chain_remain + 1e-9) break;
5055+
5056+
double chain_leftover = chain_remain - chain_dur;
5057+
if (chain_leftover < 1e-9) break; // no meaningful leftover
5058+
5059+
// chain_prev completed within budget. Its displacement was already
5060+
// committed by tpUpdateCycle. Mark for removal.
5061+
chain_prev->remove = 1;
5062+
5063+
// Get the next segment to chain into
5064+
TC_STRUCT *chain_tc = tcqItem(&tp->queue, chain_queue_idx);
5065+
if (!chain_tc) break;
5066+
5067+
// Must have a valid Ruckig profile to chain
5068+
int ctv = __atomic_load_n(&chain_tc->shared_9d.profile.valid,
5069+
__ATOMIC_ACQUIRE);
5070+
if (!ctv) break;
5071+
5072+
// --- Velocity / acceleration inheritance (same as lines 4958-4963) ---
5073+
chain_tc->currentvel = chain_prev->currentvel;
5074+
double maxacc_chain = tcGetTangentialMaxAccel(chain_tc);
5075+
chain_tc->currentacc = saturate(chain_prev->currentacc, maxacc_chain);
5076+
5077+
// --- Full Ruckig activation (replicates lines 4993-5024) ---
5078+
chain_tc->active = 1;
5079+
chain_tc->on_final_decel = 0;
5080+
chain_tc->blending_next = 0;
5081+
chain_tc->position_base = 0.0;
5082+
5083+
chain_tc->last_profile_generation = __atomic_load_n(
5084+
&chain_tc->shared_9d.profile.generation, __ATOMIC_ACQUIRE);
5085+
5086+
__atomic_store_n(&chain_tc->shared_9d.branch.valid, 0,
5087+
__ATOMIC_RELEASE);
5088+
__atomic_store_n(&chain_tc->shared_9d.branch.taken, 0,
5089+
__ATOMIC_RELEASE);
5090+
5091+
double chain_feed = 1.0;
5092+
if (emcmotStatus) {
5093+
if (chain_tc->canon_motion_type == EMC_MOTION_TYPE_TRAVERSE) {
5094+
chain_feed = emcmotStatus->rapid_scale;
5095+
} else {
5096+
chain_feed = emcmotStatus->feed_scale;
5097+
}
5098+
if (chain_feed < 0.0) chain_feed = 0.0;
5099+
if (chain_feed > 10.0) chain_feed = 10.0;
5100+
}
5101+
chain_tc->shared_9d.canonical_feed_scale = chain_feed;
5102+
chain_tc->shared_9d.requested_feed_scale = chain_feed;
5103+
chain_tc->shared_9d.achieved_exit_vel = 0.0;
5104+
chain_tc->shared_9d.reachability_exit_cap = -1.0;
5105+
5106+
// Pre-advance elapsed_time to leftover so Ruckig samples there
5107+
chain_tc->elapsed_time = chain_leftover;
5108+
chain_tc->cycle_time = tp->cycleTime; // reset for tpUpdateCycle
5109+
5110+
// --- Run tpUpdateCycle on chained segment ---
5111+
TC_STRUCT *chain_next2 = tcqItem(&tp->queue,
5112+
chain_queue_idx + queue_dir_step);
5113+
int chain_mode = 0;
5114+
tpUpdateCycle(tp, chain_tc, chain_next2, &chain_mode);
5115+
5116+
// Post-correct elapsed_time (same pattern as line 5176)
5117+
if (__atomic_load_n(&chain_tc->shared_9d.profile.valid,
5118+
__ATOMIC_ACQUIRE)) {
5119+
chain_tc->elapsed_time = chain_leftover + tp->cycleTime;
5120+
}
5121+
5122+
// Iterate
5123+
chain_remain = chain_leftover;
5124+
chain_prev = chain_tc;
5125+
chain_last = chain_tc;
5126+
chain_queue_idx += queue_dir_step;
5127+
chain_depth++;
5128+
}
5129+
}
5130+
50215131
// Correct profile v0 mismatch at split junction.
50225132
// When feed override changes between profile computation and junction
50235133
// arrival, the profile's v[0] doesn't match the actual junction velocity.
@@ -5062,22 +5172,25 @@ STATIC int tpHandleSplitCycle(TP_STRUCT * const tp, TC_STRUCT * const tc,
50625172
// (because cycle_time was remain_time at sample time), giving elapsed = 2*remain_time.
50635173
// But tpCheckEndCondition inside tpUpdateCycle then overwrote cycle_time to cycleTime.
50645174
// The next regular cycle should sample at remain_time + cycleTime.
5065-
if (GET_TRAJ_PLANNER_TYPE() == 2 &&
5175+
// Skip if chain-split happened — nexttc is marked for removal, and
5176+
// the chained segment's elapsed_time was already corrected in the loop.
5177+
if (chain_depth == 0 &&
5178+
GET_TRAJ_PLANNER_TYPE() == 2 &&
50665179
__atomic_load_n(&nexttc->shared_9d.profile.valid, __ATOMIC_ACQUIRE)) {
50675180
nexttc->elapsed_time = nexttc_remain_time + tp->cycleTime;
50685181
}
50695182

50705183

5071-
// Update status for the split portion
5072-
// FIXME redundant tangent check, refactor to switch
5184+
// Update status for the split portion.
5185+
// Use chain_last (== nexttc if no chain, or last chained segment).
50735186
if (tc->cycle_time > nexttc->cycle_time && tc->term_cond == TC_TERM_COND_TANGENT) {
50745187
//Majority of time spent in current segment
50755188
tpToggleDIOs(tc);
50765189
tpUpdateMovementStatus(tp, tc);
50775190
} else {
5078-
tpToggleDIOs(nexttc);
5191+
tpToggleDIOs(chain_last);
50795192
}
5080-
tpUpdateMovementStatus(tp, nexttc);
5193+
tpUpdateMovementStatus(tp, chain_last);
50815194

50825195
return TP_ERR_OK;
50835196
}
@@ -5330,8 +5443,18 @@ int tpRunCycle(TP_STRUCT * const tp, long period)
53305443

53315444

53325445
// If TC is complete, remove it from the queue.
5446+
// Chain-split may have marked multiple consecutive segments for removal
5447+
// (tc + any chained short segments). Pop all of them in one servo cycle.
5448+
// Without this loop, each remove'd segment would occupy a "dead" cycle
5449+
// with zero displacement, causing the same velocity dip we're fixing.
53335450
if (tc->remove) {
5334-
tpCompleteSegment(tp, tc);
5451+
int pop_count = 0;
5452+
while (pop_count < MAX_CHAIN_DEPTH + 2) {
5453+
TC_STRUCT *front = tcqItem(&tp->queue, 0);
5454+
if (!front || !front->remove) break;
5455+
if (tpCompleteSegment(tp, front) != TP_ERR_OK) break;
5456+
pop_count++;
5457+
}
53355458
}
53365459

53375460
return TP_ERR_OK;

0 commit comments

Comments
 (0)