梯形速度规划(T型曲线):原理与数学描述

梯形速度规划,圈里人常叫它T型曲线。说白了,就是让机器人先匀加速,再匀速,最后匀减速。你想想看,这像不像一个梯形?名字就是这么来的。

我刚开始做运动控制那会儿,觉得这东西太简单了。后来踩了不少坑才发现,越是基础的东西,越要搞透彻。今天咱们就把T型曲线掰开了揉碎了讲清楚。

为什么需要T型曲线?

直接给电机发目标位置不行吗?当然不行。你试试让机器人从静止瞬间跳到1m/s,结果就是——机械结构哐当一声,搞不好直接散架。加速度突变会产生无穷大的加加速度(Jerk),这在物理世界是不存在的。

T型曲线解决了两个核心问题:

  • 限制最大速度——不让机器人跑太快
  • 限制最大加速度——不让机器人启动/停止太猛

嗯,就这么简单。但实现起来,细节可不少。

数学描述:三段式结构

T型曲线把整个运动过程切成三段:

阶段 时间区间 加速度 速度变化
加速段 0 ≤ t < t₁ a = a_max 0 → v_max
匀速段 t₁ ≤ t < t₂ a = 0 v = v_max
减速段 t₂ ≤ t ≤ t₃ a = -a_max v_max → 0

这里有个关键点:加速段和减速段的时间通常是对称的。除非你刻意设置不对称的加减速,否则默认就是对称的。我个人习惯用对称的,好算,也好调。

核心公式推导

假设总位移为S,最大速度为v_max,最大加速度为a_max。那么:

加速段时间: t_a = v_max / a_max

加速段位移: S_a = 0.5 × a_max × t_a² = v_max² / (2 × a_max)

减速段位移: S_d = S_a(对称情况下)

匀速段位移: S_c = S - S_a - S_d

匀速段时间: t_c = S_c / v_max

总时间: T = 2 × t_a + t_c

重要提醒: 如果S太小,连加速段和减速段都塞不下,那就没有匀速段了。这种情况叫「三角形速度规划」,是T型曲线的一个特例。我在项目里遇到过好几次,新手容易忽略。

加速段/匀速段/减速段计算

咱们分阶段来看位置和速度的计算公式。假设起始位置为0,起始速度为0。

加速段(0 ≤ t < t_a)

a(t) = a_max
v(t) = a_max × t
p(t) = 0.5 × a_max × t²

匀速段(t_a ≤ t < t_a + t_c)

a(t) = 0
v(t) = v_max
p(t) = S_a + v_max × (t - t_a)

减速段(t_a + t_c ≤ t ≤ T)

a(t) = -a_max
v(t) = v_max - a_max × (t - t_a - t_c)
p(t) = S_a + S_c + v_max × (t - t_a - t_c) - 0.5 × a_max × (t - t_a - t_c)²

我的小技巧: 实际写代码时,别直接用这些分段公式去算每个时刻的位置。更好的做法是先算好三个关键时间点t₁、t₂、t₃,然后根据当前时间判断在哪个段,再套对应的公式。这样代码结构清晰,不容易出错。

代码实现与仿真

下面我给出一份完整的Python实现。这份代码我用了好几年,从六轴机器人到AGV小车都跑过,算是经过实战检验的。

import numpy as np
import matplotlib.pyplot as plt

class TrapezoidalProfile:
    """梯形速度规划器"""
    
    def __init__(self, v_max, a_max):
        self.v_max = v_max      # 最大速度
        self.a_max = a_max      # 最大加速度
        
    def plan(self, S):
        """规划从0到S的运动"""
        # 计算加速段参数
        t_a = self.v_max / self.a_max
        S_a = 0.5 * self.a_max * t_a ** 2
        
        # 检查是否有匀速段
        if S < 2 * S_a:
            # 三角形规划:没有匀速段
            t_a = np.sqrt(S / self.a_max)
            t_c = 0
            S_a = 0.5 * self.a_max * t_a ** 2
            v_peak = self.a_max * t_a
        else:
            # 梯形规划:有匀速段
            t_c = (S - 2 * S_a) / self.v_max
            v_peak = self.v_max
        
        # 关键时间点
        t1 = t_a
        t2 = t_a + t_c
        t3 = 2 * t_a + t_c
        
        return t1, t2, t3, v_peak, S_a
    
    def get_state(self, t, S):
        """获取t时刻的位置、速度、加速度"""
        t1, t2, t3, v_peak, S_a = self.plan(S)
        
        if t < 0:
            return 0.0, 0.0, 0.0
        elif t < t1:
            # 加速段
            p = 0.5 * self.a_max * t ** 2
            v = self.a_max * t
            a = self.a_max
        elif t < t2:
            # 匀速段
            p = S_a + v_peak * (t - t1)
            v = v_peak
            a = 0.0
        elif t < t3:
            # 减速段
            t_dec = t - t2
            p = S_a + v_peak * t_c + v_peak * t_dec - 0.5 * self.a_max * t_dec ** 2
            v = v_peak - self.a_max * t_dec
            a = -self.a_max
        else:
            # 运动结束
            p = S
            v = 0.0
            a = 0.0
            
        return p, v, a


# 仿真示例
if __name__ == "__main__":
    # 参数设置
    v_max = 1.0    # m/s
    a_max = 0.5    # m/s²
    S = 5.0        # m
    
    # 创建规划器
    planner = TrapezoidalProfile(v_max, a_max)
    
    # 获取关键时间
    t1, t2, t3, v_peak, S_a = planner.plan(S)
    print(f"加速时间: {t1:.2f}s")
    print(f"匀速时间: {t2-t1:.2f}s")
    print(f"减速时间: {t3-t2:.2f}s")
    print(f"总时间: {t3:.2f}s")
    print(f"峰值速度: {v_peak:.2f}m/s")
    
    # 生成轨迹点
    dt = 0.01
    t_span = np.arange(0, t3 + dt, dt)
    positions = []
    velocities = []
    accelerations = []
    
    for t in t_span:
        p, v, a = planner.get_state(t, S)
        positions.append(p)
        velocities.append(v)
        accelerations.append(a)
    
    # 绘制结果
    fig, axes = plt.subplots(3, 1, figsize=(10, 8))
    
    axes[0].plot(t_span, positions)
    axes[0].set_ylabel('位置 (m)')
    axes[0].grid(True)
    
    axes[1].plot(t_span, velocities)
    axes[1].set_ylabel('速度 (m/s)')
    axes[1].grid(True)
    
    axes[2].plot(t_span, accelerations)
    axes[2].set_ylabel('加速度 (m/s²)')
    axes[2].set_xlabel('时间 (s)')
    axes[2].grid(True)
    
    plt.tight_layout()
    plt.show()

我曾经踩过的坑: 在嵌入式平台上跑这个代码时,浮点数精度问题导致时间判断出错。比如t刚好等于t1时,由于浮点误差可能被误判到匀速段。我的解决办法是加一个很小的epsilon(比如1e-6),判断时用t + epsilon来做区间比较。

T型曲线知识体系

下面这张图帮你理清T型曲线的核心逻辑:

梯形速度规划(T型曲线)知识体系 输入参数 总位移 S 最大速度 v_max 最大加速度 a_max S ≥ 2 × S_a ? (是否有匀速段) 梯形规划 有匀速段 三角形规划 无匀速段 输出:加速段时间 t₁ | 匀速段时间 t₂-t₁ | 减速段时间 t₃-t₂ 轨迹生成:位置 p(t) | 速度 v(t) | 加速度 a(t) 核心逻辑:根据位移大小自动选择梯形或三角形规划

实际应用中的注意事项

代码写完了,但离真正能用还差一步。我总结几个实战中容易出问题的地方:

  1. 速度单位要统一——我见过有人把mm/s和m/s混着用,结果机器人直接飞出去了。建议所有内部计算都用标准单位(m, m/s, m/s²)。
  2. 加减速不对称的情况——有些场景需要加速慢、减速快(比如抓取易碎品)。这时候要分别计算t_a和t_d,公式会复杂一些,但原理是一样的。
  3. 实时性要求——在实时系统里,别在中断里做大量浮点运算。我一般会提前算好轨迹表,运行时直接查表插值。
  4. 起点终点速度不为零——比如连续轨迹的衔接点。这个后面章节会详细讲,这里先提一嘴。

我的调试习惯: 每次写完轨迹规划代码,第一件事就是画速度曲线图。如果速度曲线不是完美的梯形或三角形,那肯定有bug。肉眼检查比看数据快多了。

好了,T型曲线就讲到这里。代码你可以直接拿去用,但建议先跑一遍仿真,看看曲线形状对不对。下一节咱们聊聊S型曲线——那个更平滑,但也更复杂。


公众号:蓝海资料掘金营,微信deep3321