惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
L
LangChain Blog
博客园_首页
J
Java Code Geeks
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
小众软件
小众软件
WordPress大学
WordPress大学
V
Visual Studio Blog
T
The Blog of Author Tim Ferriss
U
Unit 42
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Announcements
Recent Announcements
C
Check Point Blog
IT之家
IT之家
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
D
Docker
有赞技术团队
有赞技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
阮一峰的网络日志
阮一峰的网络日志
I
InfoQ

博客园 - longyue

[BF学院_卷1] -- 第30讲:mixTable() 总结 —— Mixer 完整控制模型 [BF学院_卷1] -- 第29讲:applyMixToMotors() —— 最终 Motor Output 生成 [BF学院_卷1] -- 第28讲:applyMixerAdjustment() —— 为什么 Mixer 需要重新分配控制权限 [BF学院_卷1] -- 第27讲:PID Output 如何进入 Mixer —— 从控制需求到 motorMix [BF学院_卷1] -- 第26讲:calculateThrottleAndCurrentMotorEndpoints() —— Mixer 如何确定油门基础范围 [BF学院_卷1] -- 第25讲:mixTable() 总览 —— Mixer 在控制循环中的位置 [BF学院_卷1] -- 第24讲:mixer_init.c —— 飞控启动时如何准备 Mixer [BF学院_卷1] -- 第23讲:Mixer 数学模型 —— motorMixer_t 如何形成 Motor Output [BF学院_卷1] -- 第22讲:Betaflight 如何描述 Mixer —— mixer.h 数据结构 [BF学院_卷1] -- 第21讲:四旋翼为什么可以通过电机控制姿态 —— Mixer 背后的物理基础 [BF学院_卷1] -- 第20讲:从 PID Output 到 Motor Command(电机指令)——为什么飞控需要 Mixer [BF学院_卷1] -- 第19讲:PID Controller(PID控制器)——飞控到底如何理解误差并控制飞机? [BF学院_卷1] -- 第18讲:PID Controller 的最后一环——为什么飞控还需要 Feedforward(前馈控制)? [BF学院_卷1] -- 第17讲:D Term(微分控制项)——飞控如何提前感知运动变化? [BF学院_卷1] -- 第14讲:Rate Error(角速度误差)——PID 真正控制的到底是什么? [BF学院_卷1] -- 第15讲:P Term(比例控制项)——飞控是如何利用误差开始控制飞机的? [BF学院_卷1] -- 第13讲:Outer Loop 的终点——为什么还要读取 Current Angular Rate(当前角速度)? [BF学院_卷1] -- 第十二讲:Yaw Compensation(偏航补偿) [BF学院_卷1] -- 第12讲脚本 [BF学院_卷1] -- 第十一讲:Current Angular Rate(当前角速度)——飞控怎样知道飞机现在转得有多快? [BF学院_卷1] -- 第十讲:Angle Error(姿态角误差)如何变成 Desired Angular Rate(目标角速度) [BF学院_卷1] -- 第九讲:Current Angle(当前姿态角)遇见 Target Angle(目标姿态角) [BF学院_卷1] -- 第八讲:飞手打杆以后,飞控首先得到什么? [BF学院_卷1] -- 第七讲:为什么 Pitch Flip 只在 180° 看起来正确 [BF学院_卷1] -- 第7讲脚本 [BF学院_卷1] -- 第6讲:为什么 Pitch Flip 会骗人 [BF学院_卷1] -- 第6讲脚本 [BF学院_卷1] -- 第五讲:第一次修正——Pitch Flip 为什么看起来有效 [BF学院_卷1] -- 第5讲脚本 - longyue [BF学院_卷1] -- 第4讲:为什么"绕飞机自己转"不等于"朝世界转" - longyue
[BF学院_卷1] -- 第16讲:I Term(积分控制项)——飞机为什么...
longyue · 2026-08-03 · via 博客园 - longyue

第16讲:I Term(积分控制项)——飞机为什么总是差一点?


本讲知识点

  • P Term(比例控制项)为什么无法解决所有误差?
  • 什么是长期存在的控制误差?
  • I Term(积分控制项)为什么会出现?
  • I Term(积分控制项)到底在积累什么?
  • I Term(积分控制项)在控制链中的职责是什么?
  • 为什么飞机总会存在“总是差一点”的现象?
  • I Term(积分控制项)为什么能够消除长期误差?

本讲对应 Betaflight 源码

文件:

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控制器)中负责长期补偿的控制器。


P Term(比例控制项)已经很好了,为什么还需要 I Term?

假设:

目标: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 Term(比例控制项)为什么无法解决这个问题?

因为P看到的永远只有:现在差多少。

例如:300 -> 298 只差 2°/s

于是, P:差的不多,我只需要补一点。

但是, 这一点补偿,很可能永远不够。

于是:

298 -> 298 -> 298 -> 298 -> 298

飞机就会长期停留在差一点。

所以:

P看到的是:现在。

I看到的是:长期。

这就是两者最大的区别。


I Term(积分控制项)到底在看什么?

例如:

第1次循环:差 2°/s

↓

第100次循环:还是差 2°/s

↓

第500次循环:还是差 2°/s

这时候, I发现: 这已经不是偶然误差, 而是长期存在的控制偏差。

于是, I开始不断积累这个误差。

可以简单理解为:

差一点, 记下来

↓

还差一点, 继续记下来

↓

还是差一点, 继续记下来

↓

长期都在差一点, 开始增加长期补偿。

因此, I看到的并不是现在差多少, 而是长期到底差了多少。


I Term(积分控制项)的职责是什么?

如果用最简单的话去描述:

P负责立即修正,I负责长期补偿。

或者说:

P:现在差多少?

I:为什么一直都在差?

所以:

P负责解决立即误差。

I负责解决长期误差。

直到现在,我们第一次发现:PID中的三个字母,其实负责的是三种完全不同的控制职责。

其中,

P:现在。
I:长期。
D:变化速度。

它们看到的根本不是同一个世界。


控制链再次推进

现在:

Rate Error

↓

P Term

↓

I Term

已经开始共同参与控制。

这也是 PID Controller(PID控制器)开始变得有趣的地方:

它不是一个控制器,而是多个控制思想共同工作的结果。


源码补充说明

为了帮助大家回到源码进行阅读,这里简单说明一下源码中出现,但本讲没有展开讲解的几个变量。

dynCi

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 更合理地完成自己的工作。

pidRuntime.itermAccelerator

它的作用是:为 I Term 提供额外的积分增益补偿。

飞机在一些特殊情况下,例如:

  • 大油门变化;
  • 快速动作变化;

I Term 的建立速度可能会偏慢。

为了让 I Term 能够更快建立起补偿能力,Betaflight 会临时提高积分速度,因此就引入了:

itermAccelerator(积分加速器)

需要注意的是:它并不是新的 I Term,它只是让原来的 I Term 积分得更快一些。


pidRuntime.dT

表示: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(微分控制项)。

去看看飞控究竟是如何观察变化速度,并让控制变得更加稳定的。