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。

你看,误差越大,输出越大。这很符合直觉。

我曾经踩过的坑:Kp太大,电机会剧烈震荡,甚至啸叫。Kp太小,响应慢得像蜗牛。调Kp的时候,我习惯先给一个保守值,然后慢慢往上加,直到出现轻微震荡,再退回一点。

4.3 积分作用(I)——消除静差

纯比例控制有个毛病:它永远消除不了静差。

为什么会这样?你想想看,当误差很小的时候,比例输出也很小。如果这个输出刚好等于负载需要的力,那系统就停在那里了——误差永远存在。

积分作用就是来解决这个问题的。它把过去所有的误差累加起来:

u(t) = Ki * ∫ e(t) dt

只要误差还在,积分项就会一直增长,直到输出足够大,把误差彻底吃掉。

我个人习惯,在速度环里积分时间常数一般设在0.1秒到1秒之间。太小了容易超调,太大了响应太慢。

小技巧:积分饱和是个大问题。当电机被堵转时,误差一直很大,积分项会累积到非常大。等堵转解除,电机就会猛地冲出去。我一般会加一个积分限幅,或者用抗积分饱和算法。

4.4 微分作用(D)——提前预判

微分看的是误差的变化率:

u(t) = Kd * de(t)/dt

如果误差在快速减小(比如转速正在逼近目标),微分项会输出一个负值,相当于提前刹车,防止超调。

如果误差在快速增大(比如突然加载),微分项会输出一个正值,提前加速响应。

听起来很美好,对吧?但实际工程中,D是最难用的。因为微分对噪声极其敏感。传感器信号稍微抖一下,微分项就会剧烈跳动。

我在做伺服驱动器时,曾经因为D参数没调好,电机在高频抖动,整个机台都在共振。后来我加了一阶低通滤波器,才把问题压下去。

我的建议:初学者先调好PI,再考虑加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;
}
使用说明:
  1. 先调用 PID_Inc_Init 初始化参数
  2. 在定时中断里,每隔固定周期调用 PID_Inc_Calc
  3. 返回的 output 直接作为PWM占空比或电流给定
  4. 注意:Ki和Kd是积分系数和微分系数,不是时间常数
避坑指南:我曾经在STM32上跑这个代码,发现输出一直在震荡。查了半天,原来是定时器中断周期是1ms,但PID计算周期我设成了10ms。记住:采样周期必须固定,而且要和系数匹配。Ki = Kp * T / Ti,其中T是采样周期,Ti是积分时间常数。

4.7 小结

今天的内容,说白了就是三件事:

  • P是看现在,I是看过去,D是看未来
  • 增量式比位置式更适合电机速度环
  • 代码实现其实就十几行,关键是参数要调好

下一讲,我们会深入讲PID参数整定方法,包括工程上最实用的“试凑法”和“临界比例度法”。到时候我会拿一个实际电机,手把手教大家调参。

今天就到这里。代码可以先跑起来,有问题咱们下节课聊。