ADRC与PID等效:传递函数推导及MATLAB仿真实践
2026/9/9 15:19:49 网站建设 项目流程

ADRC这玩意儿玩起来确实挺有意思,尤其是当你想跟别人解释“ADRC到底是不是PID”的时候。我当年被问到这个问题,第一反应是:肯定不是啊,ADRC有观测器,PID哪来的观测器?结果真到了要写文档、要对标调参、要跟懂频域的工程师对齐思路的时候,才发现不把传递函数推一遍,很多话根本说不清楚。这篇就把ADRC的内核拆开,重点走一遍“从传递函数推导到PID等效”的完整链路,顺便给一段能直接跑的MATLAB热身代码。适合刚接触ADRC、想从原理上吃透它,或者已经在用但总感觉调参全靠试的朋友。

1. PID的抱怨与ADRC的逆袭逻辑

1.1 PID的三个“看不惯”

PID用了这么多年,大家心里都有数,它的问题不在于结构简单,而在于三个根子上的毛病。

第一个毛病是误差驱动的滞后性。PID是在误差出现之后才动作的,误差小的时候控制量就小,误差大才猛打。对一个慢变对象还好,但对象一旦有纯滞后或者大惯性,PID就会陷入“打一下、等一会儿、看错了再打”的循环,系统表现为振荡或者响应迟钝。

第二个毛病是微分项对噪声的放大。D项理论上能预测误差走向,但实际现场里的测量噪声高频分量很多,一求导就把噪声高频分量放大了,所以工业PID里往往要加滤波,加完滤波D的效果又打折,搞得很多人干脆把D关掉,只用PI。

第三个毛病是积分项的“秋后算账”。为了消除稳态误差必须加积分,但积分一强,抗饱和处理不当,系统超调就压不住;而且积分是对过去误差的累加,它不认识对象模型,遇到扰动变化,只能慢慢“磨”。

这三个毛病合在一起,本质上是同一个问题:PID对“对象是什么样”知道得太少,它只能根据输入输出误差来猜。猜得慢,就滞后;猜得猛,就振荡。

1.2 ADRC的思路:把“不知道的”变成“看得见的”

ADRC的逆袭思路特别朴素:你不是不知道对象模型吗?那我干脆把“不知道的那部分”当成一个额外的状态量,设计一个观测器把它估计出来,然后在控制量里把它减掉。

这句话值得重复一遍:ADRC不是不依赖模型,而是把模型误差、外部扰动、未建模动态这些东西全打包成一个“总扰动”,然后实时估计、实时补偿。补偿完之后,剩下的对象在控制器眼里就是一个干干净净的积分串联型。

打个比方。PID像一个看不到路况的司机,只能靠踩油门后的车速反应来调整。ADRC则像是车上装了传感器,把坡度、风力、载重全测出来,然后在踩油门的指令里把这些阻力直接抵消掉。司机剩下的工作就很简单——控制一个“没有阻力”的虚拟车。

把总扰动变成状态量,这就是“扩张状态”的意思。后面我会详细拆。

2. 扩张状态观测器(ESO)到底在估计什么

2.1 状态是怎么“扩张”出来的

拿最常见的二阶对象来推。假设被控对象可以写成:

y'' = f(y, y', w, t) + b0 * u

这里面y是输出,u是控制量。f是“总扰动”,它包括了对象内部动态、外部扰动、参数变化、未建模部分,反正所有你不知道、不想建模、建模不准的东西,全塞进f。

这个式子其实没什么神奇的,它就是牛顿第二定律的抽象版:加速度等于“所有别的力”加“控制产生的力”。

既然是二阶系统,自然状态是x1 = y,x2 = y'。那第三个数呢?把f本身也当成一个状态,令x3 = f。这就是“扩张”的含义——在原系统阶次基础上,多引入一个状态变量。

于是状态方程变成:

x1' = x2 x2' = x3 + b0 * u x3' = h

其中h是f的导数。虽然我们不知道h具体是多少,但在观测器设计里,它被当成一个未知但有限的“扰动变化率”。

2.2 观测器的结构与增益设计

有了扩张状态方程,ESO的观测器方程就能直接写出来:

z1' = z2 + β1 * (y - z1) z2' = z3 + β2 * (y - z1) + b0 * u z3' = β3 * (y - z1)

其中z1估计y,z2估计y',z3估计总扰动f。观测器增益β1、β2、β3怎么取?最常用的方法是带宽参数化,让观测器特征方程落在(s + ωo)^3上。

展开(s + ωo)^3 = s^3 + 3ωo s^2 + 3ωo^2 s + ωo^3,于是得到:

β1 = 3ωo β2 = 3ωo^2 β3 = ωo^3

这个参数化方法极大简化了整定过程。你不需要去试三个独立的β,只要定一个观测器带宽ωo即可。ωo越大,ESO追踪越快,但噪声放大也越严重。这个就是我们后面调参时的主要矛盾。

2.3 ESO的传递函数表达

ESO在时域里很直观,但如果你想从频域角度理解它,或者做扫频、做等效分析,就必须把它拉到s域里来。

把观测器方程做拉普拉斯变换,整理之后可以得到z1、z2、z3对y和u的传递函数。这里的关键是引入共同分母:

D(s) = s^3 + β1 s^2 + β2 s + β3

那么:

z1 = [(β1 s^2 + β2 s + β3) / D(s)] * y + [b0 s / D(s)] * u z2 = [(β2 s^2 + β3 s) / D(s)] * y + [b0 s(s + β1) / D(s)] * u z3 = [β3 s^2 / D(s)] * y - [b0 β3 / D(s)] * u

这几个式子看着长,但信息量很大。z1对y的传递函数在低频段趋近于1,说明z1能无差跟踪输出;z2对y的传递函数在低频段趋近于s,说明z2本质上就是对输出的微分估计;z3表达式里的第一项说明它从输出里提取低频累积信息,第二项则是把控制量带来的影响减掉,保证它估计的是“外部总扰动”而不是控制量本身。

知道这些传递函数形式,后面推导PID等效就能直接用了。

3. 从传递函数到PID等效:推导过程与边界

3.1 先把控制律写成频率域形式

线性ADRC的控制律通常写成:

u0 = kp * (r - z1) - kd * z2 u = (u0 - z3) / b0

其中r是参考输入,kp和kd是控制器增益。对于二阶对象,可以把闭环期望极点配置成:

kp = ωc^2 kd = 2ωc

ωc是控制器带宽。控制量u先由u0除以b0,再减去总扰动估计z3,这样做的目的就是“对抗”对象中的b0 u项,实现动态补偿。

把这个控制律往s域写,可以整理成从误差和输出到控制量的传递函数。整个过程稍微繁琐一点,但每一步都是线性代数。

把ESO的z1、z2、z3表达式全部代入控制律u的表达式,经过整理,从输出y到控制量u的闭环反馈补偿器可以写成一个统一的传递函数:

C(s) = [a2 s^2 + a1 s + a0] / [b0 s (s^2 + c1 s + c0)]

其中各系数为:

a2 = kp * β1 + kd * β2 + β3 a1 = kp * β2 + kd * β3 a0 = kp * β3 c1 = β1 + kd c0 = β2 + kp + kd * β1

注意分母里有一个s因子。这个s因子单独提出来,说明整个补偿器天然含有积分作用。这就是ADRC能消除稳态误差的频域根源,不是靠额外加的积分项,而是ESO结构带来的结果。

3.2 低频近似下的等效PID参数

把分母后面那个二阶多项式看成一个“滤波因子”。在低频段,也就是s趋近于0的时候,s^2项相对c0可以忽略,那么补偿器近似为:

C(s) ≈ [a2 s^2 + a1 s + a0] / [b0 * c0 * s]

这就是一个标准的PID结构,分子除以s之后变成:

Kp_eq + Ki_eq / s + Kd_eq * s

对应地,等效PID参数为:

Kp_eq = a1 / (b0 * c0) Ki_eq = a0 / (b0 * c0) Kd_eq = a2 / (b0 * c0)

把a0、a1、a2、c0的具体表达式代回去,就会看到每个等效参数都是ωc、ωo、b0的组合。也就是说,ADRC的“PID味道”是可以直接用公式算出来的。

但必须强调,这个等效是有条件的,就是我前面说的“低频段近似”。具体来说,分母中那个二阶多项式s^2 + c1 s + c0的零点所对应的转折频率,必须在闭环带宽之上。如果ESO带宽不够高,这个二阶因子的动态就不该被忽略,此时ADRC的行为会明显偏离等效PID。

3.3 边界条件:什么时候等效会失真

我实际推导、仿真下来的几点感受:

第一,b0严重不准确时,等效PID增益会大幅跑偏,但ADRC的自抗扰能力恰恰体现在这里——即使b0和真实对象增益差很多,ESO还能把“b0误差造成的等效扰动”也一并估计和补偿掉。所以b0不准确时,ADRC的实际表现依然能保持稳定,只是等效PID参数已经不能反映真实闭环特性了。

第二,测量噪声大的时候,如果把ωo调得很高,z3会把噪声当成高频扰动去补偿,控制量会表现出剧烈的抖振。这时候从频域看,就是C(s)中的高频增益被放大,等效PID里的“微分项”主导了输出。

第三,控制器带宽ωc和观测器带宽ωo的比值如果太近,闭环极点会与观测器极点相互干扰,等效PID模型会失效。经验上ωo至少要比ωc大3到5倍,我一般取5到10倍,后面会有专门说明。

所以,推导出来“ADRC低频等价于PID”,不是要证明ADRC故弄玄虚或者多此一举。它的价值在于:你用PID调了很多年,积攒了一身直觉,那这些直觉在ADRC上依然能部分复用;而当系统行为异常时,你知道该往哪个环节去找原因,而不是盲调。

4. MATLAB热身:一个最小可跑的LADRC仿真

4.1 仿真对象与参数设计

说了这么多理论,还是得跑代码。下面这段MATLAB是一个最小可跑的线性ADRC仿真,包含完整的对象模型、ESO、控制器三个部分。

我故意把“建模用b0”和“真实对象增益”设得不一致,这样ESO才有东西可估计。假设真实对象为:

y'' = -3y' + 2y + 0.5sin(3t) + 0.8u + d

这个对象里有内部动态-3y' + 2y,有非线性项0.5*sin(3t),还有外部负载扰动d。但我建模时用的b0=1.2,比真实增益0.8大了50%。这种情况下,PID基本必须重新整定,ADRC却能靠ESO一路兜住。

控制目标是让y跟踪阶跃参考r=1,并在第3秒加一个幅度1的阶跃负载扰动,看系统能不能扛住。

4.2 完整代码

%% LADRC最小仿真:ESO估计总扰动 + 动态补偿 clear; clc; close all; % 仿真参数 dt = 0.001; % 采样步长 T = 6; % 仿真时长 t = 0:dt:T; % 时间向量 n = length(t); % 对象初始状态 y = 0; dy = 0; % ESO初始状态 z1 = 0; z2 = 0; z3 = 0; % LADRC参数 wc = 5; % 控制器带宽 wo = 30; % 观测器带宽 kp = wc^2; % PD控制器比例增益 kd = 2*wc; % PD控制器微分增益 b0 = 1.2; % 建模使用的控制增益(真实对象是0.8) % 观测器增益(带宽参数化) beta1 = 3*wo; beta2 = 3*wo^2; beta3 = wo^3; % 存储结果 yout = zeros(1, n); uout = zeros(1, n); z3out = zeros(1, n); z1out = zeros(1, n); % 参考输入 r = 1; % 主循环 for k = 1:n % 外部负载扰动:第3秒后加入阶跃扰动 d = 0; if t(k) >= 3 d = 1.0; end % 对象内部动态(对控制器而言未知) f_nl = -3*dy + 2*y + 0.5*sin(3*t(k)); % --- ADRC控制律 --- u0 = kp*(r - z1) - kd*z2; u = (u0 - z3)/b0; % --- 真实对象更新(欧拉法) --- ddy = f_nl + 0.8*u + d; dy = dy + ddy*dt; y = y + dy*dt; % --- ESO更新 --- e = y - z1; dz1 = z2 + beta1*e; dz2 = z3 + beta2*e + b0*u; dz3 = beta3*e; z1 = z1 + dz1*dt; z2 = z2 + dz2*dt; z3 = z3 + dz3*dt; % 保存数据 yout(k) = y; uout(k) = u; z3out(k) = z3; z1out(k) = z1; end % 绘图 figure('Color', 'w', 'Position', [100 100 900 600]); subplot(3,1,1); plot(t, yout, 'b-', 'LineWidth', 1.5); hold on; plot(t, r*ones(size(t)), 'k--', 'LineWidth', 1); ylabel('y'); title('ADRC跟踪与抗扰'); legend('y', 'r', 'Location', 'best'); grid on; xlim([0 T]); subplot(3,1,2); plot(t, uout, 'r-', 'LineWidth', 1.2); ylabel('u'); title('控制量'); grid on; xlim([0 T]); subplot(3,1,3); plot(t, z3out, 'g-', 'LineWidth', 1.5); hold on; % 画实际总扰动的参考曲线(仅用于对照) f_actual = -3*dy + 2*y + 0.5*sin(3*t) + d*ones(size(t)); plot(t, f_actual, 'k--', 'LineWidth', 1); legend('z3估计', '实际总扰动', 'Location', 'best'); ylabel('总扰动'); title('ESO估计效果'); grid on; xlim([0 T]);

4.3 结果怎么看

跑完这段代码,你会看到三个图:

第一个图,输出y能快速跟踪阶跃参考,超调不大,大约1秒内进入稳态。第3秒加入负载扰动之后,y出现一个小凹陷,但很快被拉回来,恢复时间大概0.5秒以内。这就是ESO把扰动估计出来后直接补偿掉的效果。

第二个图,控制量u在扰动加入瞬间会有一个明显的跳变,这个跳变就是补偿动作。它不像PID那样靠误差累积一点点“磨”,而是几乎同步把扰动电压顶回去。

第三个图最关键,看z3估计值能不能追上实际总扰动。如果你把观察窗口放大,会看到z3在扰动阶跃后大概0.1秒左右追上新值。这就是ESO带宽wo=30的效果。如果把wo降到10,z3跟踪会变慢,抗扰能力明显下降;但噪声放大的风险会小很多。

你还可以试着改wc和wo,观察一个现象:wc决定响应速度,wo决定ESO估计速度,两者协调才能有好的动态和抗扰表现。b0则决定了控制量尺度,b0给大了控制量偏大,给小了控制量偏保守,但ESO都会尽量把它“圆回来”。

5. 扫频法实战:用开环传递函数标定截止频率

5.1 为什么需要扫频

搞控制的人,到了现场最关心的往往是几个硬指标:幅值裕度、相位裕度、截止频率。这些指标用传递函数一看就知道系统稳不稳、快不快。

但ADRC的参数是带宽,是b0,是kp、kd,不是穿越频率。那怎么在频域层面评估一个ADRC闭环系统?最直接的办法就是扫频。所谓扫频法,就是给被控对象或者闭环系统依次注入不同频率的正弦信号,记录输入输出的幅值比和相位差,然后画出伯德图,从伯德图里读出截止频率和裕度。

这个方法的好处是,不需要知道对象模型的解析表达式,只要系统能加正弦激励、能测响应,就能得到频率特性。对现场调试来说特别实用。

5.2 扫频法的实现要点

在MATLAB里做扫频,核心步骤是:生成一组频率点,对每个频率点仿真足够长时间直至达到稳态,然后计算输出与输入在同一频率上的幅值比和相位差。

相位差的计算有个小技巧,不要直接用xcorr或者fft去翻,最稳妥的方式是用正交解调。对每个频率ω,把输出y乘以cos(ωt)和sin(ωt),然后在一个整周期或多周期上求平均,就可以得到实部和虚部,进而算出幅值和相位。

下面给一个简洁的扫频法参考代码片段,可以直接在这个LADRC仿真里继续使用。

%% 扫频法求开环响应(参考代码片段) freqs = logspace(-1, 2, 40); % 0.1~100 rad/s,对数均匀分布 mag = zeros(size(freqs)); phase = zeros(size(freqs)); for idx = 1:length(freqs) w = freqs(idx); Ts = 2*pi/w * 20; % 跑20个周期,确保进入稳态 t_sim = 0:dt:Ts; % 注入正弦,记录输出(可复用上面的LADRC循环结构) % 这里仅示意:实际需要把上一段主循环改成“正弦输入+记录输出” % y_ss 为每个频率下的稳态输出信号 % 正交解调 cos_part = trapz(t_sim, y_ss .* cos(w*t_sim)); sin_part = trapz(t_sim, y_ss .* sin(w*t_sim)); mag(idx) = 2*sqrt(cos_part^2 + sin_part^2) / Ts; phase(idx) = atan2(sin_part, cos_part) * 180/pi; end % 画伯德图 figure; subplot(2,1,1); semilogx(freqs, 20*log10(mag), 'b-o'); grid on; ylabel('幅值 (dB)'); title('扫频法伯德图'); subplot(2,1,2); semilogx(freqs, phase, 'r-o'); grid on; xlabel('频率 (rad/s)'); ylabel('相位 (deg)');

注意,扫频法有几个坑:

第一,必须等系统进入稳态再取数据。如果从t=0就开始算,瞬态分量会污染幅值和相位测量。所以我通常每个频率点会丢掉前5到10个周期。

第二,频率点要覆盖关心的频段,不要全集中在高频或低频。建议在截止频率附近多取几个点,这样估出来的截止频率更准。

第三,如果系统存在强非线性,扫出来的“频率特性”会随注入幅值变化。此时需要固定注入幅值,并在多个幅值下扫描对比,否则结果不可比。

用扫频法验证ADRC,我一般看两个量:一是闭环的截止频率是否接近wc,二是开环幅值穿越处相位是否离开-180度足够远。如果相位裕度偏小,优先降低wc,或者适当提高wo让ESO更“稳准狠”。

5.3 截止频率与ADRC参数的经验关系

从我扫过的几个系统来看,LADRC闭环的截止频率大致在ωc附近,但不会刚好等于ωc。因为ESO的动态等效于在回路里串了一个二阶滤波器,会略微压低穿越频率。实际测出来,截止频率大概在0.6到0.9倍ωc之间。wo越低、ESO动态越重,这个折扣越大。

所以如果你想设计一个截止频率为10 rad/s的闭环,建议把wc设在12到15左右,再通过扫频结果微调。这个思路和用PID时先定一个穿越频率再反推参数的思路是一模一样的,这就是等效PID概念在工程上的真正用处。

6. 调参顺序与数字实现中的几个坑

6.1 调参顺序:先b0,再wo,最后wc

ADRC参数少,但少不代表可以乱来。我实践下来最稳的顺序是:先定b0,再升wo,最后动wc。

b0代表你对对象控制增益的“粗估”。第一版先给一个量级差不多的值就行,不需要精确。b0的量级怎么估算?可以做一个阶跃实验,给对象加一个已知幅值的u,看稳态加速度变化量,两者相除就是b0的近似值。

定好b0后,逐步升高wo,同时观察控制量毛刺。wo升到某个值,控制量开始出现明显高频抖动,说明它高到噪声敏感区了,退回一点即可。最后再升wc,直到跟踪速度满足要求。如果升wc过程中出现振荡,不要急着调回wc,先看wo是不是低了,ESO跟不上导致相位损失。

6.2 数字实现的离散化问题

很多人把连续域里的ADRC方程直接搬到单片机或者控制器里,结果发现跟仿真完全不一样。问题出在离散化。

最简单的欧拉法在采样周期比较小的时候够用,但采样周期大,比如5毫秒以上的PLC扫描周期,欧拉法会引入明显相位滞后。我一般推荐用双线性变换,或者直接用离散状态空间来实现ESO。

离散ESO可以写成:

z1[k+1] = z1[k] + T*(z2[k] + β1*(y[k]-z1[k])) z2[k+1] = z2[k] + T*(z3[k] + β2*(y[k]-z1[k]) + b0u[k]) z3[k+1] = z3[k] + Tβ3*(y[k]-z1[k])

这个形式跟连续版一一对应,工程上可以直接用。但注意,T不能太大,经验上ωo*T的乘积最好小于0.3,否则离散化误差会明显削弱ESO的观测性能。比如wo=30时,T要小于0.01秒,也就是采样频率至少100赫兹。

6.3 ESO初值、抗饱和与几个实战心得

ESO的初值一般设0就行,因为观测器本身会收敛。但要注意,如果对象初始状态离0很远,ESO需要一段时间才能追上,这段时间里的控制量会偏大。实际中最好让控制器在参考输入变化前先运行几个周期,让ESO收敛到当前工作点。

抗饱和这块经常被忽略。ADRC的控制量u里含有z3/b0这个补偿分量,如果执行器饱和了,u被限幅,但ESO内部仍然按照未饱和的u去计算,z3就会越积越偏,等退出饱和时表现得很“冲”。解决思路有两个:一是给ESO也做限幅,让它知道实际执行器的输出边界;二是检测到饱和时冻结z3的更新。工业上常用后者,简单有效。

最后分享一个我踩过的坑。有一回我在仿真里把b0设成了精确值,wo也调得很高,控制效果漂亮得不行。结果拿到现场,测量噪声一上来,控制量抖得电机都在响。后来把wo从50降到15,噪声下去了,抗扰性能虽然差了一截,但系统整体能稳定跑。ADRC调参就是这样,永远要在“跟踪快”和“噪声稳”之间找平衡,而找平衡的抓手就是wo和wc,不是kp、kd那些表面参数。

说到底,ADRC的数学门槛不高,难得是把里面每个量的物理意义和工程后果都对上号。传递函数到PID等效的推导,就是把“时域里靠观测器补偿”这个高级操作,翻译回“频域里它到底给回路增加了什么”,翻译通了,你才算真正看懂了它。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询