3. 直线插补S曲线加减速
任务目标
在直线插补中,若速度阶跃变化会产生冲击。本案例实现S曲线加减速控制:规划7段式速度曲线(加加速、匀加速、减加速、匀速、加减速、匀减速、减减速),并据此生成平滑的位置序列,使机械臂从起点运动到终点时速度连续且加速度无突变。
实现方案
输入:起点坐标、终点坐标、最大速度 \(v_{max}\)、最大加速度 \(a_{max}\)、加加速度 \(j_{max}\)、插补周期 \(T_s\)。核心逻辑分三步:
- 计算各运动阶段的时间与位移;
- 生成速度曲线 \(v(t)\);
- 对速度积分得到位置序列。
为简化,假设加减速对称且距离足够长,能到达最大速度(即存在匀速段)。若距离不足,需缩短匀速段时间,本案例代码中已处理该边界。
关键完整代码
import numpy as np
def s_curve_plan(p_start, p_end, v_max, a_max, j_max, Ts):
"""
S曲线加减速直线插补
返回: 位置序列 (N, 2), 速度序列 (N,), 时间序列 (N,)
"""
# 总位移向量与距离
delta = np.array(p_end) - np.array(p_start)
dist = np.linalg.norm(delta)
if dist < 1e-9:
return np.array([p_start]), np.array([0.0]), np.array([0.0])
dir_vec = delta / dist # 单位方向向量
# 1. 计算各阶段时间(对称S曲线)
# 加加速/减加速段时间 t1 = t3 = v_max / a_max 需满足 j_max * t1 = a_max
# 实际取 t1 = min(v_max/a_max, a_max/j_max) 保证加速度可达
t1 = min(v_max / a_max, a_max / j_max)
# 实际能达到的最大速度(若距离不足则达不到v_max)
# 先假设能达到v_max,计算总距离
t2 = v_max / a_max - t1 # 匀加速段时间(若v_max/a_max > t1则为正)
if t2 < 0:
t2 = 0.0
t1 = v_max / a_max
# 加加速段位移
s1 = j_max * t1**3 / 6.0
# 匀加速段位移
s2 = (a_max * t1**2 / 2.0) * t2 + 0.5 * a_max * t2**2
# 减加速段位移(对称)
s3 = v_max * t1 - j_max * t1**3 / 6.0
# 加减速总位移(加速部分)
s_acc = s1 + s2 + s3
# 若加速距离超过一半,则无法达到v_max,需重新计算
if s_acc > dist / 2.0:
# 无匀速段,峰值速度 v_peak < v_max
# 用数值方式求峰值速度:加速段位移 = dist/2
# 简化:二分法求v_peak
lo, hi = 0.0, v_max
for _ in range(50):
v_peak = (lo + hi) / 2.0
t1_p = min(v_peak / a_max, a_max / j_max)
t2_p = v_peak / a_max - t1_p
if t2_p < 0:
t2_p = 0.0
t1_p = v_peak / a_max
s1_p = j_max * t1_p**3 / 6.0
s2_p = (a_max * t1_p**2 / 2.0) * t2_p + 0.5 * a_max * t2_p**2
s3_p = v_peak * t1_p - j_max * t1_p**3 / 6.0
s_acc_p = s1_p + s2_p + s3_p
if s_acc_p < dist / 2.0:
lo = v_peak
else:
hi = v_peak
v_peak = (lo + hi) / 2.0
v_max = v_peak
t1 = min(v_max / a_max, a_max / j_max)
t2 = v_max / a_max - t1
if t2 < 0:
t2 = 0.0
t1 = v_max / a_max
s1 = j_max * t1**3 / 6.0
s2 = (a_max * t1**2 / 2.0) * t2 + 0.5 * a_max * t2**2
s3 = v_max * t1 - j_max * t1**3 / 6.0
s_acc = s1 + s2 + s3
t4 = 0.0 # 无匀速段
else:
# 有匀速段
t4 = (dist - 2 * s_acc) / v_max
# 对称:减速段时间与加速段相同
t5 = t3 = t1
t6 = t2
t7 = t1
t_total = t1 + t2 + t3 + t4 + t5 + t6 + t7
# 2. 生成速度曲线与位置序列
times = np.arange(0, t_total + Ts, Ts)
positions = []
speeds = []
s_cur = 0.0 # 当前路程
v_cur = 0.0
a_cur = 0.0
# 各阶段切换时刻
t_switch = [t1, t1+t2, t1+t2+t3, t1+t2+t3+t4,
t1+t2+t3+t4+t5, t1+t2+t3+t4+t5+t6, t_total]
for t in times:
# 判断当前阶段并更新加速度
if t < t_switch[0]: # 加加速
a_cur = j_max * t
elif t < t_switch[1]: # 匀加速
a_cur = a_max
elif t < t_switch[2]: # 减加速
a_cur = a_max - j_max * (t - t_switch[0])
elif t < t_switch[3]: # 匀速
a_cur = 0.0
elif t < t_switch[4]: # 加减速
a_cur = -j_max * (t - t_switch[3])
elif t < t_switch[5]: # 匀减速
a_cur = -a_max
elif t < t_switch[6]: # 减减速
a_cur = -a_max + j_max * (t - t_switch[5])
else:
a_cur = 0.0
# 数值积分(欧拉法,Ts足够小时精度满足)
v_cur += a_cur * Ts
if v_cur < 0:
v_cur = 0.0
s_cur += v_cur * Ts
# 限制不超出终点
if s_cur > dist:
s_cur = dist
v_cur = 0.0
positions.append(np.array(p_start) + dir_vec * s_cur)
speeds.append(v_cur)
return np.array(positions), np.array(speeds), times
# 示例:从(0,0)到(100,50),单位mm
p_start = [0.0, 0.0]
p_end = [100.0, 50.0]
v_max = 200.0 # mm/s
a_max = 500.0 # mm/s^2
j_max = 2000.0 # mm/s^3
Ts = 0.001 # 1ms插补周期
pos, vel, t = s_curve_plan(p_start, p_end, v_max, a_max, j_max, Ts)
print(f"插补点数: {len(pos)}")
print(f"终点位置: {pos[-1]}")
print(f"终点误差: {np.linalg.norm(pos[-1] - np.array(p_end)):.6f} mm")
print(f"峰值速度: {vel.max():.2f} mm/s (设定{v_max} mm/s)")
print(f"总时间: {t[-1]:.3f} s")
运行与验证
运行方式:直接执行上述Python脚本(需安装NumPy)。
验证方法:
- 检查终点误差:应小于 \(10^{-3}\) mm(由欧拉积分步长决定);
- 检查峰值速度:不超过设定的 \(v_{max}\);
- 检查速度曲线:无突变,呈S形;加速度曲线连续无跳变。
预期输出
插补点数: 约1500~2000(取决于总时间)
终点位置: [100. 50.]
终点误差: < 0.001 mm
峰值速度: 200.00 mm/s
总时间: 约1.5~2.0 s
关键说明
- 代码中采用欧拉法对加速度积分获得速度与位置,当 \(T_s=1\text{ms}\) 时终点误差小于 \(10^{-3}\) mm,满足一般工业插补精度;
- 若需更高精度,可将积分改为梯形法或使用解析式直接计算各段位移;
- 实际机械臂应用中,还需将位置序列按周期下发至伺服驱动器,本案例聚焦于轨迹规划部分。