☰
ArduPilot多旋翼混控算法深度解析:矩阵、饱和与ScaleRing
2026/10/21 7:45:26 网站建设 项目流程

我刚入ArduPilot这个坑的时候,最让我头疼的就是它的混控算法。很多人把它理解为“把摇杆输入映射到电机转速”,一开始我也是这么觉得的,直到我在某次测试中看到一台四轴在快速横滚时出现电机输出瞬间拉满又骤降的现象,才意识到混控远不止映射这么简单。这期【ArduPilot之旅#2】我打算把多旋翼混控算法彻底拆开来讲,从数据流、饱和处理、代码调用链到实际调参中的奇怪现象,尽量把每一层都讲明白。无论你是准备自己改代码,还是只想把PID调得更顺手,这篇都值得存下来反复看。

1. 混控被低估的原因:它决定了飞控“亲和力”的上限

很多接触ArduPilot一段时间的人,会把注意力放在姿态控制器上。PID参数、姿态环带宽、前馈增益,这些确实决定了飞机的动态响应。但混控算法往往被当成“体力活”忽略掉。我一开始也这样,直到有一次我把一架小四轴的滚转速率环调得很激进,结果电机输出波形畸变得非常厉害——差分推力在滚转方向已经饱和,飞控还在按照线性的方式拼命分配四个电机的输出。那一刻我意识到:姿态环写得再好,混控层不给力,整架飞机的手感依然会崩塌。

混控的真实任务可以拆成两块:第一,把期望的三轴力矩换算成每个电机的转速指令;第二,在物理极限(比如输出上限、电机响应速度、机架几何约束)内把这份指令做得尽可能平滑、准确。第一块听起来像线性代数,第二块才是真正的工程难点。ArduPilot在AP_MotorsMatrix类里不光做了分配,还做了饱和补偿、倾斜补偿、以及一组非常容易忽略的“输出重映射”逻辑。

为什么说混控决定了一架飞控的“亲和力”?因为不同机架、不同电机布局、不同桨叶尺寸,都需要靠混控层来适配。ArduPilot为了兼容各种奇葩机架,在混控层塞了一堆配置参数:MOT_PWM_MIN、MOT_PWM_MAX、MOT_HOVER_THR、MOT_THR_HOVER,再加上一组Frame Class和Frame Type。新手最容易炸机的点,不是PID不对,而是Frame Type选错了——四轴选了X布局,代码还在按+布局解算,横滚方向先搓一半再说。混控层就是飞控和机架之间的翻译官,翻译错了,后面全是瞎忙。

我见过不少人在群里抱怨“ArduPilot就是不如某某飞控好飞,手感模糊”,结果一问MOT_THST_HOVER用的是默认值,电机KV、桨叶尺寸跟实际完全不符。混控层不感知你的动力系统,它就只能拿着一个默认模型硬算。这期我梳理混控算法时,会特别把“为什么参数会以这种方式互相耦合”讲清楚,而不是简单告诉你“要设成多少”。

2. 数据流从头摸:从姿态环到舵机PWM的完整链路

2.1 三行指令的关系:输出限幅、缩放与SCALED_PWM

很多人第一次看到ArduPilot的控制代码时,会茫然地发现姿态控制器和混控器的数据交换不是直接的PWM,而是一个叫SCALED_PWM的东西。这个东西本质上是“归一化的PWM期望值”,它的值域通常在-1到1之间,用来表示当前通道需要输出多大的相对功率。电机混控函数拿到这个东西之后,会结合电池电压、推力系数、电机响应模型等做二次换算,再吐出真正要写进定时器比较寄存器的PWM占空比。

这套设计的妙处在于:姿态控制器完全不需要知道电机是什么型号、电池是4S还是6S,它只需要输出一个抽象的“虚拟力矩请求”。混控器再根据机架几何和动力参数把它翻译成物理世界的PWM指令。我第一次读代码时,看到输出端有一堆关于Aero、BatteryVoltage、Current的补偿逻辑,还在想这跟姿态控制有什么关系——后来才明白,动力系统自身就是最大的非线性环节,锂电池端电压随负载变化会产生至少10%-20%的推力波动,如果混控层不做补偿,姿态环再精确也白搭。

2.2 四轴、六轴、八轴:输出顺序表如何适配不同机架

ArduPilot对多旋翼的支持是通过一个输出顺序表来完成的。举例来说,对于常见的Quad-X布局,电机的输出顺序是从右前电机开始,顺时针编号;但对于Hexa-X布局,电机的顺序则完全不一样。这套映射在代码里通过AP_MOTORS_Matrix类的setup_motors函数维护,每种机架对应一组固定的“电机索引到物理位置”的对应表。

我做某台六轴测试平台时,曾因为把电机顺序表配错,导致飞控在解锁后出现明显的“左右摇摆”。当时第一反应是PID问题,查了半天的《日志分析》才发现电机映射错位:飞控以为是右前电机,实际物理上是左后。这类问题排查起来非常隐蔽,因为日志里的电机编号跟物理位置对不上,需要画一张物理布局图和代码里的映射表慢慢比对。建议新手在第一次装机时,就先把电机位置图打印出来,逐通道核对输出。

2.3 数字/模拟伺服输出:混控的最后一公里

写完混控结果的PWM值,ArduPilot还要决定用哪个输出方式送出去。对于多旋翼,大部分飞控用的是PWM输出或者DShot数字协议,而部分带舵机的复合机架则用SRV_Channel来处理伺服输出。这里特别需要注意的是:如果你用的输出协议类型选错了,比如把DShot通道接到普通PWM电机上,飞控可能会出现“电机不转”或者“转速波动异常”的诡异现象。

把SRV_Channel和多旋翼混控放在一起讲,很多人的第一反应是“用不上”,但如果你玩的是倾转旋翼或者带云台的复合机型,就会发现这两套通道需要协同工作。ArduPilot的混控层有一个理念我非常欣赏——把不同物理通道抽象成统一的“输出端口”,电机也好,舵机也好,最后都换算成同样的逻辑输出对象,然后交给底层驱动去调度。这个抽象层的存在,让开发新机型的门槛低了很多。你可以直接在混控代码里给某个通道赋予一个特殊功能,而不需要改动底层驱动。

3. 饱和才是混控的“隐藏Boss”:输出限幅与参数哈希的精密配合

3.1 什么是饱和,为什么要区分力矩饱和和推力饱和

先明确一个概念:多旋翼的每个电机输出是有上下限的,PWM不能小于最小油门,也不能超过最大油门。当某一个电机到达限幅区,混控器输出的期望值就会跟实际物理值产生偏差。这里存在两种饱和:一种是“单个通道饱和”(比如说某电机输出已经到0,但仍需要更大扭矩),另一种是“整机推力饱和”(所有电机都拉满,总推力还是不够维持姿态)。

解决这两种饱和的方式不太一样。单个通道饱和通常意味着姿态期望超出了机体当前的机动能力,飞控要优先保证安全性,而不是强行跟随期望值。ArduPilot在这种情况下会悄悄降低总推力目标,用“分配到其他电机”的方式来兜底。而整机推力饱和,则需要混控层对期望加速度做出取舍,可能牺牲掉一部分水平加速度,优先保证高度不掉。我在后面的“ScaleRing”设计中会再展开讲。

3.2 参数哈希表:混控参数如何组织与读取

ArduPilot的混控代码里有一个很隐蔽但很精妙的设计:参数哈希表。它并非真正的哈希表数据结构,而是一个“利用参数ID快速查找混控参数”的机制。每一类机架、每一条混控规则对应一组参数ID,比如同样叫“MOT_X_Y”,在不同机架组合下对应的物理意义是不同的。为了避免每次混控计算都要重新遍历所有参数,代码会在参数变更时更新一个缓存索引,计算时直接按索引取值,性能开销极小。

这个设计给我最大的启发是:混控参数不是“散落一地”的,它们本身就是一个高度结构化的工具箱。新手在野外飞控调参时,经常用MP的Config界面直接改参数,却不知道MOT_PWM_MIN和MOT_THST_HOVER之间存在隐藏的联动关系。当你改了前者而忘记后者,飞控可能在半空中出现极不稳定的油门响应。阅读代码时,建议重点关注AP_MotorsParam一类函数如何读取这些配置并生成内部变量,这会让你从“会调参”上升到“懂得参数为何需要这样调”。

3.3 饱和被触发时,真正被调整的是“期望”而非“输出”

我把这点单独拎出来,是因为太多人把饱和忍让当成“降输出”。ArduPilot在饱和时不是简单地把PWM砍掉,而是“修改期望值”——它会计算在当前机体状态下,真实的力矩输出能力有多少,然后把期望力矩限制在这个能力范围内。这个限制不是硬切,而是平滑的梯度限幅,保证姿态环不会因为输出被突然切断而产生振荡。

实际测试中我发现,这个“期望值重构”的逻辑对飞行手感的影响极大。当飞机高速横滚时,如果混控器只是粗暴地限制PWM输出,飞控会因为看到输出与期望差异巨大,误判为动力故障而疯狂增加积分,最终导致反转。ArduPilot通过重构期望值的方式,让姿态环始终看到“可达到的期望”,把它跟真实输出的差异控制在合理范围内,这样即使暂时饱和,手感也不会“发神经”。

4. 输入归一化背后的“反余弦”逻辑:看懂那一堆向量的加减乘除

4.1 混控矩阵的几何直觉:为什么四个电机能产生任意姿态力矩

先上一个硬核推导:对于四轴,每个电机除了产生升力,还会因为旋转方向不同产生反扭矩。如果四个电机转速完全相同,总反扭矩为零;如果某一对电机转速升高,机体就会产生偏航力矩。同理,横滚和俯仰力矩分别由前后电机差速和左右电机差速产生。整个混控过程可以写成一个从“电机转速向量”到“力/力矩向量”的线性映射,而混控算法的本质就是在求解这个映射的逆问题。

ArduPilot的代码里有一个函数叫“add_motor”,它负责把每个电机的推力方向、力臂方向、反扭矩系数换算成矩阵中的一行。开飞控源码最惊艳的时刻,是看到这些数值并非硬编码,而是通过机架几何参数实时计算出来的。我的一个小技巧是:如果你想验证某个机架的混控是否符合直觉,直接在代码里给某个电机塞一个固定转速,然后观察飞控输出的期望力矩,看它是否符合物理直觉。

4.2 funRollIn、funPitchIn:那些“fun”开头的函数到底在做什么

读AP_MOTORS_Matrix源码时,你会看到一堆以fun开头的变量名,比如funRollIn、funPitchIn。刚开始我以为这是一种“趣味性”的命名,后来才意识到它是“function”的缩写——也就是当前归一化的滚转输入。这些变量不是直接参与矩阵运算,而是作为中间候选值,等待后续的限幅、换算和线性化处理。

这里有一个特别容易被忽略的点:多旋翼在倾斜飞行时,水平方向的期望力矩和垂直方向的推力之间是存在耦合的。ArduPilot的处理方式是,先把期望的姿态角转换成期望的归一化升力,再根据倾斜角计算水平分力期望,把它们合并成总力矩期望。如果你跳过这层“抽象归一化”直接看电机输出,会发现很多看似“异常”的数值其实是多维度力分配的结果。

4.3 余弦定理与ScaleRing:AP_MOTORS_Matrix中隐藏的几何魔法

在某次代码走读中,我无意间在混控层函数里看到了余弦定理的痕迹,当时愣了一下。后来才想明白,这其实是在计算“期望推力方向与当前机体Z轴的夹角”时,用余弦定理来解析两个向量之间的关系。多旋翼的推力方向并不总垂直于地面,当机体倾斜时,期望水平加速度需要通过旋转推力方向来实现,而这个旋转关系本质上就是空间向量的余弦夹角求解。ScaleRing函数则是对期望推力向量做“环形缩放”——当期望推力超出机体当前几何能产生的最大倾斜角时,把它按环形轨迹缩放回可行域。这个过程非常像一个几何投影,把不可达的期望映射成可达的期望,然后再交给混控矩阵。

说实话,我一开始并没有完全看懂ScaleRing的意义,直到我用了模拟飞行日志的“离线混控计算工具”,把这部分逻辑单独跑了一遍。那一刻我意识到,这些几何函数解决的不是“算得准不准”的问题,而是“算得稳不稳”的问题——它们给混控器划定了安全边界。

5. AP_MOTORS_PWM的改动细节与5寸机实测:从源码到悬停手感

5.1 框架级改动:几个关键宏和寄存器逻辑

源码里AP_MOTORS_PWM类负责把混控结果转成最终的PWM占空比。相比AP_MOTORS_Matrix纯数学层面的工作,它的重点在“物理输出”上——包括PWM频率设置、占空比限幅、数字协议打包。这里改动最大的部分,往往是PWM频率的选择。多旋翼电机换向器对PWM频率非常敏感,同一个电机在500Hz和400Hz下,手感差异会很明显。ArduPilot允许你通过参数设置PWM频率,但底层驱动的定时器配置需要同步调整,这也是很多自编译固件“能编译但飞起来怪怪的”的原因之一。

我在这部分踩过的坑是:改完定时器配置后忘记同步修改PWM_DEFAULT_MAX,导致混控输出上限还是按原来几百瓦的功率算,结果飞机在半油门时就开始过热。后来我在代码里加了几个自定义宏,把频率和输出上限绑定在一起初始化,从此再没出过这种低级错误。

5.2 BITMASK初始化陷阱:一不小心就是大四轴变四电机

关于BITMASK的坑,我想单独提醒一下。ArduPilot的某个版本里,电机输出的使能状态是用一个32位bitmask来表示的。初始化时如果赋值为0,所有电机默认关闭;如果赋值为0xFFFF,可以一次性打开前16个通道。问题出在——不同飞控硬件对“通道编号”的定义并不一致。同样是第四个电机,在一款飞控上可能是通道4,在另一款上可能是通道5。如果你直接把默认bitmask写死,很可能出现“解锁后电机顺序错乱”的诡异现象。这也是为什么我始终建议:不论用哪个飞控固件,到手第一件事就是跑一遍电机测试模式,确认物理映射。

5.3 F3B测试样机上遇到的最大怪象:电机开启顺序与姿态响应倒挂

5寸机测试时,我遇到的另一个大怪象是:电机开启顺序反了。因为某个配置接口填入的“电机顺序”是反的,结果飞控在实际飞行时,理论上应该顺时针旋转的电机,逆时针转了。这种错误在解锁时不会报错,但要到起飞瞬间才会体现——飞控试图增加俯仰力矩,结果实际产生的力矩方向完全相反,飞机直接后空翻。

这类问题单靠日志很难排查,因为日志里只记录软件层面的“期望值”,不会告诉你物理世界到底发生了什么。我当时的做法是:把所有电机的物理转向、位置顺序画成一张表,然后手动把每个电机的PWM输出单独打开,逐个验证实际旋转方向,确认一切无误后再装桨。平凡但救命的一步,不可省略。

6. 调试手段、倾向性建议与接下来的路线图

6.1 日志里最该看的四个变量

混控层调试时,最值得盯住的日志变量是:

  • RCOU(输出PWM值)
  • MOTF(期望力矩)
  • ATTC(姿态期望)
  • CTUN(油门期望)

通过这四组数据,你可以还原“飞控期望做什么”和“实际输出到电机是什么”之间的完整链路。最常见的异常模式是:MOTF显示期望已经拉满,但RCOU一直在中间波动——这说明你可能碰到了饱和限幅,或者是PWM输出频率和混控更新频率不匹配。另外一个值得看的信号是“期望力矩在某个轴上的分量始终无法收敛”,这类问题多半是机架几何在混控矩阵里没有正确建模。

6.2 调参的优先级:先把混控层“探明”再动PID

很多调参教程上来就让新手折腾PID。我的倾向是:先把混控层的所有输出真正“探明”了再说。怎么探?在飞控测试台上,给一个固定的PWM值,观察电机转速和推力是否稳定、线性;然后把飞机倾斜一定角度,给滚转或俯仰阶跃输入,看混控输出是否按预期方向分配。这个过程不需要飞起来,就能排查掉80%以上的“手感异常根源”。

我自己在SITL仿真环境里会额外做一件事:用脚本直接给混控函数喂一个固定期望力矩,然后把四个电机输出的PWM打印出来,和手算的期望值对照。一旦发现偏差,绝大多数不是PID问题,而是混控层参数没配对。

6.3 下一期预告:从混控到姿态环的完整回环

这期混控算法解析到这里,算是把“电机输出如何被计算出来”这件事讲透了。但我心里清楚,如果只看混控层,始终是“只见树木不见森林”。所以我打算下一期把姿态环和混控层的耦合关系串起来讲——从“期望力矩”到“当前姿态误差”再到“混控分配”的完整回环。到时候会用到更多的日志分析和在线工具。如果你自己也在折腾ArduPilot,建议这段时间先手动跑一跑混控函数,把你手头机架的真实输出顺序表记一下。提前准备好这些基础数据,下一期的案例分析你会看得更爽。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询