简介:这份资源是面向自动化、电子信息工程及数学等专业学生的Matlab控制算法参考资料,聚焦自抗扰控制器(ADRC)与PID控制的实现与对比,适用于课程设计、期末大作业或毕业设计中的控制模块开发。包内共36个文件,以31个.m脚本为主,辅以4个.mdl Simulink模型和1个.asv备份文件,整体约39KB,涵盖被控对象建模、控制器设计、观测器(ESO)实现、仿真与绘图等环节,并包含fst、fal等非线性函数模块,便于读者理解自抗扰控制的核心结构。目前已有1250人学习下载,说明该方向具有较高的参考需求。读者可借助源码梳理ADRC与PID的算法流程,对照Simulink模型验证控制效果,并在此基础上自行调试参数、修改功能或扩展应用场景。需注意代码仅作参考,需具备一定基础方能看懂并解决报错。
1. 从一份 .rar 说起:Matlab 自抗扰与 PID 双控制器到底解决什么问题
如果你手里正好有一个「基于Matlab自抗扰控制器和PID控制(源码).rar」,大概率是两种情况:一是课程设计或毕设要求对比两种控制策略,二是现场被 PID 调参折磨过,想看看自抗扰(ADRC)能不能救场。这个标题背后其实是一套很典型的工程需求——用同一被控对象,把经典 PID 和自抗扰控制器放在一起跑,看谁在扰动、时滞、参数摄动下更稳。它适合做电机调速、温控、四旋翼姿态、倒立摆这类对象的人,也适合想搞懂「ADRC 到底比 PID 强在哪」的工程师。Matlab 在这里只是载体,真正值钱的是两种控制器的结构差异和调参逻辑。下面我按自己复现过的路径,把这份源码该长什么样、怎么跑、坑在哪讲清楚。
2. 自抗扰控制器和 PID 的结构差异:为什么 ADRC 多了一个「观测器」
2.1 从 PID 的误差反馈到 ADRC 的扰动估计
PID 的核心逻辑非常朴素:拿设定值和实际输出的误差 e,做比例、积分、微分加权,输出控制量。它不关心对象内部发生了什么,只靠误差反馈硬扛。这带来两个后果:积分项能消静差,但会引入相位滞后,容易振荡;微分项对噪声极其敏感,实际工程里经常要加低通滤波,等于把 D 的效果打折。
自抗扰控制器的思路不一样。它把对象里所有说不清的部分——非线性、耦合、外部扰动、参数变化——统统打包成一个「总扰动」,然后用扩张状态观测器(ESO)去实时估计这个总扰动,再在控制量里把它抵消掉。剩下的部分就变成一个近似线性的积分串联系统,用简单的误差反馈就能控好。所以 ADRC 比 PID 多的不是某个参数,而是一整个观测器环节。
用 Matlab 实现时,PID 通常就是一个pid对象或者手写三行差分;ADRC 则要写 ESO 的离散迭代、跟踪微分器(TD)和状态误差反馈(SEF)。这也是为什么一份「自抗扰+PID」的源码包,代码量往往集中在 ADRC 那几个循环里。
2.2 一阶和二阶 ADRC 的选型判断
不是所有对象都上二阶 ADRC。判断依据是对象的相对阶:如果被控对象近似一阶惯性加纯滞后,比如很多温度、液位系统,用一阶 ADRC(只有一个状态和扩张状态)就够了,参数少、好调。如果是电机转速、倒立摆这类二阶对象,才需要二阶 ADRC,ESO 要估计两个状态加一个扩张状态。
常见做法是:先写被控对象的微分方程,看最高阶导数出现在哪。一阶对象形如y' = f(y,t) + b*u,二阶对象形如y'' = f(y,y',t) + b*u。b 是控制增益的粗略估计,不需要精确,ADRC 对 b 的误差有一定容忍度,但差太多会明显影响带宽。
下面这段是二阶 ADRC 里 ESO 的离散实现骨架,我一般会先把它单独跑通再接入闭环:
% 二阶扩张状态观测器(离散欧拉法) % z1: 估计输出 y, z2: 估计 y 的导数, z3: 估计总扰动 % h: 采样步长, beta1/2/3: 观测器增益, b0: 控制增益估计 function [z1, z2, z3] = eso_update(y, u, z1, z2, z3, h, beta1, beta2, beta3, b0) e = z1 - y; % 观测误差 z1 = z1 + h * (z2 - beta1 * e); % 状态1更新 z2 = z2 + h * (z3 - beta2 * e + b0 * u); % 状态2更新,含控制输入 z3 = z3 + h * (-beta3 * e); % 扩张状态更新,估计总扰动 end逻辑说明:e是观测输出和实际输出的差,三个状态各自按误差做修正。z3没有自己的动态,完全靠误差积分出来,这就是「扩张」的含义。参数说明:h必须和实际控制周期一致,写错等于模型和现实脱节;beta1/2/3一般按观测器带宽wo配置,常用beta1=3*wo, beta2=3*wo^2, beta3=wo^3,wo越大估计越快但越容易放大噪声;b0是控制增益估计,可以先从对象模型里粗略算,再微调。
2.3 把 PID 和 ADRC 放进同一个 Simulink 框架
要对比,就得保证被控对象、采样时间、扰动信号完全一致,只换控制器。我一般用 Simulink 搭一个统一框架:左边是设定值和扰动源,中间用 MATLAB Function 块分别封装 PID 和 ADRC,右边接同一个被控对象传递函数,最后用 Scope 或 To Workspace 记录数据。
具体步骤:第一,在 Simulink 里建一个Plant子系统,用 Transfer Fcn 块写对象,比如1/(0.5s^2+s);第二,建两个并行的控制器分支,一个用 PID Block,一个用 MATLAB Function 写 ADRC;第三,用一个 Manual Switch 或 Variant Subsystem 切换,避免改结构;第四,扰动在对象输入端加一个阶跃或正弦,模拟负载突变;第五,仿真时间设够,让两种控制器都进入稳态再比较。
这里有个容易忽略的点:PID 的采样时间和 ADRC 的h必须一致,否则对比不公平。我见过有人 PID 用连续模块、ADRC 用离散 0.001s,结果 ADRC 因为采样快占了便宜,结论就不可信了。
3. 用 Matlab 跑通 ADRC 与 PID 对比的最小步骤
3.1 环境准备与源码目录的合理组织
Matlab 版本不用追新,R2018b 之后的版本跑这套东西都没问题。需要确认的是 Control System Toolbox 和 Simulink 已安装,因为 PID Block 和传递函数仿真依赖它们。如果源码包里只有 .m 脚本没有 .slx,也能跑,用ode45手写循环即可,只是没有 Simulink 直观。
我习惯把目录分成四块:plant/放对象模型,controllers/放 PID 和 ADRC 的实现,scripts/放主运行脚本和参数配置,results/放仿真数据和图。这样换对象时只动plant/,控制器不用改。源码包如果结构混乱,先花十分钟整理,后面调参会省很多时间。
一个典型的参数配置文件长这样:
% params.m —— 统一参数配置,PID 和 ADRC 共用对象参数 plant.num = [1]; % 对象分子 plant.den = [0.5 1 0]; % 对象分母:0.5s^2 + s plant.delay = 0; % 纯滞后,没有就设0 sim.Ts = 0.001; % 采样周期,两种控制器必须一致 sim.Tend = 10; % 仿真时长 sim.ref = 1; % 设定值阶跃幅值 pid.Kp = 2.0; pid.Ki = 1.5; pid.Kd = 0.1; % PID 初始参数 adrc.h = sim.Ts; % ADRC 步长与采样一致 adrc.wo = 30; % 观测器带宽 adrc.wc = 10; % 控制器带宽 adrc.b0 = 1; % 控制增益估计逻辑说明:把对象、仿真、控制器参数分开,改一个不影响其他。参数说明:adrc.wo和adrc.wc是 ADRC 最核心的两个带宽,一般wo取wc的 3 到 5 倍,观测器要比控制器快,否则估计跟不上控制;b0可以先取对象高频增益的倒数近似,比如对象稳态增益为 1 时b0取 1 附近。
3.2 PID 回路的搭建与三个参数的初值来源
PID 部分相对成熟,Matlab 里可以直接用pid对象加feedback求闭环,也可以用 Simulink 的 PID Block。手写离散 PID 更能看清细节:
% 离散位置式 PID,带积分限幅和微分低通 function [u, ei, ed_f] = pid_step(r, y, ei, ed_prev, ed_f, p) e = r - y; % 当前误差 ei = ei + e * p.Ts; % 积分累加 ei = max(min(ei, p.i_max), -p.i_max); % 积分限幅,防饱和 ed = (e - ed_prev) / p.Ts; % 微分(差分) ed_f = ed_f + p.alpha * (ed - ed_f); % 一阶低通滤波 u = p.Kp * e + p.Ki * ei + p.Kd * ed_f; end逻辑说明:位置式 PID 直接算控制量,积分项单独累加并限幅,微分项先差分再低通。参数说明:p.Ts是采样周期;p.i_max根据执行器饱和值设,比如电机 PWM 限幅对应积分上限;p.alpha是滤波系数,越小滤波越强但相位滞后越大,一般取 0.1 到 0.3。
初值来源:先用 Ziegler-Nichols 临界比例度法粗调,或者用 Matlab 的pidtune自动给一组,再手动微调。注意pidtune给的是连续参数,离散化时要用c2d,别直接抄。
3.3 ADRC 三个核心模块的代码落地
ADRC 完整实现包含 TD、ESO、SEF 三块。TD 用来安排过渡过程,避免设定值突变导致控制量冲击;ESO 前面已经给了;SEF 就是误差的比例微分组合,但用的是 TD 的输出和 ESO 的估计。
% 跟踪微分器 TD:安排过渡过程,输出 v1(跟踪r) 和 v2(v1的导数) function [v1, v2] = td_update(r, v1, v2, h, r0) fh = -r0 * r0 * (v1 - r) - 2 * r0 * v2; % 最速控制综合函数简化形式 v1 = v1 + h * v2; v2 = v2 + h * fh; end % 状态误差反馈 SEF:用 TD 和 ESO 的输出算控制量 function u0 = sef_calc(v1, v2, z1, z2, kp, kd) e1 = v1 - z1; % 跟踪误差 e2 = v2 - z2; % 导数误差 u0 = kp * e1 + kd * e2; % 线性组合 end % 总控制量:补偿扰动 u = (u0 - z3) / b0; % z3 是 ESO 估计的总扰动逻辑说明:TD 让设定值不是跳变而是平滑过渡,r0越大过渡越快;SEF 用kp、kd代替 PID 的 Kp、Ki、Kd,没有积分项,因为扰动已经被z3补偿了;最后(u0 - z3)/b0是 ADRC 的标志性一步,把估计的扰动反向抵消。参数说明:r0一般取控制器带宽的几倍;kp = wc^2,kd = 2*wc(二阶对象),wc是期望闭环带宽;b0和 ESO 里保持一致。
把这三个模块按采样周期串起来,就是完整的 ADRC 循环。跑之前先单独测 ESO:给对象一个已知扰动,看z3能不能在几个周期内跟上,跟不上就调大wo。
4. 调参与对比:ADRC 和 PID 各自的血泪经验
4.1 ADRC 的带宽整定顺序
ADRC 参数看着多,其实有明确的整定顺序:先b0,再wo,最后wc。b0决定控制量的量级,如果b0设得比真实值大很多,控制量会偏小,响应变慢;设小了控制量偏大,容易振荡。我一般先按对象模型估一个,然后看阶跃响应,控制量明显饱和就调大b0。
wo是观测器带宽,决定扰动估计速度。调的时候单独看z3曲线:给一个阶跃扰动,z3应该在 3 到 5 个1/wo时间内稳定。太小估计慢,补偿不及时;太大噪声放大,控制量抖动。wc是闭环带宽,决定跟踪速度,一般取wo的 1/3 到 1/5,保证观测器比控制器快。
对比 PID 时,PID 只有三个参数,但耦合严重,改 Kp 影响超调,改 Ki 影响静差和振荡,改 Kd 影响噪声。ADRC 的参数相对解耦,这是它在工程上越来越受欢迎的原因之一。
4.2 抗扰动对比:加同样的负载突变看恢复时间
对比实验的关键是扰动要一致。我一般做两组:一组在 t=3s 时加 20% 负载阶跃,另一组加正弦扰动。记录两种控制器的输出和恢复时间。
典型结果是:PID 在负载突变时会有明显跌落,靠积分慢慢拉回,恢复时间取决于 Ki;ADRC 因为 ESO 能快速估计扰动,跌落更小、恢复更快。但如果wo没调好,ADRC 反而会振荡,这时候别急着否定 ADRC,先回去调观测器。
下面是一个批量对比的脚本框架:
% 批量仿真对比 PID 和 ADRC results = struct(); for ctrl = {'pid', 'adrc'} assignin('base', 'ctrl_type', ctrl{1}); sim('compare_model'); % 运行 Simulink 模型 results.(ctrl{1}).y = yout; % 记录输出 results.(ctrl{1}).u = uout; % 记录控制量 end % 画图对比 figure; plot(results.pid.y, 'b'); hold on; plot(results.adrc.y, 'r'); legend('PID', 'ADRC'); xlabel('采样点'); ylabel('输出');逻辑说明:用循环切换控制器类型,Simulink 模型里用 Variant 选择,保证对象和扰动完全一致。参数说明:yout和uout是 To Workspace 块导出的变量,采样点数和sim.Tend/Ts对应。
4.3 噪声敏感性:为什么 ADRC 的微分不能直接差分
PID 的 D 项对噪声敏感是常识,ADRC 的 ESO 里也有类似微分的结构,但它是通过观测器增益和积分实现的,比直接差分鲁棒。不过这不代表 ADRC 对噪声免疫。如果测量噪声很大,z3会被噪声带偏,控制量跟着抖。
处理办法:在测量端加低通滤波,或者降低wo。我一般先看噪声幅值,如果噪声标准差超过信号幅值的 1%,就必须滤波。滤波会引入相位滞后,这时候wo要相应降低,否则观测器和滤波器互相打架。
对比时如果只给 PID 加滤波、ADRC 不加,结论就不公平。正确做法是两者测量端用同一个滤波器,或者都不加,看原始表现。
5. 避坑与排查:复现这套源码时最容易翻车的 5 个点
5.1 现象:ADRC 输出一开始就发散
原因:b0符号搞反了,或者 ESO 的beta增益按错误公式算。很多人直接抄网上的beta1=100, beta2=300,但那是针对特定wo的,换对象就不对。
解决:先确认b0符号和对象增益一致,正对象用正b0。然后按beta1=3*wo, beta2=3*wo^2, beta3=wo^3重新算,wo从 10 开始试,稳定后再加大。
5.2 现象:PID 和 ADRC 对比曲线几乎重合
原因:扰动加得太小,或者仿真时间太短没进入动态。也可能是 ADRC 的wc设得太低,退化成慢速 PID。
解决:加大扰动幅值到对象额定输入的 20% 以上,仿真时间至少覆盖 3 个过渡过程。检查 ADRC 的wc是否明显高于 PID 的实际闭环带宽,如果差不多,ADRC 的优势体现不出来。
5.3 现象:ESO 估计的 z3 一直为零
原因:z3的更新项-beta3*e里e始终很小,或者h设成了 0。也可能是代码里z3没参与下一轮迭代,被覆盖了。
解决:打印e和z3的前几十步,确认e有非零值。检查h是否等于sim.Ts。确认z3是持久变量或通过结构体传递,不是每次调用重置。
5.4 现象:仿真跑得极慢,几分钟出不来结果
原因:sim.Ts设得太小,比如 1e-6,而仿真时长 10s,步数上千万。或者 Simulink 用了变步长求解器,ADRC 的离散模块和连续对象混用导致步长被压得很小。
解决:控制周期按实际系统来,电机类 1e-4 到 1e-3 足够,温控类 0.1 到 1 都行。Simulink 里把求解器设为定步长FixedStepDiscrete,步长等于sim.Ts,速度会快很多。
5.5 现象:换一个被控对象后 ADRC 完全不能用
原因:b0和带宽都是针对原对象调的,换对象后对象增益和动态变了,b0严重失配。
解决:换对象后重新估b0,wo和wc按新对象的响应速度重设。ADRC 的鲁棒性有边界,不是随便换对象都不调参。我一般保留一套「对象参数→ADRC 参数」的映射脚本,换对象时自动算初值。
6. 进阶技巧:用蒙特卡洛扫参看 ADRC 的鲁棒边界
调参调到后面,单条曲线已经看不出问题了。我习惯用蒙特卡洛扫参,把对象参数和控制器参数都加随机扰动,看两种控制器的成功率。具体做法:对象增益在 ±30% 内随机,时间常数在 ±20% 内随机,每种控制器跑 200 次,记录超调小于 10%、调节时间小于 2s 的比例。
% 蒙特卡洛鲁棒性对比 N = 200; success = struct('pid', 0, 'adrc', 0); for i = 1:N plant_gain = 1 + 0.3 * (2*rand - 1); % 增益 ±30% plant_tau = 1 + 0.2 * (2*rand - 1); % 时间常数 ±20% % 更新对象参数后分别跑 PID 和 ADRC % 判断超调和调节时间是否达标,达标则 success 加一 end fprintf('PID 成功率: %.1f%%, ADRC 成功率: %.1f%%\n', ... 100*success.pid/N, 100*success.adrc/N);逻辑说明:每次随机生成对象参数,跑完两种控制器后按同一标准判定。参数说明:扰动范围根据实际对象的不确定性定,电机参数一般 ±20% 到 ±30%,温控对象可能更大。判定阈值按工艺要求设,没有统一标准。
扫参结果通常显示:ADRC 在对象增益变化大时成功率明显高于 PID,但在噪声大、采样慢的场景下优势缩小。这个边界比任何单条曲线都有说服力。我自己踩过的最大坑是只在一组参数下对比就下结论,后来被现场工况打脸。现在养成的习惯是:任何控制器对比,先跑蒙特卡洛,再看典型曲线。希望帮到你。
本文还有配套的精品资源,点击获取