[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.4Pitch 需要: +0.3Yaw 需要: +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.3Motor2 = 0.2Motor3 = 0.5Motor4 = -0.1
那么:
motorMixMax = 0.5motorMixMin = -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.8Motor B: Roll -0.8
如果简单限制 "超过范围直接截断",
可能变成:
Motor A: 0.8Motor 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.8motorMix = 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.1motorMix = -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.2M2 -0.2M3 +0.2M4 -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.3motorMix 最小值 = -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,在两个目标值之间进行线性插值。
输入:
throttle0.0 ~ 1.0
对应输出范围:
motorMix[i] + motorMixDelta 到 motorMix[i] - motorMixDelta
也就是说,随着油门变化,motorMix 会在两个范围之间移动。
低油门时发生什么?
假设 throttle = 0,那么,输出接近:
motorMix[i] + motorMixDelta
表面看,好像所有 motorMix 都增加了。
但是这里的关键不是"增加姿态控制",而是"重新调整混控中心"。
原因是:低油门最大的问题是:
Motor Output = Throttle + motorMix
容易出现向下溢出,
例如,原始:
Throttle = 0.1motorMix = -0.3
结果:
Motor Output = -0.2
电机输出被限制到最低值 (0.0)。
因此, Linear Mixer 在低油门区域,通过移动 motorMix 的工作范围,减少电机输出进入负值区域的概率。
目的, 不是让飞机增加升力,而是保护姿态控制差动量不要被低端限制吞掉。
高油门时发生什么?
假设, throttle = 1, 那么, 输出接近:
motorMix[i] - motorMixDelta
原因, 高油门区域的问题相反, Motor Output容易超过最大值。
例如:
Throttle = 0.9motorMix = +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 的电机输出值。