4、控制理论核心:PID控制原理、串级PID结构设计、前馈控制与补偿、自适应控制基础
各位同学,欢迎来到第四章。前面我们聊了传感器、执行器,还有系统建模。但说实话,那些都是“硬件”和“基础”。真正让四轴飞得稳、飞得准的,是控制算法。这一章,我们直接切入核心——PID及其进阶玩法。
我个人习惯把控制算法比作“飞控的灵魂”。你硬件再好,算法不行,飞起来也是“醉汉”。反过来,算法调好了,哪怕硬件差点意思,也能飞得有模有样。今天我们就把这灵魂的四层功夫拆开揉碎。
本章核心脉络:从最基础的PID原理出发,理解为什么单环不够用,进而引出串级PID的结构优势。再通过前馈控制解决“响应慢”的痛点,最后用自适应控制来应对“参数变化”的难题。这是一条从“能飞”到“飞得稳”再到“飞得聪明”的进阶之路。
4.1 PID控制原理:飞控的“万金油”
PID,比例-积分-微分。说白了,就是根据“现在、过去、未来”的误差来算控制量。比例管现在,误差大就用力推;积分管过去,把累积的静差消掉;微分管未来,看到误差变大的趋势就提前刹车。
我在项目中遇到过最典型的例子:一个新手调飞机,只用了P和I,结果飞机总是来回晃。为什么?因为缺少D项来抑制超调。你想想看,就像你开车,光看距离踩油门(P),发现距离没到就猛踩,结果过了又猛刹,能不晃吗?加上D,就是让你提前预判,平稳减速。
代码实现其实很简单,但细节决定成败:
// 位置式PID(伪代码)
float pid_update(float setpoint, float measurement, float dt) {
float error = setpoint - measurement;
// 比例项
float p_out = Kp * error;
// 积分项(带限幅)
integral += error * dt;
integral = constrain(integral, -integral_limit, integral_limit);
float i_out = Ki * integral;
// 微分项(注意:对测量值微分,避免“微分爆炸”)
float derivative = (measurement - last_measurement) / dt;
float d_out = Kd * derivative;
last_measurement = measurement;
return p_out + i_out - d_out; // 注意微分项是减号!
}
避坑指南:我曾经在微分项上吃过亏。直接对误差求导,设定值突变时微分项会瞬间爆炸。后来我改成对测量值求导,世界就清净了。记住:微分是对“变化”敏感,不是对“误差”敏感。
4.2 串级PID结构设计:为什么单环不够用?
单环PID,就是直接拿角度误差算油门。这在理想环境下没问题。但现实是,飞机一遇到风,角度还没变,角速度先变了。单环PID要等到角度误差产生了才反应,黄花菜都凉了。
串级PID的思路很简单:内环控角速度,外环控角度。外环算出来的目标角速度,交给内环去追。内环跑得快(一般100-200Hz),专门对付高频扰动;外环跑得慢(50-100Hz),负责跟踪指令。
我建议你记住这个黄金法则:内环要“快准狠”,外环要“稳准柔”。
| 特性 | 外环(角度环) | 内环(角速度环) |
|---|---|---|
| 控制目标 | 跟踪期望角度 | 跟踪期望角速度 |
| 运行频率 | 50-100 Hz | 100-200 Hz |
| 主要作用 | 消除稳态误差 | 抑制扰动、提高响应 |
| 调参顺序 | 先调好内环,再调外环 | 先调P,再加D,最后加I |
| 典型K值 | Kp较小,Ki较大 | Kp较大,Kd适中 |
调参顺序很重要。我见过有人上来就调外环,结果内环没锁住,飞机像弹簧一样乱跳。正确的做法是:先锁死内环,再调外环。先把内环的P调到飞机不抖,再加D增加阻尼,最后加I消除静差。内环稳了,外环随便调调就很好飞。
4.3 前馈控制与补偿:让响应“快半拍”
PID有个天生的毛病:它是有误差才动。这就导致响应总是慢半拍。比如你要飞机快速倾斜30度,PID得先看到误差,然后慢慢追过去。前馈控制就是解决这个问题的。
前馈的思路是:我知道你要干什么,我先给你一个预判量。比如你要快速倾斜,我直接根据目标角速度算出一个基础油门增量,叠加到PID输出上。这样飞机一开始就有个“初速度”,PID只需要做微调就行。
说白了,前馈是“开环”的,它不依赖反馈。所以它不能单独用,必须和PID配合。我习惯把前馈比作“老司机预判”,PID是“新手司机纠偏”。两者结合,又快又稳。
// 带前馈的串级PID(伪代码)
float outer_loop(float target_angle, float current_angle) {
float angle_error = target_angle - current_angle;
float target_rate = Kp_angle * angle_error; // 外环输出目标角速度
return target_rate;
}
float inner_loop(float target_rate, float current_rate) {
float rate_error = target_rate - current_rate;
// 前馈项:根据目标角速度直接计算
float feedforward = Kff * target_rate;
// PID项
float p_term = Kp_rate * rate_error;
float d_term = Kd_rate * (current_rate - last_rate) / dt;
return feedforward + p_term - d_term;
}
注意:前馈系数Kff不能太大。我曾经在一次竞速项目中,为了追求响应速度,把前馈加得太猛,结果飞机一打杆就“窜”出去,差点炸机。前馈是“助攻”,不是“主力”,主力永远是PID。
4.4 自适应控制基础:让算法学会“随机应变”
前面讲的PID、串级、前馈,都有一个共同前提:模型是固定的。但现实是,飞机在空中会变。电池电压下降,电机效率降低;挂载不同,转动惯量变化;甚至温度变化都会影响传感器零偏。固定参数的PID,很难在所有工况下都最优。
自适应控制,就是让控制器能在线调整自己的参数。这里我介绍一种最实用的方法:增益调度(Gain Scheduling)。
增益调度不是真的“自适应”,它更像一个查表。我们事先把不同工况(比如不同电压、不同空速)下的最优PID参数测出来,存成一张表。飞行中根据当前状态查表,切换参数。
| 电池电压 (V) | 推荐 Kp_rate | 推荐 Ki_rate | 推荐 Kd_rate |
|---|---|---|---|
| 12.6 (满电) | 0.15 | 0.01 | 0.002 |
| 11.4 (中等) | 0.18 | 0.012 | 0.0025 |
| 10.2 (低电) | 0.22 | 0.015 | 0.003 |
你看,电压低了,电机响应变慢,我们就需要增大Kp来补偿。这就是增益调度的核心思想。
更高级的自适应控制,比如模型参考自适应控制(MRAC),它会实时比较实际输出和理想模型的输出,然后调整控制器参数,让实际系统去逼近理想模型。这个比较复杂,但效果也更好。我在做高精度载荷投放项目时用过,能把姿态波动控制在0.1度以内。
我的建议:如果你刚开始接触自适应控制,先从增益调度入手。它简单、可靠、容易调试。等你把增益调度玩熟了,再去碰MRAC或自整定PID。步子迈大了,容易扯着蛋。