ARTICLE DETAIL

资讯详情

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

ArduPilot位置控制核心:sqrt_controller开方控制器原理与调参指南

ArduPilot位置控制核心:sqrt_controller开方控制器原理与调参指南 聊APM飞控位置控制里绕不开的一个函数就是sqrt_controller也就是常说的开方控制器。很多人第一次在源码里看到这个名字第一反应是“什么东西开方”——其实是速度指令和位置误差之间那条曲线的关系。ArduPilot的定高、悬停、过航点、绕圈很多关键动作背后都在调这个函数。今天这篇就把这个函数从头到尾拆一遍数学原理、源码实现、调用位置、调参避坑都过一遍。适合想把位置控制搞清楚或者准备二次开发在位置环动手脚的读者即使你只想调好参数理解它也能帮你少走很多弯路。1. 先弄明白sqrt_controller到底在解决什么问题1.1 位置控制里的“刹车距离”难题想象一下飞控要根据当前位置和目标点的误差给出期望速度。误差大的时候总不能一直给最大速度吧快到目标时必须提前刹车减速否则就会冲过头再绕回来来回震荡。传统做法是用PIDP项直接等比放大误差。但误差很大时P项会给出超大速度指令——内环限幅后实际就是猛冲。猛冲为什么不行不是怕超调是物理上刹不住。四旋翼靠倾斜机身产生水平加速度倾斜角越大加速度越大。如果给定速度太大机身再怎么倾也拉不出足够的反向加速度飞机就会以很高的速度冲过目标点然后又要花很长时间绕回来。这在航点任务里特别明显飞机绕着目标点“画圈”速度越快画得越大。开方控制器就是干这个的根据误差大小输出一个“刚好能在目标点刹住”的速度参考。用到的算式就是匀减速公式的变形v sqrt(2 * a * error)。其中的物理意义是——在误差为error的位置如果允许的最大减速度是a那么最多能以速度sqrt(2aerror)飞向目标到了目标正好能把速度降到0既不超调也不浪费时间。1.2 从“距离-速度曲线”看懂三段设计sqrt_controller的输入输出曲线分成两段中间用linear_dist衔接。APM源码里的核心逻辑是这样的大误差区|error| linear_dist采用开方曲线 v sign(error) * sqrt(2 * a * |error|)。误差越大速度增长速度越平缓接近目标时自动平滑减速。小误差区|error| linear_dist采用线性曲线 v rate * error / linear_dist。等效于一个比例控制器但增益是经过计算的。分界点 linear_dist rate² / (2 * a)这个距离的含义是以最大速度rate飞行以加速度a匀减速到0所需要的最小距离。在分界点处两段函数值相等正好都是rate或-rate所以曲线整体连续不会出现速度跳变。这个分界点设计得非常巧妙它把“大误差时物理可行”和“小误差时快速收敛”两个需求无缝拼接起来。为什么要有一个小误差线性段而不是全程开方主要有两个原因。第一开方函数在误差接近零时斜率趋近无穷大。如果直接用纯开方控制误差越小增益越大一点微小扰动就会被放大成剧烈修正飞机会高频抖动严重时直接振荡发散。第二误差可能是负的对负数开方没有实数解必须分方向处理。线性段正好把“接近目标”这个区域的增益拉到一个可控的有限值让控制行为平滑且可预测。1.3 为什么它比纯P控制更适合做速度前馈把几种方案放在一起对比逻辑会更清楚方案大误差行为小误差行为特点线性P控制速度随误差线性增长极易超出物理极限增益恒定比较容易调大误差必须限幅否则冲击严重纯开方控制速度按sqrt增长增速放缓天然接近加速度约束斜率无穷大容易振荡无线性段接近目标时会抖振开方控制器开方段限制末段减速线性段等效有限高增益收敛快、平滑、物理可行从更宏观的角度看开方控制器其实是在近似“梯形速度剖面”。理想的时间最优控制应该是全速加速、匀速巡航、全速减速像一个梯形。但工程上直接做切换控制容易产生冲击而且对参数极其敏感。开方控制器用一条平滑曲线逼近这个剖面输出直接作为内环速度PID的“参考速度”配合内环的加速度限制就得到了接近最优的响应同时没有开关切换的毛刺。2. 源码层面的逐行拆解2.1 函数原型与参数含义ArduPilot里的sqrt_controller有好几个版本分别服务位置控制、姿态控制和导航模块。最核心的是AC_PosControl里的这个完整实现如下float AC_PosControl::sqrt_controller(float error, float rate_cps, float accel_cmss, float dt) { // 如果最大速率或最大加速度为零退化为简单的线性控制 if (is_zero(accel_cmss) || is_zero(rate_cps)) { return error * rate_cps; } // 计算线性区间与开方区间的分界距离 float linear_dist 0.5f * rate_cps * rate_cps / accel_cmss; // 正向大误差使用开方公式 if (error linear_dist) { return safe_sqrt(2.0f * accel_cmss * error); } // 负向大误差使用开方公式注意符号处理 if (error -linear_dist) { return -safe_sqrt(2.0f * accel_cmss * -error); } // 小误差线性段等效高增益比例控制 return rate_cps * error / linear_dist; }参数含义要对照APM的物理单位来理解error是位置误差单位厘米cmrate_cps是最大巡航速率单位厘米/秒cm/saccel_cmss是最大加速度单位厘米/秒²cm/s²dt是控制周期目前这个函数体里实际上没用到dt算是保留参数接口。这里需要注意APM的单位制和常见教材里的米制不一样。ArduPilot内部为了整数效率和日志可读性位置用厘米、速度用厘米每秒、加速度用厘米每二次方秒。调参时在Mission Planner里看到的WPNAV_SPEED是cm/sWPNAV_ACCEL是cm/s²千万别按米去理解不然数字会差一百倍。2.2 三个核心公式的由来先看linear_dist。匀减速过程满足 v_end² v_start² - 2 * a * s。要从速度rate刹到0至少需要距离所以 s rate² / (2a)。这个距离就是linear_dist。反过来给定剩余距离error允许的最大起始速度就是v sqrt(2 * a * error)这就是开方公式的来源。再看线性段公式。在误差从0到linear_dist的范围内期望速度从0线性增长到rate所以比例系数就是rate / linear_dist。代入linear_dist展开等效P增益是2 * accel / rate。也就是说线性段的增益由加速度和最大速度共同决定加速度越大增益越高最大速度越大增益越低。这个等效增益通常比随便拍脑袋定的位置P增益要合理得多因为它本身是从飞机物理能力推导出来的。safe_sqrt在ArduPilot的AP_Math库里有定义本质上是对负数输入的保护参数小于等于0时返回0否则正常开方。虽然调用方通常已经做了正负判断但这个保护函数还是保留了防止边缘情况出现NaN。2.3 配套函数与函数族开方控制器不是孤立存在的它有一个函数族。姿态控制模块里有一个版本处理的角度误差和角速度单位是centidegrees体系。导航模块里还有limit_sqrt_controller和inv_sqrt_controller。limit_sqrt_controller的作用是把输入误差直接映射成“限制后的速度”和常规sqrt_controller的区别在于它更纯粹常用于判断目标点是否到达、或者计算路径末端的减速起点。inv_sqrt_controller则反过来已知当前速度反推“等效误差距离”在航点到达判定里很有用速度越快判定“到达”的半径就得越大否则飞机还没停稳就切下一个航点。姿态控制版本的调用方式类似但它处理的是角度误差到期望角速度的转换。例如在AC_AttitudeControl::input_euler_angle_roll_pitch_euler_rate_yaw里飞控会把用户给定的姿态角误差通过sqrt_controller换算成期望的旋转角速度再交给内环角速度控制器执行。这样摇杆打满也不会让飞机“啪”地一下猛转过去而是平滑地加速再减速。2.4 主要调用位置速查模块文件典型场景输入误差输出AC_PosControl.cpp水平位置控制水平位置误差期望水平速度AC_PosControl.cpp垂直高度控制高度误差期望爬升速度AC_WPNav.cpp航点导航到航点距离期望巡航速度AC_Circle.cpp绕圈控制半径误差径向速度修正AC_Loiter.cpp悬停/慢速漂移漂移距离期望速度抑制AC_AttitudeControl.cpp姿态角输入整形姿态角误差期望角速度这些调用点的函数签名略有差异但核心思想完全一样把“误差”翻译成“速度参考”让内环去执行。3. 在实际APM系统里它是怎么参与控制的3.1 水平位置控制里的“两条腿走路”以Loiter悬停模式为例水平方向上有两级控制。外环是位置环把当前的GPS或光流位置误差通过开方控制器转成期望水平速度。内环是速度环用PID把期望速度与实际速度的偏差转成期望加速度再换算成期望倾斜角。姿态环再把倾斜角转成角速度指令最后由电机混控输出油门。在AC_PosControl::update_pos_xy_controller里核心逻辑大致是这样// 位置误差转期望速度 _target_vel_xy sqrt_controller(pos_error, _speed_cms, _accel_cmss, dt); // 再做一个硬限幅确保速度指令不超过最大速度 _target_vel_xy.limit_length(_speed_cms);速度指令即使经过开方计算在大误差时仍然可能超过最大巡航速度所以外面还要再拉一遍限幅。这个“再限幅”是很多人容易忽略的细节我在看代码时也经常看到有人只改了rate_cps参数却忘了外层还有一个constrain导致速度曲线和预期对不上。位置前馈是“坚决不超速”的关键。如果没有开方控制器位置环用普通P控制大风或者人为推动下误差一大P输出直接超过内环能力飞机会出现那种“猛冲一下又急停”的抽风感。有了开方以后速度参考本身就是物理可行的内环只需要老老实实跟踪就行。3.2 垂直高度控制的“梯形速度剖面”很多人以为AltHold是直接油门控制高度其实不对。在ArduPilot 4.x里高度通道用的是级联控制位置外环把高度误差通过开方控制器转成期望爬升率再由垂直速度PID转成油门修正量。这个设计在起飞和降落场景里特别重要。比如一键起飞目标高度可能是3米初始误差接近0外环给出一个逐渐增大的爬升速度参考。刚开始飞机以最大爬升率往上走接近目标高度时开方曲线自动把参考速度压下来飞机就平滑地停在新高度上不会有“坐电梯”的顿挫感。垂直方向的开方控制器和水平方向有一点区别爬升和下降的动力特性不对称所以APM分别有WPNAV_SPEED_UP和WPNAV_SPEED_DN两个参数。在实际控制里上升时用爬升限速参数下降时用下降限速参数但加速度参数通常共用一个。这个细节在做定高降落时很关键下降速度给太小会迟迟落不了地给太大又会触发地面效应导致弹跳。3.3 姿态控制里的输入整形开方控制器不只用在位置环姿态控制里也有它的影子。用户摇杆或航点指令给出的期望姿态角如果直接硬转过去角速度和角加速度很容易超过飞行器能力表现出来就是机身剧烈点头或者横滚冲击。ArduPilot在姿态控制里对输入做了“整形”处理把姿态角误差通过开方控制器先换算成期望角速度再对角速度做限速和限加速度。这样不管摇杆怎么粗暴操作机体收到的都是平滑的期望角速度曲线。很多飞手觉得ArduPilot比某些开源固件“稳”很大一部分功劳来自这里不是PID参数玄学而是它有意识地用数学模型限制了快变通道的物理量。3.4 用SITL复现一整套控制链路如果你想亲眼看看这个函数的效果最好的办法是用SITL软件在环仿真不需要真机就能复现。第一步下载ArduPilot源码并配置环境git clone官方仓库安装依赖后编译Copter固件。第二步用sim_vehicle.py启动SITL连接Mission Planner地面站。第三步在Mission Planner里设一个简单任务比如起飞后飞向50米外的一个航点。第四步飞行过程中打开日志记录重点关注位置控制相关的PSC组和导航相关的NTUN组。飞完一次后在Mission Planner里按CtrlF打开日志曲线看PSC.PErr位置误差曲线和PSC.VelX/VelY期望速度曲线。你会发现期望速度随位置误差的变化完全就是开方曲线和线性曲线的组合形态误差大时速度增长平缓误差开始变小时速度快速下降等误差进入线性段后速度几乎按线性关系收敛。这个可视化比读任何源码都直观。4. 调参与避坑指南4.1 关键参数对照表开方控制器的输入基本都来自几个以WPNAV和ATC开头的调参参数。理解这张表你在Mission Planner里就知道该动谁了参数默认值视固件版本含义影响sqrt_controller哪个输入WPNAV_SPEED1000 cm/s最大水平巡航速度rate_cpsWPNAV_ACCEL100 cm/s²水平最大加速度accel_cmssWPNAV_SPEED_UP250 cm/s最大爬升速度rate_cps垂直WPNAV_SPEED_DN150 cm/s最大下降速度rate_cps垂直WPNAV_ACCEL_Z100 cm/s²垂直最大加速度accel_cmss垂直ATC_ACCEL_P_MAX视固件版本俯仰角速度加速度限制accel_cds姿态ATC_ACCEL_R_MAX视固件版本横滚角速度加速度限制accel_cds姿态ANGLE_MAX4500 cdeg最大倾斜角决定可达加速度上限注意默认值只是参考不同固件版本差异不小务必以你地面站显示的实际默认值为准。我遇到过有人在4.0固件上照抄4.3的调参结果飞机响应完全不是一回事。4.2 不同机型和场景怎么设开方控制器的参数不是“越大越快”那么简单核心是速度、加速度和机体动力要匹配。小型穿越机推力足、响应快WPNAV_SPEED可以放到1200到1800 cm/sWPNAV_ACCEL放到200到400 cm/s²。重型航拍机机动性差WPNAV_SPEED建议800到1000WPNAV_ACCEL建议80到120。固定翼和垂起则应该更保守水平加速度给60到100就差不多了。判断参数是否匹配可以算一个数linear_dist rate² / (2 * accel)。假设WPNAV_SPEED1000WPNAV_ACCEL100那么linear_dist10000cm也就是100米。意思是误差在100米以内时控制器全程使用线性段误差50米时速度参考是500cm/s。这个值算出来如果明显超出你的典型任务半径说明线性段太长等价于一个固定P控制器开方的好处基本没吃到。我说一个自己的调机案例。一台大型六轴默认参数下航点切换时机头明显乱点观察日志发现水平期望速度一直在限幅值附近振荡。查下来是WPNAV_ACCEL100对这个重量级的飞机太小了线性段太长位置误差一直落在线性区速度参考被P控制放大后反复打满。把WPNAV_ACCEL提到180之后linear_dist缩短到约28米误差大时进入开方段速度曲线平滑了很多点头问题消失。4.3 我踩过的坑第一个坑只调速度不调加速度。有段时间我把WPNAV_SPEED拉到2000但WPNAV_ACCEL没动结果飞机在航点附近画圈。原因是linear_dist被拉得非常大速度参考早早进入线性下降区还没到航点速度就掉光了飞机走得特别慢绕来绕去。后来把WPNAV_ACCEL同步提到250才恢复正常。第二个坑高度控制里盲目调大WPNAV_ACCEL_Z。油门通道本身有较大的延迟加速度限制给太大垂直速度环会变得很冲高度到点后总有一点过冲再回落。这不是开方控制器本身的问题而是垂直方向的气动和推力响应比水平方向滞后建议从100开始配合日志观察PSC.VelZ期望爬升率和实际爬升率的跟随情况再逐步调整。第三个坑在SITL里测试时觉得“反正不炸机”各种极端参数乱试。虽然软件在环不会真摔但SITL的电机混控、运动模型和真机差别很大特别在气动阻尼和延时上SITL里表现良好的参数上真机可能完全两个样。建议SITL只用来验证控制逻辑和参数方向最终参数还是得靠真机小范围试飞。4.4 二次开发常见魔改如果你在改源码最常见的需求是改变“减速曲线形状”。默认是0.5次方也就是平方根。有人觉得收敛太慢想改成error的0.8次方相当于远端速度更大、更激进。实测效果是确实能让飞机更快靠近目标但姿态波动和超调也会明显上升原因是远端速度大进入线性段时速度变化率也大对速度环冲击更强。如果你要改务必在SITL里先跑几十遍不同航点组合确认不会出现发散。还有人想直接去掉线性段全程开方。实际跑起来会发现误差接近零时期望速度抖动很厉害因为开方函数在零点的斜率无穷大频率噪声全部被放大成速度抖动。听我一句劝APM这个线性段设计是有道理的真想改也应该保留。5. 常见问题与排查技巧实录5.1 为什么目标点很远飞机会“猛冲一下又急停”这个现象通常不是开方控制器本身有问题而是外层限幅和内环能力不匹配。检查顺序第一步看WPNAV_SPEED是不是设置远大于内环能跟上的速度第二步看ANGLE_MAX有没有被调得过大导致机体允许的倾斜角远超速度环的跟随能力第三步看日志里期望速度和实际速度的偏差如果偏差一直很大说明需要先降速或提高内环PID。还有一种情况是位置误差跳变。比如GPS信号切换、光流重置位置误差瞬间从一个值跳到另一个很大的值外环输出的速度参考也会瞬间跳变。这是数据源问题不是控制器问题要先去处理传感器融合而不是盲目调小加速度。5.2 快速多航点时机身点头/前后抖动多航点连续飞行时飞控往往在还没完全停稳时就切换下一个航点此时位置误差的方向会快速变化。开方控制器对误差方向的突变非常敏感因为误差符号一变输出的速度符号也要跟着变这个速度跳变如果超过内环角加速度限制机身就会出现点头或抖动。处理思路有两个方向。一是降低航点切换带来的冲击在AC_WPNav里给转角处的路径做平滑或者降低巡航速度二是把WPNAV_ACCEL适当调小一点让速度参考的变化率降低。注意不要同时把速度也调太低否则任务效率太差。5.3 如何通过日志确认开方控制器工作状态Mission Planner的日志功能是排查这类问题最有力的工具。位置控制相关的数据在PSC组导航数据在NTUN组。打开日志曲线后重点看几组对应关系PSC.PErr位置误差和PSC.VelX/VelY期望速度的对应关系应该呈现开方曲线的形态PSC.VelX/VelY期望速度和实际速度的偏差正常情况下应该很小如果偏差持续偏大说明速度环没跟上前馈。还有一个技巧把期望速度的曲线和平常打的油门曲线叠在一起看。期望速度变化最剧烈的时刻往往是油门补偿需要发力的时刻。如果你发现油门补偿经常滞后大概率不是开方控制器的问题而是垂直速度PID和推力前馈没调好。5.4 自己改源码之后怎么从SITL开始验证如果你决定在AC_PosControl或AC_WPNav里改开方控制器的逻辑我的流程是这样的先git clone整个ArduPilot仓库在本机编译SITL版本修改libraries里对应的cpp文件重新编译后用sim_vehicle.py启动。在地面站里先跑一个简单任务观察PSC和NTUN日志有没有异常曲线再逐步加大任务复杂度比如多航点、大速度、大风扰动。工具链方面Mission Planner是地面站首选源码阅读用支持C的IDE或编辑器都行命令行交互用MAVProxy。说实话如果你只是调参数Mission Planner完全够用但如果要动控制器逻辑这套“源码SITL日志回放”的组合才是效率最高的光靠真机试错又贵又慢。6. 一个容易被忽略的线性与开方衔接细节回到函数本身有一个细节我想重点提醒linear_dist位置的斜率并不是连续的。开方分支在分界点的斜率是a/rate线性分支的斜率是2a/rate正好差了一倍。这意味着从开方区进入线性区时速度参考的“变化率”有一个向下的跳变直观感受就是飞机快要到目标时减速会更“果断”一点。这个特性不是bug是有意为之。开方段负责远端的大尺度减速线性段负责近端的小误差收敛两段交接时稍微加快一点收敛速度可以让飞机更快进入悬停精度范围。但这也带来了一个副作用如果内环速度PID的D项调得过大这个斜率跳变会被放大表现出来就是临到目标点时机身轻微顿一下。如果你在日志里发现期望速度曲线在linear_dist附近有明显拐点同时姿态角有对应的小波动不要急着去怀疑开方控制器先检查速度环的D项和滤波时间常数。理解到这个层面你对sqrt_controller的认知就不只是“一个函数”了而是整个ArduPilot位置-速度-姿态级联控制里最关键的“误差到速度”翻译器。我自己在实际调机里拿到一架新飞机时不会先去抠PID而是先把WPNAV_SPEED、WPNAV_ACCEL和ANGLE_MAX按动力水平设定好再看位置环还抖不抖。原因很简单外环的速度参考如果本身就不合理内环怎么调都是亡羊补牢。开方控制器恰恰就是那个把“合理速度”计算出来的关键把这个函数吃透你在APM的位置控制、航线飞行、自主降落这些场景里都会多一份确定性。
返回列表