










文件:
src/main/flight/pid.c
函数:
pidController()
本讲涉及源码:
pidData[axis].I += pidRuntime.pidCoefficient[axis].Ki
* pidRuntime.dT
* errorRate;
本讲重点讲解:
errorRate
为什么需要被长期积累,以及:
pidData[axis].I
为什么能够消除长期存在的控制误差。
上一讲中,我们已经得到Rate Error(角速度误差),
并且知道 P Term(比例控制项)负责解决的问题是:现在差多少?
控制链已经推进到了:
Rate Error
↓
P Term
但是,飞机并不会因为拥有了 P Term(比例控制项),就一定能够完全达到目标值。
现实世界中的控制误差,往往比我们想象得更加复杂。
现实中的飞机经常会出现:
总是差一点;
总是慢一点;
总是推不够;
总是需要长期补偿。
因此,本讲将正式进入:
I Term(积分控制项)
去理解它为什么会成为 PID Controller(PID控制器)中负责长期补偿的控制器。
假设:
目标:300°/s
现实:250°/s
于是:
Rate Error = 50°/s
P Term(比例控制项)开始工作:
差了 50 -> 开始增加控制量 -> 飞机开始加速
经过一段时间以后:
300°/s -> 290°/s -> 295°/s -> 297°/s -> 298°/s
这时候看起来已经非常接近目标值了, 但是问题来了:
为什么它永远都是:298°/s?而不是:300°/s?
现实中的飞机永远不是一个理想模型。
例如:
这些因素都会导致:飞机总是差一点。
例如:目标 300°/s, 现实 298°/s
又或者:目标 300°/s, 现实 295°/s
甚至:目标 300°/s, 现实一直都是 290°/s
此时会出现一种非常有意思的现象:飞机没有完全控制失败;但它也没有完全控制成功。
它会长期停留在总是差一点。
这就是:
长期控制误差(Steady-state Error)。
因为P看到的永远只有:现在差多少。
例如:300 -> 298 只差 2°/s
于是, P:差的不多,我只需要补一点。
但是, 这一点补偿,很可能永远不够。
于是:
298 -> 298 -> 298 -> 298 -> 298
飞机就会长期停留在差一点。
所以:
P看到的是:现在。
I看到的是:长期。
这就是两者最大的区别。
例如:
第1次循环:差 2°/s
↓
第100次循环:还是差 2°/s
↓
第500次循环:还是差 2°/s
这时候, I发现: 这已经不是偶然误差, 而是长期存在的控制偏差。
于是, I开始不断积累这个误差。
可以简单理解为:
差一点, 记下来
↓
还差一点, 继续记下来
↓
还是差一点, 继续记下来
↓
长期都在差一点, 开始增加长期补偿。
因此, I看到的并不是现在差多少, 而是长期到底差了多少。
如果用最简单的话去描述:
P负责立即修正,I负责长期补偿。
或者说:
P:现在差多少?
I:为什么一直都在差?
所以:
P负责解决立即误差。
I负责解决长期误差。
直到现在,我们第一次发现:PID中的三个字母,其实负责的是三种完全不同的控制职责。
其中,
P:现在。
I:长期。
D:变化速度。
它们看到的根本不是同一个世界。
现在:
Rate Error
↓
P Term
↓
I Term
已经开始共同参与控制。
这也是 PID Controller(PID控制器)开始变得有趣的地方:
它不是一个控制器,而是多个控制思想共同工作的结果。
为了帮助大家回到源码进行阅读,这里简单说明一下源码中出现,但本讲没有展开讲解的几个变量。
const float iTermChange = (Ki + pidRuntime.itermAccelerator)
* dynCi
* pidRuntime.dT
* itermErrorRate;
这里的 dynCi 表示 Dynamic Integral Coefficient(动态积分系数)。
它的作用是:动态调整 I Term 的积分速度, 因为飞机在不同的飞行状态下,对 I Term 的需求其实并不完全相同。
我们根据代码来进行讲述:
dynCi = constrainf((1.0f - getMotorMixRange()) * pidRuntime.itermWindupPointInv, 0.0f, 1.0f);
const float itermWindupPoint = pidProfile->itermWindupPointPercent / 100.0f;
pidRuntime.itermWindupPointInv = 1.0f / (1.0f - itermWindupPoint);
首先,机体的调整的姿态空间是有限的,而函数 getMotorMixRange() 就是获取飞机姿态调整空间已占用的百分比。
而 itermWindupPointPercent 就是机体调整空间的百分比阀值,该阀值之内,本次I Term增量完整增加;超过阀值,则按比例缩减,直至不增加。
(1.0f - getMotorMixRange()) / (1.0f - itermWindupPoint) 则表示用余量来计算百分比,如:
itermWindupPointPercent 为80,表示机体调整度从 80% 开始衰减,那么(1.0f - itermWindupPoint)的值为0.2
当机体调整余量充足时,mixRange() 返回 0.6, 计算就是 (1-0.6)/0.2 = 2, dynCi = constrainf(2, 0, 1) = 1。
当机体调整余量不足时, mixRange() 返回 0.9, 计算就是 (1-0.9)/0.2 = 0.5, dynCi = 0.5
需要注意的是:
dynCi 并不会改变 I Term 的控制职责,它只是帮助 I Term 更合理地完成自己的工作。
它的作用是:为 I Term 提供额外的积分增益补偿。
飞机在一些特殊情况下,例如:
I Term 的建立速度可能会偏慢。
为了让 I Term 能够更快建立起补偿能力,Betaflight 会临时提高积分速度,因此就引入了:
itermAccelerator(积分加速器)
需要注意的是:它并不是新的 I Term,它只是让原来的 I Term 积分得更快一些。
表示:PID Loop(PID控制循环)的时间间隔。
积分 = 时间 × 误差
如果不知道过去了多少时间,控制器就无法知道 这一帧应该积分多少误差。
因此:误差越大, 时间越长, 积分增加越多。
这也是 I Term 之所以被称为 “Integral(积分项)” 的原因。
因此,这几项参数分别承担着不同的职责:
Ki : 决定 I Term 的基础积分速度
itermAccelerator : 决定是否需要临时提高积分速度
dynCi : 决定当前飞行状态下应该采用怎样的积分速度
dT : 决定这一帧应该积分多少时间
最终得到:iTermChange
虽然源码看起来出现了很多变量,但它们并没有改变 I Term 的控制职责。
本讲真正需要建立的核心思想仍然只有一句话:
I Term(积分项)的职责,就是记住那些长期存在的误差,并持续补偿它们。
其它变量,都只是帮助 I Term 更稳定、更合理地完成自己的工作。
本讲我们正式进入了 I Term(积分控制项)的控制职责。
P Term(比例控制项)负责解决“现在差多少”的问题,而 I Term(积分控制项)负责解决“为什么一直都在差”的问题。当飞机长期存在控制偏差时,I Term 会不断积累这些长期误差,并逐渐增加补偿量,从而帮助飞机最终达到目标状态。
至此,我们已经完成了 PID Controller(PID控制器)中的两个核心控制职责:
现在差多少?
↓
长期差多少?
现在,PID Controller(PID控制器)已经拥有了:
P:现在差多少?
I:长期差多少?
但是,它仍然无法判断:飞机现在的变化是不是太快了?
下一讲,我们将正式进入:
D Term(微分控制项)。
去看看飞控究竟是如何观察变化速度,并让控制变得更加稳定的。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。