4、单电机速度环PID控制(上):PID控制理论入门(P/I/D作用)、位置式PID与增量式PID区别、C语言实现增量式PID控制器
各位同学,欢迎来到第四讲。
从这节课开始,我们正式进入PID控制的世界。说实话,PID是电机控制里最基础、也最实用的算法。我做了十几年电机控制,遇到过各种花里胡哨的控制方案,但最后发现,90%的工程问题,一个调好的PID就能解决。
今天我们先讲理论,再讲代码。别急,我会尽量把枯燥的东西讲得接地气一点。
4.1 PID到底在干什么?
先问大家一个问题:你让电机转1000转/分,它凭什么能稳定在1000转?
说白了,PID就是一个“纠偏”的算法。它不断看当前转速和目标转速之间的差距,然后决定给电机加多少电。
这个差距,我们叫它“误差”,用 e(t) 表示。
PID的三个字母,代表三种不同的纠偏方式:
- P(比例):看现在误差有多大,误差大就猛加力,误差小就少加力。
- I(积分):看过去误差累积了多少,专门对付那些“一直差一点点”的情况。
- D(微分):预测未来误差的变化趋势,提前刹车或加速。
嗯,这里要注意:D不是必须的。很多场合只用PI就够了。我在做风机控制时,就只用PI,因为D对噪声太敏感了。
4.2 比例作用(P)——最直接的纠偏
比例控制是最直观的。公式很简单:
u(t) = Kp * e(t)
Kp是比例系数。e(t)是当前误差。
举个例子:目标转速1000,当前800,误差200。如果Kp=0.1,那输出就是20。如果当前变成950,误差50,输出就是5。
你看,误差越大,输出越大。这很符合直觉。
4.3 积分作用(I)——消除静差
纯比例控制有个毛病:它永远消除不了静差。
为什么会这样?你想想看,当误差很小的时候,比例输出也很小。如果这个输出刚好等于负载需要的力,那系统就停在那里了——误差永远存在。
积分作用就是来解决这个问题的。它把过去所有的误差累加起来:
u(t) = Ki * ∫ e(t) dt
只要误差还在,积分项就会一直增长,直到输出足够大,把误差彻底吃掉。
我个人习惯,在速度环里积分时间常数一般设在0.1秒到1秒之间。太小了容易超调,太大了响应太慢。
4.4 微分作用(D)——提前预判
微分看的是误差的变化率:
u(t) = Kd * de(t)/dt
如果误差在快速减小(比如转速正在逼近目标),微分项会输出一个负值,相当于提前刹车,防止超调。
如果误差在快速增大(比如突然加载),微分项会输出一个正值,提前加速响应。
听起来很美好,对吧?但实际工程中,D是最难用的。因为微分对噪声极其敏感。传感器信号稍微抖一下,微分项就会剧烈跳动。
我在做伺服驱动器时,曾经因为D参数没调好,电机在高频抖动,整个机台都在共振。后来我加了一阶低通滤波器,才把问题压下去。
4.5 位置式PID vs 增量式PID
这是面试常考题,也是实际开发必须搞清楚的。
位置式PID:直接计算完整的控制量 u(k)。公式是:
u(k) = Kp*e(k) + Ki*∑e(i) + Kd*[e(k) - e(k-1)]
它的问题是:积分项需要累加所有历史误差。一旦积分饱和,或者控制器重启,输出会跳变。
增量式PID:只计算控制量的变化量 Δu(k)。公式是:
Δu(k) = Kp*[e(k)-e(k-1)] + Ki*e(k) + Kd*[e(k)-2e(k-1)+e(k-2)]
实际输出:u(k) = u(k-1) + Δu(k)
增量式的优点很明显:
- 没有积分累加,不会积分饱和
- 输出变化平滑,不会突变
- 容易实现手动/自动切换
我个人在电机速度环里,几乎只用增量式。位置式我一般只在位置环或者需要绝对输出值的场合才用。
| 对比项 | 位置式PID | 增量式PID |
|---|---|---|
| 输出含义 | 直接控制量(如占空比) | 控制量的增量 |
| 积分处理 | 需累加所有误差 | 无积分累加项 |
| 积分饱和风险 | 高 | 低 |
| 输出突变风险 | 高(重启或切换时) | 低 |
| 适用场景 | 位置环、需要绝对输出 | 速度环、电流环 |
4.6 C语言实现增量式PID控制器
好了,理论讲完了,咱们直接上代码。这是我个人常用的一个结构体实现:
typedef struct {
float Kp; // 比例系数
float Ki; // 积分系数
float Kd; // 微分系数
float target; // 目标值
float last_error; // 上一次误差 e(k-1)
float prev_error; // 上上次误差 e(k-2)
float output; // 当前输出
} PID_Inc_t;
// 初始化PID
void PID_Inc_Init(PID_Inc_t *pid, float kp, float ki, float kd) {
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->target = 0.0f;
pid->last_error = 0.0f;
pid->prev_error = 0.0f;
pid->output = 0.0f;
}
// 增量式PID计算
float PID_Inc_Calc(PID_Inc_t *pid, float current) {
float error = pid->target - current;
// 计算增量
float delta = pid->Kp * (error - pid->last_error)
+ pid->Ki * error
+ pid->Kd * (error - 2.0f*pid->last_error + pid->prev_error);
// 更新输出
pid->output += delta;
// 更新历史误差
pid->prev_error = pid->last_error;
pid->last_error = error;
return pid->output;
}
- 先调用 PID_Inc_Init 初始化参数
- 在定时中断里,每隔固定周期调用 PID_Inc_Calc
- 返回的 output 直接作为PWM占空比或电流给定
- 注意:Ki和Kd是积分系数和微分系数,不是时间常数
4.7 小结
今天的内容,说白了就是三件事:
- P是看现在,I是看过去,D是看未来
- 增量式比位置式更适合电机速度环
- 代码实现其实就十几行,关键是参数要调好
下一讲,我们会深入讲PID参数整定方法,包括工程上最实用的“试凑法”和“临界比例度法”。到时候我会拿一个实际电机,手把手教大家调参。
今天就到这里。代码可以先跑起来,有问题咱们下节课聊。