[BF学院_卷1] -- 第28讲:applyMixerAdjustment() —— 为什么 Mixer 需要重新分配控制权限

第28讲:applyMixerAdjustment() —— 为什么 Mixer 需要重新分配控制权限

本讲知识点

  • Mixer 输出饱和问题;
  • motorMixRange 的物理意义;
  • 为什么 PID 控制需求和 Throttle 叠加后可能超过电机能力;
  • applyMixerAdjustment() 的设计目标;
  • Legacy Mixer 如何通过缩放保持姿态控制比例;
  • motorMixNormalizationFactor 的计算原理;
  • Airmode 对 Mixer 权限分配的影响;
  • applyMixerAdjustmentLinear() 与 Legacy Mixer 的区别;
  • MIXER_EZLANDING 模式为什么需要独立调整算法。

本讲对应 Betaflight 源码

主要函数:

mixTable(timeUs_t currentTimeUs)

本讲重点分析:

applyMixerAdjustment()

applyMixerAdjustmentLinear()

applyMixerAdjustmentEzLand()

调用位置:

motorMixRange = motorMixMax - motorMixMin;

switch (mixerConfig()->mixer_type) {
case MIXER_LEGACY:
    applyMixerAdjustment(motorMix, motorMixMin, motorMixMax, airmodeEnabled);
    break;

case MIXER_LINEAR:
case MIXER_DYNAMIC:
    applyMixerAdjustmentLinear(motorMix, airmodeEnabled);
    break;

case MIXER_EZLANDING:
    applyMixerAdjustmentEzLand(motorMix, motorMixMin, motorMixMax);
    break;
}

本讲学习目标

理解 Mixer 计算出来的 "motorMix" 并不是最终电机输出。

它只是姿态控制修正量, 但是飞机实际控制还需要加入 "Throttle",

最终:

Throttle + motorMix -> Motor Output

问题是:

如果多个控制需求同时存在,电机是否一定有能力同时满足?

答案:不一定。

因此 Mixer 必须具备:

控制权限重新分配能力

也就是, 当输出超过电机能力时,不是简单截断,而是在保证控制效果的前提下重新调整。


回顾上一讲

上一讲分析了:

PID Output -> motorMix

转换过程。

PID Controller 输出:

Roll 控制需求

Pitch 控制需求

Yaw 控制需求

Mixer 根据:

float mix =
    scaledAxisPidRoll  * activeMixer[i].roll +
    scaledAxisPidPitch * activeMixer[i].pitch +
    scaledAxisPidYaw   * activeMixer[i].yaw;

计算每个电机需要增加或者减少多少姿态修正量, 得到 "motorMix[]"。


为什么 motorMix 不能直接输出?

假设一个四轴飞机,某个瞬间,飞控希望:

Throttle = 0.8

表示需要 80% 基础推力。

同时,飞机发生姿态误差:

Roll 需要:    +0.4

Pitch 需要:   +0.3

Yaw 需要:     +0.2

某个电机最终可能得到:

0.8 + 0.4 + 0.3 + 0.2 = 1.7

但是电机最大输出:"1.0"。

因此, 出现 "输出饱和"。


电机输出饱和会发生什么?

如果 Mixer 不处理, 最终输出阶段会执行:

constrainf()

类似:

超过最大值 -> 强制限制到最大值

例如,

原始: Motor A = 1.7

限制: Motor A = 1.0

问题,被截掉的不只是 Throttle,还包括 Roll / Pitch / Yaw 控制量。

结果:

PID 要求:改变姿态

实际:控制权限丢失

飞机表现:

  • 翻滚响应不足;
  • 抗扰能力下降;
  • 高油门动作变差。

因此,Mixer 不能简单让输出被截断,它需要提前调整:

控制需求比例

motorMixRange —— Mixer 控制范围

在 mixTable() 中:

motorMixRange = motorMixMax - motorMixMin;

这里计算所有电机 motorMix 的最大跨度。

例如,四个电机:

Motor1 = -0.3

Motor2 = 0.2

Motor3 = 0.5

Motor4 = -0.1

那么:

motorMixMax = 0.5

motorMixMin = -0.3

所以:

motorMixRange = 0.5 - (-0.3) = 0.8

表示当前姿态控制需要占用 "0.8" 的电机输出空间。

如果:"motorMixRange <= 1.0", 说明姿态控制需求在允许范围内。

如果:"motorMixRange > 1.0", 说明姿态控制需求已经超过 Mixer 可用范围。

需要压缩。


Legacy Mixer:applyMixerAdjustment()

Legacy Mixer 的核心思想:不是改变控制方向,而是整体缩小控制幅度。

也就是说, 原本:

Roll + Pitch + Yaw

之间的比例保持不变, 只是整体降低。

例如,

原始: Roll : Pitch : Yaw = 4 : 2 : 1

超过范围后:

缩小: 2 : 1 : 0.5

比例仍然:

4 : 2 : 1

这样, 飞机仍然按照原来的控制方向运动。


motorMixNormalizationFactor —— 混控缩放因子

核心代码:

const float motorMixNormalizationFactor = motorMixRange > 1.0f ?
    airmodeTransitionPercent / motorMixRange :
    airmodeTransitionPercent;

拆开理解。

第一种情况:

motorMixRange <= 1.0

说明控制需求没有超过范围, 因此不需要压缩。

结果:

normalizationFactor = 1

第二种情况:

motorMixRange > 1.0

说明超出范围。

例如:"motorMixRange = 1.5", 表示需求超过能力:50%。

因此, 需要:"1 / 1.5" 比例缩小。

最终:

motorMix[i] *= motorMixNormalizationFactor;

作用:所有电机的姿态修正量同步缩小。


为什么不能直接限制 motorMix?

假设, 两个电机:

Motor A:    Roll +0.8

Motor B:    Roll -0.8

如果简单限制 "超过范围直接截断",

可能变成:

Motor A:    0.8

Motor B:    -0.3

结果Roll 力矩比例改变, 飞机得到错误控制。

所以, Mixer Adjustment 的原则:

保持控制比例 -> 压缩控制幅度

normalizedMotorMixMin / normalizedMotorMixMax 的缩放

在 applyMixerAdjustment() 中,完成 motorMix 压缩以后,源码继续计算:

const float normalizedMotorMixMin = motorMixMin * motorMixNormalizationFactor;
const float normalizedMotorMixMax = motorMixMax * motorMixNormalizationFactor;

这两行的作用比较简单,因为前面已经对 motorMix 进行了缩放:

motorMix[i] *= motorMixNormalizationFactor;

所以原来的 "motorMixMin" 和 "motorMixMax" 也必须同步缩放。

否则电机混控修正量已经变化,但是后续 throttle 限制仍然使用旧范围,

最终计算出来的 Motor Output 仍然可能发生饱和。


throttle 为什么也需要限制?

接下来是整个函数最关键的一行:

throttle = constrainf(throttle,
                      -normalizedMotorMixMin,
                      1.0f - normalizedMotorMixMax);

理解这一行,需要回到 Mixer 最终输出公式:

Motor Output = Throttle + motorMix

其中 "Throttle" 提供基础推力,而 "motorMix" 提供 Roll / Pitch / Yaw 姿态修正。

例如, 某个电机:

Throttle = 0.8

motorMix = 0.4

那么:

Motor Output = 0.8 + 0.4 = 1.2

但是电机输出范围只能是 0.0 ~ 1.0, 因此最终必须将 1.2 限制到 0.0 ~ 1.0 之内。

问题在于,如果提前发生限制,那么这个电机原本应该提供的姿态修正就丢失了。

也就是说:

电机饱和 -> 姿态控制权限下降 -> 飞机响应变差

所以 Mixer 的策略不是先计算超限,再裁剪输出。

而是在计算之前,主动调整 throttle。


throttle 限制的数学含义

假设,某个时刻,motorMix 最大值 = +0.3。

表示某个电机为了姿态控制,最多需要额外增加 0.3。

那么,Throttle 最大只能:

1.0 - 0.3 = 0.7

因为:

Throttle + motorMix = 0.7 + 0.3 = 1.0

刚好达到最大输出。

因此:

1.0f - normalizedMotorMixMax

表示再此刻,油门允许的最大值,防止最高输出电机超过范围。


反过来,如果motorMix 最小值 = -0.2,

说明某个电机为了姿态控制,需要减少0.2,那么 throttle 不能太低。

否则:

Throttle + motorMix

可能小于 0.0。

例如:

Throttle = 0.1

motorMix = -0.2

结果:

Motor Output = 0.1 - 0.2 = -0.1

同样超出了电机输出范围。

所以,Throttle 最小值需要保证 Throttle - 0.2 >= 0,即Throttle >= 0.2。

因此:

-normalizedMotorMixMin

表示油门允许的最小值。


所以完整代码:

throttle = constrainf(throttle,
                      -normalizedMotorMixMin,
                      1.0f - normalizedMotorMixMax);

实际完成的是:根据当前姿态控制需求,反向限制油门范围。

最终保证:

0.0 <= Throttle + motorMix <= 1.0

从而避免:

电机输出饱和 -> 姿态控制权限丢失

因此,applyMixerAdjustment() 的完整思想可以总结为:

多个控制需求同时进入 Mixer

        ↓

Throttle + Roll/Pitch/Yaw Mix

        ↓

可能超过电机能力范围

        ↓

调整 motorMix 范围

        ↓

限制 Throttle

        ↓

保证最终 Motor Output 不越界

这也是为什么 Mixer 不是简单地:

Throttle + PID输出

而需要进行:

混控范围分析 -> 归一化 -> 权限分配 -> 输出保护

的原因。


Airmode 对 Mixer 调整的影响

代码:

if (!airmodeEnabled && throttle < 0.5f)

表示在普通模式低油门时,会主动降低姿态控制权限,减少当前姿态影响油门的输出。

而在 Airmode 时,低油门下,也要尽量以保持姿态为主。


补充一下,这里的“降低姿态控制权限”,是指在 Throttle + motorMix 这个公式中,

可以对motorMix这个姿态分量进行大一点的衰减,从而让Throttle油门保持多一点的原始输入值。


applyMixerAdjustmentLinear() —— Linear / Dynamic Mixer 的动态混控算法

Legacy Mixer 的核心思想是先计算完整的motorMix,然后根据整体混控范围:

motorMixRange = motorMixMax - motorMixMin

进行统一缩放。

这种方式的问题是,它只关注当前 motorMix 是否超过范围,

而不会根据当前油门位置,动态调整姿态控制空间。

因此,Betaflight 引入:

MIXER_LINEAR
MIXER_DYNAMIC

两种 Mixer 调整方式。

它们的核心思想是:根据当前 Throttle 所处区域,提前调整 motorMix 的可用范围,

避免最终 Motor Output = Throttle + motorMix 发生饱和。


为什么 Linear Mixer 需要动态调整?

首先回顾 Mixer 输出公式:

Motor Output = Throttle + motorMix

其中,

Throttle代表所有电机共同增加的基础推力。

motorMix代表为了产生Roll,Pitch,Yaw 而产生的差动修正。

因此,motorMix 本身不是推力需求,而是电机之间的输出差异。

例如:

M1 +0.2

M2 -0.2

M3 +0.2

M4 -0.2

平均值仍然为 0。

所以不会整体增加升力,只是改变电机之间的推力分配。


但是,当 Throttle 处于极端区域时,可用输出空间会发生变化。

低油门区域

例如:Throttle = 0.1,此时电机基础输出已经接近最低值。

如果某个电机:

Motor Output = 0.1 - 0.3 = -0.2

那么 Motor Output 会被限制到最低输出 (0.0)。

结果, 原本希望产生的姿态差异消失, 这叫:

低端饱和

高油门区域

例如:Throttle = 0.9, 此时电机基础输出已经接近最大值。

如果某个电机:

Motor Output = 0.9 + 0.3 = 1.2

那么, 输出会被限制到最大值 (1.0)。

结果, 同样会损失姿态控制能力, 这叫:

高端饱和

因此, Linear Mixer 的目标不是简单增加或者减少 motorMix。

而是根据油门位置,调整姿态修正量允许占用的输出空间, 避免:

Throttle + motorMix

超过电机物理范围 (0.0 ~ 1.0) 。


核心代码分析

函数:

applyMixerAdjustmentLinear(float *motorMix, const bool airmodeEnabled)

核心代码:

float motorDeltaScale = 0.5f;

const float motorMixDelta = motorDeltaScale * motorMixRange;

其中, motorMixRange 表示当前所有电机混控需求的范围:

最大 motorMix - 最小 motorMix

例如,

motorMix 最大值 = 0.3

motorMix 最小值 = -0.3

那么, motorMixRange = 0.6。

因此:

motorMixDelta = 0.5 × 0.6 = 0.3

它表示:当前姿态控制范围的一半。


关键算法

代码:

motorMix[i] = scaleRangef(throttle, 0.0f, 1.0f,
            motorMix[i] + motorMixDelta,
            motorMix[i] - motorMixDelta);

这里的scaleRangef(),作用是:根据 throttle,在两个目标值之间进行线性插值。

输入:

throttle

0.0 ~ 1.0

对应输出范围:

motorMix[i] + motorMixDelta 到 motorMix[i] - motorMixDelta

也就是说,随着油门变化,motorMix 会在两个范围之间移动。


低油门时发生什么?

假设 throttle = 0,那么,输出接近:

motorMix[i] + motorMixDelta

表面看,好像所有 motorMix 都增加了。

但是这里的关键不是"增加姿态控制",而是"重新调整混控中心"。

原因是:低油门最大的问题是:

Motor Output = Throttle + motorMix

容易出现向下溢出,

例如,原始:

Throttle = 0.1

motorMix = -0.3

结果:

Motor Output = -0.2

电机输出被限制到最低值 (0.0)。

因此, Linear Mixer 在低油门区域,通过移动 motorMix 的工作范围,减少电机输出进入负值区域的概率。

目的, 不是让飞机增加升力,而是保护姿态控制差动量不要被低端限制吞掉。


高油门时发生什么?

假设, throttle = 1, 那么, 输出接近:

motorMix[i] - motorMixDelta

原因, 高油门区域的问题相反, Motor Output容易超过最大值。

例如:

Throttle = 0.9

motorMix = +0.3

结果:Motor Output = 1.2, 超过最大输出。

因此, Linear Mixer 在高油门区域,调整 motorMix 工作范围,减少"向上饱和"发生的概率。


这里的"姿态控制权限"是什么意思?

之前经常提到:

减少姿态控制权限

容易产生误解。

它不是指PID 输出变小, 也不是指飞机不响应姿态变化。

实际含义是:在有限的电机输出范围内 (Throttle + motorMix),

两个需求需要竞争空间, 当油门接近极端:

  • 低油门时,低端空间不足;
  • 高油门时,高端空间不足。

Mixer 会主动压缩姿态修正能够占用的空间,优先保证 Motor Output 不越界。

这就是:

动态混控调整

Linear Mixer 与 Legacy Mixer 的区别

Legacy:

计算 motorMix -> 发现范围过大 -> 整体缩放 motorMix

特点:统一处理。


Linear:

根据 throttle -> 动态调整 motorMix 范围 -> 提前避免饱和

特点:根据当前工作点调整。

因此,Linear Mixer 更关注不同油门区域的可用控制空间,而不是单纯压缩已经产生的混控结果。


MIXER_DYNAMIC 的意义

源码中:

case MIXER_LINEAR:
case MIXER_DYNAMIC:
    applyMixerAdjustmentLinear(motorMix, airmodeEnabled);

两者共用:

applyMixerAdjustmentLinear()

说明它们采用相同的动态混控思想, 区别主要体现在 "motorMix" 补偿阶段。

MIXER_LINEAR使用motorMixDelta进行补偿,

MIXER_DYNAMIC使用motorMix[i]本身的绝对值进行补偿。


为什么需要 Linear / Dynamic Mixer?

因为不同飞机需求不同。

Legacy Mixer:特点:简单,稳定,兼容性强,但是控制方式固定。

Linear / Dynamic:可以根据当前油门状态,动态调整姿态控制权限,适合需要更细致控制的飞机。


applyMixerAdjustmentEzLand()

MIXER_EZLANDING 模式, 专门用于:

降落辅助

它的目标不是最大化姿态权限。

而是保证下降过程稳定, 所以算法关注:

Throttle 上限

下降速度

控制余量

核心思想:降落时, 如果姿态修正需求过大,不能让某个电机突然冲高。

因此需要限制最大油门范围, 避免输出饱和影响降落稳定。


Mixer Adjustment 在控制链中的位置

完整流程:

RC 输入

↓

PID Controller

↓

Roll/Pitch/Yaw 控制需求

↓

motorMix

↓

Mixer Adjustment

↓

Throttle + motorMix

↓

Motor Output

↓

ESC

其中, Mixer Adjustment 负责保证控制需求适应电机能力。


本讲小结

本讲分析了为什么 Mixer 不能直接把:

Throttle + motorMix

发送给电机。

因为电机输出能力有限,多个控制需求可能产生输出饱和,

如果直接限制会导致姿态控制权限丢失。

因此 Betaflight 引入 Mixer Adjustment,通过:

  • Legacy Mixer 的整体缩放;
  • Linear / Dynamic Mixer 的动态调整;
  • EZLanding 的特殊限制;

重新分配电机控制权限。

最终完成:

超过能力的控制需求 -> 重新分配 -> 保持最大有效控制能力

下一讲预告

下一讲继续分析:

applyMixToMotors()

重点:

motorMix + Throttle -> motorOutput

也就是Mixer 最后一步, 如何把姿态修正量转换成真正发送给 ESC 的电机输出值。

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

导航