4、经典PID控制:PID原理、在发动机控制中的应用、参数整定方法(Ziegler-Nichols等)

各位同学,今天我们聊聊PID控制。说实话,在航空发动机控制这个圈子里混了十几年,PID是我打交道最多的算法,没有之一。别看现在什么现代控制、智能控制吹得天花乱坠,真到了发动机台架试车或者飞控系统里,PID依然是压舱石。

4.1 PID控制原理:说白了就是三个字

PID,全称比例-积分-微分控制。我习惯把它拆成三兄弟来看:

  • 比例(P):看现在。误差有多大,我就掰多大劲儿。你跑偏了1度,我立马给1度的修正量。
  • 积分(I):看过去。误差一直存在?那我慢慢累积力量,直到把它消除。比如发动机转速一直偏低0.5%,积分项就会持续增加油量。
  • 微分(D):看未来。误差变化快了?我提前踩刹车或者加油门,防止超调。说白了就是预测一下趋势。

数学表达式很简单:

u(t) = Kp * e(t) + Ki * ∫e(t)dt + Kd * de(t)/dt

其中u(t)是控制输出,e(t)是误差。嗯,这里要注意,实际工程中我们用的多是位置式PID或者增量式PID,后面我会讲到。

我的经验:刚入行时,我总觉得积分项越强越好,结果有一次在发动机慢车状态调试,积分饱和导致油门猛增,差点触发超温保护。后来我学乖了——积分项一定要加限幅和抗饱和处理。

4.2 在发动机控制中的应用场景

航空发动机控制,说白了就是管好燃油流量、可调叶片角度、放气活门这些执行机构。PID在这里面扮演什么角色?我举几个典型例子:

4.2.1 转速闭环控制

这是最经典的应用。给定一个目标转速(比如高压转子转速NH),通过调节燃油流量Wf来跟踪。我参与过一个项目,发动机在过渡态时转速波动很大,单纯靠P控制根本压不住,加了D之后才稳住。

4.2.2 温度限制保护

涡轮前温度T4是红线参数。我们通常用PID做温度限制器——当温度接近上限时,PID自动介入,把燃油流量往下拉。说白了就是“保命模式”。

4.2.3 可调静子叶片(VSV)控制

VSV角度需要随转速和压比调节。早期我用查表法,后来发现用PID做闭环控制,响应更快,而且能自适应发动机老化带来的偏差。

避坑指南:我曾经在VSV控制中直接用位置式PID,结果发现执行机构频繁抖动。后来改成增量式PID,输出变化更平滑,抖动问题就解决了。

4.3 参数整定方法:Ziegler-Nichols及其他

参数整定,说白了就是找Kp、Ki、Kd这三个数。很多新手喜欢一上来就调参,调了半天还是震荡。我建议先掌握经典方法。

4.3.1 Ziegler-Nichols频域法(闭环整定)

这个方法我用了很多年。步骤很简单:

  1. 先把Ki和Kd设成0,只留Kp。
  2. 慢慢增大Kp,直到系统输出出现等幅振荡。
  3. 记下此时的临界增益Ku和振荡周期Tu。
  4. 按下面表格计算参数:
控制器类型 Kp Ki Kd
P 0.5 * Ku - -
PI 0.45 * Ku 1.2 * Kp / Tu -
PID 0.6 * Ku 2 * Kp / Tu Kp * Tu / 8

注意:这个方法要求系统能稳定振荡。有些发动机模型在临界增益下会发散,这时候就别硬试了。我曾经在一个高增益的燃油计量阀上试过,结果振荡幅度越来越大,差点把执行机构烧了。

4.3.2 Ziegler-Nichols开环法(阶跃响应法)

这个方法更安全。给系统一个阶跃输入,记录响应曲线。找到滞后时间L和时间常数T,然后:

  • P控制器:Kp = T / L
  • PI控制器:Kp = 0.9 * T / L, Ki = 0.3 / L
  • PID控制器:Kp = 1.2 * T / L, Ki = 0.6 / L, Kd = 0.5 * L

我个人习惯用这个方法做初值,然后再微调。你想想看,发动机这种大惯性系统,开环阶跃响应很容易做,而且安全。

4.3.3 工程实用技巧

除了Z-N法,我还会用以下方法:

  • 试凑法:先调P到系统临界稳定,再加I消除静差,最后加D抑制超调。说白了就是“先粗调后细调”。
  • 衰减曲线法:让系统出现4:1衰减振荡,记录参数。这个方法比Z-N法更温和,适合发动机这种对超调敏感的系统。
  • 基于模型的自整定:现在很多数字控制器支持在线辨识参数。我最近在做一个项目,用递推最小二乘法在线估计发动机模型,然后自动计算PID参数。效果不错,但计算量稍大。

我的建议:初学者先别急着用高级方法。把Z-N法吃透,再结合试凑法,基本能解决90%的工程问题。我见过太多人一上来就搞什么模糊PID、神经网络PID,结果连基本参数都整定不好。

4.4 代码示例:增量式PID实现

下面是我常用的增量式PID代码,C语言风格,适合嵌入式实现:

typedef struct {
    float Kp, Ki, Kd;
    float error_prev;   // 上一次误差
    float error_prev2;  // 上上次误差
    float output;       // 当前输出
} PID_Controller;

void PID_Init(PID_Controller *pid, float kp, float ki, float kd) {
    pid->Kp = kp;
    pid->Ki = ki;
    pid->Kd = kd;
    pid->error_prev = 0;
    pid->error_prev2 = 0;
    pid->output = 0;
}

float PID_Update(PID_Controller *pid, float setpoint, float measurement) {
    float error = setpoint - measurement;
    // 增量式输出
    float delta = pid->Kp * (error - pid->error_prev) 
                + pid->Ki * error 
                + pid->Kd * (error - 2*pid->error_prev + pid->error_prev2);
    pid->output += delta;
    // 更新历史误差
    pid->error_prev2 = pid->error_prev;
    pid->error_prev = error;
    return pid->output;
}

使用提示:这段代码里我用了增量式,好处是输出不会突变,而且容易加限幅。实际使用时,记得在PID_Update后面加一句输出限幅,比如:if(pid->output > MAX_OUTPUT) pid->output = MAX_OUTPUT;

4.5 总结与思考

PID控制,说白了就是三个参数的博弈。在航空发动机领域,它之所以经久不衰,是因为简单、可靠、可解释。你想想看,一个飞控系统的安全认证有多严格?现代控制理论再漂亮,拿不出足够的验证数据,谁敢用?

我个人觉得,学PID控制,关键不是背公式,而是理解“反馈”的本质。发动机是一个非线性、时变的系统,但PID通过不断修正,硬是把它管得服服帖帖。这就是工程智慧。

下一章我们会讲更高级的现代控制方法,但请记住:先把PID玩透了,再谈别的。