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

推荐订阅源

Google DeepMind News
Google DeepMind News
G
Google Developers Blog
博客园 - 三生石上(FineUI控件)
B
Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hugging Face - Blog
Hugging Face - Blog
C
Check Point Blog
V
V2EX
Vercel News
Vercel News
U
Unit 42
Recent Announcements
Recent Announcements
Last Week in AI
Last Week in AI
J
Java Code Geeks
WordPress大学
WordPress大学
罗磊的独立博客
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
小众软件
小众软件
M
MIT News - Artificial intelligence
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
云风的 BLOG
云风的 BLOG

博客园 - 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] -- 第16讲:I Term(积分控制项)——飞机为什么总是差一点? [BF学院_卷1] -- 第14讲:Rate Error(角速度误差)——PID 真正控制的到底是什么? [BF学院_卷1] -- 第15讲:P Term(比例控制项)——飞控是如何利用误差开始控制飞机的? [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] -- 第13讲:Outer Loop 的终点——为什么还要读取...
longyue · 2026-08-03 · via 博客园 - longyue

第13讲:Outer Loop 的终点——为什么还要读取 Current Angular Rate(当前角速度)?


本讲知识点

  • Outer Loop(外环)已经结束了吗?
  • Desired Angular Rate(目标角速度)为什么不能直接送给电机?
  • 飞控为什么还要读取陀螺仪角速度?
  • Current Angular Rate(当前角速度)在控制链中的职责
  • Rate Error(角速度误差)的来源
  • Outer Loop 与 Inner Loop 的控制职责交接
  • 第一次进入 pidController() 的真正控制流程

本讲对应 Betaflight 源码

文件:

src/main/flight/pid.c

函数:

pidController()

本讲涉及源码:

const float gyroRate = gyro.gyroADCf[axis];

float errorRate = currentPidSetpoint - gyroRate;

本讲重点讲解第一行源码:

const float gyroRate = gyro.gyroADCf[axis];

以及第二行源码为什么必须这样计算。


本讲学习目标

这一讲,我们正式离开 Outer Loop(外环),进入 Inner Loop(内环)的入口。

上一讲结束时,Outer Loop 已经完成了自己的全部职责,并输出了:

Desired Angular Rate(目标角速度)

但是,仅仅知道“飞机应该转多快”还远远不够, 因为飞控仍然不知道 “飞机现在到底转得有多快”。

因此,本讲将建立:

Current Angular Rate(当前角速度)

这个变量,并理解它为什么会成为整个内环控制的起点。


回顾上一讲

上一讲结束时,我们已经得到:

Desired Angular Rate(目标角速度)

它来自:

Angle Error

↓

Desired Angular Rate

也就是说,Outer Loop(外环)已经完成了自己的控制职责。

控制链推进到了:

RC Stick

↓

Target Angle

↓

Current Angle

↓

Angle Error

↓

Desired Angular Rate

很多人在这里都会产生一个疑问:

飞机已经知道自己应该每秒旋转多少度了,为什么还不能直接去控制电机?

答案就是:

因为"目标"永远不是"现实"。

飞机知道"应该转多少",并不代表它真的转了那么多

假设,飞手希望飞机快速向右滚转,经过外环计算以后:

Desired Angular Rate = 300°/s

这意味着:

飞控希望飞机每秒旋转 300°。

但是,这只是“希望值”,并不是“实际值”。

现实中的飞机受到很多因素影响,例如:

  • 电机推力不足;
  • 电池电压下降;
  • 螺旋桨效率变化;
  • 飞行中的空气阻力;
  • 外界风力干扰。

因此,真实世界中的飞机,很可能正在以 220°/s 旋转,也可能 350°/s,甚至根本没有开始旋转。

于是,一个新的问题出现了:

飞控怎么知道,飞机现在到底转了多少?

这正是 Inner Loop(内环)首先要解决的问题。


飞控必须先观察现实世界

任何控制系统,都必须同时拥有两类信息:

第一类:目标值(Target)

第二类:实际值(Current)

如果只有目标,没有实际值,控制器永远不知道自己有没有控制成功。

举一个生活中的例子:

假设汽车定速巡航设定 100 km/h,如果汽车 ECU(电子控制单元)不知道当前速度是多少,它还能控制油门吗?

当然不能,因为:它不知道现在是80?95?100?120?

所以,汽车必须实时读取当前速度,飞控也是完全一样。

Desired Angular Rate(目标角速度)只是应该转多少。

真正决定控制是否成功的,是现在转多少。

于是,飞控开始读取Current Angular Rate(当前角速度)


第一行源码

于是,我们来到 pidController() 中真正进入内环后的第一行核心代码:

const float gyroRate = gyro.gyroADCf[axis];

这行代码非常短,但它完成了一件极其重要的事情:

把现实世界中的飞机旋转速度,带回控制器。

这里的 gyro.gyroADCf 表示陀螺仪实时测得的角速度。

注意,它不是目标角速度,也不是计算出来的角速度。

而是飞机此时此刻,真正正在旋转的角速度。

这是整个 Inner Loop 唯一相信的现实数据。


为什么一定要相信陀螺仪?

因为,只有陀螺仪真正知道飞机现在发生了什么。

例如:

外环希望 300°/s,但是陀螺仪返回 225°/s,飞控立刻知道 飞机转慢了。

如果陀螺仪返回 330°/s,飞控马上知道 飞机转快了。

因此,陀螺仪不是在告诉飞控应该怎么控制,它是在告诉飞控现实已经发生了什么。

直到现在,控制系统终于同时拥有了:

Target

↓

Current

下一步,它们终于可以进行比较。

控制链继续推进:

Desired Angular Rate

↓

Current Angular Rate

↓

Rate Error

而这一步,正是下一讲要建立的:

Rate Error(角速度误差)。

本讲小结

本讲正式完成了控制职责从 Outer Loop(外环)到 Inner Loop(内环)的交接。

Outer Loop 输出的是 Desired Angular Rate(目标角速度),它描述的是飞机"应该转多快";而 Inner Loop 首先读取陀螺仪提供的 Current Angular Rate(当前角速度),获得飞机"现在实际转多快"。

至此,控制器第一次同时拥有了目标值和实际值,为下一步计算角速度误差做好了准备。


下一讲预告

现在,飞控已经同时知道:

应该转多快;

现在转多快。

那么,它将如何判断:

还差多少?

下一讲,我们正式建立:

Rate Error(角速度误差)

这是整个 Rate PID(角速度控制器)真正开始工作的第一步。