[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 数据生成每个电机的输出。

posted on 2026-08-03 15:41  longyue  阅读(8)  评论(0)    收藏  举报

导航