7. 速度规划与加减速:梯形加减速、S形加减速、速度曲线生成

速度规划,说白了就是让电机“怎么跑”的问题。你想想看,如果一上来就给电机发最高速的脉冲,它肯定“哐”一下冲出去,然后要么丢步,要么机械结构直接震散架。我早年做一台三轴点胶机时就吃过这个亏——电机直接啸叫,胶水甩得到处都是。从那以后,我每次做运动控制,第一件事就是先把加减速曲线画出来。

这一章,咱们就聊聊两种最经典的加减速策略:梯形和S形。我会结合我在RTOS任务里的实际写法,把速度曲线生成这件事讲透。

7.1 梯形加减速:简单粗暴,但够用

梯形加减速,名字很形象。速度曲线像个梯形——匀加速、匀速、匀减速三段。这是最基础的算法,也是我入行时第一个手写的速度规划函数。

核心思想: 加速度恒定,速度线性变化。

公式:v(t) = v₀ + a·t

其中 v₀ 是初速度,a 是加速度,t 是时间。

梯形加减速的优点是计算量极小,适合在RTOS的中断服务函数里实时计算。缺点嘛,也很明显——加速度突变。在加速开始和结束的瞬间,加速度从0跳变到a,或者从a跳变到0,这会导致机械冲击。我做过一个测试,用梯形加减速驱动一个500g的负载,在高速启停时,平台末端会有肉眼可见的抖动。

不过,对于大多数对启停平稳性要求不高的场景,比如传送带、简单的XY平台,梯形加减速完全够用。我个人的习惯是:如果负载惯量比小于5,我就用梯形;超过5,我会考虑S形。

7.1.1 梯形加减速的RTOS任务实现

在RTOS里,我通常把速度规划放在一个周期任务里,比如每1ms执行一次。任务里维护一个状态机:加速、匀速、减速、停止。

// 梯形加减速状态机示例
typedef enum {
    ACCEL,
    CRUISE,
    DECEL,
    STOP
} MotionState;

void SpeedProfileTask(void *param) {
    MotionState state = ACCEL;
    float current_speed = 0.0f;
    float target_speed = 1000.0f; // 目标速度,单位:脉冲/秒
    float accel = 500.0f;         // 加速度
    float decel = 500.0f;         // 减速度
    float total_distance = 5000;  // 总距离,单位:脉冲
    float accel_distance, decel_distance, cruise_distance;

    // 预先计算三段距离
    accel_distance = (target_speed * target_speed) / (2 * accel);
    decel_distance = (target_speed * target_speed) / (2 * decel);
    cruise_distance = total_distance - accel_distance - decel_distance;

    float traveled = 0.0f;

    while (1) {
        switch (state) {
            case ACCEL:
                current_speed += accel * 0.001f; // 每1ms加速
                if (current_speed >= target_speed) {
                    current_speed = target_speed;
                    state = CRUISE;
                }
                break;
            case CRUISE:
                // 保持匀速
                if (traveled >= accel_distance + cruise_distance) {
                    state = DECEL;
                }
                break;
            case DECEL:
                current_speed -= decel * 0.001f;
                if (current_speed <= 0.0f) {
                    current_speed = 0.0f;
                    state = STOP;
                }
                break;
            case STOP:
                // 停止,等待下一个指令
                break;
        }
        // 根据current_speed计算脉冲间隔,发送给电机
        traveled += current_speed * 0.001f;
        vTaskDelay(pdMS_TO_TICKS(1));
    }
}

我的经验: 梯形加减速的加速度值不要设得太激进。我曾经为了赶节拍,把加速度设到电机额定值的90%,结果跑了半小时电机就过热保护了。建议留20%的余量。

7.2 S形加减速:丝滑,但计算量大

S形加减速,顾名思义,速度曲线像个拉长的S。它的核心是让加速度也平滑变化,而不是突变。说白了,就是加加速度(Jerk)是有限的。

为什么要用S形?我举个例子。有一次我做一台高精度点胶机,要求点胶头在转弯时不能有丝毫抖动,否则胶水会拉丝。梯形加减速根本不行,一减速一加速,胶水就断断续续。换成S形加减速后,问题立刻解决。嗯,这里要注意:S形加减速不是万能的,它主要解决的是“加速度突变”带来的冲击问题。

7.2.1 S形加减速的数学原理

S形加减速通常用三段式或七段式。七段式最常用:加加速、匀加速、减加速、匀速、加减速、匀减速、减减速。你看,光是名字就比梯形复杂多了。

阶段 加速度变化 速度变化
加加速 从0增加到最大加速度 缓慢增加
匀加速 保持最大加速度 线性增加
减加速 从最大加速度减到0 缓慢增加至匀速
匀速 0 恒定
加减速 从0增加到最大减速度 缓慢减少
匀减速 保持最大减速度 线性减少
减减速 从最大减速度减到0 缓慢减少至0

公式上,S形加减速需要引入加加速度J(Jerk)。速度是加速度的积分,加速度是加加速度的积分。所以:

v(t) = v₀ + ∫a(t)dt = v₀ + ∫(∫J dt)dt

看着有点绕?没关系,实际写代码时,我们通常用查表法或者分段多项式来算,不会真的去解积分方程。

7.2.2 S形加减速的RTOS实现

在RTOS里实现S形加减速,我建议用查表法。预先算好一个周期的速度曲线表,运行时直接查表插值。这样既保证了实时性,又避免了在中断里做浮点运算。

// S形加减速查表法示例
#define TABLE_SIZE 1000
float speed_table[TABLE_SIZE]; // 预先计算好的速度曲线表

void InitSShapeTable(float max_speed, float accel, float jerk) {
    // 这里用简化的S形曲线:正弦型S形
    for (int i = 0; i < TABLE_SIZE; i++) {
        float t = (float)i / TABLE_SIZE;
        // 使用正弦函数模拟S形
        speed_table[i] = max_speed * (0.5f - 0.5f * cosf(M_PI * t));
    }
}

float GetSShapeSpeed(float progress) {
    // progress: 0.0 ~ 1.0
    int index = (int)(progress * (TABLE_SIZE - 1));
    return speed_table[index];
}

注意: 查表法虽然快,但精度受限于表的大小。我建议表的大小至少1024点,否则在低速段会出现明显的阶梯感。另外,如果总距离很短,可能还没加速到最高速就要减速了,这时候需要做“三角型”S形处理——也就是没有匀速段。

7.3 速度曲线生成:从理论到代码

速度曲线生成,说白了就是把上面讲的加减速算法,变成一个可以在RTOS里跑起来的“速度发生器”。我个人习惯把它封装成一个模块,对外只提供三个接口:初始化、设置参数、获取当前速度。

为什么要封装?因为在实际项目中,速度规划往往不是孤立的。它要和位置环、电流环配合。我见过很多新手把速度规划代码和电机驱动代码混在一起写,最后改一个参数要改三个地方,非常痛苦。

7.3.1 速度曲线生成的通用流程

  1. 参数计算阶段: 根据目标速度、加速度、加加速度、总距离,计算出各段的长度和时间。
  2. 实时查表阶段: 在RTOS周期任务中,根据当前已走距离或时间,查表得到当前速度。
  3. 速度输出阶段: 将速度值转换为脉冲频率或模拟电压,发送给电机驱动器。

这里有一个坑,我曾经踩过:在参数计算阶段,一定要判断“能不能跑到目标速度”。如果总距离太短,可能刚加速就要减速,这时候要自动降速。我管这个叫“三角型速度规划”。

避坑指南: 我曾经在一个项目中,没有做距离判断,直接按梯形加减速算。结果总距离只有100个脉冲,目标速度设了1000脉冲/秒。加速到一半发现距离不够了,电机直接冲过终点,造成了机械碰撞。从那以后,我每次做速度规划,第一件事就是算“最短加速距离”。

7.3.2 梯形 vs S形:怎么选?

我个人的选择标准是这样的:

  • 梯形加减速: 适合启停不频繁、对冲击不敏感的场景。比如传送带、风扇、水泵。计算量小,代码简单。
  • S形加减速: 适合高精度、高平稳性要求的场景。比如数控机床、机器人、点胶机。虽然计算量大,但效果确实好。

你想想看,如果做一台玩具小车,梯形加减速完全够用。但如果做一台医疗注射泵,你敢用梯形吗?针头一抖,病人就遭罪了。

7.4 知识体系与核心逻辑

下面这张SVG图,是我自己总结的速度规划知识体系。你看一眼就能明白整个逻辑链条。

速度规划与加减速知识体系 速度规划 梯形加减速 S形加减速 加速度恒定,速度线性变化 计算量小,适合低要求场景 缺点:加速度突变,有冲击 加加速度有限,加速度平滑 适合高精度、高平稳性场景 缺点:计算量大,实现复杂 RTOS任务实现 查表法 / 状态机 / 实时计算

这张图把梯形和S形的核心特点、优缺点、以及RTOS实现方式都串起来了。你写代码前,先对着这张图理清思路,会少走很多弯路。

我的建议: 刚开始做速度规划时,先用梯形加减速把整个流程跑通。等你对RTOS任务调度、脉冲生成这些底层机制熟悉了,再换成S形。一口吃不成胖子,运动控制尤其如此。