
开头前阵子在技术群里看到有人问“PID 调了这么多年好歹有套成熟口诀ADRC 有没有类似的参数表能不能别一上来就讲公式”我当时回了一句能而且早该有人整理了。自抗扰控制ADRC的火爆不是没道理它把“模型不确定性”和“外部扰动”统一扔给扩张状态观测器去扛比 PID 多了一双看透扰动的眼睛。但对绝大多数刚接触它的人来说最劝退的不是韩京清教授那篇经典论文的数学推导而是 Simulink 里那堆非线性函数、观测器增益、带宽参数凑到一起之后根本不知道先动哪一个。这篇文章就是来拆掉这层墙的。我会完全站在“调参实战”的角度手把手带你在 MATLAB/Simulink 里把 ADRC 三大件——跟踪微分器TD、扩张状态观测器ESO、非线性状态误差反馈NLSEF一个一个搭出来每一处模块怎么接、参数初始值怎么定、调参顺序怎么走全给你捋清楚最后直接给一份能抄作业的完整参数表。适合刚接触 ADRC 的学生、刚用 ADRC 做项目验证的工程师以及那些“PID 已经很熟但想迈出下一步”的同行。1. ADRC 无非三件事安排过渡、做估计、做反馈很多人一打开 ADRC 的资料就被“自抗扰”“扰动补偿”“扩张状态”这些词震住了其实拆开看就三个功能模块每个模块解决一个问题。1.1 三个模块各自干什么活先说 TD跟踪微分器。它的任务很朴素给你那个阶跃信号“安排过渡”。PID 最让人头疼的地方是给定值突变时误差瞬间拉满控制器会猛输出系统跟着超调。TD 能把一个阶跃信号处理成一条平滑的过渡曲线同时还能提取出它的“速度”也就是微分信号。换句话说TD 相当于给信号加了个“缓启动”加“滤波器”让你不再需要对给定值做一堆前置处理。然后是 ESO扩张状态观测器。这是 ADRC 的灵魂。它不只能观察到系统的输出和输出的一阶导还能额外“扩张”出一个状态把系统内部建模误差、外部扰动、未建模动态全部打包成一个“总扰动”实时估出来。你不需要精确知道干扰是摩擦力、负载突变还是模型参数漂移ESO 会自动告诉你“等效的总扰动大概是多少”然后你再拿这个估计值去补偿掉它。最后是 NLSEF非线性状态误差反馈。它把 TD 给出的光滑给定和微分与 ESO 给出的输出估计和速度估计相减得到误差再做一个“非线性组合”。这个环节替代了 PID 的线性 P、D 组合用非线性函数让“小误差增益大、大误差增益小”效果比固定增益更灵活相当于低速时紧抓、高速时放松很多工况下能显著减小超调。1.2 数学模型先写下来从公式到 Simulink 的映射关系要搭 Simulink第一步是把数学结构落到方程。以一个常见的二阶系统为例ADRC 的核心方程如下TD 离散形式x1(k1) x1(k) h * x2(k) x2(k1) x2(k) h * fhan(x1 - v, x2, r, h0)这里的fhan是最速控制综合函数里面带两个可调参数r决定跟踪速度快慢h0是滤波因子能抑制微分噪声。h是仿真步长。ESO 离散形式e z1 - y z1(k1) z1(k) h * (z2(k) - β01 * fal(e, α1, δ)) z2(k1) z2(k) h * (z3(k) - β02 * fal(e, α1, δ) b0 * u) z3(k1) z3(k) - h * β03 * fal(e, α2, δ)z1是输出估计z2是速度估计z3是总扰动估计。fal是韩京清提出的非线性函数β01、β02、β03是观测器增益b0是控制增益估计也就是被控对象输入到加速度之间的增益你能给个大概值就行。NLSEF 控制量生成e1 x1 - z1 e2 x2 - z2 u0 kp * fal(e1, α1, δ) kd * fal(e2, α2, δ) u u0 - z3 / b0这里的kp、kd相当于非线性 PD 的增益u0是“不补偿扰动时的基础控制量”最后一项- z3 / b0就是扰动补偿。看清楚这个结构之后调参逻辑就出来了先让 TD 把给定信号平滑好再让 ESO 把扰动估计准最后用 NLSEF 把误差拉回来。三块各管各的调参不是一锅粥而是一个递进过程。2. Simulink 里搭 TD三种做法里我最推荐这种方式TD 是整个结构里最容易搭的但越简单越有人搭错。我见过很多人直接拿一个传递函数r/(sr)去“等效”跟踪微分器结果发现跟踪没问题微分信号全是噪声。这里我给出三种做法并讲讲各自的优劣。2.1 用 MATLAB Function 写 fhan最省事我的第一选择是在 Simulink 里拖一个MATLAB Function模块把fhan函数用代码写进去再用两个Integrator模块搭离散积分。MATLAB Function的好处是代码可读性高调参的时候一目了然不用在模块图里穿梭。下面是一份可以直接用的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; a (abs(y) d) * a2 (abs(y) d) * (a0 y); fh -r * (abs(a) d) * sign(a) - r * (abs(a) d) * a / d; end这段代码在很多项目里都会被复用建议存成一个.m文件放到工作目录里Simulink 的MATLAB Function模块直接调用。代码里用了sign()函数注意 Simulink 里sign(0)的处理是返回 0在原版公式里这一步没问题。2.2 在模型里搭出 TD 的完整结构有了fhan搭 TD 就顺手了。按下面步骤来拖入两个Integrator模块一个输出x1跟踪信号一个输出x2跟踪信号的微分注意给两个积分器设置合适的初始值一般设成被控对象初始输出值这样跟踪起点和实际输出一致不会产生启动冲击。将外部给定信号v与x1相减送入MATLAB Function模块的第一个输入。x2直接送入MATLAB Function的第二个输入。fhan的输出乘以仿真步长h作为第二个积分器的输入增量。第二个积分器输出x2再乘h加到x1的积分器上完成离散积分。简化写法是直接在一个MATLAB Function里把两步离散积分都写完输入只需要v和上一次的x1、x2输出x1_next和x2_next然后再用Unit Delay模块形成状态迭代。这种方式更适合做代码生成的同学谁用谁知道。2.3 r、h0 到底怎么定r越大跟踪越快但过头了过渡过程的“急加速”会让执行器压力变大还会放大噪声。h0是滤波因子比仿真步长h稍大一些能有效滤掉微分信号中的高频噪声。我的经验是先设r 50起步观察跟踪曲线再逐步增加h0一般取h的 2~10 倍比如仿真步长h 0.001时h0从0.005开始试如果对象响应本身比较慢r别超过对象可达速度的 2 倍否则会看到输出“追不上”的现象。自己的体会是TD 参数不会特别敏感它的设计自由度比 ESO 高得多所以千万别在这儿卡太久。3. ESO 搭起来不难难的是把 β 参数从经验值变成稳定值ESO 是整个 ADRC 中最值钱的部分。没有它TD 加 NLSEF 就是个非线性 PD有了它系统才有了“看见扰动并补偿掉”的能力。但 ESO 也是最容易让人弃坑的部分——β 一不对观测器输出就发散或者抖得像筛子。3.1 ESO 的 Simulink 实现方式ESO 的状态方程有三个我直接在MATLAB Function模块里写离散递推避免用连续积分器带来的代数环问题。代码结构如下function [z1_next, z2_next, z3_next] eso_update(z1, z2, z3, y, u, h, beta01, beta02, beta03, b0, alpha1, alpha2, delta) % fal 函数 fe fal(z1 - y, alpha1, delta); fe1 fal(z1 - y, alpha2, delta); z1_next z1 h * (z2 - beta01 * fe); z2_next z2 h * (z3 - beta02 * fe b0 * u); z3_next z3 - h * beta03 * fe1; end function f fal(x, alpha, delta) % 非线性函数 fal f (abs(x) delta) * abs(x)^alpha * sign(x) (abs(x) delta) * x / delta^(1 - alpha); end这里把z1、z2、z3作为状态用Unit Delay模块把上一次的估计值送回来。y接被控对象的测量输出u接你计算出的控制量。注意一点b0不是真实增益是你对被控对象增益的估计ESO把“实际增益与 b0 的差异”也一并算进总扰动里所以b0不需要精确匹配但不能差一个数量级。3.2 带宽法一秒估算 β01、β02、β03很多论文里给了 β 的公式但对新手来说不友好。这里上一个工程上最好用的方法——带宽法。ESO 的观测器增益可以按下面规律设定设定观测器带宽ωo一般取控制器预期带宽ωc的 3~10 倍β01 3 * ωoβ02 3 * ωo^2β03 ωo^3比如你预估系统闭环带宽在 20 rad/s 左右那ωo取 100 rad/s则 β01 300β02 30000β03 1000000。这个公式我一用就是好几年几乎没有失手。但实际仿真时ωo不是越大越好。带宽大了ESO 响应快扰动估计也准但会把测量噪声放大导致控制量高频率抖动甚至把仿真步长逼得非常小才能稳定。我的经验是先用带宽法算出初值然后往小调直到 z1 能紧贴 y 且 z2 不抖为止。3.3 为什么 z3 总是不收敛很多人遇到的现象是前两个状态 z1、z2 跟踪得很好但 z3扩张状态抖得离谱或者收敛不到真实扰动值。这个问题的原因有几种按概率排序b0 给错了。b0 偏小ESO 会把“真实增益和 b0 的差值”当作扰动的一部分z3 会明显偏离真实扰动b0 偏大补偿过头系统会震荡。非线性函数的 δ 取得太小。δ 太小fal在零点附近增益奇高微小的输出噪声就会被放大z3 跟着抖。把 δ 取在0.01 ~ 0.1之间问题会缓解很多。仿真步长太大。ESO 本身是一个高增益观测器步长大了很容易数值发散尤其是 β03 是 ωo 的三次方在 ωo 超过 200 后对步长非常敏感。建议先试试把步长缩小 10 倍如果 z3 变稳说明就是这个原因。我在实际项目里最喜欢的做法是先把 ESO 单独拎出来做开环测试。断开 TD 和 NLSEF或者干脆给固定控制量 u观察 z1、z2 是否能跟上实际输出比如一个正弦激励z3 是否稳定在一个合理范围。这个过程能帮你把观测器的内部问题先解决掉再去做闭环联调。这一步千万别跳。4. NLSEF 和控制量生成把非线性写进 MATLAB FunctionNLSEF 结构比 TD 和 ESO 简单但它决定了系统的“反馈性格”。有的人只改了 kp、kd 就感觉 ADRC 不过如此其实是非线性函数的几个参数还没吃透。4.1 控制律结构上面升一级的写法先看最基本的控制量生成function u nLsef(e1, e2, z3, b0, kp, kd, alpha1, alpha2, delta) u0 kp * fal(e1, alpha1, delta) kd * fal(e2, alpha2, delta); u u0 - z3 / b0; endfal函数和 ESO 里那个是同一个可以直接复用。e1 x1 - z1其中x1是 TD 生成的跟踪信号z1是 ESO 估计的系统输出e2 x2 - z2是跟踪速度和输出速度估计的误差。控制量的物理含义很清晰先把 PD 型控制量算出来再从里面减去z3 / b0把 ESO 看到的扰动直接抵消掉。这个结构直接决定了 ADRC 为什么对模型不精确不敏感——因为大多数模型误差都被 z3 兜住了。4.2 kp、kd、α1、α2、δ 的整定逻辑NLSEF 参数里kp、kd最像 PID但它俩不是直接对应比例和微分增益而是配合非线性函数工作的。kp决定主回路刚度。太小系统软绵绵误差收敛慢太大则会出现“哒哒哒”的抖振。kd是阻尼用来压住超调。调大 kd 能迅速稳定过渡过程但太大相当于给系统加了很强的“刹车”响应会变慢。α1一般取0.5到0.75α2取1.25到1.5。α1小于 1 意味着误差小时增益大误差大时增益被压住α2大于 1 则让微分误差在小误差区较小避免微小的抖动引起输出乱跳。δ是线性区间宽度。它设定了“误差小到什么程度就按线性处理”一般取0.01 ~ 0.1。取太大会失去非线性优势取太小则容易抖动。一个实用的整定出口是先把 α1、α2、δ 固定为常见值比如0.75、1.25、0.01然后只动 kp、kd等系统响应形态满意了再微调非线性参数。不要上来就七个参数一起调那等于给自己挖坑。5. 一次完整调参实录从被控对象到全套参数表理论讲完了下面来一段完整的实战。我会选一个典型二阶对象走一遍完整的调参流程最终给出一份可直接复用的参数表。5.1 被控对象选型与仿真环境搭建我选的被控对象是一个带延迟的直流电机转速模型等效传递函数写为G(s) 400 / (s^2 50s)这个对象有惯性和阻尼比较接近工程常见的转速环。仿真用定步长h 0.001求解器选ode4四阶龙格库塔整个 Simulink 模型大概结构是TD 接收阶跃给定输出x1、x2ESO 接收对象输出y和控制量u输出z1、z2、z3NLSEF 把误差组合成u并叠加扰动补偿。5.2 调参顺序先 TD再 ESO最后 NLSEF我调参从来不是上来就全接上而是分三步走第一步把对象输出反馈直接接到一个 PID 控制器上或者干脆开环先弄清楚对象本身的响应快慢和增益量级。算出稳态增益大概是400/50 8时间常数约0.04s。这说明对象响应相当快适合把 TD 的r定在 100 到 200 之间。第二步接上 TD单独观察x1是否平滑无超调地跟踪阶跃x2是否光滑。如果有高频毛刺加大h0。第三步断开 TD 到 NLSEF 的误差通路把 ESO 的z1接到跟踪信号x1上给 ESO 一个电机的实测输出和真实控制量跑一段匀速工况观察z3是否收敛到一个常数。这一步能确认 ESO 没有发散、z3 的估计值符合物理直觉。第四步把环路全部闭合用带宽法给出 β 初值再逐项微调。5.3 完整参数表照着填就能跑下面这份参数表是以h 0.001、定步长为前提的如果你用的是变步长请把 TD 和 ESO 的离散积分改为连续积分器或者保持定步长仿真否则参数意义会漂移。TD 参数表参数推荐值说明r120跟踪快慢对象响应时间约 0.04s 时设为 100~200 均可h00.005滤波因子为仿真步长的 5 倍x1 初值0与对象输出初值一致x2 初值0微分初值一般设 0ESO 参数表参数推荐值说明ωo150观测器带宽先设 3~10 倍控制器带宽β014503 * ωoβ02675003 * ωo^2β033375000ωo^3b04对象增益估计值真实增益约 8这里故意取一半α1ESO0.5非线性函数指数α2ESO0.25总扰动状态的非线性指数δESO0.01线性区间宽度窄和 ESO 里的 α 取值可以不同别被我上面的参数表固定住思维。NLSEF 参数表参数推荐值说明kp300主反馈增益从 50 开始加kd40阻尼增益先给小值再加α1NLSEF0.75误差非线性指数α2NLSEF1.25误差微分非线性指数δNLSEF0.01线性区间宽度合起来之后阶跃给定从 0 跳到 1系统的超调量大约在 3% 以内调节时间约 0.1s。这个表现已经比同环境下调好的 PID 更干净更重要的是我把对象增益从 8 变成 12 或者从 8 变成 6ADRC 依然能维持相近的动态性能这就是 z3 补偿在起作用。如果仿真中出现了控制量饱和问题记得在u输出端加一个Saturation模块并且把饱和后的实际控制量再送进 ESO 和对象否则 ESO 估计的总扰动会出现偏差因为 ESO 内部用的是“理想控制量”而对象真实接收的却是限幅后的值。6. 我在 ADRC 仿真里踩过的五个坑你大概率也会踩这部分不写不知道写下来全是泪。下面五个坑我都在项目里真实遇到过每一个都花了不少时间去排查现在整理出来希望能帮你绕开。6.1 用了 Derivative 模块提取信号全盘皆输有人为了省事直接在 Simulink 里用Derivative模块对测量输出求导得到速度信号再喂给 ESO 或直接用。这是大忌。实际测量信号一定有噪声Derivative会把噪声放大到不可用的程度。正确的做法是把速度交给 ESO 去估计也就是用z2而不是对 y 求导。ESO 的设计初衷就是让你不碰导数。6.2 定步长和变步长混用参数跟着漂我在同一套模型上先用变步长调好了参数换成定步长后系统直接发散。原因是 TD、ESO 的离散迭代方程里带步长h如果求解器实际步长和代码里写的h不一致整个模型等于在算另一套系统。建议从一开始就定步长仿真把所有离散模块的h设成同一个值别让 Simulink 自动变步长。6.3 饱和和限幅的位置放错控制量输出常常要限幅但限幅之后不能只把限幅前的值送给执行器而要把限幅后的值同时反馈给被控对象和 ESO。很多人只在执行器那边加了个SaturationESO 看到的还是限幅前的u结果 z3 疯狂往上飘因为它把“执行器没执行的那部分”也当成了扰动。这个细节最容易让人怀疑 ESO 是不是坏了。6.4 α 参数乱改非线性变成非稳定fal函数里的 α 不是随手填的。α 小于 1 的部分会让误差很小时增益很大如果 α 太小比如 0.1系统容易产生高频自激。α 大于 1 的部分又会让大误差时增益过大导致发散。建议先使用上面参数表里的推荐值等整个系统跑顺了再小幅调整每次不超过 0.1。6.5 b0 的估算偏差太大扰动补偿形同虚设b0是唯一一个需要你对被控对象“有点感觉”的参数。你不需要精确知道它但方向不能错。比如对象稳态增益是 8你把 b0 设成 800ESO 会把“实际对象远没有你想象的那么灵敏”当成巨大的负扰动系统输出会瘫软无力反过来 b0 设成 0.01ESO 会认为对象灵敏度太低拼命补偿结果就是震荡外加控制量饱和。我一般用“对象稳态增益 / 对象时间常数”粗略估算 b0 的初值。刚接触 ADRC 的同行往往会花很多时间研究非线性函数的细节实际上工程调参最重要的还是那个铁三角TD 的 r、ESO 的带宽对应 β、NLSEF 的 kp/kd。把这几个抓住了剩下的无非是在这个骨架上润色。调参过程中记录每一次参数组合下的阶跃响应曲线多攒几组你会慢慢建立起直觉——哪些地方该加带宽哪些地方该加阻尼一眼就能看出来。我如今回头再看当初被 ADRC 公式劝退的日子最大的感触是它没那么神但也确实没那么难。搭好模型按顺序调参你会很快收获一条比 PID 更沉着的阶跃曲线。