3. 直线插补S曲线加减速

任务目标

在直线插补中,若速度阶跃变化会产生冲击。本案例实现S曲线加减速控制:规划7段式速度曲线(加加速、匀加速、减加速、匀速、加减速、匀减速、减减速),并据此生成平滑的位置序列,使机械臂从起点运动到终点时速度连续且加速度无突变。

实现方案

输入:起点坐标、终点坐标、最大速度 \(v_{max}\)、最大加速度 \(a_{max}\)、加加速度 \(j_{max}\)、插补周期 \(T_s\)。核心逻辑分三步:

  1. 计算各运动阶段的时间与位移;
  2. 生成速度曲线 \(v(t)\);
  3. 对速度积分得到位置序列。

为简化,假设加减速对称且距离足够长,能到达最大速度(即存在匀速段)。若距离不足,需缩短匀速段时间,本案例代码中已处理该边界。

关键完整代码

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
S曲线加减速速度规划(7段式) 时间 t 速度 v 加加速 匀加速 减加速 匀速 加减速 匀减速 减减速 v_max 速度曲线 v(t)

关键说明

  • 代码中采用欧拉法对加速度积分获得速度与位置,当 \(T_s=1\text{ms}\) 时终点误差小于 \(10^{-3}\) mm,满足一般工业插补精度;
  • 若需更高精度,可将积分改为梯形法或使用解析式直接计算各段位移;
  • 实际机械臂应用中,还需将位置序列按周期下发至伺服驱动器,本案例聚焦于轨迹规划部分。