资讯详情 ADRC自抗扰控制Matlab仿真:TD、ESO与带宽法参数整定实战
📅 2026/10/13 19:31:39
简介ADRC自抗扰控制的MATLAB仿真实现资源包面向学习先进控制理论的高校学生、科研人员以及需要快速搭建自抗扰验证平台的工程师尤其适合在Simulink环境中完成从算法原理到仿真落地的衔接。内容覆盖误差观测器设计、扩展状态机构建、控制器参数整定与仿真分析等完整环节可帮助读者理解ADRC如何实时估计并补偿系统的不确定性与外部干扰。压缩包共26个文件大小仅191KB主要包含.m脚本与S函数源代码、.mdl仿真模型、.mexw64编译后的可执行S函数、.mat数据文件以及C源码既能直接运行也能按需二次开发资源轻量但结构完整。已有3018人学习下载。使用后可掌握基于S函数搭建ADRC模型的方法获得NESO等扩展状态观测器设计参考、调参脚本与实际仿真模型便于验证输出响应、分析干扰估计能力还可作为课程设计、项目预研或实际控制方案验证的起点为后续复杂系统应用提供可复用框架。1. ADRC的matlab仿真先把「总扰动」这件事想明白给一个二阶对象做ADRC的matlab仿真多数人第一次跑通波形时都觉得不难跟踪微分器把阶跃抹成平滑过渡扩张状态观测器把扰动估出来误差反馈一合成输出就乖乖贴上去。真正让这个仿真有价值的是后面那一脚——在某个时刻突然塞进一个阶跃扰动看输出被顶起来多少、多久压回去、控制量有没有发抖。ADRC的核心主张是把外部扰动、未建模动态、参数变化全部折算成输入端的一个总扰动然后用扩张状态观测器ESO把它实时估出来并补偿掉。仿真阶段不把这个环节验证透后面接实物几乎必然翻车。这篇文章按我实际在用的路径讲先拆ADRC的三个模块和数学形式再给三条Matlab落地路线m脚本、Simulink、S-Function接着讲带宽法整定和参数顺序最后给一批避坑记录和一个批量扫参脚本。新手能照着跑通熟手可以直接跳到自己需要的章节。2. ADRC的三个核心模块TD、ESO、NLSEF在闭环里各干什么2.1 跟踪微分器把阶跃「抹平」的过渡过程直接用阶跃做给定值误差在起始瞬间最大反馈控制器为了追赶这个误差会给出很大的控制量超调几乎不可避免。跟踪微分器TD的思路是先给给定值安排一条物理上可达的路径输出一个平滑到达目标值的时间函数v1同时输出它的微分v2。v2还有一个附带价值——避免对测量信号做数值微分带来的噪声放大。ADRC的提出者用的是二阶最速控制系统离散形式是一组递推% 最速控制综合函数 fhan function fh fhan(x1, x2, r, h0) d r * h0^2; % 线性区间宽度由速度因子和滤波因子决定 a0 h0 * x2; y x1 a0; a1 sqrt(d * (d 8*abs(y))); a2 a0 sign(y) * (a1 - d) / 2; sy (sign(y d) - sign(y - d)) / 2; a (a0 y - a2) * sy a2; sa (sign(a d) - sign(a - d)) / 2; fh -r * (a/d - sign(a)) * sa - r * sign(a); end这段函数看着绕实际作用只有一件事给定误差和误差微分返回一个受限的控制加速度让状态从当前值快速且无超调地到达目标。参数里r叫速度因子r越大过渡越快h0叫滤波因子一般取仿真步长的5到10倍用来抑制微分信号里的高频抖动。工程上我不建议去改fhan内部的任何一行直接把它当成一个封装好的「平滑器」用就行。TD的离散递推是这样进入主循环的% 每步调用一次v 是原始设定值v1 是平滑后的设定值v2 是它的导数 fh fhan(v1 - v, v2, r_td, h0); v1 v1 h * v2; v2 v2 h * fh;注意fhan的第一个输入是误差v1-v而不是v1本身这是很多初学复现时最容易写反的地方。v1初始化为0目标v是1那么第一拍误差是-1TD输出一个正向加速度让v1朝v靠拢整个过程是一条先加速后减速的平滑曲线。TD不会改变闭环稳态精度它的价值全在瞬态把阶跃变成斜坡型过渡控制量峰值显著下降。2.2 扩张状态观测器把总扰动变成第三个状态扩张状态观测器是ADRC最核心的部分。对一个二阶对象y -a1·y - a2·y b·u d把方程改写成y b0·u f其中b0是名义增益f把所有说不清的东西都装进去外部扰动d、参数偏差(a1-a1_hat)、未建模动态、以及(b-b0)·u带来的输入误差。这里的关键观察是f可以被当成第三个状态来估计。状态从两个y和y扩张成三个y、y、f所以叫「扩张状态观测器」。线性ESO的离散递推在最简形式下只有几行% 线性扩张状态观测器单步递推 % 输入: y 当前测量, u 上一拍控制量, b0 名义增益, wo 观测器带宽, h 步长 % 输出: z1 跟踪 y, z2 跟踪 y, z3 跟踪总扰动 f e y - z1; z1 z1 h * (z2 3*wo * e); z2 z2 h * (z3 3*wo^2 * e b0 * u); z3 z3 h * (wo^3 * e);三个增益3·wo、3·wo²、wo³来自带宽参数化把观测器极点全部配置在-wo处。这样原来需要试凑的三个β参数被压成一个wo这是LADRC能工程落地的关键一步。wo的物理含义很直接——它决定观测器多快能把扰动估计出来。wo越大收敛越快但也会放大测量噪声这个矛盾在第5章会专门展开。观测器能工作的前提是总扰动f的导数有界。对绝大多数机电对象这个条件都满足。仿真里验证ESO是否正常工作有一个很朴素的检查方法把z3的波形画出来和实际施加的扰动叠加参数失配项对比。如果z3能在几十毫秒内贴上去说明观测器是收敛的如果z3慢吞吞地追或者抖得没法看先查步长和wo不要急着调后面的控制器。2.3 误差反馈与扰动补偿从非线性fal到LADRC的取舍早期ADRC用非线性组合做误差反馈核心是fal函数function y fal(e, alpha, delta) if abs(e) delta y abs(e)^alpha * sign(e); % 大误差时近似幂函数 else y e / delta^(1-alpha); % 小误差时线性避免抖振 end end典型参数是α10.5、α20.25、δ0.01。好处是小误差时增益高稳态精度好代价是参数关联性强换个对象整套重调仿真阶段很容易陷入「调了三个小时没找到规律」的困境。实战里我几乎只用LADRC的形式即误差反馈采用线性PDu0 wc²·e1 2·wc·e2其中e1 v1 - z1e2 v2 - z2最后控制量u (u0 - z3) / b0这样做的理由可以直接从方程看出来如果ESO已经把f估准了z3≈f那么对象y b0·u f在代入u以后变成y ≈ u0整个对象被补偿成了两个积分器串联。对双积分器对象配置u0 wc²·e1 2·wc·e2闭环特征方程就是s² 2·wc·s wc²标准的临界阻尼二阶系统。wc就是期望闭环带宽整定目标从「猜一组非线性参数」变成「定一个带宽数字」这就是第4章带宽法的根基。3. Matlab实现的三条路线m脚本、Simulink、S-Function3.1 纯m脚本递推最小可跑通版本我习惯先走纯m脚本原因很实际不依赖Simulink环境每一步递推都摊在眼前扫参、改逻辑、排查都最快。下面这份代码是完整的ADRC对二阶对象仿真直接复制可以跑通。%% ADRC 对二阶对象的最小仿真脚本LADRC TD clear; clc; h 0.001; % 仿真步长 1ms T 5; % 仿真时长 5s N round(T/h) 1; t (0:N-1) * h; % 被控对象y a1*y a2*y b_real*u d a1 1.5; a2 3.0; b_nominal 6.0; % 控制器使用的名义增益 b_real 7.2; % 对象真实增益故意失配 20% % ADRC 参数 wc 10; % 控制器带宽 (rad/s) wo 50; % 扩张状态观测器带宽 (rad/s) r_td 30; % TD 速度因子 h0 0.005; % TD 滤波因子 % 状态初始化 x1 0; x2 0; % 对象状态 v1 0; v2 0; % TD 状态 z1 0; z2 0; z3 0; % ESO 状态 u 0; d 0; y_rec zeros(1,N); u_rec zeros(1,N); z3_rec zeros(1,N); for k 1:N % 参考输入0~3s 为 1.0之后切到 0.5 看跟踪 if t(k) 3 v 1.0; else v 0.5; end % 1) 跟踪微分器安排过渡过程 fh fhan(v1 - v, v2, r_td, h0); v1 v1 h * v2; v2 v2 h * fh; % 2) 扩张状态观测器用上一拍控制量递推无代数环 e x1 - z1; z1 z1 h * (z2 3*wo * e); z2 z2 h * (z3 3*wo^2 * e b_nominal * u); z3 z3 h * (wo^3 * e); % 3) 线性误差反馈 扰动补偿 u0 wc^2 * (v1 - z1) 2*wc * (v2 - z2); u (u0 - z3) / b_nominal; % 4) 被控对象真实模型欧拉离散 if k 2001 d 1.5; % 2s 时加入阶跃扰动 end x1 x1 h * x2; x2 x2 h * (-a1*x2 - a2*x1 b_real*u d); y_rec(k) x1; u_rec(k) u; z3_rec(k) z3; end figure; subplot(3,1,1); plot(t, y_rec); yline(1.0, --); yline(0.5, --); ylabel(y); title(输出); subplot(3,1,2); plot(t, u_rec); ylabel(u); title(控制量); subplot(3,1,3); plot(t, z3_rec); ylabel(z3); xlabel(t/s); title(总扰动估计);主循环四个步骤各有各的讲究。第一步TD的fhan输入是v1-v注意符号不能反第二步ESO递推用的u是上一拍的控制量这是刻意避免代数环的做法Simulink里同样要遵守第三步u0按临界阻尼二阶期望动态计算(u0-z3)/b0完成扰动补偿第四步对象用真实增益b_real扰动d直接加在加速度通道上等价于输入端扰动d/b_realESO估出来的z3会趋近这个等效值加上参数失配项的和。参数说明h0.001与wo50的组合h·wo0.05离离散稳定边界很远仿真不会因步长发散wc10对应闭环上升时间约0.3~0.5swo50比wc快5倍扰动估计能跟上闭环动态。跑完看z3曲线2s处应该有一个快速跳变到大约0.2~0.3的估计值——因为d/b_real 1.5/7.2 ≈ 0.208还叠加了20%增益失配引起的补偿量变化。3.2 Simulink框图被控对象加ESO的搭建方式m脚本验证完参数逻辑后我才会把模型搬进Simulink。Simulink的优势是对象可以换成任意复杂模块比如从别的工具导入的模型或查表对象而控制器部分不需要重新写递推逻辑。搭建方式按下面这张表走半小时内能搭完。模块关键设置说明Step设定值阶跃时间0终值1作为参考输入vIntegral×2对象侧初始值0构成二阶对象yGaina1、a21.5、3.0对象反馈系数Gainb_real7.2对象输入增益Constant扰动d1.5Step时间2s加在对象加速度输入端MATLAB FunctionESO内部代码见下方离散递推采样时间继承Unit Delay采样时间h把u延迟一拍喂给ESOGainwc²、2·wc100、20线性误差反馈系数Gain1/b01/6扰动补偿后的控制增益ESO的MATLAB Function块内部代码是这样function [z1o, z2o, z3o] adrc_eso(y, u_past, omega_o, b0, h) % 线性扩张状态观测器Simulink MATLAB Function 块内部离散递推 persistent z1 z2 z3 if isempty(z1) z1 0; z2 0; z3 0; end e y - z1; z1 z1 h * (z2 3*omega_o * e); z2 z2 h * (z3 3*omega_o^2 * e b0 * u_past); z3 z3 h * (omega_o^3 * e); z1o z1; z2o z2; z3o z3; end一个关键细节MATLAB Function块的输入u_past必须接在Unit Delay后面。如果不加这个延迟ESO递推用到当前拍的u而u又依赖ESO输出的z3Simulink会报代数环或者给你一个收敛缓慢的隐式解波形怎么看怎么不对。这是我见过最多的Simulink翻车点。求解器设置也容易踩坑Simulink默认的变步长ode45会在信号变化剧烈处自动加密步长而ESO的递推逻辑是按固定h写的两者不一致会让仿真结果和m脚本对不上。固定步长选ode4步长设成0.001和m脚本保持一致。对象侧如果用连续积分器模块ode4在0.001步长下对这个对象精度足够如果对象本身有更高的带宽把步长改小到0.0001再跑一遍对比。3.3 S-Function封装从一次性仿真到可复用模块当同一个ADRC控制器要在多个仿真模型里复用时把递推逻辑封装成Level-2 S-Function是更干净的做法。参数通过对话框传入状态用Dwork存储不会出现MATLAB Function块里persistent变量在仿真停止后残留的问题。function adrc_sfun(block) setup(block); end function setup(block) block.NumInputPorts 2; % 输入: [y; v] block.NumOutputPorts 1; % 输出: u block.InputPort(1).DirectFeedthrough false; % y 不直接馈通 block.InputPort(2).DirectFeedthrough false; % v 不直接馈通 block.NumDialogPrms 4; % 对话框参数: wc, wo, b0, h block.NumDworks 5; % 状态: v1 v2 z1 z2 z3 block.SampleTimes [0 1]; % 继承采样时间 block.RegBlockMethod(InitializeConditions, Init); block.RegBlockMethod(Update, Update); block.RegBlockMethod(Outputs, Outputs); end function Init(block) for i 1:5 block.Dwork(i).Data 0; end end function Update(block) % 把 3.1 节主循环里的 TD 和 ESO 递推搬到这里 % 更新 block.Dwork(1)~Dwork(5) end function Outputs(block) % 用当前状态计算 u0 和 u % 写入 block.OutputPort(1).Data endUpdate函数在每一个采样步更新五个状态Outputs函数在同一时刻根据状态计算控制量两者分离逻辑清楚也不会产生代数环。代价是S-Function的调试不如m脚本直观所以我通常是在m脚本确认算法后再封装成这个形式。给新手一个建议先不要碰S-Functionm脚本跑通、Simulink也搭通之后确实需要模块化复用时再考虑这条路线。4. 参数整定用带宽法把调参问题从9个压到3个4.1 带宽法三个数字取代一组非线性参数原始ADRC的参数包括TD的速度因子和滤波因子、ESO的三个β、NLSEF的α1、α2、δ以及两个反馈增益加起来接近10个而且互相之间有耦合。LADRC的带宽法把整定变成三个数字wc决定闭环响应速度wo决定扰动观测速度b0是对象的输入增益估计。形式需要设的参数数量工程代价原始ADRCNLSEFTD、ESO、fal相关参数9~10个参数关联性强换对象重调LADRC线性形式wc、wo、b03个每个参数有明确带宽含义控制率u0 wc²·e1 2·wc·e2的本质是把补偿后的对象配置成期望闭环极点。特征方程s²2·wc·swc²的两个根都在-wo处是临界阻尼所以wc给多少闭环响应速度就有个直接预期。wo是观测器带宽一般取wc的3~10倍。太小则扰动估计跟不上太大则噪声被放大。b0不需要精确它只是「把对象折算成双积分器」时的输入系数。b0取实际对象增益的数量级就行失配的影响以及如何判断失配程度在第5章讲。4.2 推荐整定顺序先闭环、再观测器、最后b0我自己的整定顺序固定为四步每步只看一个波形避免多个变量同时动导致问题分不清是谁引起的。第一步固定wo 3·wcb0取对象增益的数量级只调wc。从wc 5开始逐步加大观察设定值阶跃的上升时间。wc越大响应越快但控制量峰值也越大调到上升时间满足需求为止。此时如果系统已经震荡先检查TD有没有起作用、e2接线有没有接反再往下走。第二步调wo。把wo从2·wc开始往上加每次乘2观察2s处阶跃扰动引起的输出波动。扰动恢复时间会随wo增大明显缩短但当wo大到一定程度控制量开始出现高频抖动这时回退到上一个不抖的值或者取抖动的临界值乘以0.8。第三步拉偏b0。把b0分别改成实际值的0.7倍和1.3倍各跑一遍看阶跃响应和扰动恢复的变化幅度。选一个在两边测试里都不算差的值作为最终b0。注意这一步必须在wc和wo基本确定之后做因为b0的影响会和wc、wo耦合。第四步最后调TD的速度因子r。r小过渡过程长控制量柔和超调小r大过渡接近阶跃控制量峰值大。一般取「设定值幅值除以期望上升时间」的2到4倍作为起点。这一步放在最后是因为它只影响瞬态不影响ESO和反馈环的稳定性。4.3 量化评估指标用数字对比代替肉眼看波形调参时肉眼看波形容易自我欺骗尤其是超调量5%和8%在示波器级别的显示下很难分辨。我在每个仿真工程里都会放一个评估函数跑完直接输出四个数字超调量、调节时间、扰动最大偏差、扰动恢复时间。function perf quick_eval(t, y, t_dist, setpoint) % 四指标快速评估超调、调节时间、扰动最大偏差、扰动恢复时间 perf struct(Mp, 0, ts, 0, e_dist, 0, t_rec, 0); y_ss setpoint; % 跟踪段评估扰动加入之前 idx_a t t_dist; perf.Mp (max(y(idx_a)) - y_ss) / y_ss * 100; idx_settle find(abs(y(idx_a) - y_ss) 0.02 * y_ss, 1); if ~isempty(idx_settle) perf.ts t(idx_settle); end % 扰动段评估扰动加入之后 idx_b t t_dist; perf.e_dist max(abs(y(idx_b) - y_ss)); idx_rec find(abs(y(idx_b) - y_ss) 0.02 * y_ss, 1); if ~isempty(idx_rec) perf.t_rec t(idx_b(idx_rec)) - t_dist; else perf.t_rec NaN; end end四个指标分别对应不同的调参方向超调量超了就降wc或降r调节时间太长就加wc扰动最大偏差太大说明ESO还没估准就顶了一下加wo扰动恢复时间太长也是wo或b0的问题。用这组数字做对比同一个参数在改动前后的差异一目了然比「感觉波形好了一点」靠谱得多。批量扫参时这个函数会直接成为目标函数的计算单元。5. ADRC仿真避坑指南发散、抖动与模型失配这一章的每一条都是血泪经验按「现象、原因、解决」写遇到问题直接对号入座。5.1 一上电就发散步长和wo的配合不当现象仿真开始后几百步内输出就飞到正负无穷控制量剧烈震荡至仿真停止。原因最可能是仿真步长h相对观测器带宽wo过大。ESO的离散递推是显式欧拉格式稳定范围对h·wo有约束。经验上h·wo超过0.3~0.5时观测器开始抖动超过1后直接发散。另一个常见诱因是用变步长求解器跑带ESO的模型求解器在误差估计的驱动下把步长放到很大恰好跳过稳定边界。解决m脚本里把h改小重跑规则是h ≤ 1/(20·wo)起步Simulink里固定求解器为ode4步长与m脚本一致。先确认ESO单独稳定再接入闭环方法是在第3章的主循环里把控制率改成u0只让ESO跟踪对象输出看z1和z3是否收敛。5.2 输出稳定但控制量高频抖动观测器把噪声当扰动现象输出曲线看起来正常但控制量u上叠加了一层高频小幅度振荡z3波形毛糙。原因ESO的带宽wo越高对测量噪声的放大越严重。仿真里如果没有给y加噪声那抖动通常来自求解器数值误差——变步长求解器在快速动态处加密步长时引入的截断误差被ESO当成了扰动。另一个隐蔽来源是fhan里h0取得过小导致v2出现微幅振荡进而通过e2传入控制量。解决先检查h0是否为仿真步长的5~10倍再把求解器固定为定步长最后考虑对y通道加一阶低通滤波时间常数取1/(5·wo)到1/(10·wo)在不明显滞后观测器的前提下滤掉高频成分。wo本身也够用就好不要盲目追求「观测器越快越好」。5.3 扰动来了压不住b0与真实增益失配现象2s加阶跃扰动后输出被顶起一个明显的尖峰恢复缓慢或者恢复过程出现多次来回振荡。稳态误差反而不明显因为对象的积分作用会把残余误差逐渐吃掉。原因b0严重失配。b0取得比真实增益大补偿量(u0-z3)/b0偏小扰动抑制力度不足恢复慢b0取得偏小补偿量偏大恢复过程容易过冲震荡。需要区分的是ESO估计的z3里本身就包含了(b-b0)·u的影响所以中等程度的失配不会导致稳态误差影响主要集中在动态过程。解决做一次b0拉偏测试把b0分别乘以0.7、1.0、1.3跑三组对比扰动恢复时间。如果0.7倍和1.3倍的恢复时间差很多说明当前b0处在过灵敏区取一个让两端表现都不差的中间值。我一般在仿真里把b0的实际值估到±20%以内就够用了过分追求精确反而会在真实对象上失去鲁棒性。5.4 超调比PID还大TD被「短路」了现象闭环响应出现明显超调而且看起来和PID没区别甚至更差。原因最直接的原因是TD没有正常工作v1几乎就是阶跃本身。常见情况有三种一是fhan里误差符号写反导致v1反向跑二是r_td设得过大过渡时间和阶跃没区别三是直接在e1处用了v而不是v1把TD旁路了。另一个隐蔽原因是e2的符号错了——v2-z2如果接成了z2-v2闭环特征方程变成s²2·wc·s-wc²有一个正实部极点系统必振荡。解决把v1和v2画出来确认v1是平滑爬坡曲线而不是阶跃再把z2和v2两条曲线叠在一起看极性稳态时二者应该基本重合动态过程v2始终超前于z2。这两条曲线检查完超调问题基本能定位。5.5 同样参数结果对不上求解器与离散化差异现象m脚本跑出来的波形和Simulink里搭的模型对不上甚至同一份Simulink模型换台电脑结果不同。原因m脚本是固定步长欧拉递推Simulink默认变步长求解器会在动态剧烈处自动改步长对带ESO的刚性系统尤其敏感。另外MATLAB Function块里persistent变量的初值在仿真启动时统一为0但如果之前跑过一次旧参数的仿真没有清除persistent会残留导致第二次运行结果异常。解决仿真环境统一为定步长ode4h固定为0.001模型配置里把「Start simulation from steady state」之类选项关掉每次改完参数用clear all把persistent变量清掉再跑。m脚本和Simulink对照时确保两边h、wc、wo、b0四个数字完全一致包括小数位。6. 批量扫参脚本把参数可行域画出来再动手精调单组参数调参只能回答「这组参数行不行」回答不了「哪一片区域可行」。我把第3章的m脚本主循环封装成run_adrc(wc, wo, b0)函数返回quick_eval计算的指标然后用网格扫描把wc和wo的二维可行域直接画成图。%% wc × wo 网格扫描b0 固定为名义值 b0 6.0; wc_list 4:2:20; wo_list 10:10:150; Mp_map zeros(length(wc_list), length(wo_list)); Rec_map zeros(length(wc_list), length(wo_list)); for i 1:length(wc_list) for j 1:length(wo_list) perf run_adrc(wc_list(i), wo_list(j), b0); Mp_map(i,j) perf.Mp; Rec_map(i,j) perf.t_rec; end end figure; subplot(1,2,1); contourf(wc_list, wo_list, Mp_map); colorbar; xlabel(wc (rad/s)); ylabel(wo (rad/s)); title(超调量 %); subplot(1,2,2); contourf(wc_list, wo_list, Rec_map); colorbar; xlabel(wc (rad/s)); ylabel(wo (rad/s)); title(扰动恢复时间 s);扫描规模控制在几百组以内每组仿真时间5s、步长1ms单核跑完大约几分钟。两张等高线图叠在一起看能直观找到超调量低和恢复时间短的交叉区域。我一般先把网格画粗锁定区域后再用第4章的整定顺序在局部做细调避免上来就直接在一个小范围内苦试。这个脚本的价值不只是找参数还能验证控制器对参数偏差的容忍度。把b0从6.0改成4.8和7.2各扫一遍如果可行域的形状变化不大说明参数设计是鲁棒的如果可行域明显缩小甚至消失就得回去重新审视模型或者换控制结构。我现在拿到一个新对象第一件事永远是先跑一遍网格扫描把可行域圈出来再回到第4章的顺序精调。这个习惯帮我少踩了很多坑也希望帮到你。本文还有配套的精品资源点击获取