四旋翼无人机PID轨迹跟踪控制仿真:从模型搭建到调参实战 很多刚接触无人机控制的人第一反应就是赶紧去买台真机来试。但实际上如果没先在MATLAB里把基于PID的轨迹跟踪控制仿真跑通真机试飞大概率会变成交学费现场。我自己当年做四旋翼轨迹跟踪控制项目时就是从标题里这种“基于PID的四旋翼无人机轨迹跟踪控制-仿真程序”入手的——在Simulink环境里用S-Function搭被控对象模型再配上双层PID控制结构让飞机沿圆形轨迹稳定飞行。这篇博文就把整个思路从头到尾讲透从四旋翼数学模型怎么搭、PID控制架构为什么选外环位置内环姿态、到Simulink模型每一步怎么布置再到最后调参时踩过的那些坑。不管你是刚入门控制方向的学生还是准备把仿真迁移到实物平台上的工程师照着这套思路都能少走很多弯路。1. 建模仿真前必须想明白的问题模型从哪里来、用到什么程度1.1 坐标系、状态变量和基本假设在Simulink里写四旋翼模型之前最重要的事不是急着拖模块而是把坐标系约定搞清楚。很多仿真结果一团糟不是因为控制算法有问题而是因为模型里坐标系用混了。我习惯的做法是用两组坐标系一个是地面惯性系用来描述飞机的位置x, y, z和线速度另一个是机体坐标系用来描述飞机的姿态角和角速度。姿态角用滚转角φ、俯仰角θ、偏航角ψ来表示角速度p、q、r则对应机体轴上的三个转动分量。为了让模型不至于复杂到没法用建模时会做几个经典假设四旋翼机体是刚体不考虑桨叶和机架的弹性形变结构关于机体轴完全对称忽略空气阻力对机身的影响低速飞行时误差很小电机和螺旋桨的响应可以用一阶惯性环节近似这些假设不是偷懒而是工程上非常标准的取舍。你要是把气动阻力、陀螺效应全加进去模型方程会膨胀好几倍而PID控制器能吸收一部分未建模误差。先把基础模型跑通再逐步增加复杂度这个顺序千万不要反过来。1.2 从牛顿-欧拉方程到可编程的微分方程四旋翼的动力学方程可以从牛顿-欧拉方程推导出来。位置部分在地面系下写出来是这么个形式m·ẍ (cosφ·sinθ·cosψ sinφ·sinψ)·Tm·ÿ (cosφ·sinθ·sinψ - sinφ·cosψ)·Tm·z̈ (cosφ·cosθ)·T - m·g这里的T是四个电机产生的总推力g是重力加速度。可以看出来水平方向的加速度完全由总推力在水平方向的分量产生这就是为什么四旋翼必须先倾斜姿态才能水平移动。姿态部分在机体坐标系下用欧拉方程描述τ I·ω̇ ω × (I·ω)展开以后就是围绕三个轴的力矩平衡方程。对于仿真来说一般直接把角速度的一阶导数作为状态量的导数输出就行。仿真模型的输入通常设计成四个虚拟控制量总推力U1、滚转力矩U2、俯仰力矩U3、偏航力矩U4然后在模型内部再换算成四个电机的转速指令。1.3 参数怎么取模型里最关键的物理参数包括机身质量m、绕三个轴的转动惯量Ixx、Iyy、Izz、机臂长度l以及螺旋桨的拉力系数和扭矩系数。具体数值可以参考以下量级跟很多小型穿越机的参数比较接近参数符号典型值单位机身质量m1.2kg滚转转动惯量Ixx0.012kg·m²俯仰转动惯量Iyy0.012kg·m²偏航转动惯量Izz0.022kg·m²机臂长度l0.25m推力系数kF1.0e-5N/(rad/s)²扭矩系数kM2.0e-6N·m/(rad/s)²注意这些参数不一定要精确仿真阶段更重要的是量级正确。等到后面做实物迁移时才需要认真做参数辨识。仿真阶段参数差个20%PID整定后照样能飞但如果你把惯性矩的数量级搞错了控制增益怎么调都会发散的。2. 控制结构选择位置环在外层、姿态环在内层以及为什么2.1 直接对姿态做PID为什么不够很多初学者一开始的想法是把轨迹跟踪当成一个简单的三通道PID问题——误差是位置偏差输出是油门和姿态角。这种思路不是完全错但直接给姿态角下指令是行不通的因为四旋翼的水平位置不能直接由姿态角决定而是姿态角产生的推力分量的积分效果。打个比方你要开车到一个目的地方向盘转角本身不决定位置变化得先让车转向再靠车速把位移累积起来。四旋翼也一样位置控制器的输出不能直接是电机转速而应该是“期望的姿态角”而姿态角还得由另一层控制器去执行。这也就是为什么工程上几乎都用双闭环结构。外环是位置环根据位置误差计算出期望的姿态角内环是姿态环负责快速跟踪这个期望姿态角。内环带宽远高于外环这样两个环之间才不会互相打架。2.2 位置控制器怎么算出期望姿态角以水平方向的x通道为例位置环的PID输出的是一个期望的水平加速度a_x_des。有了这个加速度之后结合当前偏航角ψ的观测值用三角函数反解就能得到期望的滚转角φ_des和俯仰角θ_des。常见的小角度近似公式是这样φ_des (a_x_des·sinψ - a_y_des·cosψ) / gθ_des (a_x_des·cosψ a_y_des·sinψ) / g这两个角就是给内环姿态控制器的输入。垂直方向则单独用一个PID通道控制总推力T。这样设计的好处是非常直观位置环只管“朝哪个方向加速”姿态环只管“把飞机摆到那个角度”各司其职。2.3 Simulink里PID的参数形式和集成位置在Simulink里实现PID你可以直接用自带的PID Controller模块也可以自己拖Gain、Integrator、Derivative模块搭出来。我个人更推荐直接在Simulink的PID模块里设置参数尤其是初学阶段因为模块自带饱和限幅和积分抗饱和选项能省掉很多手工处理。参数设置上有一点值得注意位置环的Kp通常比较小因为位置响应对应的是慢动态姿态环的Kp要大得多因为姿态角需要快速收敛。这个数量级的差异非常明显我见过有人把两层控制器参数设成一样的结果仿真直接振荡发散。比例关系可以参考姿态环Kp大约是位置环Kp的5到10倍积分系数和微分系数也按这个比例拉开。3. Simulink模型搭建与S-Function实现一步一步把方程变成可运行的模型3.1 顶层模块划分拿到一个完整的Simulink仿真模型不要从底层模块开始抓起先搭顶层结构。四旋翼轨迹跟踪仿真程序一般分成四个大模块轨迹发生器生成期望的位置轨迹可以是圆形、八字形或阶跃信号位置控制器输入期望位置和实际位置输出期望姿态角姿态控制器输入期望姿态角和实际姿态角输出四个虚拟控制量四旋翼动力学模型用S-Function实现输入控制量输出位置、速度、姿态角、角速度这四个模块的信号流向是单向的轨迹发生器 → 位置控制器 → 姿态控制器 → 动力学模型 → 反馈回到控制器。这个单向数据流非常关键如果信号回流形成了代数环仿真就会卡死或奇慢无比后面专门讲这个问题。3.2 S-Function里写什么动力学模型选择用S-Function而不是纯模块搭建是因为状态方程写起来更清晰尤其在实现完整非线性模型时模块搭法会扯出一大团连线后期维护非常痛苦。S-Function的Level-2版本支持连续状态直接在mdlDerivatives回调里写状态导数就行。核心的状态量我习惯排成12维向量x [x, y, z, u, v, w, φ, θ, ψ, p, q, r]其中u、v、w是三个方向的速度分量。S-Function接收的输入则是四个虚拟控制量。在mdlDerivatives里干的活儿无非是把控制量根据执行器分配矩阵换算成四个电机的推力再把推力合成为机体坐标系下的总力和力矩最后套用前面那组动力学方程算出所有状态量的导数。提示S-Function里最容易出bug的地方是Euler角的换算。特别是把角速度q和姿态角速率之间转换时那张转换矩阵千万别抄错正弦和余弦的位置写反了飞机在仿真里就会像抽风一样乱转。3.3 轨迹发生器、示波器与可视化轨迹发生器用简单的MATLAB Function模块就能写。比如生成一条圆形轨迹function [xd, yd, zd] trajectory_gen(t) R 2.0; % 轨迹半径米 omega 0.4; % 角速度rad/s z_hover 1.5; % 悬停高度米 xd R * cos(omega * t); yd R * sin(omega * t); zd z_hover; end仿真过程中观察数据我会同时开三个Scope一个观察三维位置跟踪曲线一个观察姿态角响应还有一个观察控制量输出是否饱和。如果只是看最后结果中间过程很难定位问题出在哪个环节。可视化方面可以在MATLAB里写一个循环脚本把仿真输出的位置数据逐帧画成三维动画用小球表示期望位置用带姿态的小模型表示实际位置。虽然效果不如VR Sink那么炫但胜在简单可靠而且不依赖额外的工具箱。4. 仿真踩坑记录代数环、采样时间、单位这些坑我挨个填过4.1 代数环与死锁Simulink里最常见、最恼人的错误就是代数环。出现代数环的本质是信号在同一个仿真步长内形成了循环依赖模块之间互相等着对方先算出结果结果谁都没法先动。四旋翼模型里最容易出现代数环的位置是反馈回路和控制器的Derivative模块直接相连的时候。解决代数环的思路有三个我按推荐程度排一下在反馈回路里加一个单位延迟Memory或Unit Delay模块切断同一步长内的循环依赖用滤波器替代纯微分项比如把PID里的Derivative改成带一阶低通滤波的实际微分器在模型设置里启用“Algebraic Loop Solver”但这个办法治标不治本会让仿真速度明显变慢我实际排查下来的经验是先把Simulink的诊断信息打开它能直接报出代数环出现的位置和路径。然后优先用“加单位延迟”的方式切断回路对轨迹跟踪效果的影响几乎可以忽略。4.2 采样时间设置对跟踪效果的影响另一个非常隐蔽的问题是采样时间设置不统一。有人会把PID Controller模块的采样时间设成0.01秒S-Function里又设成离散采样而轨迹发生器用的是连续时间信号。结果就是控制量更新的节奏和模型计算的节奏对不上仿真出来的曲线毛刺特别多。我的建议是整个仿真程序统一使用固定步长求解器比如定步长4阶龙格库塔步长设0.001秒。控制器和模型都用连续时间处理不故意引入离散化这样先保证逻辑正确后面如果需要做机载代码生成再单独做离散化验证。4.3 单位与数值量级这个坑说出来有点丢人但它确实是新手最容易犯的错误之一。角度和弧度的混用是重灾区。姿态控制器里期望角是弧度但你如果用deg输入PID增益的量级就会全乱。我一开始就因为角度单位问题姿态环的P增益怎么调都感觉系统软绵绵的后来发现是角度单位搞错了。检查单位其实有个简单的经验法则把控制器给定值设成零看系统能不能稳定悬停。如果悬停时控制量输出和重力补偿量匹配那单位基本没问题。反之如果悬停时油门输出明显偏大或偏小先去检查是不是单位换算出错了。5. 参数整定与结果分析怎么从振荡调到稳定跟踪5.1 整定顺序与方法PID参数整定没有捷径但有非常明确的顺序。四旋翼这种多环系统切记从内环往外环调。你先让姿态环能稳定跟踪阶跃姿态角指令再去动位置环。如果顺序反过来外环一给姿态指令姿态环根本跟不住整个系统看起来就像失控一样。我的整定流程是这样的只用姿态环PD控制给定一个5度的滚转角阶跃观察超调量和调节时间慢慢增大姿态环Kp直到出现等幅振荡记录这个临界值再把Kp取到临界值的60%到70%加入微分项Kd用来抑制超调观察曲线是否变得平滑姿态环稳定后再切换到位置环用同样的方法调位置环的Kp和Kd最后才加积分项I用来消除稳态误差但积分增益一定不能大否则很容易引发低频振荡用这个流程从零开始调通圆轨迹跟踪我一般在一个小时内就能完成而最开始凭感觉乱调参数耗了一整天也没收敛。5.2 典型仿真结果现象对照调参过程中会反复看到几种典型的响应曲线我把它们的表现和解决办法整理成了下面的对照表现象可能的根因应对方法位置误差高频振荡姿态环Kp过大减小姿态环Kp或增大Kd轨迹跟踪缓慢、滞后明显位置环Kp过小增大位置环Kp圆轨迹稳态半径偏大积分项不足适当增大位置环Ki滚转和俯仰通道互相耦合振荡两个通道参数差异太大保证x、y通道参数一致起飞瞬间冲高后掉下来垂直通道积分饱和开启积分抗饱和并限制输出这张表是我调参过程中一点点攒下来的每一个现象背后都对应着一类模型或参数问题。如果仿真结果对上了表中某一行优先按照对应方法去排查比盲目地乱调参数高效得多。5.3 量化评估跟踪效果光看Scope里曲线重合还不算完最好对跟踪效果做一个量化评估。我的做法是记录整个仿真周期内每个时刻的位置误差然后计算均方根误差。比如圆形轨迹的跟踪RMS误差小于0.1米就算基本合格小于0.05米就算调得不错。计算误差可以用一个简单的MATLAB脚本处理仿真输出的数据集data logsout.getElement(state_log).Values; t data.Time; x data.Data(:, 1); % 期望轨迹 xd 2 * cos(0.4 * t); ex xd - x; rms_error sqrt(mean(ex.^2)); fprintf(RMS位置误差: %.4f m\n, rms_error);这里还有个经验之谈只看均方根误差还不够最好也看一下最大误差出现的时间点。如果最大误差总是出现在轨迹的拐弯处说明控制器带宽不足需要对姿态环做进一步的加固而不是简单调位置环增益。6. 从仿真走向实物的几个关键提醒6.1 直奔实机前必须在仿真里补的东西仿真跑通了离实机还有一段不短的距离。实物平台上的四旋翼和仿真模型最大的差别在于传感器有噪声、执行器有延迟、机体参数有偏差。所以在迁移到实物之前我强烈建议在仿真模型里手动加三类扰动——传感器高斯白噪声、执行器的一阶惯性延迟、以及±15%左右的参数偏差。如果加了这三类扰动之后控制器还能保持稳定跟踪移植到实机上的把握才会大一些。6.2 代码生成和硬件在环的心得如果计划把Simulink模型部署到飞控板上建议提前把控制器的连续模块全部离散化并且把采样周期统一到一个固定值。用Simulink的代码生成工具生成C语言代码之前确保模型里没有触发子系统以外的事件驱动模块否则生成的代码逻辑容易出问题。更稳妥的做法是先做硬件在环仿真把控制器代码跑在一个真实的控制板上被控对象仍然是Simulink里的模型这样能在不接螺旋桨的前提下发现接线和通讯的问题。我在实际做硬件在环验证时发现很多在纯仿真里不明显的时序问题一换成真实控制板就暴露了。最典型的就是串口通讯的丢帧导致姿态反馈信号偶尔跳变。这时候光靠PID是扛不住的必须在反馈链路里加滑动窗口滤波或者把控制器的输出限幅做得更紧一些。提醒从仿真到实物PID参数大概率要缩水一半以上。仿真里为了展示效果而调得很“硬”的参数放到真机上会因为高频噪声和执行器饱和而剧烈抖振。我的做法是把仿真姿态环Kp先乘以0.5Kd乘以0.8再实机试飞迭代安全性会好非常多。6.3 后续可以有这样几种扩展方向仿真模型一旦稳定运行剩下的发挥空间其实很大。如果想让自己的项目更有深度可以从几个方向往上加料把PID换成分级动态逆控制解决强机动场景下PID控制量过大的问题在轨迹发生器里加入速度约束和加速度约束让期望轨迹更贴合真实飞行能力或者把外环升级为模型预测控制器解决延迟问题。这些方向都以当前这个Simulink模型为基础并不需要推翻重来。我自己回看整个项目的最大收获反而不是PID参数调得多好而是明白了“分层控制”这件事的本质——每一层控制器只需要关心自己的控制目标把复杂的多变量控制问题拆解成几个单变量问题的叠加。这个思想适用于四旋翼也适用于很多其他运动控制系统。如果你正卡在四旋翼仿真程序跑不通或者调参不收敛的问题上建议先别急着猛调参数回到模型本身用Scope把每一层信号都拉出来看看。先确认姿态环跟踪是否干净再确认位置环输出是否合理然后一层层往外推进。这个排查思路比我走过的弯路更值得你花时间记住。最后分享一个小技巧把仿真模型里的PID增益单独用一个变量定义在基础工作区里而不是直接填数字。这样你整定参数时只需要改工作区的变量再按一次仿真按钮就行不用一遍遍打开模块对话框。调参效率会提高得很明显而且后面做参数扫描、批量实验都方便得多。