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

推荐订阅源

J
Java Code Geeks
博客园 - 司徒正美
博客园 - 【当耐特】
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
宝玉的分享
宝玉的分享
V
V2EX
S
SegmentFault 最新的问题
V
Visual Studio Blog
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
Jina AI
Jina AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园_首页
L
LangChain Blog
D
Docker
腾讯CDC

博客园 - 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] -- 第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] -- 第七讲:为什么 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] -- 第八讲:飞手打杆以后,飞控首先得到什么?
longyue · 2026-08-03 · via 博客园 - longyue

第八讲:飞手打杆以后,飞控首先得到什么?

  • Angle Mode(自稳模式)的控制链路总览
  • 飞手真正控制的是什么
  • 为什么遥控器不是直接控制电机
  • angleTarget(目标姿态角)的来源
  • angleTarget(目标姿态角)为什么这样计算
  • 控制链路正式开始

本讲对应 Betaflight 源码

源码文件

src/main/flight/pid.c

核心函数

pidLevel()

核心变量

currentPidSetpoint

angleTarget

本讲对应控制流程

RC(遥控器)
        │
        ▼
currentPidSetpoint
        │
        ▼
angleTarget(目标姿态角)

本讲学习目标

阅读完本讲以后,你应该能够回答:

□ 飞手真正控制的是什么?

□ currentPidSetpoint 从哪里来?

□ angleTarget(目标姿态角)是什么?

□ angleTarget 为什么这样计算?

□ 飞控为什么要先得到 angleTarget,而不是直接控制电机?


从这一讲开始,我们正式进入 Betaflight

前面的几讲,

我们一直讨论:

飞机应该怎样运动。

例如:

Yaw 应该绕 Earth Frame(地球坐标系)旋转。

Pitch Flip 为什么只能用于 180°。

这些内容,

都是为了建立:

空间直觉。

但是,

从这一讲开始,

我们正式进入:

Betaflight 源码。

我们的目标,

也发生了变化。

以后,

每一讲都会围绕一个核心问题:

飞控是怎样把飞手的控制意图,

一步一步翻译成电机控制命令的?

整个过程,

我们都会对应:

Betaflight 源码,

逐行理解。


飞手真正控制的是什么?

很多初学者,

都会认为:

飞手控制的是:

电机。

或者:

飞手控制的是:

飞机姿态。

其实,

这两种说法,

都不准确。

飞手真正控制的是:

遥控器摇杆。

例如:

Pitch(俯仰)

向前推一点。

飞控收到的,

只是:

一个摇杆输入。

此时,

飞控仍然不知道:

飞机应该转到多少度。

因此,

第一步,

必须把:

摇杆输入

翻译成:

飞机希望到达的姿态。

这个姿态,

就是:

angleTarget(目标姿态角)。

angleTarget(目标姿态角)到底来自哪里?

对应源码:

float angleTarget =
angleLimit
*
currentPidSetpoint
*
pidRuntime.maxRcRateInv[axis];

以后,

阅读源码,

我们遵循统一的方法。

首先,

回答第一个问题。


angleTarget(目标姿态角)是什么?

一句话来说:

angleTarget

=

飞手希望飞机最终停留的位置。

例如:

飞手希望:

Pitch = 30°

那么:

angleTarget

=

30°

注意:

这里仍然只是:

目标。

飞机,

此时,

还没有开始运动。

飞控,

只是知道:

飞手希望飞机,

最终停留在这里。

currentPidSetpoint 到底来自哪里?

继续看源码:

float angleTarget =
angleLimit
*
currentPidSetpoint
*
pidRuntime.maxRcRateInv[axis];

阅读源码时,

按照我们的新规范,

第二步要回答:

currentPidSetpoint

来自哪里?

答案是:

RC(遥控器)

更准确地说,

对应源码:

float currentPidSetpoint =
getSetpointRate(axis);

因此:

整个来源可以理解成:

RC(遥控器)

↓

getSetpointRate()

↓

currentPidSetpoint

这里,

我们暂时不用深入:

Rates(摇杆曲线)

因为:

它属于:

RC 输入模块。

后面会单独介绍。

目前,

只需要知道:

currentPidSetpoint

=

RC 当前摇杆输入。

angleLimit 是什么?

继续看:

angleLimit

它表示:

ANGLE 模式允许的最大姿态角。

例如:

如果:

angleLimit

=

55°

那么:

飞机最大只能飞到:

Pitch = ±55°

Roll = ±55°

即使:

飞手把摇杆推到底,

飞机也不会继续倾斜。

因此:

angleLimit

决定了:

ANGLE 模式的最大姿态角。

maxRcRateInv 又是什么?

最后还有:

pidRuntime.maxRcRateInv[axis]

名字看起来很复杂。

其实,

它只是:

maxRcRate

的倒数。

作用只有一个:

把 RC 输入,

转换成:

0~1 的比例。

例如:

假设:

最大 RC 输入

=

500°/s

那么:

摇杆当前:

250°/s

经过:

maxRcRateInv

以后:

得到:

0.5

也就是说:

当前摇杆,

推到了:

50%。

因此:

这一项,

真正表示的是:

RC 当前打杆比例。

为什么三者相乘?

现在,

三个变量,

已经全部认识了。

currentPidSetpoint

↓

当前 RC 输入
maxRcRateInv

↓

转换成打杆比例
angleLimit

↓

最大允许姿态角

于是:

整个公式:

angleTarget =
angleLimit
*
currentPidSetpoint
*
pidRuntime.maxRcRateInv[axis];

其实就是:

RC 当前比例

×

最大姿态角

得到:

Target Angle(目标姿态角)

例如:

ANGLE 最大姿态角

=

60°

飞手:

摇杆推到:

50%

那么:

angleTarget

=

30°

飞控立即知道:

飞手希望:

飞机最终停留在:

30°。

到这里,飞控真正完成了第一次翻译

飞手,

一直认为:

自己只是:

推了一下摇杆。

但是,

飞控已经完成了一次非常重要的翻译。

它把:

RC 输入

翻译成:

Target Angle(目标姿态角)

整个过程可以表示成:

RC(遥控器)

↓

currentPidSetpoint

↓

RC 当前比例

↓

angleTarget(目标姿态角)

这也是:

Angle Mode(自稳模式)

真正开始工作的第一步。


但是,还有一个问题

现在,

飞控已经知道:

Target Angle

=

30°

可是,

它仍然不能控制飞机。

原因非常简单。

飞控只知道:

希望飞机去哪里。

但是,

它还不知道:

飞机现在在哪里。

因此,

控制系统还缺少另一半信息:

Current Angle(当前姿态角)

只有:

Target Angle

+

Current Angle

同时存在,

飞控才能计算:

Angle Error(姿态角误差)

真正开始控制飞机。


本讲小结

这一讲,

飞控完成了控制链中的第一次翻译:

RC(遥控器)

↓

currentPidSetpoint

↓

angleTarget(目标姿态角)

其中:

currentPidSetpoint

=

RC 当前摇杆输入。
angleLimit

=

ANGLE 模式允许的最大姿态角。
maxRcRateInv

=

把 RC 输入转换成打杆比例。

最终:

飞控得到:

Target Angle(目标姿态角)

它表示:

飞手希望飞机最终停留的位置。

但是,

飞控仍然不知道:

飞机现在到底在哪里。

因此,

下一讲,

我们将继续寻找:

Current Angle(当前姿态角)

并计算:

Angle Error(姿态角误差)。

下一讲预告

下一讲,

我们将正式进入:

const float currentAngle =
(attitude.raw[axis]
-angleTrim->raw[axis])
/10.0f;

这一行源码。

重点理解:

Current Angle(当前姿态角)

到底来自哪里?

以及:

为什么:

Target Angle

-

Current Angle

就能够得到:

Angle Error(姿态角误差)。