调固定翼飞控的朋友大概率都遇到过这个场景:飞机上天后,横滚方向一直在画S形,明明航向设好了,可航迹总是在目标线两侧来回抽;高度也像是在坐电梯,油门推了不加速、升降舵拉了高度又迅速掉速度。我在第一次用Pixhawk飞固定翼的时候,被这两个问题折磨了整整一个周末。后来才明白,水平通道和垂直通道分别由两套核心算法撑着——L1导航算法负责“怎么沿航迹走”,TECS总能量控制负责“怎么管油门和升降舵”。这篇文章从原理讲到代码,把参数整定和踩坑经验一次说透,适合正在调Pixhawk固定翼的新手,也适合想从传统PID思维转到现代控制思路的老手。先说清楚一件事,本文以ArduPilot固件为例,但L1和TECS的核心思想在PX4里同样存在,你把原理吃透,换平台只是换个参数入口而已。
1. 为什么固定翼控制要单独研究L1和TECS
1.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.85 |
| L1_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/s |
| TECS_SINK_MIN | 最小下沉率 | 巡航下滑段的参考值,一般设成0.5到1 |
| TECS_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. 实战配置与代码解析:在Pixhawk(ArduPilot)上跑起来
4.1 固件选择与硬件准备
Pixhawk只是飞控硬件平台,固件可以是ArduPilot,也可以是PX4。这两个都支持固定翼,但ArduPilot的ArduPlane对固定翼的支持更成熟,L1和TECS的代码结构也更直观,所以我个人建议玩固定翼优先考虑ArduPlane固件。PX4的固定翼控制也有路径跟踪和速度高度控制,但参数命名和调试方法和ArduPilot差异较大,新手先别两头抓。
硬件上,除非你只是跑纯仿真,否则我强烈建议固定翼装机时配备空速计。TECS是依赖空速的算法,没有空速计,它只能靠GPS地速去估计,遇到风一吹就完全乱套。我见过有人为了省事不装空速计,结果每次侧风降落时油门和俯仰都在高频振荡,这就是TECS拿不到真实空速的典型症状。
装机之后,常规校准一个都不能少:罗盘校准、水平校准、遥控器行程校准、舵机中位微调。特别注意固定翼的方向舵和升降舵的舵面方向,错了的话飞机一离地就会画一个诡异的弧线,跟算法没关系。
4.2 关键参数配置清单
下面是ArduPlane里跑固定翼自动飞行前,我建议先检查一遍的参数清单:
| 参数名 | 建议初始值 | 说明 |
|---|---|---|
| NAV_L1_PERIOD | 20~25 | L1距离的基准周期,按巡航速度微调 |
| L1_DAMPING | 0.7 | 航迹阻尼,振荡时提到0.8 |
| L1_N_P | 1.0 | 侧向加速度前馈,谨慎调整 |
| TECS_SPEED | 实测巡航空速 | 务必和实际速度匹配 |
| TECS_TIME_CONST | 5.0 | 垂直通道响应快慢 |
| TECS_THR_DAMP | 0.1 | 油门阻尼 |
| TECS_PITCH_DAMP | 0.1 | 俯仰阻尼 |
| TECS_SINK_MAX | 2.0~3.0 | 限制最大下沉率 |
| ARSPD_USE | 1 | 固定翼务必开启空速计 |
| 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提供的SITL(Software 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参数扫掠开始,先让虚拟飞机把路走一遍,真机上天的时候心里会踏实很多。