ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ArduPilot开方控制器sqrt_controller源码解析与调参实战

ArduPilot开方控制器sqrt_controller源码解析与调参实战 玩APM飞控的朋友尤其是喜欢深挖固件源码的人应该都见过sqrt_controller这个名字。它在ArduPilot也就是大家习惯称呼的APM飞控固件里出现频率极高姿态控制、位置控制、航点导航里都有它的影子但你问它具体干了什么、为什么这么干很多人就只说得出“一个开方控制器”。这篇文章我就把这个函数彻底拆开结合源码、控制逻辑和实际调参经验一次讲透。这篇文章适合所有用ArduPilot系飞控的玩家、开发者以及想搞懂多旋翼/固定翼控制原理的入门者。不管你是用Mission Planner调参的新手还是想给飞控写自己的控制代码的老手搞清楚sqrt_controller都会让你对飞控的行为有完全不一样的理解。看完你会明白为什么位置环P值不能乱调、为什么到航点会“刹不住”、为什么姿态回中有时快有时慢这些看似玄学的问题根源都在这个不起眼的开方函数里。1. 先搞懂为什么飞控里需要一个“开方控制器”开方控制器并不是ArduPilot的独创它在很多机器人和无人机控制库里都是标配。要理解它得先从飞控的控制链路说起。1.1 位置环到速度环的链路误差是怎么变成输出的多旋翼飞控里最外层的控制目标是“位置”。飞控拿到期望位置对比当前实际位置得到一个位置误差然后把这个误差换算成期望速度交给内层的速度控制器速度控制器再把速度误差换算成期望加速度最终通过姿态控制变成油门和倾角。整条链路是一级一级嵌套的每一级都在做同一件事把上级的“期望”和当前“实际”的差值变成一个下级的“期望值”。sqrt_controller就出现在“误差转期望速度”和“误差转期望角速度”这一层。它是这条链路上一个非常关键的非线性变换器。你调试时看到的PSC_POSXY_P、ATC_ANG_RLL_P这些P值很多最终都会通过sqrt_controller这个函数作用到误差上而不是直接简单地乘一下。1.2 纯P控制器在大误差下的两个毛病有人会问直接用P控制把误差线性放大不行吗误差大就猛输出误差小就温柔一点听起来挺合理。但真用起来会有两个很头疼的问题。第一个问题是大误差下的超调。位置误差很大的时候比如飞机被风吹偏了十米纯P控制会按照误差线性给一个很大的期望速度。这个期望速度本身就很高等飞机追回到目标点附近时速度已经飙起来了想刹都刹不住结果就是冲过头然后来回震荡好几圈才能稳住。这就像开车的时候离路口还很远就一脚地板油踩到底到了斑马线跟前才发现刹不住只能冲过去再倒回来来回折腾。第二个问题是小误差下的灵敏度矛盾。如果把P值调大让飞机在误差小的时候反应够快那大误差时输出就会更猛震荡更严重如果把P值调小大误差时是温和了但飞机离目标一两米时又“懒洋洋”的半天回不到正点悬停位置总感觉飘。说白了线性P控制无法同时满足“大误差时温柔”和“小误差时精准”这两个需求。1.3 平方根映射的直觉误差大时温柔误差小时精准sqrt_controller的思路很巧妙它对误差先开根号再映射成输出。误差是4米时输出并不是4倍关系而是2倍关系误差是0.25米时输出反而有0.5倍关系。用数学语言说这个函数的斜率增益是随误差变化而变化的误差越大有效增益越低误差越小有效增益越高。这正好解决了纯P控制的矛盾。大误差时输出温和给飞机留出减速缓冲的空间小误差时增益高让飞机能精准咬住目标点不松口。更妙的是这个函数还有一个物理含义它模拟的是“以恒定减速度刹停”的逻辑。想象你在滑板上想要在给定的距离内停下来那么最大允许的初速度就正比于距离的平方根也正比于你能用的刹车加速度的平方根。开方控制器里那个second_ord_lim参数本质上就是你在限制飞机允许使用的加速度上限。2. 源码拆解sqrt_controller到底怎么算这是很多人最关心的一节。网上关于这个函数的讨论不少但大多语焉不详。我直接贴一段和ArduPilot实际实现一致的sqrt_controller代码然后逐行拆解。不同版本细节上会有细微区别但核心逻辑高度一致。2.1 函数签名与核心实现在ArduPilot的AC_PosControl、AC_AttitudeControl等库里都能看到类似实现我以最常见的版本为例。// 将误差转换为控制输出 // error : 输入误差(位置误差或角度误差) // p : 比例增益 // second_ord_lim: 二阶限制(最大速度/最大角速度变化率) // dt : 控制周期(秒) float AC_PosControl::sqrt_controller(float error, float p, float second_ord_lim, float dt) { // 安全限幅: 当P增益乘以误差超过某个上限时, 返回线性P输出 float safety_limit second_ord_lim * 4.0f; if (second_ord_lim 0.0f || p * error safety_limit) { return p * error; } // 线性区边界: 由最大二阶限制和P增益共同决定 float linear_region second_ord_lim / p; // 误差为正, 且超过线性区: 走平方根曲线 if (error linear_region) { return sqrtf(2.0f * second_ord_lim * error); } // 误差为负, 且绝对值超过线性区: 走平方根曲线的负半轴 if (error -linear_region) { return -sqrtf(2.0f * second_ord_lim * -error); } // 误差在线性区以内: 使用纯比例输出 return p * error; }读这段代码时重点抓住三条分支的顺序。它不是单纯“所有误差都开根号”而是分了三段线性区、平方根正区、平方根负区最前面还挂了一个安全锁。我先解释参数含义再讲这三个区是怎么切分的。2.2 三个区域的切换逻辑与安全锁先看参数。p就是你在Mission Planner里看到的那些P值比如位置环的PSC_POSXY_P、姿态环的ATC_ANG_RLL_P。second_ord_lim是二阶限制量在位置环里对应最大加速度如PSC_ACCZ_X或代码里的_accel_xy在姿态环里对应最大角加速度ATC_ACCEL_RLL_MAX这类参数。dt是控制周期在部分版本里参与前馈运算核心映射逻辑不依赖它也能成立。线性区的边界由linear_region second_ord_lim / p给出。这个式子很关键它决定了误差多大时从线性过渡到平方根。误差比这个边界小说明误差在“可精细修正”范围内直接用P控制保证收敛的速度和精度误差比这个边界大说明已经偏得比较远需要“提前规划刹车距离”此时改用平方根曲线输出让期望速度不至于过大给内环留出减速空间。最前面的安全锁值得单独说。safety_limit second_ord_lim * 4.0f当p * error超过这个值时会直接返回p * error也就是说走线性P而不走平方根。为什么这么设计因为平方根函数的斜率虽然总体低于P但如果second_ord_lim很大平方根曲线可能拉得很高输出照样可能失控。安全锁相当于兜底一旦线性输出都超过安全上限说明误差已经大到离谱这时候与其继续“温柔减速”不如直接按P控制先拉回来避免出现输出反而比线性更大的尴尬局面。顺带一提这个4.0f不是拍脑袋来的它对应的是平方根函数在线性区边界处斜率的一个合理上限。等于在斜率上做了一个保护防止开方段在高增益P值下变得过“陡”。如果你自己改固件玩不建议随意改这个系数除非你确实清楚自己在做什么。2.3 dt在前馈与预测里的作用有一部分版本的sqrt_controller签名里带了dt有人可能好奇它到底用在哪儿。说实话在多数位置控制场景里dt并不直接参与上述三条分支的计算它更多是用在调用方控制器会根据dt做离散化处理或者用误差变化率做一个前馈项让高阶环路的响应更平滑。举一个实际例子在AC_PosControl::update_pos_xy_controller()里位置误差经sqrt_controller转成期望速度后会再经过速度控制器计算期望加速度。速度控制器内部对期望速度做微分时就需要dt。如果你自己写控制代码不要把这个dt丢掉至少要在离散化积分时用到它否则整个回路的动态特性会在不同运行频率下产生明显差异。3. 它在APM里的实际用处与调参影响源码看明白了还得回到实际飞控行为上来。sqrt_controller在ArduPilot里主要出现在三个地方理解这三处你就明白调参时那些参数为什么相互关联了。3.1 多旋翼位置控制器位置误差转期望速度多旋翼悬停或定点飞行时飞控持续计算水平位置误差再通过sqrt_controller把它变成期望水平速度。这个期望速度会被送到速度环速度环再输出期望倾角。也就是说你在Mission Planner里调的PSC_POSXY_P并不是直接决定“给多大的倾角去追位置”而是先决定“位置误差映射成多大的期望速度”。这个区别非常重要。很多新手把PSC_POSXY_P调大期望飞机回中更猛结果发现飞机会“冲过头”。原因就在这里P值变大后线性区边界second_ord_lim / p变小同样的误差更容易落在平方根区。平方根区输出虽然平滑但如果飞行速度已经很高内环减速不及时过冲自然就来了。正确的做法是先确认速度环和加速度限制是否合理再回头看位置环P值。再强调一个细节PSC_ACCZ_X水平方向最大加速度直接作为second_ord_lim传给sqrt_controller。你在调参软件里把它调大不只是“飞机响应更猛”这么简单它还会改变开方函数的曲率影响整个减速策略的提前量。我见过不少人遇到“到航点刹不住”时一味减小PSC_ACCZ_X结果飞机又变得很肉其实问题常常出在位置环P值和这个加速度限的匹配关系上。3.2 姿态控制角度误差转期望角速度多旋翼的姿态控制同样大量使用sqrt_controller。外环角度控制器得到横滚角或俯仰角的期望角度和当前角度的误差后会通过sqrt_controller把角度误差转成期望角速度然后交给内环角速度控制器。这就是为什么ATC_ANG_RLL_P、ATC_ANG_PIT_P在ArduPilot里不是简单相乘的关系。自稳模式下打杆飞控收到的期望角度和当前角度之间的误差越大角速度目标增长就越“收敛”误差越小角速度目标收敛得越利落。这带来的体感是大动作打杆时飞机姿态变化不会突然猛转而是先快速启动、再平滑收尾小幅修正时飞机又能迅速把姿态“咬”回到水平。如果你的飞机自稳模式下回中会先抖动一下或者大舵量打杆时感觉“转得太猛”大概率是姿态环角度P值和角加速度限制如ATC_ACCEL_RLL_MAX配合得不合适。顺着sqrt_controller的分区逻辑排查会比盲目加减P值高效得多。3.3 航点飞行与自动刹车平滑减速的关键自动航线飞行时sqrt_controller的表现更明显。飞机飞向一个航点时位置误差很大sqrt_controller输出的期望速度也跟着变大但增长速度远低于线性P。随着飞机不断靠近目标点误差缩小期望速度也被“规划”得越来越小。整个过程像是一辆车在导航提示下提前减速而不是到了路口才急刹。很多人飞自动航线时遇到“过冲”问题一上来就怀疑WPNAV_ACCEL或者WPNAV_SPEED。确实这两个参数直接影响飞行速度与加速度上限但真正让飞机“知道”减速时机的是航点导航模块里对距离误差调用sqrt_controller的那个环节。WPNAV_ACCEL的值作为second_ord_lim被传入后决定了飞机在什么距离上开始线性降低期望速度。我在自己的飞机上测试过假设WPNAV_ACCEL为4m/s²位置环P值为1.5那么线性区边界大约是4/1.52.67米。也就是说飞机距离航点大概2.67米以内时控制行为纯线性超过这个距离就进入平方根规划区开始提前减速。想看飞机是否在合适距离开始减速看日志里NkF1类的期望速度和实际位置差最直观。3.4 参数之间的联动与推荐调法既然sqrt_controller把P值和加速度限制耦合在一起调参时就不能孤立地看待任何一个参数。这里我整理了一张常用参数联动表方便你对照排查。现象可能原因排查方向回中慢、悬停漂位置环P值偏低或加速度限制偏低先小幅加PSC_POSXY_P再观察PSC_ACCZ_X是否过小到航点刹不住、过冲加速度限制偏大或位置环P值过大适当降低WPNAV_ACCEL检查PSC_POSXY_P是否偏高大舵量打杆猛、姿态过冲姿态环角度P过大或角加速度限制偏大降低ATC_ANG_RLL_P/ATC_ANG_PIT_P或限制ATC_ACCEL_RLL_MAX小修正抖、高频振角速度环增益过高或滤波过少先看角速度环ATC_RAT_RLL_P再回头检查角度环P我的习惯是调位置环时先把水平加速度限制设成一个合理的中间值多旋翼通常2到5m/s²之间然后每次只动PSC_POSXY_P观察日志里实际速度是否能在接近目标时平滑回落到零。等到“追点”行为正常了再回头看加速度限制对航线速度的影响。这个顺序能最大程度减少变量避免P值、加速度上限和速度上限互相干扰。4. 常见问题与排查实录理论说了一堆最后还是得回到实战。我自己在调机过程中踩过不少坑也帮朋友排查过不少类似问题这里挑几个典型的案例分享出来供大家参考。4.1 悬停回中慢、位置漂移朋友的一台四轴悬停时总往一个方向缓慢飘打杆回中后感觉飞机“不太愿意”回来总要迟滞一下才开始动。排查日志发现位置误差有1米多时期望速度输出只有0.3m/s左右明显偏低。问题就是PSC_POSXY_P太小导致线性区边界偏大大部分时间都落在纯线性区小误差时响应偏弱。处理办法是把PSC_POSXY_P从默认值往上加每次加0.5同时观察悬停时日志里期望速度的峰值。加到位置误差0.5米时期望速度能有0.5m/s以上飞机回中手感就正常了。注意不要一次加太多否则容易引出下一个问题。4.2 到航点刹不住、过冲明显自动航线模式下飞机接近航点时经常冲过头然后绕个圈再回来。日志里能看到飞机距离目标点还剩下10米时速度依然有8m/s左右直到最后两米速度才下降但已经来不及了。这个案例典型的WPNAV_ACCEL偏大平方根减速区虽然存在但加速度上限太高导致减速策略“启动晚”。同时PSC_POSXY_P也偏大让线性区边界变得更小飞机以为自己还有充足的减速距离。我把WPNAV_ACCEL从默认的6m/s²降到3.5m/s²同时把PSC_POSXY_P降回接近默认值再飞同样的航线过冲距离肉眼可见地缩短了。4.3 高频抖动/振荡还有一次碰到的问题是悬停时轻微的高频抖动飞起来不明显一旦定点就感觉机身颤。从日志看位置和速度曲线都有高频分量角速度环输出也像锯齿一样。这种问题和sqrt_controller本身关系不大更多是P值过大导致线性区输出斜率过高给角速度环注入了过多高频激励。排查时先看角速度环滤波和增益如果角速度环本身没问题再回头看角度环P是不是拉太高了。那次最后是把ATC_ANG_RLL_P从6.5降到5.2抖动就消失了定点精度反而没有明显下降。4.4 大风/高速下响应迟钝有经验的飞手会发现大风天里飞机被吹偏后回中响应总是“肉肉的”好像故意不使劲。这其实是sqrt_controller的平方根区在起作用位置误差比较大的时候期望速度增长缓慢看起来就是响应迟钝。想要在抗风场景下更果断一些合理做法不是单纯加大P值而是适当提高second_ord_lim比如PSC_ACCZ_X让平方根规划的“刹车距离”更长、允许的期望速度更大。这样一来风把飞机吹远时飞控会更早地提升期望速度去对抗漂移同时仍保持接近目标时的减速平滑性。如果只加大P线性区边界变小反而可能在强风中诱发震荡。4.5 快速调参口诀与工具参考最后分享一个我自己的排查顺口溜虽然有点糙但实测好用先看限幅再看P线性边界心中记大误差看限幅小误差看P值过冲先查加速度抖动回头查增益。排查时我习惯用Mission Planner的日志分析功能或者用MAVProxy导数据后绘图。重点看这几条曲线实际速度、期望速度、位置误差。期望速度和位置误差的关系是否像一条“先陡后缓”的曲线基本就是sqrt_controller是否工作正常的外在表现。如果期望速度在误差大时依然跟着误差线性涨说明该处可能没有走平方根分支如果期望速度在小误差时增长过猛说明线性区或者P值需要重新匹配。5. 基于源码扩展自己如何复现一个开方控制器到了这一步如果你想在自己的项目里用上sqrt_controller或者给飞控写自定义控制逻辑我给你一个可以直接套用的思路。不需要完整的ArduPilot环境用Arduino、Python仿真、甚至ST32裸机都能实现。5.1 最小实现与测试基准下面这个函数是平台无关的最小实现输入误差和两个关键参数输出控制量。float sqrt_controller_basic(float error, float kp, float max_rate) { if (max_rate 0.0f) { return kp * error; } float linear_region max_rate / kp; if (error linear_region) { return sqrtf(2.0f * max_rate * error); } else if (error -linear_region) { return -sqrtf(2.0f * max_rate * -error); } else { return kp * error; } }你可以用下面几组数据做基线测试error1.0, kp2.0, max_rate3.0时线性区边界为1.5因为误差没有超过边界输出为2.0。当error4.0时超过边界输出为sqrt(2*3*4)4.899而线性P输出是8.0。可以看出平方根段明显压低了控制量这就是它能防止大误差下猛输出的原因。5.2 在仿真里验证收敛行为建议你在Python或MATLAB里跑一个一阶系统仿真假设控制对象是理想的速度输出看看开方控制器和纯P控制在同样增益下的阶跃响应区别。你会发现纯P控制的响应快但超调大开方控制器会有一段“先快后慢”的收敛过程最终两者的稳态误差都可以收敛到零但开方控制器的超调量明显更低。我自己在仿真里还对比过“只调P”和“Pmax_rate联动”的差异只调P会让超调不可控联动调参则能在几乎不增加超调的前提下把上升时间缩短20%以上。这也是为什么ArduPilot官方在调参文档里总是强调改位置环P值时要同步审查加速度限制——这两个参数在sqrt_controller里本来就是同一个表达式的两个变量。6. 注意事项汇总与个人体会最后把一些零散但重要的经验集中列一下都是文档里未必会写、但实际调机时容易踩坑的地方。注意改位置环或姿态环P值前先确认对应second_ord_lim加速度/角加速度限制没有被调得过大或过小。这两个参数是“配对使用”的单独动任何一个都可能让sqrt_controller进入你不想要的工作区间。先说线性区边界。很多教程教你“P值决定响应快慢”这话没错但在ArduPilot里不够准确。P值变大线性区边界变小同样的误差更容易进入到平方根区实际响应反而可能变得“先猛后软”。所以改P后一定要看日志里的期望速度曲线而不是只看手感。手感骗人的情况太多了。再说安全锁。那个p * error safety_limit的兜底分支不少人在自写控制代码时会忽略。但飞控这种系统误差瞬间冲高是常事比如GPS跳变、遥控器误操作没有这个上限平方根输出也可能被拉到非常危险的值。哪怕你不是在ArduPilot生态里写代码也建议保留类似的限幅逻辑。安全优先永远是第一原则。然后是我个人的调参顺序习惯先把所有内环角速度环、速度环调稳再碰外环的位置环和姿态环。因为sqrt_controller的平方根段本质上是在给内环“减速”如果内环本身响应就拖沓外环再平滑也白搭。顺序反了的话你会在两个环路的相互牵连里耗尽耐心。关于工具除了Mission PlannerArduPilot社区常用的还有MAVProxy和Plotjuggler都可以用来观察期望值和实际值的匹配程度。如果你经常调参强烈建议学会看日志里对应控制器的期望速度和实际速度曲线那种“期望速度是否平滑、是否在接近目标时回落”的形态比任何参数表格都直观。最后再分享一个小技巧如果你在用ArduPilot的仿真环境比如SITL可以在仿真里故意把PSC_POSXY_P和PSC_ACCZ_X分别拉到极端值观察sqrt_controller三条分支各自的表现。仿真里折腾成本几乎为零却能帮你建立起非常扎实的参数直觉。我自己就是靠着在SITL里反复试错才真正看懂了这个开方控制器的脾气。
返回列表