☰
ArduPilot位置控制核心:sqrt_controller开方控制器原理与调参指南
2026/10/4 9:28:56 网站建设 项目流程

聊APM飞控,位置控制里绕不开的一个函数就是sqrt_controller,也就是常说的开方控制器。很多人第一次在源码里看到这个名字,第一反应是“什么东西开方?”——其实是速度指令和位置误差之间那条曲线的关系。ArduPilot的定高、悬停、过航点、绕圈,很多关键动作背后都在调这个函数。今天这篇就把这个函数从头到尾拆一遍,数学原理、源码实现、调用位置、调参避坑都过一遍。适合想把位置控制搞清楚,或者准备二次开发在位置环动手脚的读者;即使你只想调好参数,理解它也能帮你少走很多弯路。

1. 先弄明白:sqrt_controller到底在解决什么问题

1.1 位置控制里的“刹车距离”难题

想象一下:飞控要根据当前位置和目标点的误差给出期望速度。误差大的时候,总不能一直给最大速度吧?快到目标时必须提前刹车减速,否则就会冲过头,再绕回来,来回震荡。传统做法是用PID,P项直接等比放大误差。但误差很大时,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是位置误差,单位厘米(cm);rate_cps是最大巡航速率,单位厘米/秒(cm/s);accel_cmss是最大加速度,单位厘米/秒²(cm/s²);dt是控制周期,目前这个函数体里实际上没用到dt,算是保留参数接口。

这里需要注意APM的单位制和常见教材里的米制不一样。ArduPilot内部为了整数效率和日志可读性,位置用厘米、速度用厘米每秒、加速度用厘米每二次方秒。调参时在Mission Planner里看到的WPNAV_SPEED是cm/s,WPNAV_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里按Ctrl+F打开日志曲线,看PSC.PErr位置误差曲线和PSC.VelX/VelY期望速度曲线。你会发现期望速度随位置误差的变化,完全就是开方曲线和线性曲线的组合形态:误差大时速度增长平缓,误差开始变小时速度快速下降,等误差进入线性段后,速度几乎按线性关系收敛。这个可视化比读任何源码都直观。

4. 调参与避坑指南

4.1 关键参数对照表

开方控制器的输入,基本都来自几个以WPNAV和ATC开头的调参参数。理解这张表,你在Mission Planner里就知道该动谁了:

参数默认值(视固件版本)含义影响sqrt_controller哪个输入
WPNAV_SPEED1000 cm/s最大水平巡航速度rate_cps
WPNAV_ACCEL100 cm/s²水平最大加速度accel_cmss
WPNAV_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/s,WPNAV_ACCEL放到200到400 cm/s²。重型航拍机机动性差,WPNAV_SPEED建议800到1000,WPNAV_ACCEL建议80到120。固定翼和垂起则应该更保守,水平加速度给60到100就差不多了。

判断参数是否匹配,可以算一个数:linear_dist = rate² / (2 * accel)。假设WPNAV_SPEED=1000,WPNAV_ACCEL=100,那么linear_dist=10000cm,也就是100米。意思是误差在100米以内时,控制器全程使用线性段,误差50米时速度参考是500cm/s。这个值算出来如果明显超出你的典型任务半径,说明线性段太长,等价于一个固定P控制器,开方的好处基本没吃到。

我说一个自己的调机案例。一台大型六轴,默认参数下航点切换时机头明显乱点,观察日志发现水平期望速度一直在限幅值附近振荡。查下来是WPNAV_ACCEL=100对这个重量级的飞机太小了,线性段太长,位置误差一直落在线性区,速度参考被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的位置控制、航线飞行、自主降落这些场景里,都会多一份确定性。

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

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

立即咨询