3. S形速度规划:从梯形到S形的进化
梯形速度规划简单粗暴,但有个硬伤——加速度突变。电机启动瞬间加速度从0跳到最大值,停止时又突然归零。你想想看,这就像开车时一脚油门踩到底、一脚刹车踩到底,乘客不晕才怪。
S形速度规划就是为了解决这个问题。它给加速度加了个“缓启动”和“缓停止”,让整个运动过程更平滑。我个人习惯把S形规划看作是梯形规划的“升级版”——梯形只约束速度和加速度,S形多了一个约束:加加速度(Jerk)。
3.1 7段式结构:S形曲线的骨架
标准的S形速度曲线由7个阶段组成。我刚开始接触时觉得7段好复杂,其实拆开看就清楚了:
- 加加速段(T1):加速度从0线性增加到正向最大值。速度曲线呈二次上升。
- 匀加速段(T2):加速度保持正向最大值。速度线性上升。
- 减加速段(T3):加速度从正向最大值线性减小到0。速度曲线呈二次上升,但曲率变缓。
- 匀速段(T4):加速度为0。速度保持最大值。
- 加减速段(T5):加速度从0线性减小到负向最大值。速度曲线呈二次下降。
- 匀减速段(T6):加速度保持负向最大值。速度线性下降。
- 减减速段(T7):加速度从负向最大值线性增加到0。速度曲线呈二次下降,最终归零。
说白了,前3段是加速过程的“软启动”,后3段是减速过程的“软停止”,中间夹一段匀速。如果行程太短,匀加速段(T2)和匀减速段(T6)可能会消失,变成5段甚至4段S形。
核心要点:7段式S形曲线的本质,是对加速度的变化率(Jerk)进行约束。Jerk值越小,曲线越平滑,但运动时间越长。
3.2 加加速度(Jerk)约束:S形的灵魂
Jerk是S形规划区别于梯形规划的关键。梯形规划只约束v_max和a_max,而S形规划额外约束j_max(最大加加速度)。
数学上,Jerk是加速度对时间的导数:
j(t) = da(t)/dt
在加加速段(T1):j = +j_max
在减加速段(T3):j = -j_max
在加减速段(T5):j = -j_max
在减减速段(T7):j = +j_max
其他阶段:j = 0
我在项目中遇到过一个问题:Jerk设得太小,运动时间变得不可接受;设得太大,又失去了S形的意义。后来我总结了一个经验——Jerk值通常取加速度最大值的5~10倍。比如最大加速度是2 m/s²,Jerk就取10~20 m/s³。这个范围在大多数工业场景下表现不错。
避坑指南:我曾经在一个高速分拣项目里把Jerk设成了加速度的100倍,结果S形曲线几乎退化成梯形,电机启动时依然有明显的冲击感。后来把Jerk降到加速度的8倍,问题就解决了。
3.3 S形 vs 梯形:到底差在哪?
直接上对比表,一目了然:
| 对比维度 | 梯形速度规划 | S形速度规划 |
|---|---|---|
| 约束层级 | 速度、加速度 | 速度、加速度、加加速度 |
| 加速度曲线 | 矩形(突变) | 梯形(渐变) |
| 运动时间 | 短 | 较长(约增加10%~30%) |
| 冲击/振动 | 大(加速度突变) | 小(加速度连续变化) |
| 适用场景 | 对时间敏感、负载轻 | 对平滑度敏感、负载重或精密 |
| 实现复杂度 | 低(几行代码) | 中高(需分段计算) |
说白了,梯形规划追求“快”,S形规划追求“稳”。如果你的机器人只是搬运轻小零件,梯形完全够用。但如果是精密装配、医疗设备或重载搬运,我建议你上S形——省下来的振动和磨损,远比那点时间差值钱。
3.4 代码实现:一个可用的S形规划器
下面是我常用的S形速度规划实现。它接受起点、终点、最大速度、最大加速度和最大Jerk,输出每个时刻的位置、速度和加速度。
import numpy as np
class SCurvePlanner:
def __init__(self, v_max, a_max, j_max):
self.v_max = v_max
self.a_max = a_max
self.j_max = j_max
def plan(self, q0, q1, dt=0.001):
"""
S形速度规划
q0: 起始位置
q1: 目标位置
dt: 时间步长
返回: (t, pos, vel, acc)
"""
# 计算各阶段时间
# 加加速/减加速时间
T_j = self.a_max / self.j_max
# 匀加速时间(如果存在)
v_lim = min(self.v_max,
np.sqrt((q1 - q0) * self.j_max + self.a_max**2))
# 这里简化处理:假设有完整的7段
# 实际项目中需要根据位移判断各段是否存在
# 总时间估算(简化版)
T_total = 2 * T_j + (v_lim / self.a_max) + (q1 - q0) / v_lim
# 生成时间序列
t = np.arange(0, T_total, dt)
n = len(t)
pos = np.zeros(n)
vel = np.zeros(n)
acc = np.zeros(n)
# 分段计算(核心逻辑)
for i in range(n):
ti = t[i]
# 阶段判断与计算
if ti < T_j: # T1: 加加速
acc[i] = self.j_max * ti
vel[i] = 0.5 * self.j_max * ti**2
pos[i] = (1/6) * self.j_max * ti**3
elif ti < T_j + v_lim/self.a_max: # T2: 匀加速
# ... 实际代码需完整实现所有7段
pass
# ... 后续阶段省略,完整版见项目源码
return t, pos, vel, acc
注意:上面的代码是简化版,只展示了核心结构。实际项目中需要处理边界条件——比如位移太短时,匀加速段和匀减速段会消失。我建议你在实现时先判断位移是否足够长,再决定采用7段、5段还是4段S形。
3.5 实际项目中的选择建议
最后给几个实用建议:
- 什么时候用梯形?——点对点快速移动、负载轻、对时间要求苛刻。比如小型AGV的空载移动。
- 什么时候用S形?——精密定位、重载搬运、人机协作场景。比如协作机器人的末端执行器运动。
- Jerk怎么调?——从加速度的5倍开始试,如果振动明显就降低,如果时间太长就提高。我一般用二分法调参,很快能找到平衡点。
嗯,S形规划的核心就这些。记住一点:没有最好的规划,只有最合适的规划。梯形和S形各有各的舞台,关键看你的应用场景更在意什么。