最近不少同学问我同一个问题:“网上都说LADRC比PID强,我是不是该从PID转过去学它?”如果你也想在Simulink里把LADRC和PID放在同一张图上,看看它们的跟踪响应和抗扰动能力到底差多少,那这篇就是给你准备的。我会从模型搭建开始,一步步讲清楚LADRC的原理、参数整定和仿真对比过程,不跳步,不藏着掖着,适合刚接触自抗扰控制的读者,也适合想把PID调参经验再往前推一步的工程师。
我自己的体会是:只看公式很难把LADRC用起来,但如果能在Simulink里亲手搭一遍,一边调参一边观察扩张状态观测器的输出,很多直觉性的东西会一下子建立起来。这篇文章的内容就是我当时踩坑、看文献、反复仿真才总结出来的一套入门路线,希望能帮你少走弯路。
1. 为什么要把LADRC和PID放在一起比
1.1 PID的本质是“事后纠错”,LADRC是“事前估计”
PID控制器用的是误差的比例、积分、微分。比例项对当前误差做出反应,积分项对历史误差累积做出反应,微分项大致预测误差变化趋势。这里面的问题是:PID并不知道外部扰动到底是什么,它只能等系统输出偏离了设定值,通过误差变化来“倒推”出一个合适的控制量。积分项尤其典型——它一直在慢慢攒“历史欠账”,遇到持续负载变化时,要先偏差再慢慢把输出顶上去,响应总是慢半拍。
你在实际系统里应该也见过这种情况:温度炉子加热时,打开炉门冷空气灌进来,温度出现了偏差,PID脑子里没有“门开了”这个信息,只能根据偏差一点点调整占空比。如果扰动大、变化快,积分项还没来得及补偿,温度已经掉了一大截。这就是PID“被动纠错”的局限。
LADRC的思路则完全不同。它把外部扰动、未建模动态、参数摄动统统看成一个“总扰动”,再用扩张状态观测器把这个总扰动实时估出来,然后在前向通道里反向补偿掉。注意这个“扩张状态”的理解:普通状态观测器只能估计系统的原始状态变量,而扩张状态观测器额外增加了一个状态,专门用来代表总扰动。这个状态不是直接测量的,而是通过输入输出数据“推算”出来的,属于一种在线主动估计。
所以你可以把LADRC的补偿过程理解成:系统里有个看不见的“捣乱小孩”,PID只能等他搞了破坏、看到结果再去补救,而LADRC派了一个“监控摄像头”一直盯着他,小孩刚伸手,就把他的力气给抵消掉了。这个比喻并不夸张,仿真里你能清清楚楚看到,加入同样的负载扰动,LADRC的输出偏差峰值往往只有PID的几分之一。
1.2 为什么入门阶段最推荐用Simulink做对比
入门阶段,最大的问题不是“不知道LADRC公式”,而是“不知道这些状态变量在系统里到底怎么跑”。Simulink把传递函数、积分器、增益、示波器都摆在桌面上,你可以把每一步信号拉出来看:设定值、反馈值、ESO的三个状态、控制量、扰动信号,全部可视化。
比如你可以把扩张状态观测器估计出的总扰动信号z3,和实际注入的扰动信号放到同一个Scope里,一眼就能看出估计准不准、相位是否滞后。这在其他环境里要额外写很多绘图代码,而在Simulink里就是拖一个模块的事。
另外,Simulink模型可以很方便地转成C代码,或者通过外部模式(External Mode)直接跑在嵌入式设备上,这正好接上了你后面做实物验证的路子。很多初学者学完仿真后不知道怎么落地,其实从Simulink模型到Embedded Coder生成C代码,之后再烧到控制板里,是一条非常成熟的路径。
2. 仿真环境搭建:被控对象和控制器实现
2.1 选一个能体现差异的被控对象
对比实验最重要的第一步,是选一个“足够典型”的被控对象。如果选一个纯一阶惯性对象,PID和LADRC差距不会太大,体现不出LADRC的价值;如果选一个高阶或者纯延迟对象,又对参数整定要求太高,新手容易直接劝退。
我的建议是选一个二阶欠阻尼对象,比如:
G(s) = 1 / (s^2 + 0.5s + 1)
这个对象的阻尼比约为0.25,开环增益为1,阶跃响应自带超调,本身就有一定“脾气”,很适合用来考察控制器的跟踪和抗扰能力。试着在MATLAB里建一下:
num = 1; den = [1 0.5 1]; G_obj = tf(num, den);如果你手头有更贴近实际工程的对象,比如直流电机速度环模型,也可以换成带惯量和阻尼的一阶系统,但为了第一次上手,先把上面这个二阶对象做熟,后面再换复杂对象。
2.2 PID部分的搭建细节
在Simulink里新建一个空白模型,从Continuous库拖一个Transfer Fcn或State-Space模块,填入上面这个对象。控制器我建议直接用Continuous库里的PID Controller模块,不要自己用增益模块徒手搭,因为官方PID模块自带积分抗饱和(Anti-windup)和输出限幅选项,能少踩很多坑。
双击PID模块,把P、I、D参数填好。这里要注意:为了公平对比,PID的输出限幅也要设成和LADRC一样,比如都限制在[-10, 10]范围内。如果你只给PID加限幅而不给LADRC加,结果会失真。仿真中,控制器输出如果长时间顶在限幅值上,会发生积分饱和,导致系统来回震荡,这也是很多新手说“PID越调越乱”的原因之一。
关于PID参数初值,我建议先用手动试凑法大致走一遍:先设I=0、D=0,只调P,观察响应曲线;接着加一点I消除稳态误差;最后如果超调太大,再加一点D。拿上面这个对象来说,一组可以接受的初值是Kp=5、Ki=2、Kd=0.5。注意,这组参数不是唯一的,后面你完全可以按自己的需求重新整定。
还有一个细节:微分项对测量噪声很敏感。Simulink的PID Controller模块里有一个滤波器系数N,默认可能是100,意思是对纯微分项近似加了一个一阶低通滤波。如果你发现高频噪声导致控制量毛刺很大,就把N降到10~30之间试一下。这个经验在实物调试时特别有用,因为真实传感器的噪声远比仿真里的白噪声大。
2.3 LADRC控制器的搭建与原理对应
LADRC的核心就是扩张状态观测器加一个线性状态反馈。先写一下连续时间下的离散表达,方便你在Simulink里照着搭。对于一个相对阶为2的对象,LESO的方程可以写成:
z1_dot = z2 + l1 * (y - z1)
z2_dot = z3 + l2 * (y - z1) + b0 * u
z3_dot = l3 * (y - z1)
其中,z1估计对象输出,z2估计输出的导数,z3估计总扰动。控制律则采用:
u = (u0 - z3) / b0
u0 = kp * (r - z1) - kd * z2
这里r是设定值,b0是控制增益的估计值,可以理解为“对象输入到输出二阶导数的增益”。你可以把b0想象成控制量的“杠杆比”:同样的控制电压,电机加速度多大、温度上升速率多快,都体现在b0里。
为了减少待调参数,LADRC通常采用“带宽法”整定。把观测器的三个极点都放在同一个频率wo上,控制器两个极点都放在wc上,于是:
l1 = 3 * wo
l2 = 3 * wo^2
l3 = wo^3
kp = wc^2
kd = 2 * wc
简单来说,你只需要调两个数:控制器带宽wc和观测器带宽wo。对新手来说,这是LADRC最友好的地方。wo一般取wc的3~10倍,意思是观测器要比控制器“看得快”,这样估计值才能跟上实际状态变化。
在Simulink里搭建时,我建议直接用Integrator、Gain、Add、Sum等基础模块按方程接线,而不是一上来就写S-Function。这样做的好处是,每一路信号都能单独拉出来看,便于理解。你可以在MATLAB工作区预先定义好参数,然后在模型里直接填变量名:
b0 = 1; wc = 6; wo = 30; l1 = 3 * wo; l2 = 3 * wo^2; l3 = wo^3; kp = wc^2; kd = 2 * wc;模块搭好后,把系统中y、r、u、z1、z2、z3这些信号都送到To Workspace里,后面批量仿真和绘图就全靠它们了。顺便提醒一下,别把z1和反馈信号y直接当作一回事,早期我犯过这个错误,导致控制器本质上是开环控制。正确的接法是:输出反馈y只送给ESO做校正,而控制器中的反馈完全用z1、z2这两个估计值。
2.4 给控制器包一层子系统
模型搭通之后,建议把LADRC控制器封装成一个子系统(Subsystem),对外只留r、y、u三个端口。这样做的好处很多:一方面模型看着清爽,另一方面后面换对象、换场景时不需要改动内部结构,只需要改子系统内部参数或者b0、wc这些变量。
封装时右键点击选中相关模块,选择Create Subsystem from Selection,然后给子系统加一个Mask,把wc、wo、b0做成可以修改的“旋钮”。我特别推荐这么做,因为对比实验需要反复改参数,每次双击子系统就能改,比在MATLAB命令行里改变量再重新运行还要方便。
3. 对比实验设计:从参数整定到批量仿真
3.1 设计两个典型测试场景
一个公平的对比实验,不能只比“阶跃响应谁更快”,因为不同控制器本来就有不同的调教风格。建议至少跑两个场景:
第一个场景是设定值跟踪:在t=1s时,设定值从0阶跃到1,比较超调量、调节时间、稳态误差。这个主要考察控制器的跟踪能力和动态品质。
第二个场景是输入端扰动抑制:在t=10s时,在对象输入端叠加一个幅值为0.5的阶跃扰动信号,相当于给系统突然加上一个不小的负载变化,比较输出的跌落深度和恢复时间。这个才真正体现出LADRC和PID在抗扰能力上的差异。
在Simulink里可以放两个Step模块,一个接在设定值通道,另一个接在对象控制量之后、被控对象之前,注意一定要把扰动加在对象输入端,不要加在输出端。加在输出端属于测量噪声,加在输入端才是负载扰动,两者对控制器的考验完全不同。
我习惯的接法是:设定值Step输出进入控制器,控制器的输出先经过Sum模块,和扰动Step相加,再进被控对象。这样扰动信号从控制通道注入,更接近真实系统里负载变化对执行器带动的影响。
3.2 PID参数的整定顺序
很多教程会直接给你一组PID参数,然后跑出来对比说LADRC更好。这种对比很不公平,因为PID的参数可能根本没调到位。我建议你用临界比例法找一个过得去的PID作为对比基准,别拿一组随手填的参数糊弄自己。
临界比例法的操作步骤如下:先把PID模块里的I、D设为0,P设为很小的值,比如0.1;然后逐步增大P,观察系统输出,直到输出出现等幅震荡,记下此时的临界增益Kc和振荡周期Tc。之后再根据经验公式(Ziegler-Nichols)算出P、I、D初值,再手动微调。
拿前面那个二阶对象,临界振荡点对应的Kc大约在2附近。通过Z-N公式可以得到一组有超调但不至于发散的PID参数。如果你想看更平滑的响应,可以在此基础上降低P,增加一点D,让超调控制在10%以内。总之,花10分钟给PID调到“不丢人”的状态,再和LADRC对比,结论才站得住脚。
3.3 LADRC参数的整定路线
LADRC的整定有自己的次序,我建议按照“先定wc,再定wo,最后调b0”的顺序来。
先把b0按你对对象模型的估计设为一个合理值,上面这个对象可以直接取b0=1。然后从wc=1开始,看跟踪响应。如果响应太慢,就把wc逐步提高到3、5、8。wc越大,系统响应越快,但控制量也越大,而且对噪声越敏感。我建议wc取值范围先限制在1~20之间反复试。
确定wc之后,再调wo。wo通常取wc的3~10倍,典型值可以先取5*wc。如果发现z3波形与实际扰动对不上,比如相位明显滞后或者估值偏差很大,就把wo提高;如果控制量上有很多高频毛刺,说明wo太高、把噪声也放大了,需要降下来。
很多人把wo理解成“越大越好”,这是一个误区。wo本质上是观测器的带宽,带宽越高,估计速度越快,但同时通噪声也越多。实际系统里传感器一般都有零点几毫伏的白噪声,wo调得太大之后,z3会疯狂抖动,控制量也会跟着抖。这也是为什么后来有人提出降噪LESO、加入滤波器的改进方案。
3.4 用脚本批量跑仿真
手动在Simulink里改参数跑一次当然够用,但对比实验如果只跑一组就显得单薄。我习惯写一个简单的MATLAB脚本,用sim命令批量修改wc、wo、b0,自动跑完并把曲线画在一起。
% 定义需要扫描的参数组 wc_list = [2, 5, 10]; wo_ratio = 5; b0_list = [0.5, 1, 2]; figure; hold on; for i = 1:length(wc_list) % 更新工作区变量 wc = wc_list(i); wo = wo_ratio * wc; b0 = 1; % 重新配置LESO参数 l1 = 3 * wo; l2 = 3 * wo^2; l3 = wo^3; kp = wc^2; kd = 2 * wc; % 运行Simulink模型,注意模型名和路径 simOut = sim('compare_ladrc_pid.slx'); % 取数据,假设你用了To Workspace模块,变量名为simout t = simOut.simout.time; y = simOut.simout.signals(1).values; u = simOut.simout.signals(2).values; plot(t, y, 'LineWidth', 1.5); end legend('wc=2', 'wc=5', 'wc=10'); xlabel('时间/s'); ylabel('输出'); grid on;跑脚本前,记得把模型里的Scope不要接收所有信号,因为To Workspace需要显式输出信号。通常我放置三个To Workspace模块:一个存输出y,一个存控制量u,一个存ESO的三路状态z1、z2、z3,数据结构统一用Array或Timeseries。批量扫描时,同一模型反复调用sim函数,MATLAB工作区的变量更新后模型会自动引用新参数,这一点比GUI操作方便很多。
4. 仿真结果解读:响应、抗扰与控制量
4.1 典型波形和指标对比
跑完一组有代表性的参数后,你会看到比较明显的差异。以wc=6、wo=30、b0=1的LADRC和一组调好的PID为例,我得到的一组典型结果是:
| 比较项 | PID | LADRC |
|---|---|---|
| 设定值超调 | 约18% | 约4% |
| 2%调节时间 | 约4.5s | 约2.8s |
| 10s扰动导致的输出跌落 | 约0.32 | 约0.12 |
| 扰动后恢复时间 | 约5.2s | 约1.5s |
| 控制量峰值 | 约1.5 | 约2.1 |
这个表格只是示意,具体数值会随你的参数和对象不同而变,但规律是稳定的:在带宽足够的情况下,LADRC的跟踪更快、超调更小,抗扰能力明显更强。代价是控制量更“猛”,执行器压力更大。如果你的执行器有小马拉大车的顾虑,需要在控制器带宽上做些取舍。
为什么会有这样的差异?关键在于,PID面对扰动时,系统输出已经偏出去一段,才开始纠偏;而LADRC的ESO能提前把扰动“看出来”,并且在扰动还没有真正造成大偏差时就把控制量补偿上去。这个提前量不是预测算法那种基于未来轨迹的预测,而是基于输入输出数据的状态估计,它把扰动的动态包含在一个扩张状态中,所以理论上可以做到对阶跃扰动无静差地抵消。
4.2 观察ESO估计准不准,是调参的关键
调试LADRC最有用的一招,是把z3和实际扰动同时画出来。在模型中,你可以在扰动Step之后再接一条线,经过To Workspace送给工作区,然后和z3一起绘制。
如果z3曲线远远落后于真实扰动,说明wo太低,观测器反应跟不上。如果z3的波形在扰动加入前就有很大毛刺,说明wo太高或者测量噪声太大。如果z3的稳态值比扰动实际幅值小,很可能b0设得太小,控制器补偿量不足;如果z3明显发散到离谱的值,还需要检查模型是否接成了代数环。
我在初学阶段,有一回把z3接到了错误的位置,导致ESO估计的是输出误差而不是输出本身,结果z3完全不合理。后来我干脆把z1、y、z3三个信号放在同一个Scope里,每次改参数后先看这三个量,再决定下一步怎么调。这个习惯帮我少走了很多弯路。
4.3 PID调得好,是否就能战胜LADRC?
你可能会想:“既然可以仔细调PID,是不是也能调到和LADRC差不多?”在某些限定条件下,确实可以。比如对象模型非常精确、参数不变化、外部扰动以小阶跃为主、工作点固定,PID经过精心整定,完全可以达到不错的动态性能。这也是为什么很多老系统用PID用了二十年也没换。
但真实系统最怕的是“工作点漂移”和“参数摄动”。同一个PID参数,在负载轻的时候调好了,负载变重之后可能就震荡;在环境温度高的时候调好了,冬天一启动又超调。LADRC把未建模动态和参数变化合并进总扰动,由观测器在线估计,所以对工作点变化的适应能力更强。你可以在仿真里做一个简单的实验:t=0~20s用G(s)=1/(s^2+0.5s+1),t=20s以后把被控对象切换成G(s)=2/(s^2+0.3s+1),即增益和阻尼都变了。这种情况下,固定参数的PID通常会明显变差,而LADRC只要wo足够,仍能维持较好的响应。
当然,LADRC也不是万能药。遇到大延迟对象、非最小相位对象,或者执行器饱和严重的情况,基础LADRC直接上会出问题,需要改进结构或者引入预测信息。做对比实验时,别急着断言“LADRC全面碾压PID”,要结合具体场景下结论,这既是工程素养,也是写技术报告时的基本要求。
5. 常见问题与排查技巧实录
5.1 仿真直接发散或出现NaN、Inf
这是新手最容易碰到的问题。我遇到过的常见原因有这么几类:
一是求解器步长太大。Simulink默认可能会选较大的可变步长,对LADRC这种含高带宽观测器的系统来说,容易造成数值不稳定。解决办法是把最大步长设小一点,比如0.01s,或者直接换用ode15s这类适合刚性系统的求解器。
二是b0严重失配。b0如果比实际值小太多,相当于控制器增益被放大,系统容易震荡;b0比实际值大太多,控制作用又过于温和,甚至补偿不足。建议先从b0=1这个数量级试起,再上下各试一档。
三是ESO初值设置不合理。一般取z1、z2、z3的初始值为0,但如果你把被控对象输出初值设成了非0,ESO初始会有很大的偏差,导致第一个周期控制量巨大。先让所有初值统一,或者让z1初值等于实际输出初值。
四是控制器输出没有限幅。LADRC的补偿项z3/b0在扰动突变瞬间可能非常大,如果输出没有饱和模块,给到对象的控制量会是一个夸张的数,进而引发数值爆炸。在控制器输出端加一个Saturation模块,限幅范围根据实际执行器能力设定,是最基础的保护措施。
5.2 出现代数环报警
代数环在Simulink里表现为一个闭环里没有积分器或者Memory模块,信号在同一个步长内来回自洽求解,有时候能解出来,但效率低,还容易有数值问题。LADRC的ESO内部有积分器,正常不会出现代数环,但如果你的控制器是纯增益模块搭出来的,比如用MATLAB Function直接实现了LESO方程而内部没有离散状态,就可能触发代数环。
解决代数环最简单的办法是在反馈通道里放一个Memory模块,相当于引入一个步长的延迟。要注意,这个延迟会降低观测器的相位裕度,所以千万别在所有通道上都乱加Memory。更推荐的做法是把ESO离散化,用离散积分器搭状态方程,这样既能避免代数环,也和后面要生成C代码的路径一致。
5.3 Scope曲线看不出东西或自动空白
多半是因为工作区变量没定义好。Simulink模型里的增益模块引用了变量l1、l2、l3,如果你只清空了工作区再运行模型,就会报错,或者参数变成空值。解决方法是把参数初始化写在一个脚本里,并挂到模型的PreLoadFcn回调上。具体操作:模型属性 > 回调 > PreLoadFcn,填入“init_ladrc_params”。这样每次打开模型,参数都会自动加载,不会出现手动运行脚本后忘掉的尴尬。
还有一次,我把To Workspace模块的保存格式设成了Array,结果simOut.signals结构访问不到数据。如果你确实遇到了类似问题,建议把保存格式统一设为Timeseries,访问方式更直观,画图也简单。
5.4 离散化与C代码生成的衔接
仿真完成后如果要做嵌入式实现,需要把连续LADRC离散化。最简单的离散化方法就是欧拉法,直接把导数换成差分:
z1[k+1] = z1[k] + Ts * (z2[k] + l1 * (y[k] - z1[k]))
z2[k+1] = z2[k] + Ts * (z3[k] + l2 * (y[k] - z1[k]) + b0 * u[k])
z3[k+1] = z3[k] + Ts * (l3 * (y[k] - z1[k]))
采样周期Ts的选择很重要,一般建议至少满足Ts <= 1 / (20 * wo),如果wo=30 rad/s,Ts要小于1.7ms,实际取1ms比较稳妥。如果你用的是Simulink的离散模块,还可以直接通过Embedded Coder生成C代码,前提是把离散求解器设置好,并把模型参数设置为定常。
这里提一个和PID相关的注意点:如果你后来想用增量式PID思想实现离散LADRC,同样要注意输出限幅和反计算抗饱和,因为LADRC的控制律本身不内置抗饱和结构,饱和时需要对z3或控制量限幅做额外处理。
5.5 常见问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 仿真直接发散或NaN | 最大步长过大、b0严重失配、初值不合理 | 降低最大步长、检查b0数量级、初始化z1初值 |
| 控制量高频乱抖 | wo过高、测量噪声大、D项没有滤波 | 降低wo、增加测量滤波、调整PID微分系数 |
| 抗扰补偿不足 | b0偏大、wo偏低 | 减小b0、提高wo |
| 持续振荡或极限环 | 控制器输出连续顶限幅 | 增加输出限幅、加入抗饱和处理 |
| SCOPE无曲线 | 工作区变量丢失、To Workspace格式错误 | 配置PreLoadFcn,改用Timeseries保存格式 |
| 代数环报警 | MATLAB Function内部无状态延迟 | 改用离散积分器或在反馈中加Memory模块 |
6. 从仿真到嵌入式部署:一个过来人的扩展建议
6.1 别急着上LADRC,先做对象辨识和b0估计
很多初学者仿真里跑得很顺,一到实物就崩,最常见的原因就是b0估计得太随便。b0不是一个可以随便拍脑袋的数,它代表着“控制量到输出二阶导”的增益。如果你的对象是直流电机速度环,可以根据电机转动惯量和力矩常数估算;如果是温控系统,需要结合热容热阻特性做辨识。
我的建议是:先对被控对象做阶跃响应实验,用系统辨识工具箱拟合出二阶模型,再从模型的最高频段增益反推b0。表达式大概是:如果辨识出的模型是K/(s^2+a1*s+a2),那么高频段增益近似等于K,所以b0可以取K。这里的K不是开环增益?对,对于传递函数分母s^2项系数为1的模型,b0就是分子的静态增益。仿真验证这一步必须要做,它决定了你的ESO在实物上能不能稳定。
6.2 和级联PID结合,而不是直接取代
在真实的电机控制、无人机姿态控制、伺服系统中,你不需要把原来的PID全盘换成LADRC。更务实的做法是:内环保留传统的PI电流环,因为它带宽高、要求快,调好了很稳;外环位置或速度环改用LADRC,这样可以获得更强的抗负载扰动能力,又不至于让整个系统因为大换血而失控。
这种“外环LADRC、内环PI”的结构在Simulink里搭建并不复杂,只需要把内环当成一个等效被控对象,再在外面包一层LADRC即可。你还可以顺便对比一下“内外环都用PID”和“内环PI外环LADRC”两种结构的差异,会很有意思。我实测下来,外环LADRC对导轨摩擦变化、负载突变的抑制效果要明显好于单纯PID,启动阶段也不容易产生过大的超调。
6.3 把ESO的状态放到监控界面上,是价值最大的一步
如果你有上位机或者串口调试工具,建议在代码里把z1、z2、z3这三个状态实时发出来,用曲线工具画出来看。z3的动态直接反映了系统承受的总扰动,当你给系统加上负载、改变工况时,z3会跟着变化,你能立刻“看到”扰动的大小和方向。这种感知,是PID控制器给不了你的。
我个人在实际操作中的体会是:学LADRC不要一上来就追求参数最优化,先把模型跑通、把ESO三路状态看清,再动手调参,会顺畅很多。最后再分享一个小技巧:批量仿真时不要让参数一次性跨度过大,比如wo从30直接跳到100,很多模型会直接发散,你先从30跳到45,再跳到60,这样能明显减少数值突变的概率。希望这份Simulink入门指南,能帮你把LADRC从“听说的控制方法”变成“自己能用的控制工具箱”。