4. PID控制基础:比例、积分、微分的作用、PID参数整定方法(Ziegler-Nichols)、数字PID实现

各位同学,欢迎来到第四讲。今天咱们聊聊PID控制。说实话,PID是电机控制里最经典、最实用的算法,没有之一。我做了十几年电机控制,90%以上的项目都是用PID解决的。你想想看,一个算法能火这么多年,一定有它的道理。

4.1 比例、积分、微分——三个“性格迥异”的兄弟

PID说白了就是三个环节的线性组合。每个环节都有自己的脾气,咱们一个一个来看。

4.1.1 比例环节(P)—— 反应最快的那个

比例环节,就是“看到偏差就干活”。偏差越大,输出越大。公式很简单:u(t) = Kp * e(t)

我在项目中遇到过这样一个情况:一个直流电机速度环,只加P的时候,电机响应特别快,但就是稳不住。目标转速1000转,它能在950到1050之间来回晃。这就是典型的“静差”问题。比例系数调大了,震荡更厉害;调小了,响应又太慢。

核心要点:P是“现在”的控制器。它看的是当前偏差,反应快,但消除不了稳态误差。

4.1.2 积分环节(I)—— 那个“记仇”的家伙

积分环节,说白了就是“算总账”。它把过去的偏差都累加起来。公式:u(t) = Ki * ∫e(t)dt

为什么要加I?因为P搞不定的静差,I能搞定。你想想看,只要偏差存在,积分就会一直累积,输出就会一直增加,直到偏差消失。这就是为什么加了I之后,系统能“精准”地到达目标值。

注意:积分是把双刃剑。积分太强,系统会“过冲”,甚至震荡。我曾经在一个温控项目里,积分系数设大了,温度直接冲过目标20度才回来,差点把设备烧了。

4.1.3 微分环节(D)—— 那个“预言家”

微分环节看的是偏差的变化趋势。公式:u(t) = Kd * de(t)/dt

它有什么用?举个例子:你开车看到前面红灯,如果只看距离(P),你会一直踩油门直到很近才刹车,肯定过冲。但如果能看到距离变化的速度(D),你就会提前减速,平稳停下。

D环节就是干这个的——预测未来,提前抑制。但要注意,微分对噪声特别敏感。我建议在电机控制中,如果编码器分辨率不高,或者速度信号有毛刺,最好别加D,或者加个低通滤波再微分。

4.2 PID参数整定方法——Ziegler-Nichols法

参数怎么调?这是每个工程师都会问的问题。我刚开始做控制的时候,也是靠“试”,调一个参数跑一次,效率极低。后来学了Ziegler-Nichols法,才算是入了门。

4.2.1 临界比例度法(Z-N第一法)

这个方法很简单,分三步走:

  1. 只保留P:把I和D都设为0,只留比例环节。
  2. 增大Kp直到系统等幅震荡:慢慢加大Kp,观察系统输出。当输出出现持续的、等幅的震荡时,记下此时的Kp值,称为临界增益Kcr。同时记下震荡周期Tcr。
  3. 查表计算:根据下表算出PID参数。
控制器类型 Kp Ki Kd
P 0.5 * Kcr - -
PI 0.45 * Kcr 1.2 * Kp / Tcr -
PID 0.6 * Kcr 2 * Kp / Tcr Kp * Tcr / 8

我的经验:Z-N法给出的参数通常偏“激进”,系统会有一定的过冲。实际应用中,我一般会把算出来的Kp再乘以0.7~0.8,让系统更稳健一些。

4.2.2 反应曲线法(Z-N第二法)

如果系统不允许震荡(比如某些工业过程),可以用这个方法。给系统一个阶跃输入,记录输出响应曲线,然后从曲线上提取两个参数:滞后时间L和时间常数T。查表计算即可。这个方法在电机控制中用得不多,我就不展开了。

4.3 数字PID实现——从连续到离散

模拟PID是连续的,但我们的MCU是数字的,只能处理离散信号。所以,必须把PID公式离散化。

4.3.1 位置式PID

直接对连续公式做离散化:

// 位置式PID
float pid_positional(float setpoint, float feedback) {
    float error = setpoint - feedback;
    
    // 比例项
    float p_out = Kp * error;
    
    // 积分项(累加)
    integral += error * dt;
    float i_out = Ki * integral;
    
    // 微分项
    float derivative = (error - last_error) / dt;
    float d_out = Kd * derivative;
    
    last_error = error;
    
    return p_out + i_out + d_out;
}

这个实现很直观,但有个问题:积分项会一直累加,如果长时间饱和,积分项会变得很大,导致系统响应变慢。这就是所谓的“积分饱和”问题。

避坑指南:我曾经在一个项目中,电机堵转了几秒钟,积分项累加到了天文数字。等电机恢复运行时,输出直接爆表,电机飞转。从那以后,我必加积分限幅和抗饱和处理。

4.3.2 增量式PID

增量式PID只计算输出的变化量,不计算绝对值。公式如下:

// 增量式PID
float pid_incremental(float setpoint, float feedback) {
    float error = setpoint - feedback;
    
    // 计算增量
    float delta_u = Kp * (error - last_error) 
                  + Ki * error * dt 
                  + Kd * (error - 2*last_error + last_last_error) / dt;
    
    last_last_error = last_error;
    last_error = error;
    
    // 累加输出
    output += delta_u;
    
    // 输出限幅
    if (output > max_output) output = max_output;
    if (output < min_output) output = min_output;
    
    return output;
}

增量式的优点很明显:没有积分累加问题,输出限幅也容易做。我个人的习惯是,在电机速度环和位置环中,优先使用增量式PID。

4.3.3 积分分离与抗饱和

再分享一个实用技巧:积分分离。当偏差很大时,暂时关闭积分,等偏差小了再开启。这样既能快速响应,又能保证精度。

// 积分分离PID
float pid_with_anti_windup(float setpoint, float feedback) {
    float error = setpoint - feedback;
    
    // 积分分离:偏差大于阈值时,不积分
    if (fabs(error) < INTEGRAL_THRESHOLD) {
        integral += error * dt;
    }
    
    // 抗积分饱和:限制积分范围
    if (integral > INTEGRAL_MAX) integral = INTEGRAL_MAX;
    if (integral < INTEGRAL_MIN) integral = INTEGRAL_MIN;
    
    float p_out = Kp * error;
    float i_out = Ki * integral;
    float d_out = Kd * (error - last_error) / dt;
    
    last_error = error;
    
    return p_out + i_out + d_out;
}

我的建议:在实际项目中,不要追求“完美”的PID参数。系统有扰动、有噪声、有非线性,能稳定运行、满足性能指标就够了。我见过太多工程师花一周时间调参数,最后发现加个滤波器效果更好。

好了,这一讲的内容就到这里。PID看似简单,但真正用好需要大量实践。下一讲,咱们会深入速度环和位置环的具体设计,到时候会用到今天讲的这些知识。记得动手写写代码,光看是学不会的。