Skip to content

Commit 8f5a2e3

Browse files
author
Luca Toniolo
committed
Fix S-curve lookahead and segment transitions for smooth deceleration
Refactors finishWithSpeedDist() to handle both accel/decel cases properly, adds conservative velocity estimation for lookahead that accounts for worst-case acceleration state, and fixes segment handoff to inherit actual velocity/acceleration instead of assuming instant changes.
1 parent c2a4427 commit 8f5a2e3

5 files changed

Lines changed: 176 additions & 70 deletions

File tree

configs/sim/axis/axis_9axis_scurve.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ HALFILE = axis_9axis_scurve.hal
5454
# PLANNER_TYPE: Select the acceleration profile shape
5555
# 0 = Trapezoidal (traditional - constant acceleration)
5656
# 1 = S-curve (smoother - limited jerk/derivative of acceleration)
57-
# 2 = S-curve (smoother - but sharp in corners - to be deprecated)
5857
# Default: 0 (trapezoidal)
5958
#
6059
# NOTE: This parameter can be changed at RUNTIME via HAL pin:

configs/sim/axis/axis_mm_scurve.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ HALFILE = axis_mm_scurve.hal
5454
# PLANNER_TYPE: Select the acceleration profile shape
5555
# 0 = Trapezoidal (traditional - constant acceleration)
5656
# 1 = S-curve (smoother - limited jerk/derivative of acceleration)
57-
# 2 = S-curve (smoother - but sharp in corners - to be deprecated)
5857
# Default: 0 (trapezoidal)
5958
#
6059
# NOTE: This parameter can be changed at RUNTIME via HAL pin:

src/emc/tp/sp_scurve.c

Lines changed: 123 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -983,78 +983,105 @@ double stoppingDist(double v, double a, double maxA, double maxJ/*, int* phase*/
983983
}
984984

985985
double finishWithSpeedDist(double v, double ve, double a, double maxA, double maxJ/*, int* phase*/) {
986-
// Already stopped
987-
//*phase = 0;
988-
if (fabs(v) < 0.0001) return 0;
989-
// Handle negative velocity
986+
// Handle negative velocity: transform to positive domain
990987
if (v < 0) {
991-
v = -v;
992-
a = -a;
993-
ve = -ve;
988+
v = -v;
989+
a = -a;
990+
ve = -ve;
991+
}
992+
993+
// Velocity difference too small, no accel/decel needed
994+
if (fabs(v - ve) < 0.0001 && fabs(a) < 0.0001) {
995+
return 0;
994996
}
995997

996998
double d = 0;
997999

998-
//if(ve > v)
999-
// return 0;
1000-
if(ve > v){
1001-
1000+
// ========== Acceleration case (ve > v) ==========
1001+
if (ve > v) {
1002+
// Phase 1: If a < 0 (decelerating), first bring a to 0
1003+
if (a < 0) {
1004+
double t = -a / maxJ;
1005+
d += sc_distance(t, v, a, maxJ);
1006+
v += velocity(t, a, maxJ);
1007+
a = 0;
1008+
}
1009+
1010+
// Compute required max acceleration
1011+
// Amax^2 = (ve - v) * maxJ + 0.5 * a * a
1012+
double sqrt_arg = (ve - v) * maxJ + 0.5 * a * a;
1013+
if (sqrt_arg < 0) sqrt_arg = 0;
1014+
double maxAccel = sqrt(sqrt_arg);
1015+
if (maxAccel > maxA) maxAccel = maxA;
1016+
1017+
// Phase 2: Increase a from current value to maxAccel
1018+
if (maxAccel > a) {
1019+
double t = (maxAccel - a) / maxJ;
1020+
d += sc_distance(t, v, a, maxJ);
1021+
v += velocity(t, a, maxJ);
1022+
a = maxAccel;
1023+
}
1024+
1025+
// Compute velocity when entering decel phase
1026+
double deltaV = ve - 0.5 * a * a / maxJ;
1027+
1028+
// Phase 3: Constant acceleration phase (if needed)
1029+
if (v < deltaV && a > 0.0001) {
1030+
double t = (deltaV - v) / a;
1031+
d += sc_distance(t, v, a, 0);
1032+
v += velocity(t, a, 0);
1033+
}
1034+
1035+
// Phase 4: Decrease a from maxAccel to 0, velocity reaches ve
1036+
if (a > 0.0001) {
1037+
double t = a / maxJ;
1038+
d += sc_distance(t, v, a, -maxJ);
1039+
}
1040+
1041+
return d;
10021042
}
10031043

1004-
//double vs = v;
1005-
// Compute distance and velocity change to accel = 0
1006-
if (0 < a) {
1007-
// Compute distance to decrease accel to zero
1008-
// distance(double t, double v, double a, double j)
1009-
// distance => v * t + 1/2 * a * t^2 + 1/6 * j * t^3
1010-
// velocity => a * t + 1/2 * j * t^2
1011-
double t = a / maxJ;
1012-
d += sc_distance(t, v, a, -maxJ);
1013-
v += velocity(t, a, -maxJ);
1014-
a = 0;
1015-
//if(*phase == 0) *phase = 3;
1044+
// ========== Deceleration case (v > ve) ==========
1045+
1046+
// Phase 1: If a > 0 (accelerating), first bring a to 0
1047+
if (a > 0) {
1048+
double t = a / maxJ;
1049+
d += sc_distance(t, v, a, -maxJ);
1050+
v += velocity(t, a, -maxJ);
1051+
a = 0;
10161052
}
10171053

1018-
// Compute max deccel
1019-
// PT = P0 + V0 * T + 0.5 * A0 * T^2 + J * T^3 / 6
1020-
// VT = V0 + A0 * T + J * T^2 /2
1021-
// AT = A0 + J * T
1022-
//
1023-
// Amax^2 = (Vs - Ve) * maxJ + 0.5 * a * a
1024-
double maxDeccel = -sqrt((v - ve) * maxJ + 0.5 * a * a);
1054+
// Compute required max deceleration
1055+
// Amax^2 = (v - ve) * maxJ + 0.5 * a * a
1056+
double sqrt_arg = (v - ve) * maxJ + 0.5 * a * a;
1057+
if (sqrt_arg < 0) sqrt_arg = 0;
1058+
double maxDeccel = -sqrt(sqrt_arg);
10251059
if (maxDeccel < -maxA) maxDeccel = -maxA;
1026-
//double maxDeccel = -fabs(maxA);
10271060

1028-
// Compute distance and velocity change to max deccel
1029-
// a * t + 1/2 * j * t^2
1061+
// Phase 2: Decrease a from current value to maxDeccel
10301062
if (maxDeccel < a) {
10311063
double t = (a - maxDeccel) / maxJ;
10321064
d += sc_distance(t, v, a, -maxJ);
10331065
v += velocity(t, a, -maxJ);
10341066
a = maxDeccel;
10351067
}
10361068

1037-
// Compute velocity change over remaining accel
1069+
// Compute velocity when entering decel end phase
10381070
// VT = Ve + Amax^2 / (2 * J)
10391071
double deltaV = ve + 0.5 * a * a / maxJ;
10401072

1041-
// Compute constant deccel period
1042-
// P = v * t + 1/2 * a * t^2 + 1/6 * j * t^3
1043-
// VT = V0 + A0 * T + J * T^2 /2
1044-
if (deltaV < v) {
1045-
// distance => v * t + 1/2 * a * t^2 + 1/6 * j * t^3
1046-
// velocity => a * t + 1/2 * j * t^2
1047-
double t = (v - deltaV) / -a;
1073+
// Phase 3: Constant deceleration phase (if needed)
1074+
if (deltaV < v && a < -0.0001) {
1075+
double t = (v - deltaV) / (-a);
10481076
d += sc_distance(t, v, a, 0);
10491077
v += velocity(t, a, 0);
1050-
//if(*phase == 0) *phase = 6;
10511078
}
10521079

1053-
// Compute distance to ve vel
1054-
// distance => v * t + 1/2 * a * t^2 + 1/6 * j * t^3
1055-
// A = J * T
1056-
d += sc_distance(-a / maxJ, v, a, maxJ);
1057-
//if(*phase == 0) *phase = 7;
1080+
// Phase 4: Increase a from maxDeccel to 0, velocity reaches ve
1081+
if (a < -0.0001) {
1082+
double t = (-a) / maxJ;
1083+
d += sc_distance(t, v, a, maxJ);
1084+
}
10581085

10591086
return d;
10601087
}
@@ -1351,3 +1378,52 @@ double calcSCurveSpeedWithT(double amax, double jerk, double T) {
13511378
// Use fma to optimize multiply-add operations, improving numerical stability
13521379
return fma(jerk * T1, T1 + T2, 0.0);
13531380
}
1381+
1382+
/**
1383+
* Conservative S-curve reachable velocity calculation (for lookahead optimization)
1384+
*
1385+
* Unlike findSCurveVSpeedWithEndSpeed which assumes decel starts from a=0,
1386+
* this function considers worst case: decel starting from a=maxA state.
1387+
*
1388+
* This ensures lookahead-computed velocities can smoothly decel to target
1389+
* regardless of current acc state, avoiding acc discontinuities.
1390+
*
1391+
* @param distance Available decel distance
1392+
* @param Ve Target end velocity
1393+
* @param maxA Maximum acceleration
1394+
* @param maxJ Maximum jerk
1395+
* @param req_v [output] Computed conservative reachable velocity
1396+
* @return 1 success, -1 failure
1397+
*/
1398+
int findSCurveVSpeedConservative(double distance, double Ve, double maxA, double maxJ, double* req_v) {
1399+
if (distance <= 0 || maxA <= 0 || maxJ <= 0) {
1400+
*req_v = Ve;
1401+
return -1;
1402+
}
1403+
1404+
// Extra distance needed to bring a from maxA to 0 (velocity still increasing during this)
1405+
// t = maxA / maxJ, v_increase = 0.5 * maxA^2 / maxJ
1406+
double v_increase = 0.5 * maxA * maxA / maxJ;
1407+
double d_extra = maxA * maxA / (2.0 * maxJ);
1408+
1409+
// Effective decel distance = total - extra
1410+
double effective_distance = distance - d_extra;
1411+
1412+
if (effective_distance <= 0) {
1413+
*req_v = Ve;
1414+
return -1;
1415+
}
1416+
1417+
double vs_from_zero;
1418+
int res = findSCurveVSpeedWithEndSpeed(effective_distance, Ve, maxA, maxJ, &vs_from_zero);
1419+
1420+
if (res != 1) {
1421+
*req_v = Ve;
1422+
return -1;
1423+
}
1424+
1425+
// Conservative estimate: subtract velocity increase during acc ramp-down
1426+
*req_v = fmax(vs_from_zero - v_increase * 0.5, Ve);
1427+
1428+
return 1;
1429+
}

src/emc/tp/sp_scurve.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ int getNext( simple_tp_t *tp, double Vs, double Ve, double period);
4343
double getNextPoint(simple_tp_t *tp, int n, double T, double* req_v, double* req_a);
4444
int findSCurveVSpeed(double distence,/* double maxV, */double maxA, double maxJ, double *req_v);
4545
int findSCurveVSpeedWithEndSpeed(double distence, double Ve, double maxA, double maxJ, double* req_v);
46+
int findSCurveVSpeedConservative(double distance, double Ve, double maxA, double maxJ, double* req_v);
4647
double calcDecelerateTimes(double v, double amax, double jerk, double* t1, double* t2);
4748
double calcSCurveSpeedWithT(double amax, double jerk, double T);
4849

src/emc/tp/tp.c

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,7 +1765,7 @@ STATIC int tpComputeOptimalVelocity(TP_STRUCT const * const tp, TC_STRUCT * cons
17651765

17661766
// Find the reachable velocity of tc, moving backwards in time
17671767
double vs_back = pmSqrt(pmSq(tc->finalvel) + 2.0 * acc_this * tc->target);
1768-
if(GET_TRAJ_PLANNER_TYPE() == 1 || GET_TRAJ_PLANNER_TYPE() == 2){
1768+
if(GET_TRAJ_PLANNER_TYPE() == 1){
17691769
double vs_back2;
17701770
if(findSCurveVSpeedWithEndSpeed(tc->target * 2.0 , tc->finalvel, acc_this, emcmotStatus->jerk, &vs_back2) == 1)
17711771
vs_back = vs_back2;
@@ -1890,7 +1890,7 @@ STATIC int tpRunOptimization(TP_STRUCT * const tp) {
18901890
tp_debug_print("Segment %d, type %d not finalized, continuing\n",tc->id,tc->motion_type);
18911891
// use worst-case final velocity that allows for up to 1/2 of a segment to be consumed.
18921892

1893-
if(GET_TRAJ_PLANNER_TYPE() == 1 || GET_TRAJ_PLANNER_TYPE() == 2)
1893+
if(GET_TRAJ_PLANNER_TYPE() == 1)
18941894
prev1_tc->finalvel = fmin(prev1_tc->maxvel, tpCalculateOptimizationSCurveInitialVel(tp,tc));
18951895
else
18961896
prev1_tc->finalvel = fmin(prev1_tc->maxvel, tpCalculateOptimizationInitialVel(tp,tc));
@@ -2506,7 +2506,7 @@ STATIC double estimateParabolicBlendPerformance(
25062506
double target_vel_next = tpGetMaxTargetVel(tp, nexttc);
25072507

25082508
double v_net = 0.0;
2509-
if(GET_TRAJ_PLANNER_TYPE() == 1 || GET_TRAJ_PLANNER_TYPE() == 2)
2509+
if(GET_TRAJ_PLANNER_TYPE() == 1)
25102510
tpComputeBlendSCurveVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, &v_net);
25112511
else
25122512
tpComputeBlendVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, &v_net);
@@ -2715,8 +2715,7 @@ STATIC int tcUpdateDistFromSCurveAccel(TC_STRUCT *const tc, double acc, double j
27152715
{
27162716
// If the resulting velocity is less than zero, than we're done. This
27172717
// causes a small overshoot, but in practice it is very small.
2718-
//double v_next = vel_desired;//velocity(tc->cycle_time, acc, jerk); //tc->currentvel + acc * tc->cycle_time; double velocity(double t, double a, double j);
2719-
double v_next = vel_desired;//velocity(tc->cycle_time, acc, jerk);
2718+
double v_next = vel_desired;
27202719

27212720
// update position in this tc using trapezoidal integration
27222721
// Note that progress can be greater than the target after this step.
@@ -2754,11 +2753,11 @@ STATIC int tcUpdateDistFromSCurveAccel(TC_STRUCT *const tc, double acc, double j
27542753
tc->progress = bisaturate(tc->progress, tcGetTarget(tc, TC_DIR_FORWARD), tcGetTarget(tc, TC_DIR_REVERSE));
27552754
}
27562755
}
2757-
tc->currentvel = v_next;// v_next;
2756+
tc->currentvel = v_next;
27582757
tc->currentacc = acc;
27592758

27602759
// Check if we can make the desired velocity
2761-
tc->on_final_decel = dec;//(fabs(vel_desired - tc->currentvel) < TP_VEL_EPSILON) && (acc < 0.0); //dec && (acc < 0.0);
2760+
tc->on_final_decel = dec;
27622761

27632762
return TP_ERR_OK;
27642763
}
@@ -2816,25 +2815,45 @@ int tpCalculateSCurveAccel(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_
28162815

28172816
nextSpeed(tc->currentvel, tc->currentacc, tc->cycle_time, tc_target_vel, maxaccel, maxjerk, &req_v, &req_a, &req_j);
28182817
double dlen1 = finishWithSpeedDist(tc->currentvel, tc_finalvel, tc->currentacc, maxaccel, maxjerk);
2819-
double dlen2 = finishWithSpeedDist(saturate(req_v, tc_target_vel), tc_finalvel, saturate(req_a, maxaccel), maxaccel, maxjerk);
2818+
2819+
// When velocity exceeds target, correct acceleration
2820+
// Since velocity is limited to target, acceleration should be 0 (maintain speed) or decel
2821+
double v_for_dlen2 = req_v;
2822+
double a_for_dlen2 = req_a;
2823+
if (req_v > tc_target_vel) {
2824+
v_for_dlen2 = tc_target_vel;
2825+
a_for_dlen2 = 0;
2826+
}
2827+
double dlen2 = finishWithSpeedDist(v_for_dlen2, tc_finalvel, a_for_dlen2, maxaccel, maxjerk);
28202828
double moveL = sc_distance(tc->cycle_time, tc->currentvel, tc->currentacc, req_j);
2821-
double error = fabs(dx) - dlen1; //误差
2822-
*pos_error = error;
28232829

2830+
// margin = remaining distance - decel distance
2831+
// margin > 0: can continue accelerating
2832+
// margin <= 0: must decelerate
2833+
double margin = dx - dlen1;
2834+
*pos_error = margin;
28242835

2825-
if(tc->currentvel < 1e-6 && dx > TP_POS_EPSILON && dx < 1e-4){
2826-
//nextSpeed(tc->currentvel, tc->currentacc, tc->cycle_time, tc_target_vel, maxaccel, maxjerk, &req_v, &req_a, &req_j);
2836+
// Handle special case: position overshoot or reached endpoint
2837+
if (dx <= TP_POS_EPSILON) {
2838+
nextSpeed(tc->currentvel, tc->currentacc, tc->cycle_time, tc_finalvel, maxaccel, maxjerk, &req_v, &req_a, &req_j);
2839+
res = 1;
2840+
}
2841+
else if(tc->currentvel < 1e-6 && dx > TP_POS_EPSILON && dx < 1e-4){
2842+
// Very low velocity and close to target
28272843
res = 1;
28282844
}else{
2829-
//fabs(fabs(pos_err) - decLen) <= 0.000001 || fabs(pos_err) - moveL * dir <= decLen2 || fabs(pos_err) <= decLen
2830-
if (fabs(error) <= 0.0001 || dx - moveL <= dlen2 || dx < dlen1){
2845+
// Decel conditions:
2846+
// 1. margin <= 0: decel distance >= remaining distance
2847+
// 2. dx - moveL <= dlen2: if we continue accel, next cycle remaining <= decel dist
2848+
int need_decel = (margin <= TP_POS_EPSILON) || (dx - moveL <= dlen2);
2849+
2850+
if (need_decel){
28312851
nextSpeed(tc->currentvel, tc->currentacc, tc->cycle_time, tc_finalvel, maxaccel, maxjerk, &req_v, &req_a, &req_j);
28322852
}else{
28332853
if(tc_finalvel > TP_VEL_EPSILON || tc_target_vel > TP_VEL_EPSILON)
28342854
res = 0;
28352855
}
28362856
}
2837-
//}
28382857

28392858
maxnewvel = req_v;
28402859
maxnewacc = req_a;
@@ -2848,8 +2867,7 @@ int tpCalculateSCurveAccel(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_
28482867
maxnewaccel = saturate(maxnewaccel, maxaccel);
28492868
*acc = maxnewaccel;
28502869
*vel_desired = maxnewvel;
2851-
*jerk =maxnewjerk;//(maxnewaccel - tc->currentacc) / dt;
2852-
//*vel_desired = tc->currentvel + velocity(dt, *acc, *jerk);
2870+
*jerk = maxnewjerk;
28532871

28542872
return res;
28552873
}
@@ -3470,7 +3488,7 @@ STATIC int tpUpdateCycle(TP_STRUCT * const tp,
34703488

34713489
if(mode == NULL) planner_type = 0;
34723490

3473-
if(planner_type != 1 && planner_type != 2){
3491+
if(planner_type != 1){
34743492
// If the slowdown is not too great, use velocity ramping instead of trapezoidal velocity
34753493
// Also, don't ramp up for parabolic blends
34763494
if (tc->accel_mode && tc->term_cond == TC_TERM_COND_TANGENT) {
@@ -3739,8 +3757,21 @@ STATIC int tpHandleSplitCycle(TP_STRUCT * const tp, TC_STRUCT * const tc,
37393757
switch (tc->term_cond) {
37403758
case TC_TERM_COND_TANGENT:
37413759
nexttc->cycle_time = tp->cycleTime - tc->cycle_time;
3742-
nexttc->currentvel = tc->term_vel;
3743-
tp_debug_print("Doing tangent split\n");
3760+
// In S-curve mode, use actual current velocity instead of expected term_vel
3761+
// S-curve can't change velocity instantly, term_vel is just desired value
3762+
if (GET_TRAJ_PLANNER_TYPE() == 1) {
3763+
nexttc->currentvel = tc->currentvel;
3764+
// Inherit acceleration, but limit to nexttc's allowed range
3765+
// Important for line-to-arc transitions where arc has lower tangential accel
3766+
double maxacc_next = tcGetTangentialMaxAccel(nexttc);
3767+
nexttc->currentacc = saturate(tc->currentacc, maxacc_next);
3768+
tp_debug_print("Doing tangent split (S-curve): vel=%f, acc=%f (limited by %f)\n",
3769+
nexttc->currentvel, nexttc->currentacc, maxacc_next);
3770+
} else {
3771+
// Trapezoidal: can use term_vel (assumes instant velocity change)
3772+
nexttc->currentvel = tc->term_vel;
3773+
tp_debug_print("Doing tangent split (trapezoidal): vel=%f\n", nexttc->currentvel);
3774+
}
37443775
break;
37453776
case TC_TERM_COND_PARABOLIC:
37463777
break;
@@ -3799,7 +3830,7 @@ STATIC int tpHandleRegularCycle(TP_STRUCT * const tp,
37993830
double target_vel_this = tpGetRealTargetVel(tp, tc);
38003831
double target_vel_next = tpGetRealTargetVel(tp, nexttc);
38013832

3802-
if(mode != -5 && (GET_TRAJ_PLANNER_TYPE() == 1 || GET_TRAJ_PLANNER_TYPE() == 2))
3833+
if(mode != -5 && GET_TRAJ_PLANNER_TYPE() == 1)
38033834
tpComputeBlendSCurveVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, NULL);
38043835
else
38053836
tpComputeBlendVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, NULL);

0 commit comments

Comments
 (0)