[BF学院_卷1] -- 第22讲:Betaflight 如何描述 Mixer —— mixer.h 数据结构
第22讲:Betaflight 如何描述 Mixer —— mixer.h 数据结构
本讲知识点
- Betaflight 为什么需要使用数据结构描述 Mixer(混控器)
- mixerMode_e 如何描述不同飞行器结构
- mixerType_e 如何描述不同 Mixer 计算方式
- motorMixer_t 如何描述单个电机参与控制的方式
- throttle、roll、pitch、yaw 四个参数分别代表什么
- mixer_t 如何组合一个完整 Mixer
- mixerConfig_t 如何保存用户配置和运行参数
- customMotorMixer 如何支持自定义混控
- motorCount 如何确定当前飞行器拥有多少电机
本讲对应 Betaflight 源码
文件:
src/main/flight/mixer.h
本讲主要分析:
typedef enum mixerMode
{
...
} mixerMode_e;
typedef enum mixerType
{
...
} mixerType_e;
typedef struct motorMixer_s {
float throttle;
float roll;
float pitch;
float yaw;
} motorMixer_t;
typedef struct mixer_s {
uint8_t motorCount;
uint8_t useServo;
const motorMixer_t *motor;
} mixer_t;
typedef struct mixerConfig_s {
uint8_t mixerMode;
bool yaw_motors_reversed;
uint8_t crashflip_motor_percent;
uint8_t crashflip_expo;
uint8_t mixer_type;
...
} mixerConfig_t;
回顾上一讲
上一讲,我们完成了 Mixer(混控器)的物理基础建立。
我们知道:PID Controller(PID控制器)输出的不是电机转速。
它输出的是:飞机需要产生什么运动。
例如:Roll(横滚), Pitch(俯仰), Yaw(偏航), Throttle(油门)。
但是电机只能理解:提高转速, 降低转速。
因此需要:
飞机运动需求
↓
电机输出分配
↓
Motor Command(电机指令)
这个转换过程,就是 Mixer。
但是,现在出现新的问题:如果在程序中实现 Mixer,飞控需要知道:
有几个电机?
每个电机应该受到哪些控制影响?
Roll 对这个电机影响多少?
Pitch 对这个电机影响多少?
Yaw 对这个电机影响多少?
因此,Betaflight 需要一种方式描述 Mixer。
这一讲,就是进入源码,理解:
Betaflight 如何描述 Mixer
Mixer 在源码中不是一个算法,而是一组数据
在上一讲中,我们从功能角度理解 Mixer:
Throttle + Roll + Pitch + Yaw
↓
Motor 1
Motor 2
Motor 3
Motor 4
但是,在代码中,Mixer 并不是简单写死:
如果是四轴:
Motor1 = ...
Motor2 = ...
Motor3 = ...
Motor4 = ...
因为 Betaflight 支持很多不同飞行器:
例如:
四旋翼,六旋翼,八旋翼,三旋翼,固定翼
不同结构:
- 电机数量不同;
- 电机位置不同;
- 电机旋转方向不同;
- Roll/Pitch/Yaw 对电机影响不同。
因此,Betaflight 将 Mixer 抽象成:
飞行器结构描述 + 电机控制权重描述 + 运行配置
mixerMode_e:描述飞行器类型
首先看:
typedef enum mixerMode
{
MIXER_TRI = 1,
MIXER_QUADP = 2,
MIXER_QUADX = 3,
...
} mixerMode_e;
这个枚举描述:当前飞行器是什么结构。
例如,MIXER_QUADX 表示:X 型四旋翼,
也就是我们上一讲一直讨论的 Motor 1 ~ Motor 4 组成的四轴结构。
为什么 Betaflight 需要知道飞行器类型?
因为不同结构,电机分布不同。
例如,四旋翼:
M1 M2
M4 M3
六旋翼:
M1 M2
M6 M3
M5 M4
它们产生 Roll、Pitch、Yaw 的方式不同。
因此:飞行器结构决定 Mixer 方式。
需要注意,mixerMode_e 描述的是硬件结构,不是具体计算过程。
它回答:
这是什么飞机?
而不是:
Mixer 怎么计算?
mixerType_e:描述 Mixer 计算类型
接下来:
typedef enum mixerType
{
MIXER_LEGACY = 0,
MIXER_LINEAR = 1,
MIXER_DYNAMIC = 2,
MIXER_EZLANDING = 3,
} mixerType_e;
描述的是:Mixer 输出计算方式,
也就是当 Mixer 已经知道:
Throttle,Roll,Pitch,Yaw
以后,应该采用什么策略生成 Motor Output。
MIXER_LEGACY
传统 Mixer 计算方式,也就是最基础的:
Motor Output = Throttle + Roll 权重 + Pitch 权重 + Yaw 权重
例如:
Motor1 = Throttle + Roll + Pitch + Yaw
这是最经典的 Mixer 思想。
MIXER_LINEAR
Linear Mixer(线性混控)。
核心思想:保持控制输入与电机输出之间的线性关系。
例如,Roll 增加 10%,那么对应电机影响:
增加固定比例
不会根据其它状态进行复杂调整。
MIXER_DYNAMIC
Dynamic Mixer(动态混控)。
它允许 Mixer 根据当前状态动态调整输出。
例如,不同油门区域:
低油门,保持姿态控制能力
高油门,限制输出变化
它主要用于改善不同飞行状态下的控制效果。
MIXER_EZLANDING
用于辅助降落相关逻辑。
它不是改变 Mixer 的基本职责,仍然是:
控制需求 -> 电机输出
只是增加特殊情况下的输出处理。
因此:
mixerMode_e 和 mixerType_e 的区别:
| 类型 | 作用 |
|---|---|
| mixerMode_e | 描述什么飞机 |
| mixerType_e | 描述如何计算 Mixer 输出 |
motorMixer_t:描述单个电机如何参与控制
接下来进入 Mixer 最核心的数据结构:
typedef struct motorMixer_s {
float throttle;
float roll;
float pitch;
float yaw;
} motorMixer_t;
这个结构描述 "一个电机" 在 Mixer 中承担什么作用。
上一讲我们知道, Mixer 本质:
Throttle + Roll + Pitch + Yaw -> Motor Output
那么程序需要知道,每个电机:
Throttle 占多少?
Roll 影响多少?
Pitch 影响多少?
Yaw 影响多少?
这就是 motorMixer_t。
throttle 成员:油门权重
代码:
float throttle;
表示: Throttle 对这个电机输出的影响比例.
例如, 四旋翼悬停, 所有电机都需要提供升力。
因此通常:
Motor1 throttle = 1
Motor2 throttle = 1
Motor3 throttle = 1
Motor4 throttle = 1
意思:油门输入会平均作用到所有电机
roll 成员:横滚控制权重
代码:
float roll;
表示:Roll 控制量对这个电机的影响方向和大小。
例如,飞机需要向右 Roll。
Mixer 需要:一侧电机增加推力,另一侧电机减少推力。
因此不同电机 roll 权重不同。
例如:
左侧电机:roll = +1
右侧电机:roll = -1
表示:Roll 输入会让两侧产生相反变化。
pitch 成员:俯仰控制权重
代码:
float pitch;
作用类似:“roll”,但是控制方向不同。
它描述:Pitch 控制量如何影响这个电机。
例如,飞机需要向前 Pitch,需要后方电机增加推力,前方电机减少推力,
因此,前后电机:pitch 权重相反。
yaw 成员:偏航控制权重
代码:
float yaw;
表示:Yaw 控制量如何影响这个电机。
Yaw 与 Roll、Pitch 不同。
Roll/Pitch 主要利用 “升力差” 产生力矩。
Yaw 利用:“旋翼旋转反作用力” 产生力矩。
因此,CW 电机和 CCW 电机Yaw 权重方向相反。
例如:
CW 电机:yaw = +1
CCW 电机:yaw = -1
这样,增加某些电机输出,就可以改变顺时针反作用力与逆时针反作用力之间的平衡,最终产生 Yaw。
一个完整 motorMixer_t 的直觉
前面我们已经知道,Mixer 的任务是:
Throttle + Roll + Pitch + Yaw -> Motor Output
但是,不同电机在飞机上的位置不同,因此它们承担的控制职责也不同。
例如:左前电机, 右前电机, 左后电机, 右后电机, 它们距离飞机中心的位置不同,旋转方向也不同。
所以,当飞机需要 Roll(横滚), Pitch(俯仰), Yaw(偏航)时,每个电机需要增加或者减少的量并不相同。
Betaflight 使用 motorMixer_t 描述这种关系。
例如,一个电机的 Mixer 参数:
{
throttle = 1.0,
roll = 0.5,
pitch = -0.5,
yaw = 1.0
}
他表示这个电机最终输出,需要按照这些比例参与四种控制需求。
具体来看:
throttle = 1.0
表示:这个电机完整参与基础油门控制。
假设当前:Throttle = 50,那么这个电机获得:
50 × 1.0 = 50
也就是说:油门增加,这个电机增加同样比例的推力。
四旋翼悬停时,所有电机通常都会有类似的:throttle = 1.0,因为所有电机都需要共同提供升力。
roll = 0.5
表示:这个电机参与 Roll 控制,并且贡献方向为正。
假设飞机需要:Roll = 20,那么这个电机产生:
20 × 0.5 = 10
的 Roll 修正量。
也就是说:Roll 控制需求,这个电机增加 10 的输出。
如果另一个电机:
roll = -0.5
那么同样的 Roll 命令,会让它减少 10 的输出。
因此,通过正负不同的 Roll 权重:一部分电机增加推力,另一部分电机减少推力。
飞机两侧产生推力差,最终形成 Roll 力矩。
pitch = -0.5
表示:这个电机参与 Pitch 控制,但是方向相反。
假设:Pitch = 20,那么:
20 × (-0.5) = -10
表示:这个电机需要减少输出。
为什么需要负号?
因为这个电机的位置决定了:
增加推力 -> 产生某方向 Pitch 力矩
还是:
减少推力 -> 产生该方向 Pitch 力矩
不同位置的电机方向不同。
所以 Mixer 必须通过正负号描述:哪个电机增加,哪个电机减少。
yaw = 1.0
表示:这个电机参与 Yaw 控制,并且方向为正。
假设:Yaw = 10,那么:
10 × 1.0 = 10
这个电机增加输出。
但是,Yaw 与 Roll、Pitch 不同,Roll 和 Pitch 主要利用:
推力差 -> 产生倾斜力矩
而 Yaw 利用:
旋翼旋转方向产生的反作用力 -> 产生旋转力矩
因此,不同旋转方向的电机,需要拥有不同符号的 Yaw 权重。
例如:
CW 电机,yaw = +1
CCW 电机,yaw = -1
这样当 Yaw 命令产生时:
部分电机增加,部分电机减少
↓
顺逆时针反作用力失去平衡
↓
飞机产生 Yaw
因此,一个 motorMixer_t:
{
throttle = 1.0,
roll = 0.5,
pitch = -0.5,
yaw = 1.0
}
真正表达的是:
这个电机:100%参与基础升力,
Roll 控制时,按照 +0.5 的比例响应,
Pitch 控制时,按照 -0.5 的比例响应,
Yaw 控制时,按照 +1.0 的比例响应
最终 Mixer 会根据:
Throttle,Roll,Pitch,Yaw
四个控制需求,与这个电机自己的:
throttle,roll,pitch,yaw
权重相乘,再相加:
Motor Output = Throttle × throttle权重
+ Roll × roll权重
+ Pitch × pitch权重
+ Yaw × yaw权重
因此,motorMixer_t 本质上就是:
描述某一个电机,如何把飞机级控制需求转换为自身输出变化的参数表。
后续分析 mixTable() 时,Betaflight 正是利用这些权重,把:
PID Output
↓
Throttle / Roll / Pitch / Yaw
↓
每一个 Motor Output
完成真正的控制分配。
mixer_t:描述一个完整 Mixer
继续看:
typedef struct mixer_s {
uint8_t motorCount;
uint8_t useServo;
const motorMixer_t *motor;
} mixer_t;
这个结构表示:一个完整 Mixer 配置。
包含:
motorCount
uint8_t motorCount;
表示:这个 Mixer 有多少个电机
例如,四旋翼:motorCount = 4,
六旋翼:motorCount = 6。
useServo
uint8_t useServo;
表示:当前 Mixer 是否需要 Servo(舵机)。
因为 Betaflight 不只支持多旋翼,也支持:
固定翼, 三角翼, 其它结构
这些可能包含舵机控制。
motor
const motorMixer_t *motor;
这是最关键的数据。
它指向:每个电机的 Mixer 权重表,
也就是:
Motor1 的 throttle/roll/pitch/yaw
Motor2 的 throttle/roll/pitch/yaw
Motor3 的 throttle/roll/pitch/yaw
...
mixerConfig_t:运行时配置
最后观察:
typedef struct mixerConfig_s {
uint8_t mixerMode;
bool yaw_motors_reversed;
uint8_t crashflip_motor_percent;
uint8_t crashflip_expo;
uint8_t mixer_type;
} mixerConfig_t;
它保存:用户选择的 Mixer 配置。
mixerMode
决定:使用哪一种飞行器结构,例如:MIXER_QUADX。
yaw_motors_reversed
控制:Yaw 电机方向处理, 用于特殊硬件布局。
crashflip_motor_percent
与 "Flip Over After Crash" 功能相关,用于控制翻转时的电机输出比例。
mixer_type
决定:Mixer 使用哪一种计算方式, 对应:mixerType_e。
customMotorMixer:为什么需要自定义 Mixer?
源码:
PG_DECLARE_ARRAY(
motorMixer_t,
MAX_SUPPORTED_MOTORS,
customMotorMixer
);
表示 Betaflight 支持用户提供:自己的 motorMixer_t 表,
因为,不同飞行器结构可能非常多。
例如,特殊布局:
非标准四轴,实验飞行器,特殊电机排列
如果只能使用固定 Mixer,程序无法支持。
因此,Betaflight 提供:“Custom Mixer(自定义混控)”,
让用户直接定义每个电机:throttle,roll, pitch, yaw 的权重。
本讲控制链回归
现在回到整个控制链:
上一讲:
PID Output(PID输出)
↓
Mixer
这一讲,我们回答:Mixer 在代码中是什么?
答案:它不是一个简单函数,而是一组描述数据:
飞行器类型
↓
Mixer 类型
↓
电机数量
↓
每个电机控制权重
↓
运行配置
最终形成:“Mixer 数据模型”。
本讲总结
本讲完成了 Mixer 源码进入前的第一步:理解 Betaflight 如何描述 Mixer。
核心认识:
第一:mixerMode_e 描述:什么飞机。
第二:mixerType_e 描述:Mixer 使用什么计算策略。
第三:motorMixer_t 描述:单个电机如何受到 Throttle、Roll、Pitch、Yaw 影响。
第四:mixer_t 描述:完整 Mixer 结构。
第五:mixerConfig_t 保存:用户配置和运行参数。
至此,控制链推进到:
Mixer 概念
↓
Mixer 数据结构
↓
下一讲:Mixer 数学模型
下一讲,我们将进一步回答:
Throttle、Roll、Pitch、Yaw 到底如何通过 motorMixer_t 中的权重,计算出每一个 Motor Output?
也就是:Mixer Matrix(混控矩阵)的数学原理。
浙公网安备 33010602011771号