












本讲对应 Betaflight 源码
源码文件
src/main/flight/pid.c核心函数
pidLevel()核心变量
currentPidSetpoint
angleTarget本讲对应控制流程
RC(遥控器) │ ▼ currentPidSetpoint │ ▼ angleTarget(目标姿态角)
本讲学习目标
阅读完本讲以后,你应该能够回答:
□ 飞手真正控制的是什么?
□ currentPidSetpoint 从哪里来?
□ angleTarget(目标姿态角)是什么?
□ angleTarget 为什么这样计算?
□ 飞控为什么要先得到 angleTarget,而不是直接控制电机?
前面的几讲,
我们一直讨论:
飞机应该怎样运动。
例如:
Yaw 应该绕 Earth Frame(地球坐标系)旋转。
Pitch Flip 为什么只能用于 180°。
这些内容,
都是为了建立:
空间直觉。
但是,
从这一讲开始,
我们正式进入:
Betaflight 源码。
我们的目标,
也发生了变化。
以后,
每一讲都会围绕一个核心问题:
飞控是怎样把飞手的控制意图,
一步一步翻译成电机控制命令的?
整个过程,
我们都会对应:
Betaflight 源码,
逐行理解。
很多初学者,
都会认为:
飞手控制的是:
电机。
或者:
飞手控制的是:
飞机姿态。
其实,
这两种说法,
都不准确。
飞手真正控制的是:
遥控器摇杆。
例如:
Pitch(俯仰)
向前推一点。
飞控收到的,
只是:
一个摇杆输入。
此时,
飞控仍然不知道:
飞机应该转到多少度。
因此,
第一步,
必须把:
摇杆输入
翻译成:
飞机希望到达的姿态。
这个姿态,
就是:
angleTarget(目标姿态角)。
对应源码:
float angleTarget =
angleLimit
*
currentPidSetpoint
*
pidRuntime.maxRcRateInv[axis];
以后,
阅读源码,
我们遵循统一的方法。
首先,
回答第一个问题。
一句话来说:
angleTarget
=
飞手希望飞机最终停留的位置。
例如:
飞手希望:
Pitch = 30°
那么:
angleTarget
=
30°
注意:
这里仍然只是:
目标。
飞机,
此时,
还没有开始运动。
飞控,
只是知道:
飞手希望飞机,
最终停留在这里。
继续看源码:
float angleTarget =
angleLimit
*
currentPidSetpoint
*
pidRuntime.maxRcRateInv[axis];
阅读源码时,
按照我们的新规范,
第二步要回答:
currentPidSetpoint
来自哪里?
答案是:
RC(遥控器)
更准确地说,
对应源码:
float currentPidSetpoint =
getSetpointRate(axis);
因此:
整个来源可以理解成:
RC(遥控器)
↓
getSetpointRate()
↓
currentPidSetpoint
这里,
我们暂时不用深入:
Rates(摇杆曲线)
因为:
它属于:
RC 输入模块。
后面会单独介绍。
目前,
只需要知道:
currentPidSetpoint
=
RC 当前摇杆输入。
继续看:
angleLimit
它表示:
ANGLE 模式允许的最大姿态角。
例如:
如果:
angleLimit
=
55°
那么:
飞机最大只能飞到:
Pitch = ±55°
Roll = ±55°
即使:
飞手把摇杆推到底,
飞机也不会继续倾斜。
因此:
angleLimit
决定了:
ANGLE 模式的最大姿态角。
最后还有:
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(姿态角误差)。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。