运动曲线模拟器:梯形与 S 形曲线
把同一段运动并排规划成梯形曲线和限制跃度的 S 形曲线,拖动各项限值,看位置、速度、加速度和跃度如何随之变化。
- 类别
- 控制系统
- 时长
- 30–60 分钟
- 平台
- 浏览器 · Arduino
01 / 从这里开始
简介
每一条电机指令背后都藏着一份时间表。让一根轴去某个位置,总得有什么来决定它多快加速到位、巡航多久、何时开始减速。这个实验台把这份时间表变得看得见:设定一段运动,设定机器的能力,然后看梯形曲线和 S 形曲线并排为它做规划——包括那些你的限值无法同时满足的情形。
Live lab / Analytic profile planner
Motion profile simulator
Set a move and watch both profiles plan it. Drag the limits to push the move into a triangle, starve the jerk until acceleration never saturates, and read what the S-curve costs.
Four stacked plots share a time axis. Each is the derivative of the one above it, so a corner in velocity becomes a step in acceleration and an impulse in jerk. The trapezoidal profile is drawn in the accent colour, the S-curve alongside it.
- Trapezoidal
- 1.200 s
- S-curve
- 1.300 s
- Jerk cost
- +0.100 s
- Peak speed
- 100 mm/s
- Peak accel
- 500 mm/s²
- Cruise
- 0.700 s
Keyboard: focus the plots, then use Space to play/pause, B to rewind, R to reset, M to cycle which profiles are shown, and F for full screen.
控件
距离和最高速度描述你想要的运动。加速度和跃度上限描述这台机器 实际能做到什么。实验台在你每次拖动时都会重新规划——在曲线响应之前 没有哪个按钮需要按。
显示用来在两条曲线同时显示和单独显示某一条之间切换。 播放让一个播放头实时划过图表,这是最快体会“S 形曲线完成得更晚”的方式。
限值下面的那条说明会告诉你一个限值什么时候变得不可达, 而这正是这些滑块值得拖动的原因。
原理
四张图叠在一起,每一张都是上面那张的导数。这种堆叠本身就是论证: 速度上的一个拐角就是加速度上的一个阶跃,而加速度上的一个阶跃 就是跃度上的一个冲激。这个不连续性在你不断求导时从不消失—— 它只是往下移,直到变得无界。
梯形曲线以恒定速率加速、巡航,然后减速。速度是一个梯形, 加速度是一个方波,而跃度是拐角处的一组冲激。这些冲激正是机器 听见的“敲门声”,也是柔顺传动系统会用来回应的振铃。
S 形曲线同时限制跃度。加速度在 t_j = a_max / j_max 的时间内爬升、
保持、再爬升回来,于是速度获得了圆角。它恰好花掉一个跃度时间,
换来的是有界的冲击。
两种曲线都不可能永远满足每一个限值。短于 v²/a 的运动永远达不到最高速度。
最高速度低于 a²/j 的 S 形曲线永远达不到加速度极限。两者都很普通,
而且在这里都看得见。
算法
实验台是求解这条曲线,而不是积分它。每一段的跃度都是常数, 所以在一段之内:
a(τ) = a₀ + j·τ
v(τ) = v₀ + a₀·τ + j·τ²/2
x(τ) = x₀ + v₀·τ + a₀·τ²/2 + j·τ³/6
这些式子是精确的,这正是运动能精确落在目标上而不是落在附近的原因, 也是拖动一个滑块能瞬间重新规划的原因。
挑选峰值速度只用到一个恒等式:一段速度斜坡关于其中点是奇对称的,
所以无论它是什么形状,都恰好覆盖 v_peak/2 × duration。
这把“有没有巡航段?“变成了一次单纯的比较,
也让受距离限制的峰值可以用闭式解出来——在加速度仍然饱和时是一个二次方程,
而一旦不饱和,就是一个立方根。
源代码
一个小到可以直接粘进 sketch 里的梯形曲线生成器。它返回你的控制回路
在时刻 t 应当追逐的位置:
// Target position t seconds into a trapezoidal move of `d` mm.
float profilePosition(float t, float d, float vMax, float a) {
float tAcc = vMax / a;
float dAcc = vMax * vMax / (2 * a);
if (2 * dAcc > d) { // too short to reach vMax
float tPeak = sqrtf(d / a); // triangular profile
if (t >= 2 * tPeak) return d;
if (t < tPeak) return 0.5f * a * t * t;
float left = 2 * tPeak - t;
return d - 0.5f * a * left * left;
}
float tCruise = (d - 2 * dAcc) / vMax;
float tTotal = 2 * tAcc + tCruise;
if (t >= tTotal) return d;
if (t < tAcc) return 0.5f * a * t * t;
if (t < tAcc + tCruise) return dAcc + vMax * (t - tAcc);
float left = tTotal - t;
return d - 0.5f * a * left * left;
}
每个控制周期调用它一次,并把结果喂给负责闭合回路的东西。 AccelStepper 在步进电机内部做的是等价的事;它没有 S 形曲线的选项, 所以跃度受限的曲线得由你自己生成。
假设与硬件差异
这些曲线假定的是一根刚性、不受约束的轴:没有摩擦、没有 背隙、没有皮带拉伸、没有丢步, 而且峰值电流随时可以取用。真实的步进电机 或直流轴都有随速度下降的扭矩、把跃度变成振铃的柔顺性, 以及会饱和的驱动器——这正是 S 形曲线对皮带驱动的机械臂有帮助、 而在一根短而硬的滑轨上却纯属浪费时间的原因。这里的采样率也是理想的; 在单片机上,你的定时器稳定性就是精度下限。请把这段运动读作 你现有的控制器应当追逐的设定点,然后拿它实际拉动的机械结构去核对。
电路图
这个实验台没有专属电路——曲线是算术的一层,不是一个零件。 它位于你的指令源和你已有的回路之间:
target position → PROFILE → setpoint → controller → driver → motor
↑ │
└────── encoder ─────────┘
接线就是你的电机本来就需要的东西:一个按斜坡所需峰值电流选型的 驱动器,以及在你想要闭合回路时 加上编码器反馈。 改变的只有控制器被要求去跟随的东西。
完整推导、Marlin 里“jerk”这个词造成的困惑,以及为什么加曲线常常能修好 一个看起来整定糟糕的 PID,都在 运动曲线:梯形与 S 形加速度里。
硬件清单
元器件
- 一个步进电机或带编码器的直流电机
- 一个按曲线所需峰值电流选型的驱动器
- 一个定时器稳定到足以保持采样率的控制器
- 一个刚性的安装结构——正是柔顺性把跃度变成了振铃
探索关联图谱
哪里用到这个模拟器
以这个实验台为基础的项目、学习路线和教程。
继续搭建
下载资源
完成项目时可以参考本页上的这些资料。可下载的项目包只会在其源文件和版本发布之后才会添加。
常见问题
常见问题解答
为什么我的轴永远达不到我设定的最高速度?
因为这段运动太短了。达到最高速度需要 v²/2a 的助跑距离,停下来还需要同样的距离,所以任何短于 v²/a 的运动,都会在斜坡结束之前就走完了。速度曲线变成了三角形而不是梯形,其峰值为 √(a·d),与你配置的数值无关。对这些运动来说,提高速度上限毫无作用——只有提高加速度才有用。把距离滑块拖到阈值以下,实验台就会告诉你这一点。
S 形曲线总是比梯形曲线更好吗?
不是。它总是更慢,而它所解决的问题并不总是困扰着你的机器。S 形曲线限制的是跃度,这在传动系统存在柔顺性时很重要——皮带、长连杆、机械臂末端的重型工具——因为加速度的阶跃会激起这种柔顺,之后它就会振铃。在一根短而硬的直驱轴上,振铃可能小到无法测量,多花的时间就纯属损失。用这个实验台看清代价,再判断你的机器是否在为它确实需要的东西买单。
跃度限制实际上要花掉多少时间?
恰好一个跃度时间,t_j = a_max / j_max,前提是两种曲线达到相同的最高速度和相同的加速度。不是两个,而且——令人意外的是——与运动距离的长短无关。S 形曲线的加速段更长,所以它在巡航之前走过的距离更多,于是巡航段缩短,把大部分时间又还了回来。只有跃度时间本身是无法挽回的。拖动距离滑块,看着跃度的代价纹丝不动。
为什么加速度永远达不到我设定的加速度上限?
因为跃度上限对它来说太低了。加速度爬升到 a_max 再降回来,需要 a_max²/j_max 的速度变化,所以如果你的最高速度低于这个值,加速度本身就会变成三角形,峰值变为 √(v·j)。这和短距离运动是同一个“空间不够用”的故事,只是高了一阶导数。每当出现这种情况,实验台都会指出来。
曲线在我的代码里应该放在哪里?
放在控制器之前,而不是控制器内部。曲线生成的是一个设定点——轴此刻应该在的位置——而你原有的东西跟随它即可:一个步进调度器、一个 PID 回路、一次舵机写入。这就是为什么加上运动曲线常常能修好一根看起来整定得很糟的轴。阶跃指令要求 PID 给出无穷大的输出,所以它的误差一开始就是整段运动的距离,而积分项会在输出饱和期间不断累积。面对一个经过曲线规划的目标,误差始终很小,同样的增益也就表现正常了。
延伸阅读
参考资料
权威资料,帮助你越过本模拟器有限的教学模型,进一步深入学习。