搞自控这些年,PID几乎是所有人的“第一口奶”。但做工程做得越久,越会发现PID在对付强耦合、大惯量、参数时变、外部扰动明显的对象时,调参调到头秃,还是压不住超调、稳不住精度、扛不住扰动。大概十年前我接触到韩京清先生提出的自抗扰控制(ADRC),那种“原来扰动还能这样处理”的感觉至今记得。这篇内容就围绕ADRC的核心构成、算法原理和实际案例,把我从理论到仿真再到样机调试的完整套路拆给大家看。无论你是刚接触ADRC的研究生,还是已经在现场被PID折磨多年的工程师,这篇文章都能给你一条清晰可落地的路径。
ADRC全称Active Disturbance Rejection Control,直译是“主动扰动抑制控制”。它厉害的地方在于不依赖精确的被控对象数学模型,而是把系统内部的未建模动态和外部扰动统一看成“总扰动”,然后通过扩张状态观测器(ESO)实时估计并补偿掉。这个思想在工程上极有价值——我们搞控制的人真正怕的不是已知的非线性,而是“不知道还有什么力在作用”的系统。ADRC用一套通用框架解决这个问题,从数学模型到仿真代码到实际电机/温控/飞控场景都能复用。
1. 内容整体设计与思路拆解
1.1 从PID痛点说起,为什么ADRC能“另辟蹊径”
传统PID基于“误差消除误差”的思想。你去调节一个系统,看到偏差就加大比例,看到偏差变化率就加微分,看到稳态误差就加积分。这套逻辑在单输入单输出、线性时不变、扰动缓慢的场景下非常顺手,可一旦换到强非线性、强耦合、负载突变的环境,PID就暴露两个先天问题。
第一,PID的误差信号直接来自目标值和反馈值,但目标值突变的时候,误差突然变大,比例项会猛推执行机构,造成大幅超调。现场调试最典型的画面就是设定值一阶跃,电机转速“呜”的一声冲过头,然后再慢慢回调。为了压超调只能牺牲响应速度,控制器的比例项越调越小,结果外部扰动一来又压不住了。
第二,PID的积分项靠时间累积消除稳态误差,但积分累积过快会引起积分饱和,导致系统振荡。尤其是在存在执行机构限幅时,积分项一直堆,等到误差反向时根本退不出来。你会在现场看到那种“均匀呼吸式”的振荡,频率不高但一直存在,就是积分在作怪。
ADRC的逻辑完全不同。它不直接对阵目标值变化,而是先利用跟踪微分器(TD)对设定值安排一个平滑过渡过程,让被控量有时间跟上指令变化而不产生激进的冲击。同时,ESO把系统内部非线性、参数不确定性、外部负载变化全部打包成“总扰动”,在控制量计算时反向抵消。这个框架本质上是在告诉控制器:你不要猜测扰动是什么,我观测出来,然后在控制量里直接减掉。
1.2 ADRC三大核心构成:TD、ESO、NLSEF的整体分工
韩京清先生提出的ADRC经典结构包含三个环节:跟踪微分器、扩张状态观测器、非线性状态误差反馈控制律。有些工程简化版可以去掉TD或者用线性组合替代NLSEF,但要想完整理解ADRC的体系,必须从这三个环节入手。
跟踪微分器解决“目标值突变带来的输出冲击”问题。它接收设定值,输出两个信号:跟踪信号v1(平滑后的设定值)和近似微分v2(设定值的变化率)。即便设定值是一个阶跃信号,经过TD之后得到的v1也是一个连续可导的过渡过程,而v2则是这个过渡过程的导数。
扩张状态观测器是整个ADRC最核心的模块,也是我下面会重点拆解的内容。它利用被控对象的输入量和输出量,实时估计出系统的各个状态,并额外扩张出一个状态来表示“总扰动”。ESO的厉害之处在于它不区分模型误差和外部扰动,而是统一处理。你不需要精确建模,只需要知道系统的大致增益b0,再把模型误差和外部扰动全归入总扰动。
非线性状态误差反馈控制律则负责最终的控制决策。它把TD输出和ESO估计值之间的误差进行非线性组合,生成初始控制量u0,再减去总扰动补偿量,得到最终控制量。这里的非线性组合函数通常采用韩京清提出的fal函数,也可以用线性形式简化,差别主要体现在响应速度和抗扰动性能上。
这三个环节的衔接关系可以这样理解:TD帮你“铺路”,让设定值变化不那么突然;ESO帮你“看清战场”,把总扰动识别出来;NLSEF帮你“做决策”,在已知扰动的基础上精准操控执行机构。三者配合,形成一个不依赖精确模型的鲁棒控制框架。
2. 核心细节解析与实操要点
2.1 跟踪微分器原理与fhan函数的秘密
跟踪微分器解决的是工程中“差分放大噪声”的经典难题。很多控制系统需要信号的微分项,但直接对测量信号做差分,噪声会被急剧放大。比如转速信号叠加了几十毫伏的噪声,差分后可能变成几伏的尖峰脉冲,控制器根本没法用。
韩京清提出的TD本质上是一个二阶最速控制系统。它利用最速综合函数fhan来构造,让跟踪信号能以最快的速度跟上设定值,同时又没有超调。fhan函数的表达式包含目标值和跟踪值之间的误差、跟踪速度以及线性区间宽度等参数,具体形式是:
fs = -r * (a / (abs(a) + δ)) 近似形式完整的fhan函数需要计算两组临时变量,核心逻辑是根据误差和误差变化率判断当前处于什么“状态”,然后给出最速控制量。这个函数的本质是求解二阶积分器的最速控制问题,让系统能在有限时间内到达目标且不产生超调。
在工程实现上,TD的采样周期需要和主控制周期一致。r参数越大,跟踪速度越快,但也会要求执行机构有更强的快速响应能力。r太小,系统响应会过于迟钝;r太大,TD输出的过渡过程接近阶跃,失去平滑意义。一般先按系统执行机构能承受的最大速度整定,再根据仿真或现场表现微调。
我最初在电机调速系统里用TD时,设置r=600,过渡时间大约0.1秒,效果很好。后来又换到温控系统,惯性很大,r只能设到10左右。这提醒大家:TD的时间尺度必须和被控对象的动态时间尺度匹配,不能一套参数走天下。
2.2 扩张状态观测器ESO的核心机理与公式拆解
ESO是整个ADRC的灵魂。理解ESO要从“扩张状态”这四个字说起。一个二阶被控对象,通常有两个状态:位置和速度。ESO除了估计这两个状态外,还额外扩张一个状态,用来估计系统的总扰动。这样一来,观测器从二阶变成了三阶,多出来的那个状态就是“扩张状态”。
以一个典型的二阶系统为例,被控对象可以写成:
x1_dot = x2 x2_dot = f(x1, x2, w(t)) + b0 * u y = x1其中f(x1, x2, w(t))表示系统内部非线性、参数不确定性、外部扰动w(t)的总和,也就是我们要估计的“总扰动”。b0是控制增益的估计值。ESO的形式为:
z1_dot = z2 + β1 * (y - z1) z2_dot = z3 + β2 * (y - z1) + b0 * u z3_dot = β3 * (y - z1)三个观测器状态分别估计:z1估计输出y,z2估计x2,z3估计总扰动f。这里的β1、β2、β3是观测器增益。观测器的核心逻辑是:利用输出误差y - z1来修正各个状态的估计值,而z3在观测误差收敛后,就会趋近于系统的总扰动。
数学上,只要观测器增益配置得当,z3就能在有限时间内收敛到总扰动的真实值。这个收敛过程不需要知道f的具体表达式,也就是说你不需要建模扰动,只要观测器带宽足够,它就能“看见”扰动。
关于增益整定的经典做法是高志强老师推广的带宽法。令观测器特征方程配置到理想极点形式:
s³ + β1s² + β2s + β3 = (s + ω₀)³展开后得到β1 = 3ω₀,β2 = 3ω₀²,β3 = ω₀³。ω₀就是观测器带宽。这个方法的工程意义极大,你只需要整定一个参数ω₀,就能确定整个ESO的三个增益,大幅降低了调参难度。
ω₀越大,观测器收敛越快,估计精度越高,但不能无限增大。因为ω₀增大意味着β值增大,观测器对测量噪声的放大也越厉害。实际工程中一般取系统控制带宽的3到10倍。样机测试时比PID调参更直观的趋势是:ω₀太小系统动态响应慢、抗扰动差,ω₀太大会出现高频噪声,甚至导致执行机构剧烈抖动。
2.3 控制律设计:线性 vs 非线性,怎么选
早期ADRC教材里最经典的是非线性状态误差反馈控制律,误差经过fal函数处理后再进行比例和微分组合。fal函数的表达式是:
fal(e, α, δ) = e / δ^(1-α) 当|e| ≤ δ fal(e, α, δ) = |e|^α * sign(e) 当|e| > δfal的思想是:当误差较大时,用小于1的指数α(如0.5)得到一个“亚线性”增益,避免控制量过大;误差较小时,线性区间δ保证增益平滑,不会产生抖振。本质上是实现“小误差大增益、大误差小增益”的非线性控制效果。
这个方法理论上很优雅,但工程实践中有一个绕不开的困难:非线性函数的参数α和δ对控制器性能很敏感,不同工况下最优值差别很大。而且非线性ESO在高增益条件下的稳定性分析比较复杂,想从理论上证明闭环系统的稳定性难度不小。
因此,我在实际项目中更推荐线性ADRC作为入门和工程落地的第一选择。线性ADRC将ESO简化为线性观测器结构,控制律直接采用PD组合形式:
u0 = kp * (v1 - z1) + kd * (v2 - z2) u = (u0 - z3) / b0其中kp = ωc²,kd = 2ωc,ωc是控制器带宽。这样做的好处是:参数整定从“调一堆α、δ、b0”变成“调两个带宽ω₀和ωc”,再有b0这个唯一的模型参数。整定规则非常明确,大脑负担小很多。
不过非线性ADRC在学习研究和特定高性能场景中还是有价值的。如果你的对象需要极致的快速性和扰动抑制能力,而被控对象的执行机构余量较大,非线性fal的“小误差大增益”特性确实能带来性能提升。我的建议是先用线性版本跑通整个控制链路,理解系统行为,再按需引入非线性环节。直接上非线性,参数太多互相纠缠,出了问题很难定位。
3. 实操过程与核心环节实现
3.1 案例对象:带非线性摩擦和负载扰动的电机位置伺服系统
为了把ADRC讲透,这里用一个工程上常见的电机位置伺服系统作为案例对象。假设被控对象是一个直流电机驱动的转台,系统的动态可以写成:
θ_ddot = -a * θ_dot - c * sign(θ_dot) + b * u + d(t)其中θ是转台角度,a是阻尼系数,c是库仑摩擦力矩相关的系数,sign是符号函数,d(t)是外部负载扰动,u是电压控制输入。这个对象的难点在于摩擦非线性项c * sign使得低速时系统难以精确到位,而外部负载扰动d(t)会直接影响稳态精度和动态性能。
现在用ADRC控制器来设计位置环。为了简化处理,选取b0作为控制增益的估计值,它不需要精确等于b,只要量级正确即可。这就是ADRC的优势之一:不需要知道确切的b值,ESO会把真实b与b0之间的差异也当作总扰动的一部分,在z3中估计并补偿掉。
系统是二阶的,所以控制器设计一个二阶ADRC。ESO扩张一个状态来估计总扰动,因此ESO是三阶的。我们用常微分方程组描述整个ADRC闭环系统,然后在仿真工具中搭建模型。如果你用的是MATLAB/Simulink,建议用S-Function或者MATLAB Function模块来写离散ADRC控制器,这样从仿真迁移到嵌入式代码时,只需要把C语言版本的函数移植过去。
3.2 仿真模型搭建与核心算法代码实现
在Simulink中搭建的步骤大致如下:首先建立被控对象模型,写入电机动力学方程,包含非线性摩擦和负载扰动。然后建立ADRC控制器子系统,包含三个模块:跟踪微分器、扩张状态观测器、非线性状态误差反馈控制律。最后把控制器输出接到被控对象的输入端,形成闭环。
下面给出一个可供直接参考的二阶线性ADRC离散化代码,以MATLAB Function形式编写:
function [u, z1, z2, z3, v1, v2] = adrc_controller(y, v, ts, w0, wc, b0) % 输入:y-实际输出,v-设定值,ts-采样周期 % 输入:w0-观测器带宽,wc-控制器带宽,b0-控制增益估计 % 输出:u-控制量,z1 z2 z3-状态估计,v1 v2-过渡信号及其导数 persistent z1p z2p z3p v1p v2p if isempty(z1p) z1p = 0; z2p = 0; z3p = 0; v1p = 0; v2p = 0; end % 跟踪微分器TD(离散最速形式,简化为线性跟踪) r = wc * 3; v1 = v1p + ts * v2p; v2 = v2p + ts * (-r * (v1p - v) - 2 * sqrt(r) * v2p); % 扩张状态观测器ESO(离散欧拉法) beta1 = 3 * w0; beta2 = 3 * w0^2; beta3 = w0^3; e = y - z1p; z1 = z1p + ts * (z2p + beta1 * e); z2 = z2p + ts * (z3p + beta2 * e + b0 * u_prev); z3 = z3p + ts * (beta3 * e); % 控制律(线性PD + 扰动补偿) u0 = wc^2 * (v1 - z1) + 2 * wc * (v2 - z2); u = (u0 - z3) / b0; % 更新持久变量 z1p = z1; z2p = z2; z3p = z3; v1p = v1; v2p = v2; end这段代码有个细节值得注意:u_prev。ESO的更新需要用到上一时刻的控制量u,但在Simulink的MATLAB Function里,u是在当前步计算出来的,直接用会造成代数环。解决办法是把u也声明为persistent变量,用上一拍的值参与ESO计算。这是工程实现中很容易踩的坑,很多人仿真直接报错就是因为这个。
参数初设方面,被控系统真实b值大约在2.5左右,b0可以设成2。采样周期ts取0.001秒。观测器带宽w0先取30,控制器带宽wc取10。后续根据响应曲线再调整。仿真时长到2秒,设定值在0.2秒时从0阶跃到1,负载扰动在1秒时加入幅值为0.5的阶跃,检验系统的抗扰能力。
3.3 参数整定方法与仿真结果分析
参数整定的顺序非常重要。我第一次用ADRC的时候把三个模块的参数混在一起调,结果根本分不清是哪个参数引起的振荡。后来总结出正确的操作顺序。
第一步,先不接TD,直接让设定值走阶跃,只调试ESO和控制律参数。第二步,把ESO的w0固定在一个适中值,让控制律的wc从较小的值逐步增大。观察系统在阶跃响应下的超调量和上升时间。第三步,把wc固定下来,再逐步提升w0。观察系统在负载扰动下的恢复时间。最后,再引入TD,用平滑过渡消除剩余超调。
在这个案例中,我以w0=30、wc=10为初始参数运行,系统上升时间约0.35秒,无超调,但1秒加入负载扰动后转速有约0.08的偏差,恢复到稳态大约需要0.3秒。将wc提高到15,上升时间降到0.22秒,仍然无超调。将w0提高到50后,扰动引起的偏差降到0.03,恢复时间缩短到0.15秒。
和PID对照实验很能说明问题。我调试了一个经典PID控制器,用工程整定法试了很多组参数,最终做到无超调和快速响应之间平衡。在同样的阶跃设定下,PID上升时间约0.3秒,但扰动恢复需要将近0.5秒,而且负载突变瞬间的偏差比ADRC大两倍以上。这说明ADRC在“抗扰动”这一点上确实是碾压级的表现。
这个案例中,因为被控对象带有库仑摩擦,PID在低速趋近稳态时容易因摩擦非线性产生极限环振荡——位置在小范围内来回抖动。而ADRC的ESO实时估计了摩擦力的影响并补偿掉,低速段平稳收敛,没有抖动。这一点对高精度定位应用非常有价值。
4. 常见问题与排查技巧实录
4.1 高频振荡:当心观测器带宽拉太高
ESO带宽w0参数是ADRC双刃剑。w0越大,扰动估计越及时,抗扰性能越好,但超过临界值后,观测器会对测量噪声产生严重的放大效应。现象就是控制量u在高频剧烈抖振,执行机构发出尖锐的嗡鸣声,严重时直接损坏执行器。
排查方法:先操作执行机构不动,记录控制量波形。如果控制量在无输入时就有高频抖动,说明是观测器把噪声放大后,补偿项z3/b0产生了高频分量。解决办法是把w0降下来,或者在测量信号进入ESO之前加一阶低通滤波。不过滤波会带来相位延迟,建议滤波截至频率至少取ESO带宽的3倍以上,减少相位损失。
另一种更隐蔽的高频振荡来自于ESO补偿通道直接作用于执行机构。因为总扰动估计值z3包含高频成分,直接除以b0然后加到控制量里,高频噪声就被注入到物理系统。工程上可以在u0中增加小量的减振项,或在z3补偿路径中串联低通滤波器。很多老工程师会在ADRC外围加个简单二阶低通,非常有效。
4.2 初值跳变引发的积分型尖峰
当系统从停止状态启动时,如果ESO的初始状态变量都是0,而实际被控对象的位置存在偏差,观测误差会很大。β1乘上大误差,会瞬间产生一个巨大的z1修正量,控制量可能出现尖峰脉冲。
解决方案有两种。第一种是在启动时将ESO的状态初值重置为当前测量值。在数字实现里,检测到首次上电或使能信号边沿时,把z1初始化为当前的y值,z2和z3保持为0。这样观测器从零误差状态起步,不会产生启动冲击。
第二种方案是采用“软启动”策略,把设定值经过TD生成平滑过渡的同时,把ESO的z3经过一个斜坡启动。在实际工程中,很多运动控制系统要求上电时输出轴不能突然动作,这个斜坡策略能保证执行机构平滑启动。
我在一次现场调试中遇到过启动瞬间电流直接顶到驱动器限幅值的问题,排查半天确认ESO初值没设置对。后来在代码中加了初值对齐逻辑,启动冲击完全消失。这个问题非常隐蔽,仿真时因为模型初始状态基本都是0,不容易暴露,但一到实物上就原形毕露。
4.3 b0漂移导致估计失准的应对方法
b0是ADRC中唯一需要依赖对象知识的参数。b0取值偏离实际值b时,ESO会把(b - b0)u这一项也当成扰动估计出来。只要b0和真实b在一个量级范围内,系统仍然能工作,但性能会有所下降。b0比实际值偏小,会导致补偿不足;偏大,会导致过补偿和振荡。
对参数变化大的对象,比如负载惯量随工况大幅变化的机械臂,固定b0可能导致性能波动。解决办法是引入自适应机制,让b0随着系统惯量或增益的变化在线调整。工程上比较实用的做法是用一个增益调度表,根据可测的工况信号切换不同的b0值。
更轻量的做法是“范围自适应”。设定b0在额定值的0.5倍到2倍之间线性变化,通过ESO的估计误差来修正。这个方法不要求精确建模,但需要有明确的可测关联信号。如果对象增益变化完全不可测,那就只能靠提高观测器带宽来兜底,让ESO更快地跟踪总扰动变化。
4.4 常见问题速查表
| 故障现象 | 可能原因 | 排查方法 |
|---|---|---|
| 控制量高频抖振 | w0过高,噪声放大 | 降低w0,或在测量通道加低通滤波 |
| 启动瞬间冲击电流大 | ESO初值未对齐 | 启动时用当前测量值重置z1初值 |
| 设定值阶跃时超调明显 | TD的r参数过大或wc过高 | 减小r或wc,延长过渡过程 |
| 稳态精度差 | b0偏差太大 | 重新辨识b0,或提高w0 |
| 抗扰恢复时间过长 | w0和wc偏小 | 逐步增大w0,并配合提升wc |
| 系统低速抖动 | 摩擦非线性补偿不足 | 提高w0,确保ESO及时估计摩擦项 |
实战中,定位问题要有边界思维。先判断是跟踪问题(设定值响应)还是抗扰问题(扰动响应),然后分别对应TD/控制律参数和ESO参数。不要一上来就全面调整,那个折腾劲我深有体会。
5. 进阶经验:从仿真迁移到嵌入式系统的几个关键细节
5.1 离散化与采样周期的取舍
大多数ADRC理论推导基于连续时间,但实际控制器必须在数字处理器上离散执行。最常用的是欧拉法和双线性变换法。观测器通常采用欧拉法即可,因为实际采样率足够高时,精度差异不明显。
采样周期ts的选择是ADRC能否成功落地的关键。理论上采样频率至少是观测器带宽的10倍以上,否则离散化误差会严重影响估计精度。也就是说,如果w0取50,采样频率至少要500Hz,实际中我建议取到1000Hz以上,留足裕量。在DSP或ARM上,ADRC的计算量并不大,每秒1000次控制在现代芯片上毫无压力。
采样周期固定后,TD的离散化也需要同步。如果TD采用最速控制函数的精确离散形式,复杂度稍高,但能保证稳态精度。简化版线性TD在采样周期很短时可以正常工作,我实测20kHz控制周期下两者几乎没有差别。
5.2 限幅和抗饱和处理
ADRC的ESO在控制量u被限幅时会出现观测器发散风险。原因在于,发生限幅后,实际输入到被控对象的不是u而是限幅值,但ESO使用的是计算出的u,两者不一致导致ESO的状态估计产生偏差。如果限幅持续较长时间,偏差会累积,z3的估计值会持续增大,最终失控。
解决方案有两种。一种是做“条件积分”式处理:检测到限幅后,冻结z3的更新,防止扰动估计无限增大。另一种更彻底:构建ESO时使用实际输出到被控对象的限幅后控制量,而不是计算出的控制量。
具体实现上,在代码里要先算u,然后做限幅,用限幅后的u_sat去更新ESO。这个细节不处理好,系统在接近限幅的工况下非常容易失控。我见过不少仿真里好好的代码一加限幅就炸,都是因为这个原因。
5.3 代码移植时的数据结构和性能优化
从MATLAB迁移到C代码,最自然的方式是将ADRC核心函数封装为独立的模块。建议用结构体保存所有状态参数,方便在多个控制轴之间复用。例如:
typedef struct { float ts; float w0; float wc; float b0; float z1, z2, z3; float v1, v2; float beta1, beta2, beta3; float limit; } ADRC_Handle;初始化函数负责根据w0和wc预计算beta1、beta2、beta3以及kp、kd。主控制周期函数接收目标值v和反馈值y,返回控制量u。所有状态更新都在函数内部完成,外部只需要保持一个实例。
性能上,ADRC的主要计算量集中在ESO和三阶状态更新,使用float类型即可满足大部分控制场景,但在精度要求较高或需要长时间积分时建议用double。代码中尽量避免使用三角函数和除法,尤其是除法,在DSP上耗时较高。b0的除法可以通过预计算1/b0,在运行时改为乘法。
5.4 现场调试口诀
多年调试下来,ADRC现场参数的获得让我总结了一套很实用的“口诀”:
- 先定采样周期,再定b0量级,这是地基。b0可以用阶跃响应法粗测,从控制量阶跃到输出变化率的比值获得。
- wc从小到大调,直到系统响应速度满意且不振荡。这一步相当于调PD的增益。
- w0从wc的3倍开始往上涨,直到扰动抑制效果满意或噪声开始明显。
- TD的r参数最后调,用来平衡超调与快速性。
口诀的要点是“分层整定,逐步逼近”。每一个参数都只在特定方向上有主导影响,分开调就不容易迷路。切记不要同时动两个参数。
5.5 和现代控制方法的融合思路
ADRC并不是孤立的。近年来很多研究把它和模型预测控制、滑模控制、模糊控制等方法结合,形成混合控制方案。比如在MPC框架里用ESO做扰动估计,可以减少MPC对精确模型的需求;在滑模控制中引入ESO可以削弱抖振,因为扰动被前馈补偿后,滑模增益不需要设得过大。
如果你已经在工程中熟练使用PID,可以尝试把ESO只作为扰动前馈模块,接入现有PID环路中。这种改造方式对现有系统影响最小,又能显著提升抗扰能力。我实测过在温控系统中做这种改造,设定值响应曲线几乎不变,但负载阶跃引起的温度波动幅度下降了60%以上。
从更宏观的视角看,ADRC的“观测-补偿”思想可以被迁移到任何需要估计未知动态的系统中,比如数据驱动控制、故障诊断、观测器设计等领域。韩京清先生当年提出这套体系的初衷就是“让控制更贴近工程实践”,今天这个目标依然有很强的现实意义。