
调固定翼飞控的朋友大概率都遇到过这个场景飞机上天后横滚方向一直在画S形明明航向设好了可航迹总是在目标线两侧来回抽高度也像是在坐电梯油门推了不加速、升降舵拉了高度又迅速掉速度。我在第一次用Pixhawk飞固定翼的时候被这两个问题折磨了整整一个周末。后来才明白水平通道和垂直通道分别由两套核心算法撑着——L1导航算法负责“怎么沿航迹走”TECS总能量控制负责“怎么管油门和升降舵”。这篇文章从原理讲到代码把参数整定和踩坑经验一次说透适合正在调Pixhawk固定翼的新手也适合想从传统PID思维转到现代控制思路的老手。先说清楚一件事本文以ArduPilot固件为例但L1和TECS的核心思想在PX4里同样存在你把原理吃透换平台只是换个参数入口而已。1. 为什么固定翼控制要单独研究L1和TECS1.1 先把一个概念掰清楚飞行控制里的L1和机器学习里的L1正则化这里必须先做个名词澄清因为网上搜索“L1算法”时大概率会蹦出一堆机器学习的文章什么L1正则化、L1损失跟飞控半毛钱关系都没有。术语所属领域核心作用L1正则化机器学习在损失函数中加入权重绝对值之和让部分权重变为0得到稀疏模型L2正则化机器学习在损失函数中加入权重平方和让权重整体变小防止过拟合L1损失 / L2损失机器学习指MAE和MSE两种误差度量用于回归任务L1制导固定翼飞控通过飞机前方一个虚拟参考点计算期望侧向加速度实现航迹跟踪简单说机器学习里的L1正则化解决的是“模型稀疏性”问题L1损失解决的是“误差怎么度量”的问题它们关注的是数据空间里的数值规律。而飞控里的L1制导是MIT的Rolf Rysdyk在2002年前后提出的非线性制导律解决的是“飞机怎么沿着规划好的航线飞”的问题。纯属同名千万别混着看不然调参的时候查资料能被绕晕。另外多说一句如果你以后写程序用到了“L1损失”和“L2损失”什么场景下该加哪个也有讲究L1损失对离群点不敏感适合误差比较脏的场景L2损失对离群点惩罚严重但整体更平滑。这跟在飞控里调L1完全无关仅仅因为名字撞车提醒你不要被搜索关联误导。1.2 固定翼控制回路的“分工逻辑”Pixhawk上的固定翼自动飞行控制回路不是一坨代码全包圆而是分层协作。外环是制导层负责根据当前位置、速度、目标航点算出“飞机下一步应该往哪使劲”中环是姿态层负责把制导层的指令变成横滚角、俯仰角内环是角速率层负责真正驱动舵面和油门。L1和TECS都属于外环制导层但分工非常明确L1处理水平通道它接收当前地速、航迹信息和目标航线输出一个期望横滚角。TECS处理垂直通道它接收当前空速、高度和期望空速、期望高度输出期望俯仰角和油门值。这两个通道最终都会送到姿态控制器由姿态控制器去统一调度舵面。你甚至可以这么理解L1决定“飞机往哪边压坡度”TECS决定“飞机抬头还是低头、油门给大还是给小”。这两套算法协同固定翼才能既沿着航线飞又保持速度高度稳定。为什么不用传统PID一个回路调到底因为固定翼不是四旋翼那样的强耦合弱非线性模型固定翼的速度和高度互相影响你拉升降舵爬升动能转换成势能空速会掉你推油门加速升力增加又会改变高度轨迹。用串级PID硬调经常是速度环和高度环互相打架调三天不出效果。L1和TECS的价值就是把这种复杂耦合拆解成“水平”和“垂直”两个相对独立的问题降低整定难度。2. L1导航算法拆解从几何直觉到代码2.1 L1的核心思路跟着前方一个点飞L1制导的精髓特别容易理解你把它想象成开车你要沿着一条直线车道走但你的车头方向和车道方向没对齐怎么打方向最平滑不是死盯着车头正前方而是盯着车道上“前方一段距离”的某个点朝着那个点打方向随着车往前开这个目标点不断往前滚动方向盘自然就越来越缓和。L1算法也一样。它在期望航迹上取一个点这个点到飞机的距离是一个固定值L1然后飞机用地速方向作为参考计算地速方向与飞机到目标点连线之间的夹角η。期望侧向加速度的公式是a_cmd 2 * V² * sin(η) / L1其中V是地速L1是前方参考距离η是地速方向到目标点连线的夹角。这个公式的精妙之处在于sin(η)让控制量在误差大时响应快、误差小时自然衰减而2/L1这个系数让整个闭环呈现出一阶惯性的平滑特征。原论文里推导过这个制导律在线性化之后闭环极点是带有0.707阻尼比的二阶系统也就是说你不需要额外去调阻尼天生就比较稳。实际工程中你会发现L1距离取得越大航迹跟踪越柔和但转弯响应越迟钝取得越小跟踪越激进但也容易振荡。2.2 参数如何设L1距离、阻尼比与侧向的前馈在ArduPilot的固定翼固件里L1制导相关参数主要集中在三个参数名作用推荐调整思路NAV_L1_PERIOD决定L1距离实际L1 PERIOD * 当前地速默认值常见在20到25巡航速度快可以适当减小慢速手抛机可以加大L1_DAMPING阻尼比默认0.7左右一般保持默认即可航迹振荡时可提高到0.75~0.85L1_N_P期望航向变化率的前馈增益默认1.0过大会导致航迹出现S形超调我最开始调机时习惯性地把NAV_L1_PERIOD调到10结果飞机在航线上左右横摆像喝醉了酒。后来才反应过来L1距离不是随便定的它本质上是“你希望飞机提前多远的距离开始预测转弯”距离太短控制器相当于一个高增益的比例控制动态上必然震荡。正确的做法是先根据飞机的巡航速度和转弯半径估算一般让L1距离在15到40米左右手抛小机取小值2米翼展以上的载机取大值。还有一个容易忽略的点L1公式里的V用的是地速而不是空速。原因很简单导航是地面几何问题飞机要跟踪的是地面上的航线轨迹地速才反映真实的航迹推进速度。不过TECS那边恰恰用空速这俩千万别混。2.3 从几何到工程侧向加速度为什么先转成横滚角L1算出来的是侧向加速度但舵机不认识侧向加速度姿态控制器只认横滚角。所以工程实现上必须做一次转换bank_angle_cmd atan(a_cmd / g)g是重力加速度。这个转换的物理含义是固定翼要产生侧向加速度唯一的办法就是从竖直方向借力把升力矢量倾斜过来。你压坡度的角度越大升力的水平分量就越大转弯就越快。从侧向加速度到期望横滚角的转换本质上就是把“几何制导”和“姿态控制”桥接起来的一步。所以你会看到Pixhawk的日志里L1输出往往是一个横滚角指令然后姿态PID再去追这个角。如果姿态控制器响应太慢、横滚通道增益过低L1算得再好也没用航迹一样会“看着在收敛、实际上在漂”。这也是我调机时一个深刻的教训制导算法只是大脑内环姿态才是肌肉单练大脑不练肌肉飞机照样飞不利索。3. TECS总能量控制深度解析油门和升降舵为什么不再打架3.1 传统PID串级在天上的“内耗”我们先来看看不用TECS、直接用串级PID控制固定翼会出什么问题。常规思路是高度误差给升降舵空速误差给油门。逻辑上听起来没毛病——高了我低头慢了给油。但固定翼有一个天然的物理耦合问题飞机爬升时一部分推力用来克服重力做功空速会下降空速下降后升力变小高度又会掉。如果你用升降舵去追高度高度一有偏差就猛拉杆速度立刻就掉速度掉了之后PID又去推油门油门推大了飞机抬头高度又超调。这个循环往复飞机在空中表现就是“点头油门喘振”而且很容易越调越乱因为两个回路的增益互相牵制。我见过不少新手的固定翼日志里油门曲线像锯齿一样上下跳动高度也能稳住但整个飞行过程非常疲惫电机和舵机都在被反复折磨。这个问题的根源不是PID参数不好而是控制结构本身没有解耦。TECS的思路就是直接从能量角度把这个问题绕过去。3.2 TECS到底在算什么总能量与能量分配率TECS的全称是Total Energy Control System总能量控制系统。它的核心思想是飞机在垂直平面内的能量包括动能和势能总能量E 0.5 * m * V² m * g * h油门应该控制的是总能量的变化率而升降舵控制的应该是能量在动能和势能之间的分配。这句话要拆开理解。先看油门如果飞机既需要加速、又需要爬升那就需要增加总能量油门必须加大如果总能量够了只是速度高度分配不合理油门就不要动让升降舵去处理。这就是“油门管总能量”的含义。再看升降舵如果能量分配需要把动能转成势能也就是减速换取爬升升降舵就抬头反过来把势能转成动能升降舵就低头。最典型的就是下滑阶段你要保持空速不变但又要降低高度这时候升力不足会让空速增加所以需要轻微低头把势能转换成动能同时配合油门收小保持总能量稳定下降。在ArduPilot的TECS实现里它会计算一个速度误差和高度误差的加权组合这个组合量叫SPE_error能量误差油门就根据这个能量误差来调节同时它还会计算能量分配率的误差俯仰通道的指令就来自这个分配率误差。用公式直观表示一下总能量误差SPE_error 空速误差项 K_h * 高度误差项油门指令throttle_dem 巡航油门 K_thr * (SPE_error 积分项)能量分配率误差SPE_dot_error 动能变化率期望误差 - 势能变化率期望误差俯仰指令pitch_dem 巡航俯仰 K_pitch * SPE_dot_error 阻尼补偿这种解耦结构的好处是高度偏差不会像传统PID那样引发油门剧烈波动速度偏差也不会导致升降舵乱点头因为油门只管“总量够不够”升降舵只管“分配对不对”两个通道从底层就不打架。3.3 TECS参数速查与整定心得ArduPilot里TECS相关参数命名很规律直接以TECS开头我整理一份常用的参数名作用调整心得TECS_SPEED巡航空速设定必须根据机型实际巡航速度设置不是默认值能用天下TECS_TIME_CONST总能量控制的响应时间常数默认5秒越大越肉越小越激进TECS_THR_DAMP油门通道阻尼过大会导致发动机响应迟钝过小则油门上下抽动TECS_PITCH_DAMP俯仰通道阻尼过大会导致俯仰动作僵硬过小飞机会点头TECS_SINK_MAX最大下沉率限制高度修正时不会俯冲太猛载机建议控制在2到3m/sTECS_SINK_MIN最小下沉率巡航下滑段的参考值一般设成0.5到1TECS_INTEG_GAIN积分增益有稳态误差时适当加大但不要超过0.3调TECS我的经验顺序是先设准TECS_SPEED。空速计必须装好TECS对空速的依赖远大于对GPS的依赖空速不准或者延迟大TECS怎么调都别扭。接着调TECS_TIME_CONST它决定了整个内环的响应快慢。然后动TECS_THR_DAMP和TECS_PITCH_DAMP这两个是核心我一般从默认值开始一次只动0.02到0.05飞一个起落看日志再说。我最开始调TECS时犯过一个错TECS_SPEED设成了12但实测巡航速度只有10导致飞机一直在空中“较劲”——油门全开想追速度升降舵又不断低头配合结果整个高度轨迹像波浪一样起伏。后来把TECS_SPEED修正回实际巡航速度问题瞬间消失。TECS的一切都建立在“速度期望合理”这个前提下速度设错后头全白调。4. 实战配置与代码解析在PixhawkArduPilot上跑起来4.1 固件选择与硬件准备Pixhawk只是飞控硬件平台固件可以是ArduPilot也可以是PX4。这两个都支持固定翼但ArduPilot的ArduPlane对固定翼的支持更成熟L1和TECS的代码结构也更直观所以我个人建议玩固定翼优先考虑ArduPlane固件。PX4的固定翼控制也有路径跟踪和速度高度控制但参数命名和调试方法和ArduPilot差异较大新手先别两头抓。硬件上除非你只是跑纯仿真否则我强烈建议固定翼装机时配备空速计。TECS是依赖空速的算法没有空速计它只能靠GPS地速去估计遇到风一吹就完全乱套。我见过有人为了省事不装空速计结果每次侧风降落时油门和俯仰都在高频振荡这就是TECS拿不到真实空速的典型症状。装机之后常规校准一个都不能少罗盘校准、水平校准、遥控器行程校准、舵机中位微调。特别注意固定翼的方向舵和升降舵的舵面方向错了的话飞机一离地就会画一个诡异的弧线跟算法没关系。4.2 关键参数配置清单下面是ArduPlane里跑固定翼自动飞行前我建议先检查一遍的参数清单参数名建议初始值说明NAV_L1_PERIOD20~25L1距离的基准周期按巡航速度微调L1_DAMPING0.7航迹阻尼振荡时提到0.8L1_N_P1.0侧向加速度前馈谨慎调整TECS_SPEED实测巡航空速务必和实际速度匹配TECS_TIME_CONST5.0垂直通道响应快慢TECS_THR_DAMP0.1油门阻尼TECS_PITCH_DAMP0.1俯仰阻尼TECS_SINK_MAX2.0~3.0限制最大下沉率ARSPD_USE1固定翼务必开启空速计TRIM_THROTTLE巡航油门百分比根据平飞日志确定这个清单不是让你照着改完就飞而是给你一个起点。很多参数在不同机型和天气下都需要再做微调但把上表里的基础值设对飞机至少不会在天上乱闹脾气。4.3 从ArduPilot源码看L1与TECS的实现先看L1。ArduPilot的L1控制代码在libraries/AP_L1_Control/AP_L1_Control.cpp里核心逻辑简化出来大概是这个样子// 简化自ArduPilot AP_L1_Control::update_navigation void L1_Control::update(const Vector2f groundspeed_vec, const Vector2f position, const Vector2f target_waypoint) { // 1. 计算当前点到航迹的横向偏差xtrack_vel和xtrack float xtrack_vel ...; float xtrack ...; // 2. 构造航迹前方距离为L1的参考点 // L1 NAV_L1_PERIOD * speed float L1_distance _L1_period * groundspeed_vec.length(); Vector2f target target_waypoint groundspeed_vec.normalized() * L1_distance; // 3. 计算地速向量与飞机到目标点连线之间的夹角eta float eta wrap_PI(atan2(target - position) - atan2(groundspeed_vec)); // 4. 经典L1制导律计算侧向加速度 float lateral_accel 2.0f * groundspeed_vec.length() * groundspeed_vec.length() * sinf(eta) / L1_distance; // 5. 由侧向加速度换算期望横滚角 _nav_bank atanf(lateral_accel / GRAVITY_MSS) * RAD_TO_DEG; }注意看第4行这个lateral_accel就是公式里的a_cmd第5行则是把加速度变成期望横滚角。整个函数没有花哨的滤波和复杂的PID计算所有的智能都浓缩在“前方取一个L1距离的参考点”这个几何操作里。L1_N_P参数在完整源码里作用在期望航向变化率的前馈项说人话就是当前方航迹在弯曲时提前一点点加横滚量让飞机切弯更跟手。再看TECS。ArduPilot的TECS代码在libraries/AP_TECS/AP_TECS.cpp里油门通道和俯仰通道是分开计算的核心思路简化如下// 简化自ArduPilot AP_TECS::update_throttle 与 update_pitch void TECS::update() { // 1. 总能量误差 速度误差 高度误差折算 float SPE_error (sp_dem - sp) _height_scale * (h_dem - h); // 2. 油门通道管总能量的“量” _throttle_dem _throttle_trim _K_thr * (SPE_error _SPE_error_int); // 3. 能量分配率误差管动能和势能的“分配” float SPE_dot_error (sp_dem_dot - sp_dot) - (h_dem_dot - h_dot); // 4. 俯仰通道由分配率误差产生期望俯仰角 _pitch_dem _pitch_trim _K_pitch * SPE_dot_error - _pitch_damp * climb_rate; }这段代码看着短但它说的很清楚油门不直接管高度俯仰也不直接管速度。油门在纠“总能量没了多少”俯仰在纠“能量是不是分错了方向”。只要总能量误差是零油门就稳在巡航值附近只要分配率正确升降舵就不会为了一点高度偏差乱拉。实际源码里还有一堆积分限幅、输出限幅、模式切换逻辑但理解了这两个核心公式日志里的油门曲线、俯仰角曲线你基本就能读懂了。4.4 先用仿真验证固定翼飞机仿真与传感器仿真调飞控最怕的就是拿真机试错一个参数不对可能炸鸡。ArduPilot提供的SITLSoftware In The Loop仿真是把真实的飞控固件跑在PC上用仿真模型模拟固定翼飞机本体和传感器输出。你可以先用SITL把L1和TECS的初步参数验证一遍再上真机。SITL的启动方式很简单在安装了ArduPilot开发环境之后一条命令就能起飞sim_vehicle.py -v ArduPlane --console --map启动后MAVProxy会模拟一架固定翼飞机你可以在里面开自动模式、玩航点任务、看姿态响应。更重要的是SITL还能做传感器仿真你可以故意注入风场扰动把GPS信号改差甚至模拟空速计延迟观察L1和TECS在极端条件下的表现。这样你就能在零风险的情况下建立对算法的直觉L1跟着前方参考点转TECS在油门和升降舵之间做能量分配这些代码层面的概念被真实的数据曲线印证之后你对它们的理解会完全不一样。我个人强烈建议实飞前至少花一个下午做SITL参数扫掠。比如把NAV_L1_PERIOD从15到30每隔2拉一组记录每一组的航迹偏差和横滚角变化很快就能找到最适合自己机型的取值区间。这个习惯帮我省下过好几架飞机的维修费。5. 飞行调试中的常见问题与排查技巧5.1 常见问题速查表这一节纯干货都是我实际调机时踩过或者见过别人踩的坑整理成速查表方便你对照。问题现象可能原因排查与对策航迹S形摆动始终压不住航线NAV_L1_PERIOD太小或L1_N_P过大适当加大L1距离降低前馈增益转弯切过头出弯后反向修正L1_DAMPING过小把阻尼比提到0.75~0.85油门上下抽动发动机喘振TECS_THR_DAMP偏小或TECS_SPEED设置错误先核对空速设置再加大阻尼高度波形振荡像过山车TECS_PITCH_DAMP过大或升降舵增益偏高减小俯仰阻尼检查姿态环升降舵增益大油门爬升但空速持续掉TECS_SPEED高于实际巡航速度太多把TECS_SPEED降到实测平飞速度附近下滑时速度稳不住越飞越快TECS_SINK_MIN设置过小导致俯仰修正不够适当提高最小下沉率参考值侧风时航迹偏离明显L1没有加入风补偿或前馈不足检查L1_N_P考虑开启侧风前馈相关项空速计数值跳变TECS频繁修正空速管堵了或安装位置受螺旋桨气流影响清空速管调整安装位置遇到问题先别急着改参数我建议你先下载飞行日志看几个关键曲线地速、空速、高度、油门输出、俯仰角期望与实际俯仰角、横滚角期望与实际横滚角。参数问题是哪一种日志里写得很清楚。很多时候你以为的“TECS参数问题”看日志发现其实是“姿态内环响应不够”白白调了一晚上。5.2 调参阶段值得长期坚持的三个习惯第一个习惯一次只改一个参数。这是老生常谈但能做到的人真的不多。L1和TECS涉及参数又多又相互影响如果你同时改了NAV_L1_PERIOD和TECS_PITCH_DAMP飞机飞出奇怪响应你根本不知道是哪个参数惹的祸。一次只动一个飞一个起落看日志再动下一个效率反而最高。第二个习惯把气象条件记录下来。固定翼的调试受风影响极大同样一组参数无风天飞得很直三级风天就可能画龙。我每次试飞都会在笔记本上记录当天风速风向、阵风情况再对应去看航迹表现。慢慢你会发现L1和TECS在不同风况下的弱点完全不同这对后续参数优化非常关键。第三个习惯仿真先行真机确认。前面说的SITL不只是入门练习用的它完全可以用来做参数风险评估。你可以在SITL里把参数往极限调一调看看算法在边缘状态下的表现提前建立对参数边界的感觉再带着这个感觉去真机上微调。写在最后最后再分享一个我自己的习惯每次新装一架固定翼我不会一上来就飞航线自动模式而是先在手动模式下做几次平飞、爬升、下降把巡航油门、巡航速度摸清楚然后把TECS_SPEED和TRIM_THROTTLE填准再开自动模式。L1和TECS其实都不想“背锅”它们只负责在你给了合理参考值时把飞机控制好参考值本身就是航模飞行员的基本功。先调水平通道的L1让飞机能直着走再调垂直通道的TECS让飞机走得稳最后再统一做动态配合。这套顺序我用了很多年几乎没失手过。你如果也在调固定翼建议从今晚的SITL参数扫掠开始先让虚拟飞机把路走一遍真机上天的时候心里会踏实很多。