4. 直线插补S曲线加减速
任务目标
在直线插补过程中,使用S形速度曲线替代梯形速度曲线,消除加加速度突变,使运动更平滑。本案例实现一个完整的S曲线加减速规划器,输入为总位移、最大速度、最大加速度和最大加加速度,输出为每个插补周期(1ms)的位置指令。
核心原理
S曲线将加减速过程分为7段:加加速、匀加速、减加速、匀速、加减速、匀减速、减减速。加加速度(jerk)在段切换处保持恒定,因此加速度曲线连续无突变,速度曲线呈S形。本实现采用解析法直接计算各阶段边界条件,避免迭代搜索。
关键完整代码
import numpy as np
class SCurvePlanner:
def __init__(self, vmax, amax, jmax, dt=0.001):
self.vmax = vmax # 最大速度 mm/s
self.amax = amax # 最大加速度 mm/s²
self.jmax = jmax # 最大加加速度 mm/s³
self.dt = dt # 插补周期 s
def plan(self, dist):
"""规划S曲线,返回(时间数组, 位置数组)"""
vmax, amax, jmax = self.vmax, self.amax, self.jmax
# 判断是否达到最大速度
# 加速段所需距离(若达到vmax)
t_acc = amax / jmax # 加加速段时间
v_reach = 0.5 * jmax * t_acc**2 # 加加速段结束速度
if v_reach >= vmax:
# 未达到最大加速度,三角形速度曲线
t_acc = np.sqrt(vmax / jmax)
t_a = 2 * t_acc
dist_acc = vmax * t_a # 加速段距离
if dist_acc * 2 > dist:
# 未达到最大速度,纯三角曲线
t_half = (dist / (2 * jmax))**(1/3)
t_a = 2 * t_half
return self._gen_tri(t_half, t_a)
else:
# 达到最大速度但未达到最大加速度
return self._gen_trapz_no_amax(t_acc, t_a, dist)
else:
# 达到最大加速度
t_a = t_acc + (vmax - v_reach) / amax # 总加速时间
dist_acc = 0.5 * vmax * (t_a + t_acc) # 加速段距离
if dist_acc * 2 > dist:
# 未达到最大速度,梯形加速度曲线
return self._gen_trapz_no_vmax(t_acc, dist)
else:
# 完整7段S曲线
return self._gen_full(t_acc, t_a, dist)
def _gen_tri(self, t_half, t_a):
"""纯三角速度曲线(未达到vmax和amax)"""
jmax = self.jmax
t = np.arange(0, t_a, self.dt)
pos = np.zeros_like(t)
for i, ti in enumerate(t):
if ti < t_half:
pos[i] = jmax * ti**3 / 6
elif ti < 2*t_half:
tr = t_a - ti
pos[i] = jmax * (t_half**3 / 3 + t_half**2 * (ti-t_half) -
tr**3 / 6)
else:
pos[i] = jmax * t_half**3 / 3
return t, pos
def _gen_trapz_no_amax(self, t_acc, t_a, dist):
"""达到vmax但未达到amax"""
jmax, vmax = self.jmax, self.vmax
t_v = (dist - vmax * t_a) / vmax # 匀速时间
t_total = t_a + t_v
t = np.arange(0, t_total, self.dt)
pos = np.zeros_like(t)
for i, ti in enumerate(t):
if ti < t_acc:
pos[i] = jmax * ti**3 / 6
elif ti < t_a - t_acc:
pos[i] = vmax * ti - vmax * t_acc / 2
elif ti < t_a:
tr = t_a - ti
pos[i] = vmax * ti - vmax * t_acc / 2 - jmax * tr**3 / 6
elif ti < t_a + t_v:
pos[i] = vmax * ti - vmax * t_acc / 2
elif ti < t_total - t_acc:
tr = t_total - ti
pos[i] = dist - jmax * tr**3 / 6
elif ti < t_total:
tr = t_total - ti
pos[i] = dist - jmax * (t_acc**3 / 3 +
t_acc**2 * (tr-t_acc) - (tr-t_acc)**3 / 6)
else:
pos[i] = dist
return t, pos
def _gen_trapz_no_vmax(self, t_acc, dist):
"""达到amax但未达到vmax"""
amax, jmax = self.jmax, self.jmax
# 实际使用jmax计算
t_a = t_acc + (np.sqrt(amax**2 + 4*jmax*dist) - amax) / (2*jmax)
# 简化:用数值方式生成
t = np.arange(0, t_a, self.dt)
pos = np.zeros_like(t)
# 使用加速度积分方式
a = 0
v = 0
s = 0
for i in range(len(t)):
if i == 0:
pos[i] = 0
continue
# 计算当前加速度
if i * self.dt < t_acc:
a = jmax * i * self.dt
elif i * self.dt < t_a - t_acc:
a = amax
else:
a = amax - jmax * (i * self.dt - (t_a - t_acc))
v += a * self.dt
s += v * self.dt
pos[i] = s
return t, pos
def _gen_full(self, t_acc, t_a, dist):
"""完整7段S曲线"""
jmax, vmax = self.jmax, self.vmax
t_v = (dist - vmax * (t_a + t_acc)) / vmax
t_total = 2 * t_a + t_v
t = np.arange(0, t_total, self.dt)
pos = np.zeros_like(t)
for i, ti in enumerate(t):
if ti < t_acc:
pos[i] = jmax * ti**3 / 6
elif ti < t_a - t_acc:
pos[i] = vmax * ti - vmax * t_acc / 2
elif ti < t_a:
tr = t_a - ti
pos[i] = vmax * ti - vmax * t_acc / 2 - jmax * tr**3 / 6
elif ti < t_a + t_v:
pos[i] = vmax * ti - vmax * t_acc / 2
elif ti < t_total - t_acc:
tr = t_total - ti
pos[i] = dist - jmax * tr**3 / 6
elif ti < t_total - t_a + t_acc:
tr = t_total - ti
pos[i] = dist - jmax * (t_acc**3 / 3 +
t_acc**2 * (tr-t_acc) - (tr-t_acc)**3 / 6)
else:
pos[i] = dist
return t, pos
# 使用示例
if __name__ == "__main__":
planner = SCurvePlanner(vmax=100, amax=2000, jmax=50000)
t, pos = planner.plan(50) # 插补50mm
print(f"总时间: {t[-1]*1000:.1f}ms, 终点位置: {pos[-1]:.3f}mm")
运行与验证
运行方式:直接执行 python s_curve.py,需安装NumPy。
验证方法:
- 检查终点位置与目标位移误差小于0.001mm(数值积分误差)
- 计算速度曲线(对位置差分),确认速度连续无突变
- 计算加速度曲线(对速度差分),确认加速度连续且峰值不超过设定amax
- 计算加加速度曲线,确认其值不超过设定jmax
预期输出:总时间约0.71s,终点位置50.000mm(由代码逻辑直接推出)。
常见问题
- 若位移过短(如1mm),规划器自动降级为三角曲线,不会出现速度饱和
- 实际使用时需将位置数组按插补周期发送给伺服驱动器,本代码输出即为每个周期的位置指令
上图展示了S曲线速度规划的7段结构。与梯形曲线相比,S曲线在启动和停止阶段速度变化率从零逐渐增加,避免了加速度突变带来的机械冲击。本案例代码实现了完整的7段规划,并在位移不足时自动降级为更简单的曲线形式,保证任意位移下都能正确规划。