教程 · 中级 · 40 分钟
运动曲线规划:梯形与 S 形加减速
为什么直接给位置指令会让电机丢步、机器撞击作响,梯形曲线如何解决这个问题,以及限制 jerk 的 S 形曲线要多花多少时间。
引言
告诉电机去某个位置,它会立刻试着到那里。不是“快点”——是立刻。这条指令里没有任何关于多快的信息,所以唯一可用的解释就是“越快越好”,而接下来发生什么由最先耗尽的那一样决定:转矩、限流、附着力,或者步进电机跟不上的能力。
运动曲线就是那份缺失的信息。它把“去 100 mm”变成一份时间表——从现在到到达之间的每一个瞬间轴该在哪——并交给控制器一个机器真能交付的速度下移动的目标。
这就是“一台机械臂到达了”和“一台机械臂到达并且不抖了”之间的区别。
裸位置指令实际上要求了什么
想象一个阶跃:t = 0 时目标从 0 mm 跳到 100 mm。
要精确跟随它,轴需要无限速度,而无限速度需要无限加速度,无限加速度需要无限力。它做不到,于是有东西要让步:
- 步进电机被下发一个在那个速度下它的转矩根本产生不出的步进率,于是丢步——而它跑在开环下,自己根本不知道丢了。位置从此永久地错。为什么会这样,以及为什么永远是四步一次。
- 带 PID 环的直流电机看到一个巨大的瞬时误差,输出饱和,积分项卷绕,然后超调。
- 舵机一头撞到最大速率到达,整个机构还在动。
这些都不是调参问题。它们是同一个问题:指令在物理上不可能。
梯形曲线
解法是下发速度,并以电机能维持的速率去改变速度。以恒定加速度升到巡航速度,保持,再以同样的速率减速。把速度对时间作图,它就是一个梯形,名字就是这么来的。
三个数定义它:距离 d、极速 v_max、加速度 a。
t_accel = v_max / a time spent ramping
d_accel = v_max² / (2a) distance used by one ramp
t_total = v_max / a + d / v_max whole move, if it reaches v_max
一次 100 mm 的移动,速度 100 mm/s,a = 500 mm/s²:每段斜坡用 0.2 s、走 10 mm,剩下 80 mm 巡航,合计 1.2 s。
下面是整件事写成时间的函数——在瞬间 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;
}
注意这个函数不是什么:它不是控制器。它生成一个设定点。你原本有的东西——步进调度器、PID 环、舵机写值——仍然负责跟随。曲线只改变它跟随的东西。
你要的移动不总是你得到的移动
那个 2 * dAcc > d 分支是人们最先遇到、也最先误诊的情况。
如果移动短于加速上去再减速下来所需的距离,轴永远到不了你设定的速度。它斜坡上升,撞到中点,立刻斜坡下降——那是三角形,不是梯形。
阈值是 d < v_max² / a。对上面那组数来说是 20 mm,所以任何短于 20 mm 的移动,无论你怎么设,速度都上不到 100 mm/s。
这就是为什么在一台做大量短移动的机器上提高 v_max 常常测不出任何变化。这些移动从来就不是速度受限的,它们是加速度受限的。做致密填充的 3D 打印机就是日常例子。
拐角仍然不连续
梯形曲线解决了速度。看加速度,问题只是挪了个地方。
t = 0 时加速度从 0 瞬间跳到 500 mm/s²。斜坡顶端又瞬间掉回 0。每一个都是加速度上的一个阶跃——而加速度的变化率是 jerk,所以加速度的一个阶跃就是一次无限的 jerk 冲激。
无限也不是机器能产生的量。它产生的是别的东西:
- 每段斜坡的起止处一声可听的敲击。
- 振铃——皮带会拉伸、联轴器会上弦,而结构是有柔顺的,所以一次突变的作用力会激励它,之后它就振荡起来。在打印机上,这就是你在锐利特征旁边看到的鬼影。
- 转矩尖峰,足以触发限流——而平均转矩那个数字说它们是够的。
S 形曲线
S 形曲线同时限制 jerk 和加速度。它不再一步跨到满加速度,而是在一个 jerk 时间 t_j = a_max / j_max 内斜坡升上去,保持,再斜坡降下来。速度曲线于是获得了圆角——名字由此而来——而加速度本身变成了一个梯形。
一次完整的 S 形移动有七个阶段:jerk 上升、保持加速度、jerk 下降、巡航,以及镜像的减速停车。
代价:正好一个 t_j
这一部分值得记进脑子,因为它让这个取舍变得具体。
当两种曲线达到同样的 v_max 和同样的 a_max 时,S 形移动多花的时间正好是 t_j——一个 jerk 时间,不是两个,而且与移动多远无关:
trapezoidal T = v_max/a + d/v_max
S-curve T = v_max/a + d/v_max + t_j where t_j = a_max / j_max
对上面那次移动:1.2 s 变成 1.3 s。这次移动上多付 8%,更长的移动上百分比更小,换来的是有界的 jerk。
之所以是一个而不是两个:S 形的加速段更长,所以在开始巡航前多走了一些距离,于是巡航段缩短,把大部分时间还了回来。只有 jerk 时间本身是不可回收的。
这里面藏着一个约束。只有当 v_max ≥ a_max² / j_max 时加速度才真的达到 a_max。低于这个值,这次移动连满加速度也到不了,你会得到一个三角形加速度曲线——同一个“距离不够”的故事,只是往上求导了一阶。
打印机固件里的“Jerk”不是 jerk
在动手调任何东西之前值得知道,因为这个词本身会主动误导你。
经典 Marlin 的 DEFAULT_XJERK不是加速度的导数。它是两次移动之间的交界处允许的最大瞬时速度变化,单位是 mm/s——一个让规划器不用减速到零就能过弯的过弯余量。有用,但是另一个量,名字和单位都是错的。
较新的 Marlin 改用 Junction Deviation,它把拐角建模成一段给定偏差(毫米)的圆弧。Klipper 用 square_corner_velocity——90° 弯以多快的速度通过。这三者都是在回答“我怎么才能不在每个拐角都停下来”,而没有一个是物理意义上的 jerk 限幅。
所以如果论坛帖子让你把 jerk 调低来止振,先确认说的是哪个量。降低交界速度能减小过弯冲击,但它并不限制单次移动内部的 da/dt。
你的 PID 可能没问题
如果你有一根闭环轴,每次移动都超调振荡,本能反应是把它调钝——降 Kp、加微分、去和超调搏斗。
先试着把设定点曲线化。
一个阶跃输入是在向 PID 控制器索要无限输出。它的误差项一开始就是整段移动的距离,微分项看到一个瞬时跳变,而积分项在输出饱和、轴无法响应时持续累积。把控制器调钝,是为了扛过那一个不可能的瞬间,却让它在所有地方都变得迟钝。
有了曲线,整个移动过程中误差一直很小,因为目标始终在轴真正能到的某个地方。那组对阶跃完全不可用的增益,对斜坡往往表现良好——而这时控制器只在纠正负载和摩擦,那正是它该干的活。你可以在 PID 模拟器里把阶跃设定点和渐变设定点对比着看。
在真实机器人上你会遇到它的地方
- 步进电机。AccelStepper的
setMaxSpeed()和setAcceleration()只给你梯形曲线,仅此而已。没有 S 形选项;要那个得自己生成设定点。 - 业余级舵机。SG90有一个你既看不见也调不了的内部控制器,所以所谓曲线化,就是把指令角度改成一步步渐变,而不是一次调用写出目标——同一个想法从外面施加。
- **带编码器的直流电机。**把位置设定点曲线化,让编码器的 PID 去跟随它。这是最见收益的组合。
- **移动机器人。**同样的限制也适用于 cmd_vel 桥上的
linear.x:下发一个速度阶跃会让轮子打滑,从而污染里程计。给 twist 加限速。 - **打印机和 CNC。**Marlin、Klipper 和 GRBL 都在规划带过弯规则的梯形移动。
出问题时
| 现象 | 通常原因 |
|---|---|
| 步进电机在负载下悄悄丢位 | 没有曲线——下发的速率超过起动转矩 |
| 机器在移动起止处敲击 | 梯形的 jerk 冲激;需要 S 形或更低的加速度 |
| 锐利特征旁振铃 | 加速度阶跃激起了结构共振 |
| 短移动无视速度设定 | 三角形曲线——加速度受限,不是速度受限 |
| 每次移动都超调振荡 | 阶跃设定点进了闭环;先曲线化再谈调参 |
| 提高加速度有用,然后突然没用 | 现在是转矩受限而不是曲线受限了 |
| 启动时里程计漂得厉害 | 速度阶跃导致轮子打滑;给指令限速 |
曲线化是大多数业余运动系统能拿到的最便宜的大幅改进,因为它只花一个函数、零硬件。机械臂模拟器是看它为什么在长连杆末端重要的好地方——底座上一个小的加速度阶跃,在夹爪端会变成看得见的晃动。在移动机器人上,同一条曲线正是让路径跟踪器能为比转弯半径更急的弯减速、而不是根本跟不上的原因。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读