Skip to content

Commit 0027f3c

Browse files
author
Luca Toniolo
committed
Unify arc jerk limiting for S-curve planner
- Rename tcUpdateCircleAccRatio() to tcUpdateArcLimits() and extend to handle both TC_CIRCULAR and TC_SPHERICAL arcs - Add comprehensive jerk-based velocity limiting for S-curve planner (planner_type 1) with three constraints: * Steady-state rotational jerk (v³/R²) with entry/exit transition budget * Normal jerk from tangential acceleration coupling (3·v·a_t/R) * Entry/exit transition jerk at arc boundaries - Apply jerk constraints to blend arcs in blendComputeParameters() - Remove obsolete pmCircleActualMaxVel() function and PmCircleLimits struct - Consolidate all arc limit calculations (acceleration and jerk) in unified tcUpdateArcLimits() - Add debug output for jerk limiting analysis This ensures consistent jerk limiting for both programmed arcs (G2/G3) and blend arcs at segment corners.
1 parent c4f6828 commit 0027f3c

5 files changed

Lines changed: 149 additions & 60 deletions

File tree

src/emc/tp/blendmath.c

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ int blendComputeParameters(BlendParameters * const param)
11561156

11571157
// Find maximum velocity allowed by accel and radius
11581158
double v_normal;
1159-
1159+
11601160
if(GET_TRAJ_PLANNER_TYPE() == 1){
11611161
v_normal = findSCurveVPeak(param->a_n_max, emcmotStatus->jerk, R_geom);
11621162
}else{
@@ -1179,7 +1179,38 @@ int blendComputeParameters(BlendParameters * const param)
11791179
double s_blend = t_blend * param->v_plan;
11801180
double R_blend = fmin(s_blend / param->phi, R_geom); //Clamp by limiting radius
11811181

1182-
param->R_plan = fmax(pmSq(param->v_plan) / param->a_n_max, R_blend);
1182+
// Calculate minimum radius needed to keep jerk within limits at v_plan
1183+
// For circular motion: j = v³/R² (worst case at corner transitions)
1184+
// Therefore: R_min = v^(3/2) / sqrt(j_max)
1185+
double R_jerk_min = 0.0;
1186+
if (GET_TRAJ_PLANNER_TYPE() == 1 && emcmotStatus->jerk > TP_POS_EPSILON) {
1187+
// R_min = v^(3/2) / sqrt(j)
1188+
double v_32 = pmSqrt(param->v_plan) * param->v_plan; // v^(3/2)
1189+
double j_sqrt = pmSqrt(emcmotStatus->jerk);
1190+
R_jerk_min = v_32 / j_sqrt;
1191+
1192+
tp_debug_print("R_jerk_min = %f (for v=%f, j=%f)\n",
1193+
R_jerk_min, param->v_plan, emcmotStatus->jerk);
1194+
}
1195+
1196+
// Calculate radius from acceleration constraint
1197+
double R_accel = pmSq(param->v_plan) / param->a_n_max;
1198+
1199+
// Apply jerk constraint - use the larger of R_blend and R_jerk_min
1200+
double R_min = fmax(R_blend, R_jerk_min);
1201+
1202+
// Final radius must satisfy both acceleration and minimum (blend/jerk) constraints
1203+
param->R_plan = fmax(R_accel, R_min);
1204+
1205+
// Calculate arc length
1206+
param->s_arc = param->R_plan * param->phi;
1207+
1208+
tp_debug_print("R_accel=%f, R_blend=%f, R_jerk_min=%f, R_plan=%f\n",
1209+
R_accel, R_blend, R_jerk_min, param->R_plan);
1210+
1211+
// Note: Velocity jerk limiting for blend arcs is now handled uniformly
1212+
// in tcUpdateArcLimits() during segment finalization, along with TC_CIRCULAR arcs.
1213+
11831214
param->d_plan = param->R_plan / tan(param->theta);
11841215

11851216
tp_debug_print("v_plan = %f\n", param->v_plan);
@@ -1662,43 +1693,6 @@ double pmCartAbsMax(PmCartesian const * const v)
16621693
}
16631694

16641695

1665-
PmCircleLimits pmCircleActualMaxVel(PmCircle const * circle,
1666-
double v_max,
1667-
double a_max)
1668-
{
1669-
double a_n_max_cutoff = BLEND_ACC_RATIO_NORMAL * a_max;
1670-
1671-
double eff_radius = pmCircleEffectiveMinRadius(circle);
1672-
// Find the acceleration necessary to reach the maximum velocity
1673-
double a_n_vmax = pmSq(v_max) / fmax(eff_radius, DOUBLE_FUZZ);
1674-
// Find the maximum velocity that still obeys our desired tangential / total acceleration ratio
1675-
double v_max_cutoff = pmSqrt(a_n_max_cutoff * eff_radius);
1676-
1677-
double v_max_actual = v_max;
1678-
double acc_ratio_tan = BLEND_ACC_RATIO_TANGENTIAL;
1679-
1680-
if (a_n_vmax > a_n_max_cutoff) {
1681-
v_max_actual = v_max_cutoff;
1682-
} else {
1683-
acc_ratio_tan = pmSqrt(1.0 - pmSq(a_n_vmax / a_max));
1684-
}
1685-
1686-
tp_debug_json_start(pmCircleActualMaxVel);
1687-
tp_debug_json_double(eff_radius);
1688-
tp_debug_json_double(v_max);
1689-
tp_debug_json_double(v_max_cutoff);
1690-
tp_debug_json_double(a_n_max_cutoff);
1691-
tp_debug_json_double(a_n_vmax);
1692-
tp_debug_json_double(acc_ratio_tan);
1693-
tp_debug_json_end();
1694-
1695-
PmCircleLimits limits = {
1696-
v_max_actual,
1697-
acc_ratio_tan
1698-
};
1699-
1700-
return limits;
1701-
}
17021696

17031697

17041698
/** @section spiralfuncs Functions to approximate spiral arc length */

src/emc/tp/blendmath.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,6 @@ int blendPoints3Print(BlendPoints3 const * const points);
243243

244244
double pmCartAbsMax(PmCartesian const * const v);
245245

246-
typedef struct {
247-
double v_max;
248-
double acc_ratio;
249-
} PmCircleLimits;
250-
251-
PmCircleLimits pmCircleActualMaxVel(const PmCircle *circle,
252-
double v_max_nominal,
253-
double a_max_nominal);
254-
255246
int findSpiralArcLengthFit(PmCircle const * const circle,
256247
SpiralArcLengthFit * const fit);
257248
int pmCircleAngleFromProgress(PmCircle const * const circle,

src/emc/tp/tc.c

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,18 @@
2323
#include "tp_types.h"
2424
#include "spherical_arc.h"
2525
#include "motion_types.h"
26+
#include "motion.h"
2627

2728
//Debug output
2829
#include "tp_debug.h"
2930

31+
// For jerk-limited arc velocity (planner_type 1)
32+
extern emcmot_status_t *emcmotStatus;
33+
34+
#ifndef GET_TRAJ_PLANNER_TYPE
35+
#define GET_TRAJ_PLANNER_TYPE() (emcmotStatus->planner_type)
36+
#endif
37+
3038

3139
double tcGetMaxTargetVel(TC_STRUCT const * const tc,
3240
double max_scale)
@@ -748,18 +756,111 @@ double pmCircle9Target(PmCircle9 const * const circ9)
748756
return helical_length;
749757
}
750758

751-
int tcUpdateCircleAccRatio(TC_STRUCT * tc)
759+
/**
760+
* Apply acceleration and jerk limits to circular/spherical arc segments.
761+
*
762+
* For any arc (TC_CIRCULAR or TC_SPHERICAL), this function:
763+
* 1. Limits velocity based on centripetal acceleration budget
764+
* 2. For planner_type 1 (S-curve), applies three jerk constraints:
765+
* - Steady-state rotational jerk: v³/R²
766+
* - Normal jerk from tangential acceleration coupling: 3·v·a_t/R
767+
* - Entry/exit transition jerk at arc boundaries
768+
* 3. Calculates the tangential acceleration ratio for the arc
769+
*
770+
* This unified approach ensures consistent jerk limiting for both
771+
* programmed arcs (G2/G3) and blend arcs at segment corners.
772+
*/
773+
int tcUpdateArcLimits(TC_STRUCT * tc)
752774
{
753-
if (tc->motion_type == TC_CIRCULAR) {
754-
PmCircleLimits limits = pmCircleActualMaxVel(&tc->coords.circle.xyz,
755-
tc->maxvel,
756-
tcGetOverallMaxAccel(tc));
757-
tc->maxvel = limits.v_max;
758-
tc->acc_ratio_tan = limits.acc_ratio;
759-
return 0;
775+
double radius, angle;
776+
777+
// Extract radius and angle based on motion type
778+
switch (tc->motion_type) {
779+
case TC_CIRCULAR:
780+
radius = pmCircleEffectiveMinRadius(&tc->coords.circle.xyz);
781+
angle = tc->coords.circle.xyz.angle;
782+
break;
783+
case TC_SPHERICAL:
784+
radius = tc->coords.arc.xyz.radius;
785+
angle = tc->coords.arc.xyz.angle;
786+
break;
787+
default:
788+
return 1; // Not an arc, nothing to do
789+
}
790+
791+
if (radius < DOUBLE_FUZZ || angle < TP_ANGLE_EPSILON) {
792+
return 1; // Degenerate arc
760793
}
761-
// TODO handle blend arc here too?
762-
return 1; //nothing to do, but not an error
794+
795+
double a_max = tcGetOverallMaxAccel(tc);
796+
double a_n_max_cutoff = BLEND_ACC_RATIO_NORMAL * a_max;
797+
798+
// Find the acceleration necessary to reach the maximum velocity
799+
double a_n_vmax = pmSq(tc->maxvel) / radius;
800+
801+
// Find the maximum velocity that still obeys our desired normal/total acceleration ratio
802+
double v_max_cutoff = pmSqrt(a_n_max_cutoff * radius);
803+
804+
double v_max_actual = tc->maxvel;
805+
double acc_ratio_tan = BLEND_ACC_RATIO_TANGENTIAL;
806+
807+
if (a_n_vmax > a_n_max_cutoff) {
808+
v_max_actual = v_max_cutoff;
809+
} else {
810+
acc_ratio_tan = pmSqrt(1.0 - pmSq(a_n_vmax / a_max));
811+
}
812+
813+
// Jerk-based velocity limiting for S-curve planner (planner_type 1)
814+
if (GET_TRAJ_PLANNER_TYPE() == 1 && emcmotStatus->jerk > TP_POS_EPSILON &&
815+
tc->cycle_time > TP_TIME_EPSILON) {
816+
817+
double jerk = emcmotStatus->jerk;
818+
double R_sq = pmSq(radius);
819+
820+
// Constraint 1: Steady-state rotational jerk + entry/exit transitions
821+
// The jerk budget is shared between steady-state (v³/R²) and transitions.
822+
// Solving: v³ ≤ R² × j × φ / (2 + φ)
823+
// The (2 + φ) term: 2 for two transitions, φ for steady-state budget
824+
double v_max_jerk_steady = cbrt(R_sq * jerk * angle / (2.0 + angle));
825+
826+
// Constraint 2: Normal jerk from tangential acceleration coupling
827+
// During S-curve ramps on arc: j_n = 3·v·a_t/R
828+
// Using BLEND_ACC_RATIO_TANGENTIAL as max tangential accel ratio
829+
double a_t_max = BLEND_ACC_RATIO_TANGENTIAL * a_max;
830+
double v_max_jerk_tan = jerk * radius / (3.0 * a_t_max);
831+
832+
// Constraint 3: Entry/exit transition jerk (centripetal accel ramp)
833+
// At line-arc boundary, centripetal accel changes from 0 to v²/R
834+
// j_entry = (v²/R) / cycle_time ≤ j_max
835+
double v_max_jerk_entry = pmSqrt(jerk * radius * tc->cycle_time);
836+
837+
double v_max_jerk = fmin(fmin(v_max_jerk_steady, v_max_jerk_tan), v_max_jerk_entry);
838+
839+
tp_debug_print("tcUpdateArcLimits: type=%d R=%f phi=%f j=%f\n",
840+
tc->motion_type, radius, angle, jerk);
841+
tp_debug_print(" v_jerk: steady=%f tan=%f entry=%f => min=%f\n",
842+
v_max_jerk_steady, v_max_jerk_tan, v_max_jerk_entry, v_max_jerk);
843+
844+
if (v_max_jerk < v_max_actual) {
845+
tp_debug_print(" Limiting v_max from %f to %f for jerk\n",
846+
v_max_actual, v_max_jerk);
847+
v_max_actual = v_max_jerk;
848+
849+
// Recalculate acc_ratio_tan for jerk-limited velocity
850+
double a_n_at_jerk_vel = pmSq(v_max_actual) / radius;
851+
if (a_n_at_jerk_vel < a_max) {
852+
acc_ratio_tan = pmSqrt(1.0 - pmSq(a_n_at_jerk_vel / a_max));
853+
}
854+
}
855+
}
856+
857+
tc->maxvel = v_max_actual;
858+
tc->acc_ratio_tan = acc_ratio_tan;
859+
860+
tp_debug_print("tcUpdateArcLimits: final v_max=%f acc_ratio_tan=%f\n",
861+
tc->maxvel, tc->acc_ratio_tan);
862+
863+
return 0;
763864
}
764865

765866
/**
@@ -785,7 +886,7 @@ int tcFinalizeLength(TC_STRUCT * const tc)
785886

786887
tcClampVelocityByLength(tc);
787888

788-
tcUpdateCircleAccRatio(tc);
889+
tcUpdateArcLimits(tc);
789890

790891
tc->finalized = 1;
791892
return TP_ERR_OK;

src/emc/tp/tc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ int tcSetupMotion(TC_STRUCT * const tc,
105105

106106
int tcSetupState(TC_STRUCT * const tc, TP_STRUCT const * const tp);
107107

108-
int tcUpdateCircleAccRatio(TC_STRUCT * tc);
108+
int tcUpdateArcLimits(TC_STRUCT * tc);
109109

110110
int tcFinalizeLength(TC_STRUCT * const tc);
111111

src/emc/tp/tp.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,9 @@ int tpAddCircle(TP_STRUCT * const tp,
22582258
//Reduce max velocity to match sample rate
22592259
tcClampVelocityByLength(&tc);
22602260

2261+
// Apply acceleration and jerk limits for circular motion
2262+
tcUpdateArcLimits(&tc);
2263+
22612264
TC_STRUCT *prev_tc;
22622265
prev_tc = tcqLast(&tp->queue);
22632266

0 commit comments

Comments
 (0)