简介:这份资源是围绕Matlab平台整理的自抗扰控制器(ADRC)与PID控制源码合集,面向自动化、电子信息工程、数学等专业的学生及控制算法初学者,可用于课程设计、期末大作业或毕业设计中的算法验证与对比研究。包内共36个文件,以31个.m脚本为主,辅以4个.mdl仿真模型和1个.asv备份文件,整体约39KB,涵盖被控对象建模、控制器实现、扩张状态观测器、跟踪微分器及仿真绘图等模块,便于读者理解ADRC与PID的算法结构与仿真流程。目前已有1250人学习下载,说明其在控制类课程与算法入门中具有一定参考价值。读者可借助这些源码搭建仿真环境,对比两种控制策略的响应特性,学习非线性函数与观测器的实现思路,并在此基础上自行调试、修改或扩展功能,作为算法学习与项目开发的参考资料。
1. 从一份 Matlab 自抗扰+PID 源码说起:它到底解决什么问题
如果你手上有一台位置伺服、四旋翼姿态环、或者温控/流量回路,被负载扰动、参数漂移、死区摩擦折腾得够呛,那你大概率已经试过 PID。PID 的好处是结构简单、参数少、工程直觉强,坏处也明显:它靠误差反馈“事后补救”,对突变扰动和模型不确定性反应总是慢半拍。自抗扰控制器(ADRC)走的是另一条路——用扩张状态观测器(ESO)把“内扰+外扰”统一估出来,再前馈补偿掉,相当于给系统配了个实时扰动黑匣子。这份“基于 Matlab 自抗扰控制器和 PID 控制(源码)”的工程,本质就是把 ADRC 和 PID 放在同一个仿真框架里对比、调参、验证,让你能直观看到两种控制律在同一被控对象上的跟踪、抗扰和鲁棒性差异。它适合做控制算法验证的工程师、做课程设计/毕设的学生,也适合想把 ADRC 真正落到电机、舵机、温控场景的人。下面我按“先立原理、再跑代码、最后避坑”的顺序,把这份源码该怎么读、怎么改、怎么复现讲清楚。
2. 自抗扰与 PID 的底层差异:为什么 ESO 是分水岭
2.1 从 PID 的“误差驱动”到 ADRC 的“扰动估计+补偿”
PID 的核心逻辑是:误差 e = r - y,输出 u = Kp·e + Ki·∫e + Kd·de/dt。它不关心扰动从哪来、系统内部发生了什么,只根据误差大小和变化趋势去“顶”。这在模型准确、扰动缓慢时够用,但一旦遇到阶跃扰动、参数摄动、非线性摩擦,PID 要么超调、要么响应慢,靠调参只能折中。
ADRC 的思路完全不同。它把被控对象写成:
y'' = f(y, y', w, t) + b0·u
其中 f 是“总扰动”,包含内部未建模动态和外部扰动,b0 是控制增益的估计值。ADRC 不去精确建模 f,而是用扩张状态观测器(ESO)把 f 当成一个额外状态实时估计出来,记作 z3。控制律再取:
u = (u0 - z3) / b0
这样扰动在进入系统之前就被“抵消”掉了。剩下的 u0 可以用最简单的 PD 形式,比如 u0 = kp·(r - z1) - kd·z2。所以 ADRC 的本质是:用观测器把扰动变成已知量,再用前馈把它消掉。ESO 就是整个控制器的分水岭——它估得准,ADRC 就强;它估不准,ADRC 可能还不如 PID。
2.2 一阶/二阶 ADRC 的 Matlab 实现骨架
工程里最常见的是一阶和二阶 ADRC。以二阶被控对象为例,核心是离散 ESO 和 PD 控制律。下面这段代码是典型的二阶 ADRC 单步更新,你可以直接对照源码里的函数结构:
function [u, z1, z2, z3] = adrc_step(r, y, z1, z2, z3, h, b0, kp, kd, beta1, beta2, beta3) % r: 设定值, y: 实际输出, h: 采样步长 % z1,z2: 状态估计, z3: 总扰动估计 % b0: 控制增益估计, kp/kd: 控制器增益 % beta1~3: ESO 观测器增益 e = z1 - y; % 观测误差 z1 = z1 + h * (z2 - beta1 * e); z2 = z2 + h * (z3 - beta2 * e); z3 = z3 + h * (-beta3 * e); % 扩张状态:总扰动 u0 = kp * (r - z1) - kd * z2; % PD 控制律 u = (u0 - z3) / b0; % 扰动补偿 end逻辑说明:前三行是离散 ESO 的欧拉积分,beta1/beta2/beta3 决定观测器收敛速度;z3 就是估出来的总扰动。u0 用 PD 形式,最后除以 b0 并减去 z3/b0,完成补偿。参数说明:h 必须和仿真步长一致,否则观测器会发散;b0 一般取对象高频增益的粗略估计,宁可偏大不要偏小;kp、kd 按带宽配置,常见做法是令 kp = wc²,kd = 2·wc,wc 是期望闭环带宽。beta1~3 通常按观测器带宽 wo 配置:beta1 = 3·wo,beta2 = 3·wo²,beta3 = wo³,wo 一般取 3~5 倍 wc。
2.3 PID 对照组的实现与参数整定
源码里 PID 部分通常就是标准位置式或增量式。为了和 ADRC 公平对比,采样步长、被控对象、扰动注入方式必须完全一致。下面是一个带抗积分饱和的 PID 单步实现:
function [u, integ] = pid_step(r, y, integ, h, Kp, Ki, Kd, u_max) % 位置式 PID + 积分限幅 e = r - y; integ = integ + Ki * e * h; integ = max(min(integ, u_max), -u_max); % 抗积分饱和 deriv = (e - (r - y)) / h; % 简化微分项 u = Kp * e + integ + Kd * deriv; u = max(min(u, u_max), -u_max); end逻辑说明:积分项单独累加并限幅,避免执行器饱和时积分继续累积导致退饱和超调。参数说明:Kp 决定响应速度,Ki 消除稳态误差但太大会振荡,Kd 抑制超调但对噪声敏感。工程上常用 Ziegler-Nichols 或内模法先给初值,再手动微调。注意:如果被控对象有纯延迟,PID 的 Kd 要慎用,否则噪声放大后执行器会抖。
3. 把源码跑起来:Matlab 环境、文件结构与最小复现步骤
3.1 环境准备与源码目录的典型构成
这份源码是 .rar 包,解压后一般包含:主仿真脚本(如 main_sim.m)、ADRC 函数文件、PID 函数文件、被控对象模型文件、绘图脚本。Matlab 版本建议 R2016b 及以上,因为脚本内定义函数、string 类型等特性在老版本上会报错。不需要额外工具箱,基础 Matlab 即可运行;如果源码里用了 Simulink 模型,则需要 Simulink 授权。
常见做法是:先不要改任何参数,直接运行主脚本,看能不能出图。如果报“未定义函数”,大概率是当前工作目录没切到源码根目录,或者函数文件名和调用名不一致。这一步是血泪经验——很多人卡在路径上,以为代码有问题。
3.2 最小复现:一个二阶对象的 ADRC vs PID 对比
下面给一个可以直接抄的最小仿真框架,把 ADRC 和 PID 放在同一个循环里跑,方便你对照源码理解:
clear; clc; h = 0.001; T = 5; N = T/h; % 被控对象:二阶惯性环节 y'' = -a*y' - b*y + c*u + d a = 2; b = 10; c = 1; d = 0; % ADRC 参数 b0 = c; wc = 20; wo = 4*wc; kp = wc^2; kd = 2*wc; beta1 = 3*wo; beta2 = 3*wo^2; beta3 = wo^3; z1=0; z2=0; z3=0; % PID 参数 Kp=30; Ki=50; Kd=1; integ=0; u_max=10; % 状态 y_adrc=0; dy_adrc=0; y_pid=0; dy_pid=0; r = 1; % 设定值 for k = 1:N t = k*h; % 扰动:2s 时加 0.5 阶跃 dist = (t >= 2) * 0.5; % ADRC [u_adrc, z1, z2, z3] = adrc_step(r, y_adrc, z1, z2, z3, h, b0, kp, kd, beta1, beta2, beta3); ddy_adrc = -a*dy_adrc - b*y_adrc + c*u_adrc + dist; dy_adrc = dy_adrc + h*ddy_adrc; y_adrc = y_adrc + h*dy_adrc; % PID [u_pid, integ] = pid_step(r, y_pid, integ, h, Kp, Ki, Kd, u_max); ddy_pid = -a*dy_pid - b*y_pid + c*u_pid + dist; dy_pid = dy_pid + h*ddy_pid; y_pid = y_pid + h*dy_pid; % 记录 Y_adrc(k)=y_adrc; Y_pid(k)=y_pid; U_adrc(k)=u_adrc; U_pid(k)=u_pid; end t = (0:N-1)*h; plot(t, Y_adrc, 'b', t, Y_pid, 'r'); legend('ADRC','PID'); grid on;逻辑说明:被控对象用欧拉法离散积分,扰动在 2s 时注入。ADRC 和 PID 共用同一个对象和扰动,保证对比公平。参数说明:b0 取 c 的估计值;wc 是期望闭环带宽,越大响应越快但控制量越大;wo 取 4 倍 wc 是常见经验,观测器要快于控制器。PID 的 Kp/Ki/Kd 需要你根据对象手动调,这里给的是一组能出图的初值。跑完后你会看到:ADRC 在扰动注入后恢复更快,PID 会有明显跌落再慢慢拉回。
3.3 参数怎么改:从“能跑”到“跑得好”
调 ADRC 的顺序是:先定 b0,再定 wc,最后定 wo。b0 偏大,控制量偏小、响应慢;b0 偏小,控制量偏大、容易振荡。wc 决定跟踪速度,一般取对象带宽的 2~5 倍。wo 决定扰动估计速度,取 3~5 倍 wc,太小则扰动补偿滞后,太大则对噪声敏感。PID 那边,先调 Kp 让响应快起来,再加 Ki 消静差,最后用 Kd 压超调。注意:如果对象有噪声,Kd 和 wo 都不能太大,否则控制量会抖。
4. 避坑与排查:ADRC+PID 仿真里最容易翻车的 5 个点
4.1 现象:ADRC 输出剧烈振荡,甚至发散
原因:最常见的是 b0 符号搞反或数量级差太多,其次是 h 和仿真步长不一致,导致 ESO 积分不匹配。还有一种情况是 wo 取得过大,观测器把噪声当扰动放大。
解决:先检查 b0 是否为正、是否接近对象高频增益。把 wo 降到 2~3 倍 wc 试。确认 adrc_step 里的 h 和主循环的 h 完全一致。如果还振,把 kp/kd 减半,先让系统稳下来再逐步加。
4.2 现象:PID 稳态误差消不掉,或者积分饱和后大幅超调
原因:Ki 太小消不掉静差,Ki 太大又振荡;积分限幅没做,执行器饱和后积分继续累积。
解决:给积分项单独限幅,限幅值取执行器上限的 0.5~0.8 倍。如果对象有死区,纯 PID 很难消静差,这时候 ADRC 的扰动补偿优势就出来了——它能把死区当成总扰动的一部分估出来补掉。
4.3 现象:ADRC 跟踪阶跃时初始控制量巨大
原因:阶跃设定值直接进 PD 的 r - z1,初始误差大,u0 冲得很高。这是 ADRC 的常见问题,叫“设定值突变冲击”。
解决:加跟踪微分器(TD)对设定值做过渡,或者用“安排过渡过程”的方式让 r 缓慢上升。源码里如果有 TD 模块,一定要用上;没有的话可以自己加一个一阶惯性滤波。
4.4 现象:仿真步长改小后结果反而变差
原因:ESO 的离散化用欧拉法,步长太小会让 beta 增益对应的数值稳定性变差,尤其是 wo 较大时。
解决:要么保持 h 在 1e-3 量级,要么改用更精确的离散化方法(如零阶保持、双线性变换)。工程上如果实际控制器采样率固定,仿真步长就按实际采样率来,不要盲目调小。
4.5 现象:换一个被控对象,ADRC 参数全废
原因:ADRC 虽然不依赖精确模型,但 b0 和带宽参数仍然和对象有关。对象增益变了,b0 不变就会补偿错误。
解决:b0 按新对象的高频增益重新估。如果对象增益变化范围大,可以考虑在线辨识 b0,或者把 b0 取保守值(偏大),牺牲一点性能换鲁棒性。这也是 ADRC 工程落地时最需要权衡的地方。
5. 进阶用法:把 ADRC 从仿真推到实际控制器的几个技巧
仿真跑通只是第一步,真正落地时你会遇到采样延迟、量化噪声、执行器非线性。我一般会做三件事:第一,把 ESO 的带宽 wo 压到仿真值的 0.5~0.7 倍,给噪声和延迟留余量;第二,在控制量输出后加一个一阶低通滤波,截止频率取 wc 的 2~3 倍,压掉高频抖动;第三,用实际采样率重跑仿真,把 h 设成真实值,看参数是否还稳。
验证方法上,我习惯先做阶跃响应看超调和调节时间,再做扰动注入看恢复时间,最后做参数摄动(把对象增益改 ±30%)看鲁棒性。如果这三项都过,基本可以上机试。下面这张表是我常用的参数初值参考:
| 参数 | 含义 | 初值建议 | 调整方向 |
|---|---|---|---|
| b0 | 控制增益估计 | 对象高频增益 | 偏大更稳,偏小更快但易振 |
| wc | 控制器带宽 | 对象带宽 2~5 倍 | 越大越快,太大超调 |
| wo | 观测器带宽 | 3~5 倍 wc | 越大估计越快,太大噪声敏感 |
| kp | 比例增益 | wc² | 随 wc 联动 |
| kd | 微分增益 | 2·wc | 随 wc 联动 |
| h | 采样步长 | 实际采样周期 | 必须与控制器一致 |
最后说个我自己的习惯:每次调 ADRC,我都会先把 PID 跑一遍作为基线,记下它的超调、调节时间、扰动恢复时间,然后再调 ADRC。这样你才能说清楚 ADRC 到底比 PID 强在哪、强多少。如果 ADRC 调完还不如 PID,大概率是 ESO 没调好,回去查 b0 和 wo。希望帮到你。
本文还有配套的精品资源,点击获取