
1. 拿到已调好的仿真程序第一件事不是跑而是拆做四旋翼控制的人应该都有这种经历从某个渠道拿到一份别人调好的仿真程序压缩包解压出来里面躺着几个.m文件、一个.slx模型、一个README.txt或者干脆什么都没有。这时候大多数人的第一反应是双击主脚本看到曲线出来就觉得自己已经掌握了。但实际上如果你只停留在能跑通这个层面那这份程序对你的价值连十分之一都发挥不到。我拿到这份四旋翼 ADRC 控制器仿真程序时第一件事是把目录结构完整看了一遍。这里给所有准备复现这份程序的朋友一个建议拿到任何仿真工程先建立一张程序地图搞清楚哪个文件是入口、哪个文件是模型、哪个文件是参数初始化、哪个文件是控制器再动手运行。这不只是为了跑通而是为了后面你能自主修改、调参、移植。这份程序的结构大致如下文件类型作用说明主脚本仿真入口和结果绘图设置仿真时长、初始状态、干扰方式参数初始化脚本定义四旋翼物理参数和控制器参数质量、惯量、臂长、控制器带宽等动力学模型四旋翼六自由度运动方程一般用子函数或 Simulink 模块实现ADRC控制器包含跟踪微分器、扩张状态观测器、非线性状态误差反馈控制器独立成函数方便替换状态观测器脚本用于估计总扰动ADRC 的秘密武器所在README.txt里的信息很简洁只说明了两件事在什么版本的 Matlab 下运行以及主脚本名字。这其实暴露了一个很常见的问题——很多分享出来的程序作者默认使用者已经具备足够背景知识省略掉了大量为什么这样写的信息。这篇文章想做的就是把缺的部分补上。另外如果你是首次在 Matlab 中打开这个程序建议先运行一遍checkEnv或者直接执行主脚本看看会不会因为中文字符编码、路径空格、工具箱缺失等问题报错。我曾经遇到过一次很尴尬的情况程序本身没问题但因为解压路径里带了一个中文文件夹名导致脚本读取模型文件失败。这种问题排查起来不难但很浪费时间。纯净的英文路径、干净的 workspace是跑通一切 Matlab 仿真的前提。2. 四旋翼的动力学模型这份程序到底在模拟什么物理过程很多初学者拿到仿真代码后直接从控制器开始看这其实是个误区。四旋翼仿真程序里最值钱的往往不是控制器而是那个看起来不起眼的动力学模型部分——因为模型写得好不好直接决定控制器参数能不能平移迁移到真机上。ADRC 这种控制器本质上是在跟模型较劲如果模型少了一项耦合力矩你在仿真里调出来的观测器带宽在真机上大概率是废的。这份程序采用的模型是标准的三维刚体六自由度方程。需要强调的是它做了一些在四旋翼仿真中非常通用的简化假设机体视为刚体不考虑弹性形变电机响应简化为二阶或一阶惯性环节不考虑更复杂的电调特性空气阻力用简单的阻尼系数近似而不是查表的气动模型忽略地面效应、螺旋桨陀螺效应等次要因素。用公式表达就是经典的牛顿-欧拉方程。位置环节由三个方向的合力驱动姿态环节由机体的力矩驱动两者通过旋转矩阵耦合。你可能觉得这只是标准操作但实际写代码时会发现几个容易出鬼的地方第一坐标系定义。程序里常见的定义是机体坐标系 z 轴向上x 轴指向机头方向世界坐标系采用 NED北东地或者 NWU北西天都会带来很大区别。我强烈建议你在复现时不只看方程本身而是确认程序里的旋转矩阵方向和正负号约定。这个坑几乎每个做四旋翼仿真的人都会踩一遍。有的程序里俯仰角是抬头为正有的是低头为正如果沿用你熟悉的那套控制外环很可能在仿真里表现为姿态发散。第二转动惯量的数值。惯性矩直接决定了姿态动力学的时间尺度。这份程序里的默认参数是某小型航模级别四旋翼的典型数值质量在 1kg 上下、轴距 250mm 左右。如果你换了一台更大的平台需要按比例调整惯量否则 ADRC 的观测器增益和控制器带宽全都要跟着变。第三电机模型。程序里的控制输出通常会被映射为四个电机的拉力或转速指令。大多数简化模型里螺丝推力与转速平方成正比电机响应再用一阶惯性环节近似。这个惯性时间常数常见取值 0.01s 到 0.02s对仿真结果影响超大——如果取值太大高频姿态修正指令会被严重滞后ADRC 的高带宽优势根本发挥不出来。所以看这份程序时不要跳过quad_params.m这类参数文件。里面可能没有注释但每个数字背后都有物理含义。我的习惯是拿到参数文件后先在草稿纸上列一遍这些数是否符合量纲惯量比是否符合几何常识把这些搞清楚了后面调参才有底气。2.1 模型参数与初始化脚本里那些魔法数字直接看程序的话你大概率会看到类似这样的代码块m 1.2; % 质量 kg l 0.25; % 机臂长度 m Ixx 0.02; Iyy 0.02; Izz 0.035; kF 1.5e-5; % 拉力系数 N/(rad/s)^2 kM 2e-7; % 力矩系数 N.m/(rad/s)^2 g 9.81;这些数字不是拍脑袋写的。kF和kM的选择会直接影响电机转速的饱和边界。你可以自己算一算悬停时每个电机需要的拉力大约为m*g/4 ≈ 2.94N那么对应的转速大约是sqrt(2.94 / 1.5e-5) ≈ 443 rad/s也就是大概 4200 RPM。这个转速在一个小型无刷电机的工作范围内合理。如果你发现程序里的kF小了一个数量级那么同样的拉力会要求电机转速高到物理上不可能实现仿真曲线就会看起来很虚。这类数值在注释缺失的情况下就是所谓的魔法数字。对初学者来说我的建议是不要轻易改模型参数除非你能说清楚这个改动会带来什么物理量的变化。先保持默认跑通之后再做参数敏感性分析。2.2 从仿真程序到实物移植之间隔着一个电机模型的距离严格来说很多已调好的四旋翼仿真程序控制器的参数并不能直接搬到真机上。原因就出在电机模型上。仿真里常用的电机响应模型是T * s 1 的惯性环节而在真机上电机响应还包含电池电压波动、电调算法差异、桨叶气动延时等。ADRC 最厉害的地方在于它能把所有这些未建模动态统统视为总扰动的一部分并通过扩张状态观测器估计出来。所以你会发现一个很有意思的现象用同一组 ADRC 参数在仿真和真机上都能飞——但轨迹质量会有差异。这正是扩张状态观测器的功劳它不需要精确的模型就能把模型偏差兜住。不过这里有个前提仿真模型里的未建模动态不能比真机离谱太多。如果仿真里电机用了一阶惯性而真机上的实际动态特性更接近二阶振荡那你在仿真里调好的观测器带宽在真机上可能表现为高频噪声放大。我的经验是仿真阶段可以把电机模型故意调得笨一点——一阶惯性常数设到 0.02s 以上——这样调出来的 ADRC 参数会比用理想电机模型调出来的更能扛干扰。3. ADRC 的本质为什么它比串级 PID 更适合四旋翼这种强扰动对象写到这里必须把话题转到控制器本身。很多人第一次接触 ADRC会被它的名字吓到——自抗扰听起来像某种玄学控制。我用一句话解释清楚ADRC 的核心思想是把不知道的模型偏差和外部扰动合在一起估算出来然后直接在前向通道里把它抵消掉。这句话意味着什么你不需要精确知道四旋翼的气动阻力矩是多少不需要精确知道电池电压变化对电机升力有多大影响只需要通过观测器实时估计出一个等效总扰动然后把它的作用抵消。这种用估计代替建模的思路和经典 PID 依赖误差积分的思路是完全不同的哲学。传统串级 PID 里外环和风扰动都在拉扯内环内环 PID 的积分项需要一点点消除稳态误差这个过程本质上很慢。ADRC 的做法是内环直接用扩张状态观测器把扰动估计出来不等误差累积就把它消除。体现在阶跃响应上就是超调更小、抗扰恢复更快、相位滞后更小。四旋翼本质上是一个高度非线性、强耦合、多扰动并存的被控对象正好是 ADRC 的优势区。这份程序实现的是二阶 ADRC。所谓二阶指的是被控对象的相对阶次是二阶——在四旋翼姿态控制场景下输入是力矩输出是角度中间经过角速度积分所以相对阶次是 2。二阶 ADRC 的核心组件有三个我逐个说明。3.1 跟踪微分器给期望信号降噪TD模块的作用很简单对期望姿态角进行平滑处理并提取其微分信号。例如你给一个阶跃的目标俯仰角 10°TD 不会直接让你看到一条陡峭的跳变而是输出一条快速但平滑的过渡曲线同时它还输出这条曲线的导数作为期望角速度送给控制器。这样做的目的是避免输出突变给控制通道造成过大的初始误差减少超调。在程序里TD 一般写成这样function [x1, x2] TD(r, x1, x2, h, r1) x1 x1 h * x2; x2 x2 h * -r1 * (x1 - r) - 2 * r1 * x2 / 3; end注意这里的r1是跟踪速率因子它决定过渡过程快慢。速率因子太小响应就慢太大过渡过程就接近阶跃失去了平滑意义。这份程序默认的参数大概取的是适度激进——大约 0.5 秒内完成阶跃过渡。你可以通过观察角度响应曲线的上升沿来验证。3.2 扩张状态观测器这份程序真正值钱的地方ESO扩张状态观测器是 ADRC 的心脏。它做的事情可以类比成一个内部传感器你只测量了姿态角但 ESO 能顺带估计出姿态角速度和总扰动。它把一个二阶系统扩展成三阶状态方程——真实位置、真实速度、总扰动。然后通过观测器增益把估计误差收敛到很小。程序里常见的 ESO 离散形式e z1 - y; z1 z1 h * (z2 - beta1 * e); z2 z2 h * (z3 - beta2 * e b0 * u); z3 z3 h * (-beta3 * e);这里z1是角度估计值z2是角速度估计值z3是总扰动估计值。beta1、beta2、beta3是观测器增益。正常情况下它们应当按照带宽化的方式整定——也就是把观测器极点配置到同一个位置让整个观测器只有一个可调的带宽参数wo。这就是参数少、好调的来源。我见过很多人调试 ADRC 时盯着beta1一个参数来回试其实正确的做法是只调wo然后按比例关系计算出三个 beta。公式通常是beta1 3 * wo; beta2 3 * wo^2; beta3 wo^3;这样做的好处是你不需要同时瞎试三个参数而是只关心一个物理意义明确的带宽值。这份程序之所以敢说已调好它的 ESO 参数大概率就是按带宽化公式配置的而不是随手填的。3.3 非线性状态误差反馈被过誉的非线性第三个模块是 NLSEF它接收 TD 的期望信号和 ESO 的估计信号之间的误差生成控制量。经典 ADRC 里会用fal函数做非线性变换function f fal(e, alpha, delta) if abs(e) delta f abs(e)^alpha * sign(e); else f e / delta^(1 - alpha); end end当alpha 1时小误差对应的增益更高相当于在小误差范围内放大响应。但说实话我调试过很多次 ADRC 之后发现一个事实fal的非线性增益在仿真中和线性 PD 形式的 NLSEF 差异很小。真正让 ADRC 表现优异的是 ESO 的扰动估计和补偿而不是那一点点非线性增益。很多工程实践里直接把 NLSEF 简化成u0 Kp * e1 Kd * e2效果几乎不变。这份程序保留了正统的fal形式可能是为了教学一致性也可能是因为原作者最初就是从教材代码改过来的。无论哪种你都可以在理解原理后自己尝试简化并对比曲线。4. 仿真配置与运行细节步长、解算器、干扰注入方式当你确定了模型和控制器结构都没问题之后接下来要面对的就是仿真本身的配置问题。这部分往往被文档忽略却是最容易让已调好程序变得一跑就散的地方。先说步长。这份程序用的是固定步长仿真步长h 0.001s也就是 1kHz 的控制更新率。对于四旋翼这种自动驾驶频率通常落在 500Hz 到 1kHz 的场景这是一个很现实的选择。如果你把步长改成 0.0001s曲线会变更好看但那是假象——真机上你不可能跑得起 10kHz 的控制频率。如果你把步长改成 0.01s那就要小心了数值积分误差、离散化误差、以及 ESO 采样延迟会急剧增加原本调好的参数很可能发散。这个道理很简单ADRC 的观测器带宽如果比控制频率高一个数量级离散化误差就会吃掉它的稳定性裕度。再说解算器。在 Simulink 中如果控制器是用 S 函数或子系统实现的离散逻辑那么主模型用定步长四阶龙格库塔ode4会比较合适。如果你看到的是纯.m脚本仿真那就是作者手动写了循环积分常见形式是for k 1:N % 计算控制器输出 % 计算状态导数 % 使用RK4或欧拉法更新六自由度状态 end这里有一个经验纯脚本仿真中状态更新的顺序千万不能乱。我见过一份程序作者把电机响应更新放在姿态更新后面导致每个控制周期多出一个步长的延迟。看似一个小问题但如果你把 ADRC 的观测器带宽调得很高这种延迟会让收敛性变差。所以复现时仔细核对每个更新的先后顺序。然后是关于干扰注入的配置。这份程序里应该有几种默认的干扰设置干扰方式实现方式观察指标常值风扰在每个控制周期对姿态动力学施加固定力矩稳态误差、ESO 的扰动估计曲线阶跃力矩干扰在某个时刻突然注入 0.5N·m 的力矩姿态角的动态恢复时间和超调量传感器噪声在测量角度上叠加高斯白噪声观测器的噪声抑制能力强烈建议你验证一下先跑无干扰版本确认姿态输出零稳态误差再依次注入上述干扰观察 ESO 输出的z3曲线是否和注入干扰形状一致。如果z3不能准确复现注入的扰动曲线说明 ESO 带宽不够或者参数没配对。这是检验已调好的一个硬性标准。不夸张地说这一步比看姿态曲线还重要。5. 已调好到底调的是什么参数标定顺序和典型问题排查下面这部分是整篇文章最值钱的部分。很多人拿到的程序是可以跑。但一旦你想把参数改变或者换一个工况问题就来了到底该调整哪个参数先调哪个那些已调好的参数是一组怎样存在我给大家梳理一个调试路径。这套路径来自于我自己复现多份 ADRC 仿真程序的经验也可以理解为这份程序原作者很可能走过的路。第一步先定模型参数后定控制器参数。模型参数本身不能随便动但你需要确认电机模型时间常数是 0.01s还是默认用了 0.005sb0控制输入增益的取值是否和系统方程一致在 ADRC 里b0是一个至关重要的参数——它是你对被控对象输入增益的估计值。如果b0比真实值大了 50%ESO 的扰动估计会连带偏移很多你以为的扰动里其实混入了模型失配的部分。检查方式是在仿真里切除 ESO 的补偿通道只保留前向控制然后看系统的阶跃响应是否在一个合理的范围内。第二步从观测器开始调而不是从控制器开始。先整定 ESO 的带宽wo。把控制器增益设得很保守甚至在仿真中把 NLSEF 的输出缩小到原来的十分之一只观察 ESO 的估计是否准确。观察目标给系统加一个阶跃干扰看z3跟踪扰动曲线的速度和超调。如果z3滞后太明显可以提高wo但注意wo超过一个阈值后观测器会在噪声下抖动。这个阈值和步长有关实测下来在 1kHz 仿真步长下wo设置在 30~100 rad/s 之间是比较稳的。超过 200 就属于激进调法会放大模型噪声。第三步再调控制器带宽。控制器部分带宽记为wc决定了追踪参考信号的带宽。通常设置wc比wo低三到五倍以上这样观测器能及时跟上控制回路的要求。这里有一个常用公式Kp wc^2 Kd 2 * wc初步调好的标志是阶跃响应无超调或小超调上升时间符合你的期望。这份程序里那些看起来整齐的姿态响应曲线就是这样按顺序一步步撞出来的而不是把参数随手一填得到的运气。5.1 常见问题一姿态发散到底哪里错了如果你把参数改了之后仿真在 3 秒内爆掉先别急着改回来。排查步骤如下先看角度是否正确如果角度瞬间冲上 90°以上说明控制器输出方向不对——旋转矩阵正负号、电机编号顺序、油门方向任一错了都会这样。再看角速度。如果角度还没发但角速度剧烈振荡多半是Kd过小、阻尼不足。如果角度和角速度都正常但控制量饱和检查电机转速上下限是否被触发。ADRC 补偿扰动时如果扰动估计值巨大控制量瞬间顶到饱和会造成类似 bang-bang 控制的极限环振荡。5.2 常见问题二稳态误差消不掉ESO 却显示扰动为零这个现象很有迷惑性。程序可以跑姿态响应似乎差不多但就是有 1° 左右的稳态误差。很多人会去调 ESO 的beta3但问题往往不在这里。最可能的原因是你的控制器输出没有真正作用到模型上。仔细检查你计算出的期望总的控制量是直接加到u上了还是被限幅函数截断了很多程序为了模拟真实执行机构在电机分配前加了饱和函数。如果你在调试过程中把控制增益调大导致控制量频繁饱和那么输出的有效作用力矩比期望值小稳态误差就会一直挂在那里。此时你应该把z3的估计值与实际干扰对比如果两者差了一个偏置量那基本可以锁定是饱和造成的。5.3 常见问题三为什么加了一点噪声仿真就开始抖这是 ADRC 的一个典型局限ESO 用高带宽估计扰动的同时也会把量测噪声高频分量放大。给你的建议有三个层面降低wo牺牲一点扰动估计速度换取噪声抑制能力在测量通道加一阶低通滤波器但这会引入相位延迟所以滤波截止频率应不低于wo的三倍改用降阶观测器。当不需要估计全状态时降阶 ESO 对噪声的敏感度更低只是参数整定复杂一些。这个问题的本质是在和观测器带宽-控制闭环带宽-传感器噪声三者之间做权衡没有免费的午餐。6. 在已调好的基础上继续扩展把仿真价值榨干程序能跑、参数能调这只是起点。真正有价值的工作是在这份程序基础上做增量开发。我给你几个明确的扩展方向都是我在类似项目上实际走过的路。方向一把姿态控制升级为位置控制。目前的程序大概率只做了姿态环内环仿真或者是简单的单通道角度跟踪。你可以在此基础上加入位置外环用位置误差计算期望姿态角再把姿态角期望送给内环 ADRC。注意外环的控制频率可以比内环低很多通常 50Hz~100Hz 就够因为位置环的动力学比姿态环慢一个量级。这种内外环结构是工程上最主流的四旋翼控制方案代码改动不大但逻辑上会复杂不少。方向二把阶跃测试改造成轨迹跟踪。默认的时域阶跃响应只能验证姿态稳定性无法充分检验控制器在连续机动中的表现。你可以设计一条螺旋上升轨迹同时改变 x、y、z 和偏航角强制四旋翼在高非线性区域运行观察 ADRC 在连续变化工况下的估计质量和误差曲线。实测下来ADRC 在连续轨迹跟踪中的优势比阶跃响应里体现得更明显因为参考信号持续变化时PID 的积分项往往会拖后腿。方向三加执行器饱和限制和分配逻辑。真机上每个电机的推力和力矩响应都有上下限如果你想看控制器在贴近真实的条件下是否还已调好建议在程序里加入控制分配模块把期望的总推力、滚转力矩、俯仰力矩、偏航力矩分配为四个电机的转速指令再加入转速限制。你会发现加了饱和之后的 ADRC 依然比 PID 更容易出现抗饱和的问题——因为 ESO 会把饱和造成的执行偏差当作扰动估计出来这反而是一件好事。方向四从仿真走向半物理。如果你手头有飞控硬件可以在 Matlab 里把控制器生成 C 代码嵌入硬件运行然后把传感器数据通过串口回传。这一步把 ADRC 的扰动估计放到了真实噪声环境中。我个人的建议是仿真里先别追求极致的曲线把参数调到偏保守的区间给真机留裕量因为真机上的未建模动态至少比仿真多 20% 以上。最后再分享一个小技巧。扩展完后你可以在程序的绘图区同时画出四条曲线参考信号、实际输出、控制器输出、ESO 总扰动估计。这四条线几乎能解释所有调试问题。如果扰动估计曲线噪声大看观测器参数如果控制器输出频繁饱和看执行机构限制如果实际输出偏离参考看闭环带宽。这种一眼定位法比反复调参碰运气要高效得多。