[BF学院_卷1] -- 第24讲:mixer_init.c —— 飞控启动时如何准备 Mixer
第24讲:mixer_init.c —— 飞控启动时如何准备 Mixer
本讲知识点
- mixerInit() 在 Mixer 系统启动阶段的作用;
- currentMixerMode 如何确定当前飞行器类型;
- mixerConfigureOutput() 如何加载 Mixer 参数;
- Fixed Mixer 与 Custom Mixer 的初始化区别;
- mixerRuntime.currentMixer 如何成为运行阶段 Mixer 数据来源;
- 初始化完成后的 Mixer 运行状态。
本讲对应 Betaflight 源码
主要源码:
src/main/flight/mixer.c
核心函数:
void mixerInit(mixerMode_e mixerMode)
static void mixerConfigureOutput(void)
辅助函数:
void mixerResetDisarmedMotors(void)
本讲学习目标
本讲开始进入 Mixer 初始化流程。
学习者需要理解,飞控启动以后,Mixer 并不是立即计算电机输出。
而是先完成:
飞行器类型
↓
Mixer 配置
↓
motorMixer_t 参数
↓
运行时 Mixer 数据
初始化完成以后,后续运行阶段才能根据:
Throttle,Roll,Pitch,Yaw
计算Motor Output。
回顾上一讲
上一讲我们已经建立了 Mixer 的数学基础。
飞控最终需要完成:
Throttle,Roll,Pitch,Yaw
↓
Mixer 权重计算
↓
Motor Output
但是,在进入运行计算之前,还有一个问题:
Betaflight 怎么知道:当前飞机是什么结构?应该使用哪套 Mixer?每个电机对应什么权重?
这些问题,都属于:Mixer 初始化。
飞控启动时,Mixer 需要准备什么?
观察整个控制链:
PID Controller
↓
Throttle / Roll / Pitch / Yaw
↓
Mixer
↓
Motor Output
Mixer 位于控制输出的最后阶段。
因此,它必须提前知道:
当前飞机有几个电机?每个电机的位置关系是什么?每个电机如何响应 Roll / Pitch / Yaw?
这些信息最终都会保存到 “mixerRuntime.currentMixer[]” 中。
后续运行阶段,Mixer 不需要重新寻找配置,只需要使用已经准备好的数据。
mixerInit() —— Mixer 初始化入口
源码:
void mixerInit(mixerMode_e mixerMode)
{
currentMixerMode = mixerMode;
mixerRuntime.feature3dEnabled = featureIsEnabled(FEATURE_3D);
initEscEndpoints();
...
mixerConfigureOutput();
}
这个函数完成的事情可以概括为:
接收当前 Mixer 类型 -> 准备相关运行参数 -> 加载 Mixer 输出配置
其中,真正决定 Mixer 数据来源的是:
mixerConfigureOutput();
保存当前 Mixer 类型
代码:
currentMixerMode = mixerMode;
作用:保存当前飞行器 Mixer 类型。
例如,“MIXER_QUADX” 表示:当前飞机是四旋翼 X 架构。
后续初始化过程会根据 “currentMixerMode” 选择对应 Mixer 数据。
因此, mixerMode 决定使用哪一种 Mixer。
其他初始化步骤
在进入核心函数之前,mixerInit() 还执行了一些准备工作。
feature3dEnabled
代码:
mixerRuntime.feature3dEnabled = featureIsEnabled(FEATURE_3D);
作用: 记录是否开启 3D 飞行模式。
3D 模式下,电机可能需要支持正反转推力。
本讲暂不展开 3D 电机控制,这里只需要知道:
Mixer 初始化阶段 -> 保存特殊飞行功能状态
initEscEndpoints()
代码:
initEscEndpoints();
作用:初始化 ESC 输出范围。
例如:最小输出, 最大输出,这些参数用于后续电机输出转换。
由于本讲当前重点是 Mixer,这里只保留概念:
初始化 ESC 输出边界 -> 保证后续 Motor Output 可以正确输出
mixerConfigureOutput() —— Mixer 初始化核心
真正完成 Mixer 数据准备的是:
static void mixerConfigureOutput(void)
它负责:
选择 Mixer
↓
确定电机数量
↓
加载 motorMixer_t
↓
保存到运行时数组
也就是把配置阶段的数据:
Mixer 配置 -> 运行阶段 Mixer 数据
初始化电机数量
函数开始:
mixerRuntime.motorCount = 0;
表示初始化阶段还不知道当前 Mixer 有多少个有效电机。
后续会根据:Fixed Mixer 或者 Custom Mixer 重新确定。
判断 Mixer 类型
核心判断:
if (currentMixerMode == MIXER_CUSTOM ||
currentMixerMode == MIXER_CUSTOM_TRI ||
currentMixerMode == MIXER_CUSTOM_AIRPLANE)
这里区分两类 Mixer:
Custom Mixer : 用户自定义
Fixed Mixer : Betaflight 内置
两者初始化方式不同。
Custom Mixer 初始化
进入 Custom Mixer 分支:
for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++)
{
if (customMotorMixer(i)->throttle == 0.0f) {
break;
}
mixerRuntime.currentMixer[i] = *customMotorMixer(i);
mixerRuntime.motorCount++;
}
这里的目标是:读取用户定义的 Mixer 参数, 保存为运行时 Mixer 数据。
读取 Custom Mixer 数据
代码:
customMotorMixer(i)
返回一个 motorMixer_t 结构, 也就是上一讲建立的:
throttle, roll, pitch, yaw
四个权重。
判断 Custom Mixer 结束
代码:
if (customMotorMixer(i)->throttle == 0.0f) {
break;
}
这里使用 "throttle == 0" 作为结束标志,因为正常电机都会参与基础升力。
因此,Betaflight 可以通过这个条件判断,当前已经没有有效 Mixer 数据。
保存到运行时 Mixer
代码:
mixerRuntime.currentMixer[i] = *customMotorMixer(i);
作用:复制 Custom Mixer 参数。
从 customMotorMixer() 进入 mixerRuntime.currentMixer[] 之后运行阶段:
Mixer 不再关心这个数据来自哪里,只使用 mixerRuntime.currentMixer[]。
Fixed Mixer 初始化
如果不是 Custom Mixer,进入:
else
{
mixerRuntime.motorCount = mixers[currentMixerMode].motorCount;
}
说明当前使用Betaflight 内置 Mixer,
例如,MIXER_QUADX,这种 Mixer 已经提前定义在源码中。
初始化阶段只需要根据当前 Mixer 类型,找到对应配置。
获取电机数量
代码:
mixerRuntime.motorCount = mixers[currentMixerMode].motorCount;
作用:读取当前 Mixer 包含多少个电机。
例如:
QUADX -> 4个电机
因此, mixerRuntime.motorCount = 4,这个数量会决定后续需要加载多少组 motorMixer_t。
限制最大电机数量
代码:
if (mixerRuntime.motorCount > MAX_SUPPORTED_MOTORS) {
mixerRuntime.motorCount = MAX_SUPPORTED_MOTORS;
}
作用:防止配置数量超过系统支持范围。
也就是:
Mixer配置数量 -> 系统最大支持数量
之间增加保护。
复制 Fixed Mixer 数据
代码:
if (mixers[currentMixerMode].motor) {
for (int i = 0; i < mixerRuntime.motorCount; i++)
mixerRuntime.currentMixer[i] = mixers[currentMixerMode].motor[i];
}
这里完成:
内置 Mixer 参数 -> 运行时 Mixer 参数
的转换。
为什么需要 mixerRuntime.currentMixer?
这是初始化设计中的关键。
Mixer 数据有两种来源:
固定 Mixer -> Betaflight 内置
或者:
Custom Mixer -> 用户定义
如果运行阶段直接区分:
当前是不是 Custom Mixer?
当前是不是 Fixed Mixer?
那么运行代码会变复杂。
因此 Betaflight 在初始化阶段完成统一转换:
不同来源的 Mixer 配置
↓
统一运行格式
↓
mixerRuntime.currentMixer[]
之后,运行阶段只需要使用 mixerRuntime.currentMixer[] 即可。
加载特殊 Mixer 修改
代码:
#ifdef USE_LAUNCH_CONTROL
loadLaunchControlMixer();
#endif
作用:如果开启 Launch Control 功能,允许该功能进一步调整 Mixer 参数。
当前课程重点仍然是基础 Mixer 初始化流程,因此这里只需要知道:
初始化完成后,特殊功能还可以修改 Mixer 数据
初始化未解锁电机输出
最后:
mixerResetDisarmedMotors();
调用:
void mixerResetDisarmedMotors(void)
{
for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
motor_disarmed[i] = mixerRuntime.disarmMotorOutput;
}
}
作用:设置飞机未解锁状态下 Motor Output 的初始值。
也就是:
Disarm状态 -> 每个电机输出多少
这属于电机安全控制。
本讲只需要知道, Mixer 初始化结束时,同时准备:未解锁输出状态。
mixerInit() 完整控制链
现在回到整个初始化流程:
mixerInit()
↓
保存当前 Mixer 类型
↓
准备相关运行参数
↓
mixerConfigureOutput()
↓
选择 Custom Mixer 或 Fixed Mixer
↓
加载 motorMixer_t
↓
保存到 mixerRuntime.currentMixer[]
↓
进入运行阶段 Mixer 计算
因此,mixerInit() 的职责不是计算 Motor Output,
而是:准备 Motor Output 计算所需要的 Mixer 数据。
本讲小结
本讲分析了 Mixer 初始化过程。
重点不是重新介绍 Mixer 参数,而是理解这些参数如何从配置阶段进入运行阶段。
Betaflight 在启动时,通过 mixerInit() 保存当前 Mixer 类型,并调用 mixerConfigureOutput() 根据当前模式加载对应的 motorMixer_t 数据。
无论 Mixer 来源是固定 Mixer,还是Custom Mixer,最终都会统一保存到:
mixerRuntime.currentMixer[]
这样运行阶段的 Mixer 计算就不需要关心配置来源,只需要使用已经准备好的运行数据。
下一讲预告
现在:
Mixer 配置 -> 运行时 Mixer 数据
已经准备完成。
但是飞控运行过程中,还需要不断执行:
Throttle,Roll,Pitch,Yaw -> Mixer -> Motor Output
下一讲将进入 Mixer 运行计算过程,分析 Betaflight 如何在实时循环中使用这些 Mixer 数据生成每个电机的输出。
浙公网安备 33010602011771号