3. 梯形速度规划:原理、实现、优缺点分析

梯形速度规划,这个名字听起来挺唬人。其实说白了,就是让机器人手臂的速度像爬楼梯一样——先匀加速,再匀速,最后匀减速。我在刚入行那会儿,第一次看到这个名词,还以为是什么高深的数学变换。后来发现,它就是最朴素、最实用的运动控制方法之一。

3.1 核心原理:三段式运动

为什么叫梯形?你想想看,把速度随时间的变化画出来,形状就像一个梯形。整个过程分三段:

  • 加速段:从零开始,以恒定加速度加速到目标速度
  • 匀速段:保持目标速度运行一段时间
  • 减速段:以恒定减速度减速到零

嗯,这里要注意:加速和减速的加速度大小可以不一样。我在项目中遇到过,有时候为了减少冲击,会把减速度设得比加速度小一些,让末端执行器更平稳地停下来。

核心公式

加速段时间:t₁ = v_max / a

减速段时间:t₃ = v_max / d

匀速段时间:t₂ = (总位移 - 加速段位移 - 减速段位移) / v_max

3.2 流程图:梯形速度规划的核心逻辑

下面这张图,是我自己画的一个决策流程。每次做梯形规划,脑子里都会过一遍这个逻辑:

开始规划 输入:起点、终点、v_max、a、d 能否达到 v_max? 三段式规划 加速+匀速+减速 三角形规划 只有加速+减速 计算 t₁、t₂、t₃,生成位置-时间曲线 实时插补:每个控制周期计算目标位置 输出:位置序列 规划完成

3.3 代码实现:一个可用的梯形规划器

下面是我在实际项目中用过的梯形规划代码。我习惯用C语言写底层控制,这里给你一个精简版:

typedef struct {
    float q_start;    // 起始位置
    float q_end;      // 终点位置
    float v_max;      // 最大速度
    float a;          // 加速度
    float d;          // 减速度
    float t1, t2, t3; // 三段时间
    float total_time; // 总时间
    float s1, s2, s3; // 三段位移
} TrapezoidProfile;

void Trapezoid_Init(TrapezoidProfile *prof) {
    float s_total = prof->q_end - prof->q_start;
    float s_acc = 0.5f * prof->v_max * prof->v_max / prof->a;
    float s_dec = 0.5f * prof->v_max * prof->v_max / prof->d;
    
    if (s_acc + s_dec <= fabs(s_total)) {
        // 三段式:能达到最大速度
        prof->t1 = prof->v_max / prof->a;
        prof->t3 = prof->v_max / prof->d;
        prof->s1 = 0.5f * prof->a * prof->t1 * prof->t1;
        prof->s3 = 0.5f * prof->d * prof->t3 * prof->t3;
        prof->s2 = fabs(s_total) - prof->s1 - prof->s3;
        prof->t2 = prof->s2 / prof->v_max;
    } else {
        // 三角形:达不到最大速度
        float v_peak = sqrt(2.0f * fabs(s_total) * 
                          (prof->a * prof->d) / (prof->a + prof->d));
        prof->t1 = v_peak / prof->a;
        prof->t3 = v_peak / prof->d;
        prof->t2 = 0.0f;
        prof->s1 = 0.5f * prof->a * prof->t1 * prof->t1;
        prof->s3 = 0.5f * prof->d * prof->t3 * prof->t3;
        prof->s2 = 0.0f;
    }
    prof->total_time = prof->t1 + prof->t2 + prof->t3;
}

float Trapezoid_GetPosition(TrapezoidProfile *prof, float t) {
    float s = 0.0f;
    if (t <= prof->t1) {
        // 加速段
        s = 0.5f * prof->a * t * t;
    } else if (t <= prof->t1 + prof->t2) {
        // 匀速段
        s = prof->s1 + prof->v_max * (t - prof->t1);
    } else if (t <= prof->total_time) {
        // 减速段
        float t_dec = t - prof->t1 - prof->t2;
        s = prof->s1 + prof->s2 + prof->v_max * t_dec - 0.5f * prof->d * t_dec * t_dec;
    } else {
        s = prof->s1 + prof->s2 + prof->s3;
    }
    return prof->q_start + (prof->q_end > prof->q_start ? s : -s);
}

我的经验:这段代码我用了好几年。有一个坑——s_total 的正负号一定要处理好。我曾经因为忘了取绝对值,导致机器人反向运动时规划出错,差点撞到夹具。从那以后,我所有位置计算都先取绝对值,最后再根据方向恢复符号。

3.4 优缺点分析

梯形规划不是万能的。我把它优缺点列出来,你心里有个数:

维度 优点 缺点
计算量 极低。只有几个乘除和判断,单片机都能跑
实现难度 简单。半小时就能写出来 边界条件(如距离太短)需要额外处理
运动平滑性 速度连续,没有突变 加速度在拐点处突变,产生冲击力
适用场景 点对点运动、传送带抓取、码垛 高精度轨迹跟踪、高速加工不适合
实时性 优秀。每个周期只需一次函数调用

注意:梯形规划最大的问题在于加速度不连续。在加速段结束的瞬间,加速度从 a 跳变到 0。这个跳变会引起机械振动。如果你的机器人负载比较大,或者对振动敏感,建议考虑 S 形规划。

3.5 什么时候用梯形?什么时候换别的?

我个人习惯这样判断:

  • 用梯形:负载小、速度慢、对精度要求不高的场合。比如小型桌面机械臂、教育机器人。我做过一个捡球机器人,用的就是梯形规划,跑得很稳。
  • 换S形:负载大、速度快、或者末端有精密操作。比如焊接、涂胶、高速分拣。梯形规划在这些场合容易抖,S形虽然计算量大一点,但值得。

你想想看,如果你的机器人手臂上装了一个装满水的杯子,用梯形规划急停,水肯定会洒出来。这就是加速度突变带来的问题。

3.6 避坑指南

最后,分享几个我踩过的坑:

  • 速度上限设得太高:我曾经把 v_max 设得比电机能跑到的速度还大,结果规划出来的时间全是负的。一定要做限幅。
  • 加速度和减速度不匹配:如果 a 和 d 相差太大,梯形会变成不对称形状。有时候这不是坏事,但你要心里有数。
  • 忘记处理零位移:如果起点和终点一样,梯形规划会出问题。我习惯在规划前加一个判断,位移为零就直接返回。

梯形速度规划,说白了就是运动控制里的"Hello World"。它简单、可靠、容易调试。虽然现在有更高级的规划方法,但我建议每个做机器人控制的人,都亲手写一遍梯形规划。这个过程会让你对运动控制有最直观的理解。


专注资料整理