4、运动控制核心算法模块:轨迹规划(梯形、S形)、PID控制算法、前馈控制、插补算法

各位工程师朋友,咱们今天聊聊运动控制里最核心的几个算法模块。说实话,搞了这么多年运动控制,我越来越觉得这些算法就像厨师的刀工——基本功扎实了,什么菜都能做。梯形、S形轨迹规划,PID控制,前馈补偿,还有插补算法,这四样东西你吃透了,大部分运动控制项目都能拿下。

4.1 轨迹规划:让运动“有章可循”

轨迹规划说白了,就是告诉电机“你怎么走”。不是简单地从A到B,而是要考虑速度、加速度、甚至加加速度。我见过太多新手直接给个阶跃信号,结果机器抖得像筛子一样。

4.1.1 梯形速度规划

梯形规划是最基础的。它分三段:加速、匀速、减速。形状像个梯形,所以叫这名。

核心公式:

  • 加速段:v(t) = v₀ + a·t
  • 匀速段:v(t) = v_max
  • 减速段:v(t) = v_max - a·(t - t₁)

嗯,这里要注意:梯形规划的加速度是突变的。从0直接跳到a,再从a直接跳到0。这会导致什么?冲击!

避坑指南:我曾经在一个高速贴片机项目里用了梯形规划,结果每次加减速时,机器都会剧烈抖动。后来发现是加速度突变导致的机械共振。从那以后,我对梯形规划就格外小心了。

梯形规划的代码实现其实很简单:

// 梯形速度规划核心逻辑
float calcTrapezoidalVelocity(float t, float v_max, float a, float total_dist) {
    float t_acc = v_max / a;          // 加速时间
    float t_const = total_dist / v_max - t_acc;  // 匀速时间
    
    if (t < t_acc) {
        return a * t;                 // 加速段
    } else if (t < t_acc + t_const) {
        return v_max;                 // 匀速段
    } else {
        return v_max - a * (t - t_acc - t_const); // 减速段
    }
}

4.1.2 S形速度规划

S形规划就聪明多了。它把加速度也做了平滑处理,分成加加速、减加速、匀速、加减速、减减速五段或七段。你想想看,加速度连续变化,冲击自然就小了。

我个人习惯用七段式S形规划,虽然计算量大了点,但效果确实好。核心思想是让加加速度(Jerk)保持恒定。

经验之谈:我建议你在对振动敏感的场合(比如精密加工、晶圆搬运)一定要用S形规划。虽然代码复杂些,但换来的平稳性绝对值。

// S形规划中的加加速度计算
float calcJerkLimitedVelocity(float t, float v_max, float a_max, float jerk) {
    // 七段式S形规划
    // 段1: 加加速 (t0~t1)
    // 段2: 匀加速 (t1~t2)  
    // 段3: 减加速 (t2~t3)
    // 段4: 匀速   (t3~t4)
    // 段5: 加减速 (t4~t5)
    // 段6: 匀减速 (t5~t6)
    // 段7: 减减速 (t6~t7)
    
    float t_j = a_max / jerk;  // 加加速时间
    // ... 完整实现需要分段判断
}

4.2 PID控制算法:运动控制的“定海神针”

PID控制,搞控制的没人不知道。但说实话,会用和用得好是两码事。PID的核心就三个字:比、积、微。

参数 作用 调大后的影响
Kp(比例) 快速响应误差 响应变快,但可能超调
Ki(积分) 消除稳态误差 消除静差,但可能振荡
Kd(微分) 预测误差趋势 抑制超调,但放大噪声

位置式PID公式:

u(k) = Kp·e(k) + Ki·Σe(i) + Kd·[e(k) - e(k-1)]

为什么说PID是定海神针?因为它简单、可靠、可解释。我在一个半导体设备项目里,现场调试时发现电机有低频抖动,排查了半天,最后发现是积分项饱和了。加了个积分限幅,问题就解决了。

我曾经踩过的坑:积分饱和!当误差持续存在时,积分项会一直累积,导致控制器输出饱和。等误差反向时,积分项还要花时间“退回来”,造成严重的超调。解决办法:加积分限幅,或者用抗积分饱和算法。

// 带积分限幅的增量式PID
typedef struct {
    float Kp, Ki, Kd;
    float integral;
    float prev_error;
    float integral_limit;  // 积分限幅
} PIDController;

float PID_Update(PIDController *pid, float setpoint, float feedback) {
    float error = setpoint - feedback;
    
    // 积分项带限幅
    pid->integral += error;
    if (pid->integral > pid->integral_limit) 
        pid->integral = pid->integral_limit;
    if (pid->integral < -pid->integral_limit) 
        pid->integral = -pid->integral_limit;
    
    float output = pid->Kp * error 
                 + pid->Ki * pid->integral 
                 + pid->Kd * (error - pid->prev_error);
    
    pid->prev_error = error;
    return output;
}

4.3 前馈控制:让系统“未卜先知”

PID是“事后诸葛亮”——等误差出来了才去纠正。前馈控制就不一样了,它提前知道你要干什么,直接给一个预判信号。

你想想看,如果系统有较大的惯性或滞后,光靠PID肯定不够。这时候前馈就派上用场了。

前馈+反馈复合控制:

u_total = u_feedforward + u_feedback

其中 u_feedforward = 系统逆模型 × 期望轨迹

举个例子:一个伺服系统,你希望它按S形曲线运动。PID会等位置误差出现再去调整,而前馈直接根据S形曲线的速度和加速度,提前给出对应的电流指令。

我的建议:前馈控制特别适合做轨迹跟踪。比如激光切割、3D打印这些需要高精度轨迹的场合。我做过一个项目,加了速度前馈后,跟踪误差从0.5mm降到了0.05mm,效果立竿见影。

// 速度前馈实现
float velocityFeedforward(float target_velocity, float Kvff) {
    return Kvff * target_velocity;  // Kvff是速度前馈系数
}

// 加速度前馈(用于补偿惯性力)
float accelerationFeedforward(float target_acceleration, float Kaff) {
    return Kaff * target_acceleration;  // Kaff是加速度前馈系数
}

4.4 插补算法:让运动“连点成线”

插补,就是把离散的路径点变成连续的运动轨迹。常见的插补方式有直线插补、圆弧插补,还有更复杂的样条插补。

我记得刚入行时,总觉得插补就是简单的线性插值。直到做五轴联动项目,才发现事情没那么简单。

4.4.1 直线插补

直线插补是最基础的。给定起点和终点,中间的点按比例分配。

// 直线插补
typedef struct {
    float x_start, y_start;
    float x_end, y_end;
    float total_length;
} LineInterpolator;

void LineInterp_GetPoint(LineInterpolator *line, float t, float *x, float *y) {
    // t 范围 0~1
    *x = line->x_start + t * (line->x_end - line->x_start);
    *y = line->y_start + t * (line->y_end - line->y_start);
}

4.4.2 圆弧插补

圆弧插补稍微复杂些。需要知道圆心、半径、起始角和终止角。

圆弧插补公式:

x(θ) = x_center + R·cos(θ)

y(θ) = y_center + R·sin(θ)

其中 θ 从 θ_start 到 θ_end 线性变化

注意:圆弧插补时,速度规划要基于弧长,而不是角度。否则在半径不同的圆弧上,角速度相同但线速度不同,会导致加工表面粗糙度不一致。

4.4.3 插补与轨迹规划的配合

这里有个关键点:插补和轨迹规划是配合工作的。轨迹规划决定“速度怎么变”,插补决定“路径怎么走”。两者要协同。

我习惯的做法是:先做轨迹规划,算出每个时刻的目标速度;然后根据这个速度,在插补路径上找到对应的位置点。

// 插补与轨迹规划配合
void MotionPlan_GetCommand(float t, float *pos, float *vel) {
    // 1. 轨迹规划:计算当前时刻的目标速度
    float target_vel = TrajectoryPlan_GetVelocity(t);
    
    // 2. 根据速度计算路径位置
    float path_length = IntegrateVelocity(target_vel, t);
    
    // 3. 插补:根据路径长度获取位置
    Interpolator_GetPosition(path_length, pos);
    
    // 4. 输出速度指令给前馈
    *vel = target_vel;
}

4.5 知识体系总览

说了这么多,我画张图帮你理清思路。这四大模块的关系是这样的:

运动控制核心算法模块 轨迹规划 梯形规划 → 速度突变 S形规划 → 速度平滑 输出:速度曲线 v(t) 决定“怎么走” 插补算法 直线插补 圆弧插补 样条插补 决定“走哪条路” 速度指令 PID控制 比例(P):快速响应 积分(I):消除静差 微分(D):抑制超调 反馈控制,消除误差 前馈控制 速度前馈 加速度前馈 力矩前馈 前馈补偿,提前预判 位置指令 速度/加速度 轨迹规划 + 插补 → 生成期望轨迹 → PID + 前馈 → 驱动执行器

从这张图你能看出来,轨迹规划和插补负责“生成期望”,PID和前馈负责“跟踪期望”。四者缺一不可。

最后说一句:这些算法单独拿出来都不难,难的是把它们组合成一个稳定、高效的系统。我建议你在实际项目中,先从梯形+PID开始,跑通了再加S形,然后逐步加入前馈。一步一个脚印,比什么都强。


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