Skip to content

Commit 1e0a9dc

Browse files
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 fc40c9f commit 1e0a9dc

1 file changed

Lines changed: 131 additions & 8 deletions

File tree

src/emc/tp/tp.c

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

47874786

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

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

50695182

5070-
// Update status for the split portion
5071-
// FIXME redundant tangent check, refactor to switch
5183+
// Update status for the split portion.
5184+
// Use chain_last (== nexttc if no chain, or last chained segment).
50725185
if (tc->cycle_time > nexttc->cycle_time && tc->term_cond == TC_TERM_COND_TANGENT) {
50735186
//Majority of time spent in current segment
50745187
tpToggleDIOs(tc);
50755188
tpUpdateMovementStatus(tp, tc);
50765189
} else {
5077-
tpToggleDIOs(nexttc);
5190+
tpToggleDIOs(chain_last);
50785191
}
5079-
tpUpdateMovementStatus(tp, nexttc);
5192+
tpUpdateMovementStatus(tp, chain_last);
50805193

50815194
return TP_ERR_OK;
50825195
}
@@ -5329,8 +5442,18 @@ int tpRunCycle(TP_STRUCT * const tp, long period)
53295442

53305443

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

53365459
return TP_ERR_OK;

0 commit comments

Comments
 (0)