2、S形速度规划基础:从梯形速度曲线到S形曲线
做运动控制的人,几乎都绕不开速度规划这个话题。我记得刚入行那会儿,第一个项目就是给一台三轴点胶机写运动程序。当时图省事,直接用了梯形速度曲线。结果呢?机器一启动,胶枪就在起点抖了一下,点出来的胶点歪歪扭扭。后来才明白——梯形曲线在速度切换点上的加速度突变,就是抖动的根源。
今天我们就来聊聊,怎么从梯形曲线进化到S形曲线,以及S形曲线那经典的7段式结构。
2.1 梯形速度曲线的痛点
梯形速度曲线,说白了就是三段:匀加速、匀速、匀减速。它的速度剖面像个梯形,所以得名。
但问题出在哪儿呢?
你想想看,梯形曲线的加速度是阶跃变化的。从静止到加速的瞬间,加速度从0直接跳到A;从加速到匀速的瞬间,加速度又从A直接降到0。这种突变会产生一个物理量——加加速度(Jerk),也就是加速度的变化率。理论上,梯形曲线的Jerk在切换点是无穷大。
核心结论:梯形曲线在速度切换点存在加速度突变,导致加加速度无穷大,这是机械抖动和冲击的根本原因。
我在项目中遇到过一台高速贴片机,用梯形曲线跑的时候,吸嘴在取料位置总会弹跳一下。后来换成S形曲线,弹跳直接消失了。说白了,S形曲线就是给加速度加了一个“缓坡”,让它平滑过渡。
2.2 从梯形到S形:加加速度的引入
S形曲线和梯形曲线最大的区别,就是引入了加加速度(Jerk)这个约束。
梯形曲线只约束了:
- 最大速度 Vmax
- 最大加速度 Amax
S形曲线额外约束了:
- 最大加加速度 Jmax
加加速度的单位是 m/s³,它描述了加速度的变化快慢。Jmax 越小,加速度变化越平缓,运动越柔和,但加减速时间也会变长。
我个人的习惯是,在设备调试阶段先把 Jmax 设得保守一些(比如理论值的 50%),等确认运动平稳后再逐步提高。这样能避免因为Jerk过大导致机械共振。
2.3 S形曲线的7段式结构
S形曲线完整的运动过程,可以拆成7个阶段。我画了一张图,方便你理解:
这7段分别是:
- 加加速段:加速度从0开始,以Jmax线性增加。速度呈二次曲线上升。
- 匀加速段:加速度达到Amax后保持不变。速度线性上升。
- 减加速段:加速度从Amax线性减小到0。速度继续上升,但增速放缓。
- 匀速段:加速度为0,速度保持Vmax不变。
- 加减速段:加速度从0反向增加到-Amaz。速度开始下降。
- 匀减速段:加速度保持-Amaz不变。速度线性下降。
- 减减速段:加速度从-Amaz线性减小到0。速度缓慢降到0。
我的经验:在实际项目中,并不是每次都需要完整的7段。如果运动距离很短,可能连匀速段都没有,直接就是“加加速-匀加速-减加速-加减速-匀减速-减减速”的6段结构。甚至更短时,只有“加加速-减加速-加减速-减减速”4段。要学会根据实际行程动态裁剪段数。
2.4 各阶段的数学描述
我们以加加速段为例,看看数学上怎么描述。假设加加速度为 J(常数),初始速度为 v₀,初始加速度为 a₀=0。
加加速段(0 ≤ t ≤ T₁):
- 加加速度:j(t) = J
- 加速度:a(t) = J·t
- 速度:v(t) = v₀ + ½·J·t²
- 位移:s(t) = v₀·t + ⅙·J·t³
你看,速度是时间的二次函数,位移是三次函数。这就是“S形”名字的由来——速度曲线呈S形弯曲,而不是梯形那样直来直去。
其他段的公式类似,只是初始条件和符号不同。我整理了一张表,方便你对照:
| 阶段 | 加速度 a(t) | 速度 v(t) | 位移 s(t) |
|---|---|---|---|
| ① 加加速 | J·t | v₀ + ½·J·t² | v₀·t + ⅙·J·t³ |
| ② 匀加速 | Amax | v₁ + Amax·t | v₁·t + ½·Amax·t² |
| ③ 减加速 | Amax - J·t | v₂ + Amax·t - ½·J·t² | v₂·t + ½·Amax·t² - ⅙·J·t³ |
| ④ 匀速 | 0 | Vmax | Vmax·t |
| ⑤ 加减速 | -J·t | Vmax - ½·J·t² | Vmax·t - ⅙·J·t³ |
| ⑥ 匀减速 | -Amax | v₅ - Amax·t | v₅·t - ½·Amax·t² |
| ⑦ 减减速 | -Amax + J·t | v₆ - Amax·t + ½·J·t² | v₆·t - ½·Amax·t² + ⅙·J·t³ |
注意:上表中的 t 是相对于该段起始时刻的时间偏移,不是全局时间。每段的初始速度 v₀、v₁、v₂ 等,需要根据上一段的结束速度递推计算。我曾经在代码里犯过这个错——直接用全局时间代入公式,结果速度曲线出现了跳变,排查了半天才发现是时间基准搞错了。
2.5 什么时候用S形,什么时候用梯形?
不是所有场景都需要S形曲线。我个人的选择原则是这样的:
| 场景 | 推荐曲线 | 原因 |
|---|---|---|
| 高速高精度定位(如贴片机、数控机床) | S形曲线 | 减少冲击,提高定位精度 |
| 重载搬运(如机器人、AGV) | S形曲线 | 避免货物晃动,保护机械结构 |
| 简单点位运动(如传送带启停) | 梯形曲线 | 计算简单,实时性高 |
| 低速、轻载场景 | 梯形曲线 | 冲击小,没必要增加计算量 |
说白了,S形曲线是用计算量换平滑度。如果你的控制器算力够,我建议默认用S形。毕竟现在MCU的性能越来越强,多算几个三次方程根本不是事儿。
2.6 一个简单的Python实现
最后,我写了一个极简的S形速度规划函数。它只处理加加速到匀速再到减减速的完整7段,但足够你理解核心逻辑了。
import numpy as np
def s_curve_plan(v0, vmax, amax, jmax, total_time):
"""
简化的S形速度规划(对称7段)
返回时间数组和速度数组
"""
# 计算各段时间(假设对称,且能达到最大速度)
ta = amax / jmax # 加加速/减加速段时间
tv = total_time - 4*ta # 匀速段时间(需大于0)
if tv < 0:
raise ValueError("总时间太短,无法达到最大速度")
# 时间离散化
dt = 0.001
t = np.arange(0, total_time, dt)
v = np.zeros_like(t)
# 分段计算速度
for i, ti in enumerate(t):
if ti < ta:
# 加加速段
v[i] = v0 + 0.5 * jmax * ti**2
elif ti < 2*ta:
# 匀加速段
t_local = ti - ta
v[i] = v0 + 0.5 * jmax * ta**2 + amax * t_local
elif ti < 3*ta:
# 减加速段
t_local = ti - 2*ta
v[i] = v0 + 0.5 * jmax * ta**2 + amax * ta + amax * t_local - 0.5 * jmax * t_local**2
elif ti < 3*ta + tv:
# 匀速段
v[i] = vmax
elif ti < 4*ta + tv:
# 加减速段
t_local = ti - (3*ta + tv)
v[i] = vmax - 0.5 * jmax * t_local**2
elif ti < 5*ta + tv:
# 匀减速段
t_local = ti - (4*ta + tv)
v[i] = vmax - 0.5 * jmax * ta**2 - amax * t_local
else:
# 减减速段
t_local = ti - (5*ta + tv)
v[i] = vmax - 0.5 * jmax * ta**2 - amax * ta - amax * t_local + 0.5 * jmax * t_local**2
return t, v
# 使用示例
t, v = s_curve_plan(v0=0, vmax=100, amax=500, jmax=2000, total_time=1.0)
print("速度规划完成,速度范围:", v.min(), "~", v.max())
一点补充:上面的代码为了清晰,把每段都写成了显式公式。实际工程中,我更喜欢用状态机的方式来实现——每个周期根据当前阶段和剩余时间,查表或递推计算。这样代码更易维护,也方便处理异常情况(比如中途急停)。
嗯,S形曲线的基础就聊到这儿。你可能会问:如果运动距离很短,达不到最大速度怎么办?或者,怎么在运动过程中实时调整目标位置?这些问题,我们后面会一一拆解。先把7段式的结构吃透,后面的内容就好理解了。