1. 从一个让人头疼的温控场景说起
去年帮一个做小型热处理炉的朋友调温控,炉子不大,加热功率3kW,热电偶反馈,原本用的是经典PID。问题出在升温段:设定值从室温直接跳到600℃,PID输出瞬间饱和,超调能冲到80℃以上,等它慢慢回落再稳定下来,一批工件的时间窗口已经错过了。更麻烦的是,炉门偶尔要开一下放料,一开一关,温度掉个几十度,PID重新追上去又是一次振荡。
这个场景其实很典型:被控对象有惯性、有延迟、有外部扰动,而且设定值还会大范围跳变。经典PID在这种工况下不是不能用,而是需要反复折中——调大比例带响应慢,调小比例带超调大,积分时间短了振荡,长了消除静差又慢。工程师大部分时间花在“拆东墙补西墙”上。
自抗扰控制(ADRC,Active Disturbance Rejection Control)就是冲着这类问题来的。它的核心思路跟PID完全不同:不去精确建模被控对象,而是把对象内部所有说不清、道不明的动态,加上外部扰动,统统打包成一个“总扰动”,然后用扩张状态观测器(ESO)实时估计它,再在控制量里把它抵消掉。剩下的部分,就变成一个近似积分串联型的简单系统,用最简单的比例控制就能搞定。
我第一次接触ADRC是看韩京清先生的那篇《从PID技术到“自抗扰控制”技术》,当时觉得这套东西有点“反直觉”——不建模反而能控好?后来在几个电机调速和温控项目里实际用下来,发现它在抗扰、解耦、设定值跟踪这三个维度上的表现确实比调好的PID更省心。当然,它也不是银弹,参数多了、整定有门槛、工程化落地需要一些技巧。
这篇内容就围绕ADRC这套算法,从设计思路、核心模块拆解、参数整定、实操实现到常见坑,完整讲一遍。适合已经用过PID、想找一个更抗扰方案的控制工程师,也适合刚学ADRC、被那一堆公式绕晕的学生。我会尽量用生活化的类比把原理说清楚,同时给出可以直接抄的代码和参数整定流程。
2. ADRC整体设计思路与方案选型
2.1 为什么放弃“精确建模”这条路
传统控制里,如果你要控好一个对象,第一步通常是建模:机理建模或者系统辨识,拿到传递函数或者状态空间。然后基于模型设计控制器,比如极点配置、LQR、内模控制。这套流程在对象清楚、扰动可控的场合非常有效。
但工程现场往往不是这样。我做过一个伺服平台,负载会换、摩擦会变、传动间隙随磨损增大,你花两周辨识出来的模型,过一个月就不准了。这时候基于模型设计的控制器性能会明显下降,甚至不稳定。
ADRC的哲学是:与其花大力气去追一个永远追不准的模型,不如承认“我不知道的部分”存在,然后实时估计并补偿它。这个“不知道的部分”就是总扰动,包括:
- 对象内部未建模动态(比如高阶模态、非线性摩擦)
- 参数摄动(质量变化、电阻随温度变化)
- 外部扰动(负载突变、风扰、电网波动)
- 通道间耦合(多变量系统里其他回路对本回路的影响)
把这些全部归入一个扩张状态,用观测器估计出来,控制量里减掉它,系统就“净化”了。这个思路的好处是:控制器设计不再依赖精确模型,鲁棒性天然就强。
2.2 ADRC与PID的本质区别
很多人第一次看ADRC会觉得它就是“PID加了个观测器”。这个说法对了一半。区别在于:
| 维度 | PID | ADRC |
|---|---|---|
| 误差处理 | 直接对误差做比例、积分、微分 | 用跟踪微分器安排过渡过程,误差反馈用非线性或线性组合 |
| 扰动处理 | 积分项被动消除静差,响应慢 | ESO主动估计并前馈补偿,响应快 |
| 微分信号 | 直接差分,噪声放大严重 | 用TD或ESO提取,噪声抑制好 |
| 模型依赖 | 不依赖模型,但调参靠试 | 不依赖精确模型,但需要知道系统阶数和大致增益 |
| 超调抑制 | 靠调参折中 | TD安排过渡过程,从源头减少超调 |
最关键的一点:PID的积分项是在误差出现之后才起作用,属于“事后补救”;ADRC的ESO是在扰动影响输出之前就估计出来,属于“事前补偿”。这个时间差,在快速性要求高的场合就是性能差距的来源。
2.3 ADRC的典型结构选型
标准ADRC由三部分组成:
- 跟踪微分器(TD):为设定值安排过渡过程,提取微分信号
- 扩张状态观测器(ESO):估计系统状态和总扰动
- 非线性状态误差反馈(NLSEF):根据误差和扰动估计生成控制量
工程上常见的几种变体:
- 线性ADRC(LADRC):把TD和NLSEF都线性化,只保留ESO的扩张状态思想。参数少、整定有解析公式、频域分析方便,是目前工业落地最多的形式。
- 非线性ADRC:保留fal函数等非线性环节,性能潜力更大,但参数多、整定靠经验。
- 降阶ADRC:针对一阶对象,省掉TD,直接用一阶ESO加比例控制,结构极简。
- 模型辅助ADRC:如果知道对象的大致增益b0,把它放进控制量里,ESO只需要估计“剩余扰动”,估计压力小,性能更好。
我个人的建议:新手从一阶LADRC入手,把ESO和b0这两个概念吃透,再往上加TD和非线性环节。一阶LADRC能解决大部分温控、流量、压力这类一阶惯性对象的问题,代码不到50行,参数只有三个(观测器带宽wo、控制器带宽wc、增益b0),整定有公式可循。
3. 核心模块拆解与实操要点
3.1 跟踪微分器:给设定值“踩刹车”
设定值跳变是超调的主要来源之一。你让一个惯性系统瞬间追上一个大阶跃,它必然冲过头。TD的作用就是把阶跃信号变成一条平滑的过渡曲线,同时给出这条曲线的微分。
生活类比:你开车要停到车位,如果一脚刹车踩死,车会点头、乘客不舒服;老司机是提前松油门、轻踩刹车,让车平缓滑进去。TD就是那个“老司机”。
最常用的TD形式是韩京清提出的非线性跟踪微分器:
x1(k+1) = x1(k) + h*x2(k) x2(k+1) = x2(k) + h*fhan(x1(k)-v(k), x2(k), r, h0)其中v是设定值,x1跟踪v,x2是v的微分估计,r是速度因子(越大跟踪越快),h0是滤波因子(越大滤波越强)。
实操要点:
- r的选取:r越大,过渡过程越快,但太大就失去“安排过渡”的意义。一般取r = 4~10倍的控制带宽对应的值。我通常先取r=100试,看过渡时间是否合适再调。
- h0的选取:h0一般取采样周期的5~20倍。太小滤波不够,微分噪声大;太大过渡过程变慢。
- 离散实现:fhan函数里有符号函数和饱和判断,离散实现时要注意避免除零和数值抖动。
如果你用LADRC,TD可以简化成线性形式,甚至直接用一个一阶惯性环节代替:
x1(k+1) = x1(k) + h*(v - x1(k))/TT是过渡时间常数。这个形式更简单,参数物理意义明确,我实际用下来在温控场合完全够用。
注意:TD不是必须的。如果你的设定值本身就是缓变的,或者你对超调不敏感,可以省掉TD,直接把设定值送进误差反馈。省掉TD后ADRC就退化成“ESO+比例控制”,结构更简单。
3.2 扩张状态观测器:ADRC的心脏
ESO是整个ADRC最核心、也最值得花时间理解的部分。它的任务是:根据控制量u和输出y,估计出系统的状态x1、x2,以及扩张状态x3(总扰动)。
以二阶对象为例,原系统可以写成:
x1' = x2 x2' = f(x1,x2,w) + b0*u y = x1其中f是总扰动,b0是控制增益的估计值。把f扩张成状态x3,得到:
x1' = x2 x2' = x3 + b0*u x3' = h y = x1然后设计线性ESO:
e = z1 - y z1' = z2 - beta1*e z2' = z3 - beta2*e + b0*u z3' = -beta3*ez1、z2、z3分别是x1、x2、x3的估计。beta1、beta2、beta3是观测器增益。
参数整定有解析公式:把ESO的特征方程配置成(s+wo)^3,得到:
beta1 = 3*wo beta2 = 3*wo^2 beta3 = wo^3wo是观测器带宽,物理意义是“ESO跟踪扰动的速度”。wo越大,估计越快,但对噪声越敏感。工程上一般取wo = 3~10倍的控制带宽wc。
实操要点:
- b0的选取:b0是控制增益的估计值,不需要精确,但数量级要对。比如你的对象是G(s)=K/(Ts+1),b0大约取K/T。如果b0取大了,控制量偏小,响应慢;取小了,控制量偏大,容易振荡。我一般先估算一个值,然后在这个值上下调2~3倍看效果。
- 离散实现:用欧拉法离散时,采样周期h要足够小,一般h*wo < 0.1,否则离散化误差会导致ESO性能下降甚至发散。
- 噪声处理:如果输出噪声大,可以在ESO前面加一个低通滤波器,或者降低wo。我做过一个流量控制,传感器噪声很大,wo从50降到15才稳定下来。
提示:ESO的估计能力是有限的。如果总扰动变化太快,超过wo的带宽,ESO就跟不上。这时候要么提高wo(但受噪声限制),要么用模型辅助ADRC,把已知部分前馈掉,减轻ESO负担。
3.3 状态误差反馈:把估计的扰动“抵消掉”
有了ESO的估计,控制量就很好设计了。以二阶LADRC为例:
u0 = kp*(v - z1) - kd*z2 u = (u0 - z3)/b0其中v是设定值(或TD的输出),kp、kd是控制器增益。把u代入系统方程,如果z3估计准确,那么:
x2' = f + b0*u = f + u0 - z3 ≈ u0系统就变成了一个双积分串联型:x1'=x2, x2'=u0。对这个简单系统,用PD控制就能任意配置极点。
参数整定:把闭环特征方程配置成(s+wc)^2,得到:
kp = wc^2 kd = 2*wcwc是控制器带宽,决定响应速度。wc越大响应越快,但控制量越大,对执行器要求越高。
实操要点:
- wc和wo的关系:一般wo = 3~10倍wc。wo太小,扰动估计跟不上,补偿效果差;wo太大,噪声放大。我通常先定wc(根据响应时间要求),然后wo取5倍wc试。
- 控制量限幅:ADRC的输出u一定要限幅,否则在设定值跳变或大扰动时,u可能超出执行器范围,导致积分饱和或执行器损坏。限幅值根据实际执行器能力定。
- 抗积分饱和:LADRC本身没有积分项,但ESO的z3估计在限幅期间会累积误差。可以在限幅时暂停ESO更新,或者用条件积分法处理。
3.4 一阶LADRC:最简可落地形式
很多对象(温控、流量、压力)可以近似成一阶惯性:
y' = f + b0*u这时候ADRC可以简化成:
e = z1 - y z1' = z2 - beta1*e + b0*u z2' = -beta2*e u = (kp*(v - z1) - z2)/b0参数只有三个:wo、wc、b0。整定公式:
beta1 = 2*wo beta2 = wo^2 kp = wc这个形式代码极短,我下面给一个C语言实现,可以直接移植到单片机或PLC里。
typedef struct { float wo; // 观测器带宽 float wc; // 控制器带宽 float b0; // 控制增益估计 float z1; // 状态估计 float z2; // 扰动估计 float h; // 采样周期 float u_max; // 输出限幅 float u_min; } LADRC1; float ladrc1_update(LADRC1 *adrc, float v, float y) { float e = adrc->z1 - y; float beta1 = 2 * adrc->wo; float beta2 = adrc->wo * adrc->wo; // ESO更新 adrc->z1 += adrc->h * (adrc->z2 - beta1 * e + adrc->b0 * adrc->u_last); adrc->z2 += adrc->h * (-beta2 * e); // 控制量计算 float u0 = adrc->wc * (v - adrc->z1); float u = (u0 - adrc->z2) / adrc->b0; // 限幅 if (u > adrc->u_max) u = adrc->u_max; if (u < adrc->u_min) u = adrc->u_min; adrc->u_last = u; return u; }这段代码里有个细节:ESO更新用的是上一拍的u_last,而不是当前拍算出来的u。这是离散实现的标准做法,保证因果性。如果你用当前拍的u,会引入代数环,可能导致数值问题。
4. 完整实操流程与参数整定实录
4.1 对象分析与阶数确定
拿到一个对象,第一步不是写代码,而是确定用几阶ADRC。方法很简单:
- 给对象一个阶跃输入,看输出响应。如果输出单调上升、没有明显过冲和振荡,近似一阶,用一阶ADRC。
- 如果输出有S形、或者有振荡,近似二阶,用二阶ADRC。
- 如果对象有纯延迟,把延迟时间折算成等效惯性时间,或者用Smith预估器补偿后再上ADRC。
我那个热处理炉的例子,阶跃响应是单调上升的,时间常数大约300秒,纯延迟约20秒。延迟相对时间常数很小,直接按一阶处理,b0取加热功率除以时间常数对应的增益。
4.2 b0的估算方法
b0是ADRC里唯一需要“知道一点对象信息”的参数。估算方法有三种:
- 机理法:如果知道对象物理模型,直接算。比如温控炉,b0 = P/(C*T),P是加热功率,C是热容,T是时间常数。
- 阶跃法:给一个已知阶跃u_step,测输出稳态变化y_step,b0 ≈ y_step/(u_step*T)。T是时间常数。
- 试凑法:先取一个数量级差不多的值,然后看控制效果。b0偏大,响应慢、控制量小;b0偏小,响应快、控制量大、容易振荡。
我一般用阶跃法,简单可靠。实测下来,b0允许有2~3倍的误差,ADRC都能稳住,这也是它鲁棒性的体现。
4.3 带宽整定流程
参数整定我总结了一个“三步法”:
第一步:定wc。根据你对响应时间的要求。如果希望闭环响应时间约等于对象时间常数的1/5,那么wc ≈ 5/T。比如T=300秒,wc ≈ 0.017 rad/s。实际调试时,先取一个保守值,然后逐步加大,直到响应速度满意但控制量不超限。
第二步:定wo。wo = 5~10倍wc。先取5倍,看扰动抑制效果。如果负载突变后恢复慢,加大wo;如果控制量抖动明显,减小wo。
第三步:微调b0。b0主要影响控制量的幅值。如果控制量长期饱和,说明b0偏小,适当加大;如果控制量很小但响应慢,说明b0偏大,适当减小。
下面是我在热处理炉上的一组实际参数:
| 参数 | 数值 | 说明 |
|---|---|---|
| 采样周期h | 1s | 温控慢,1秒足够 |
| wc | 0.02 rad/s | 响应时间约50s |
| wo | 0.1 rad/s | 5倍wc |
| b0 | 0.8 ℃/(s·%) | 阶跃法估算 |
| u限幅 | 0~100% | 加热器全开全关 |
实测效果:设定值从室温跳到600℃,超调小于5℃,稳定时间约120秒。开炉门扰动后,温度掉30℃,约40秒恢复。对比之前PID的超调80℃、恢复2分钟,提升明显。
4.4 离散实现与代码移植
ADRC的离散实现有几个坑:
- 采样周期选择:h要满足h*wo < 0.1,否则ESO离散化误差大。如果wo=0.1,h<1秒,我取1秒刚好。如果wo更大,h要相应减小。
- 数据类型:单片机上用float,PLC上用REAL。注意z3可能累积到较大值,要防止溢出。
- 初始化:z1初始化为当前输出y,z2初始化为0,u_last初始化为0。这样启动时不会有大跳变。
- 抗饱和:限幅时,ESO的更新可以暂停,或者把限幅前后的差值反馈回ESO。我用的是简单暂停法,效果可以。
移植到PLC的ST语言,结构类似,只是语法不同。关键是把ESO的差分方程写对,注意运算顺序。
4.5 与PID的对比测试
为了验证ADRC的效果,我在同一个炉子上做了对比测试:
| 指标 | PID(调好的) | 一阶LADRC |
|---|---|---|
| 超调量 | 15% | 4% |
| 稳定时间 | 180s | 120s |
| 开炉门恢复时间 | 120s | 40s |
| 参数个数 | 3 | 3 |
| 调参时间 | 2天 | 半天 |
PID的参数是花了2天反复折中调出来的,ADRC半天就整定好了。这个对比不是说PID不好,而是说在抗扰和设定值跟踪兼顾这个需求下,ADRC的调参负担更小。
5. 常见问题与排查技巧实录
5.1 ESO估计发散怎么办
这是新手最常遇到的问题。现象是z1、z2、z3越来越大,控制量饱和,系统失控。原因通常有三个:
- h*wo太大:离散化不稳定。解决:减小h或减小wo。
- b0符号搞反:如果b0应该是正,你写成负,ESO会往错误方向估计。解决:检查b0符号,确保u增大时y增大。
- 输出反馈极性错误:y的符号或量纲不对。解决:检查传感器标定和接线。
排查顺序:先查b0符号,再查h*wo,最后查传感器。
5.2 控制量抖动严重怎么处理
抖动通常来自噪声。ESO的z3对噪声最敏感,因为它是通过两次积分得到的。处理方法:
- 降低wo,牺牲一点扰动估计速度换平稳。
- 在y进入ESO之前加一阶低通滤波,截止频率取wo的2~5倍。
- 用非线性ESO,fal函数对小误差有线性区,能抑制噪声。
我一般先加低通滤波,简单有效。滤波会引入相位滞后,但温控这种慢过程影响不大。
5.3 设定值跟踪有静差
LADRC理论上没有积分项,但ESO的z3估计包含了稳态扰动,补偿后静差应该为零。如果有静差,检查:
- z3是否收敛到了正确的扰动值。如果z3一直在变,说明wo太小,估计不准。
- b0是否准确。b0偏差会导致控制量标定错误,产生静差。
- 限幅是否长期生效。如果u长期饱和,系统处于开环,静差无法消除。
5.4 多变量耦合怎么处理
ADRC天然有解耦能力,因为每个回路的ESO会把其他回路的影响当成扰动估计并补偿。但前提是每个回路的wo足够大,能跟上耦合扰动的变化。如果耦合很强,wo需要取很大,可能受噪声限制。这时候可以考虑:
- 用模型辅助ADRC,把已知的耦合项前馈掉。
- 用分散ADRC加解耦补偿器。
- 如果耦合特别强,考虑多变量ADRC,但参数整定复杂很多。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| ESO发散 | h*wo太大 | 检查h和wo乘积 | 减小h或wo |
| ESO发散 | b0符号错误 | 检查b0正负 | 修正符号 |
| 控制量抖动 | 噪声大 | 观察y的噪声 | 加低通滤波或降wo |
| 响应慢 | wc太小 | 计算响应时间 | 加大wc |
| 超调大 | 无TD或wc太大 | 观察设定值跳变响应 | 加TD或减小wc |
| 静差 | z3未收敛 | 观察z3波形 | 加大wo或修正b0 |
| 控制量饱和 | b0偏小 | 观察u幅值 | 加大b0 |
| 恢复慢 | wo太小 | 加扰动测试 | 加大wo |
5.6 几个独家避坑技巧
技巧一:先开环测b0,再闭环调参。不要一上来就闭环,先用开环阶跃测出对象增益和时间常数,算出b0,这样闭环调试起点就准了。
技巧二:wo从大到小调。先取一个较大的wo,看ESO估计是否快速跟上,然后逐步减小到噪声可接受的程度。反过来调容易一开始就被噪声干扰。
技巧三:用z3的波形诊断。z3是总扰动的估计,正常情况下应该平稳。如果z3大幅波动,说明wo太大或噪声太大;如果z3长期偏离零,说明有稳态扰动未补偿。
技巧四:限幅值留余量。u限幅不要设到执行器极限,留10%~20%余量,给扰动补偿留空间。
技巧五:采样周期不要随意改。ADRC的参数和h是绑定的,改了h,wo和wc都要重新调。如果必须改h,按比例调整wo和wc。
6. 从一阶到高阶的扩展思路
一阶LADRC能解决大部分慢过程的问题,但如果你遇到的是伺服、无人机、机器人这类快速对象,一阶就不够了。这时候需要二阶甚至高阶ADRC。
二阶ADRC的结构在前面已经讲过,参数整定公式是:
beta1 = 3*wo beta2 = 3*wo^2 beta3 = wo^3 kp = wc^2 kd = 2*wc参数多了一个kd,但整定逻辑和一阶一样:先定wc,再定wo,最后微调b0。
高阶ADRC(三阶以上)一般不建议用,因为参数多、整定难、对噪声敏感。如果对象确实是高阶,更好的做法是用降阶ADRC加一个前置滤波器,或者用模型辅助ADRC把已知的高阶动态前馈掉。
我在一个伺服转台项目上用过二阶LADRC,对象是电机加负载,有弹性连接,近似二阶。wc取50 rad/s,wo取300 rad/s,b0取电机转矩常数除以转动惯量。效果比PID好,尤其是在负载变化时,PID需要重新调参,ADRC不用。
7. 工程落地的一些个人体会
ADRC这套东西,理论文章很多,但工程落地的细节往往没人讲。我踩过的坑里,最大的一个是把b0当成“必须精确”的参数。早期我花大量时间辨识b0,后来发现b0差2~3倍根本不影响稳定性,只是控制量幅值有变化。ADRC的鲁棒性就体现在这里:它不靠精确模型,靠的是ESO的实时估计。
第二个坑是wo取得太大。理论上wo越大估计越快,但实际系统里噪声是无处不在的。wo大到一定程度,z3就被噪声淹没,控制量抖得没法用。我现在的习惯是:先取一个理论值,然后砍一半,看效果,再慢慢加。
第三个坑是忽略执行器动态。ADRC算出来的u是理想控制量,但执行器(阀门、加热器、电机驱动器)本身有延迟和饱和。如果执行器响应比ADRC带宽还慢,整个闭环性能就被执行器限制了。这时候要么降低wc,要么换更快的执行器。
最后分享一个小技巧:用Simulink先仿真再上实物。ADRC的代码不长,但在实物上调试风险高。我一般先在Simulink里搭一个对象模型,把ADRC代码用MATLAB Function块实现,调好参数再移植到嵌入式。这样能省很多现场调试时间。
这个内容后续还可以往几个方向扩展:一是自适应ADRC,让wo和wc根据工况自动调整;二是ADRC和MPC的结合,用ADRC做底层抗扰,MPC做上层优化;三是多变量ADRC的解耦设计。这些我还在摸索,有新的心得再分享。