资讯动态

ArduPilot多旋翼混控算法深度解析:矩阵、饱和与ScaleRing

发布时间:2026/10/22 16:24:43 来源:尧图企业网站定制
我刚入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 余弦定理与ScaleRingAP_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建议这段时间先手动跑一跑混控函数把你手头机架的真实输出顺序表记一下。提前准备好这些基础数据下一期的案例分析你会看得更爽。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑