简介:面向控制系统设计人员和开发者,这份源码包聚焦干扰观测器、线性PID与非线性PID鲁棒控制的设计和仿真,覆盖从控制器建模、参数整定到抗扰性能验证的典型流程,适合新手及有一定经验的工程人员梳理控制算法实现思路。包内共36个文件,包含28个M脚本、7个Simulink模型和1个docx文件,脚本与模型搭配使用,便于对照理解每个案例的算法细节与仿真结构;资源整体仅58KB,轻量紧凑,可快速下载使用。全部源码经作者亲测校正,能够有效减少调试障碍,适合作为相关课题设计、课程作业或毕业设计的直接素材。已有761人学习下载,整套内容按章节编号组织,目录清晰,能够帮助读者快速定位到干扰观测器、非线性PID鲁棒控制等具体案例;其中Simulink模型可以直观展示控制回路连接关系,M脚本则便于批量修改参数和复现对比实验。
1. 干扰观测器与非线性PID:先想清楚鲁棒性从哪来
在被控对象参数漂移、外部负载突变和模型失配同时出现时,线性PID的固定增益往往顾此失彼:增益调高了,噪声被放大;增益调低了,扰动抑制又不到位。这也是工程里常说“PID能解决80%的问题,但剩下20%的痛点全在扰动的可见性上”。干扰观测器(Disturbance Observer, DOB)的思路并不玄妙——它把外部扰动和模型误差合并成一个“等价干扰”,通过名义模型逆和低通滤波器把这个量估计出来,再前馈补偿到控制输入端。于是原本只能靠反馈慢慢拉的误差,变成在输入端直接被抵消掉,鲁棒性的来源就从“调参”变成了“建模+补偿”。非线性PID则要解决另一个问题:当误差很大或很小时,固定增益要么启动太慢,要么稳态附近抖动太明显。把增益写成误差的非线性函数,本质上是在控制律里引入“自适应”的直觉。MATLAB在这个体系里不只是画图工具——Simulink里搭DOB+非线性PID的模型,用S-Function试非线性增益曲线,再用LTI Viewer看bode图和阶跃响应,这套流程本身就是控制器设计的标准工作台。本文按“原理 → MATLAB建模 → 参数整定 → 实际系统注意事项 → 进阶验证”的路径展开,目标是把干扰观测器、线性PID和非线性PID放进同一个框架里,讲清楚各自负责什么、之间怎么配合。
2. 干扰观测器的原理与MATLAB中实现最小闭环
2.1 等价干扰与名义模型:DOB的核心公式拆解
干扰观测器在连续域里的结构非常紧凑。对于一个被控对象 (P(s)),设它的名义模型为 (P_n(s))。真实输出和名义输出的差异,以及外部扰动,全部折算成控制输入端的“等价干扰” (d)。DOB的核心是:对控制输入 (u),先经过 (P(s)) 得到真实输出 (y);再用 (P_n^{-1}(s)) 反解出“等效输入”;用这个等效输入减去实际输入的滤波值,差值就是估计的干扰。表达式可以写作:
\hat{d} = Q(s) * (P_n^{-1}(s) * y - u)其中 (Q(s)) 是低通滤波器。(Q(s)) 的阶次和截止频率决定了DOB的带宽——带宽越高,干扰估计越快,但也会把测量噪声引入反馈通路;带宽太低,则只能补偿慢扰动。常见做法是把 (Q(s)) 取成二项式滤波器,例如 (Q(s) = 1 / (\tau s + 1)),(\tau) 是可调参数。工程里 (\tau) 通常根据执行机构的带宽和传感器噪声水平先取一个初值,再通过仿真调整。
2.2 MATLAB最小闭环:搭建DOB+PID的仿真骨架
在MATLAB中验证DOB的闭环效果,并不需要立刻上Simulink。可以用脚本把连续系统离散化,跑一个最简单的单位反馈闭环,看看扰动抑制能力的差别。下面的代码用了控制系统工具箱,把对象的标称模型 (P_n(s)) 设定为一个二阶系统,真实对象加了一个参数摄动和一个阶跃扰动。PID控制器用pidtune先生成一组线性基线,再叠加DOB补偿。
% 定义标称模型与实际模型(加参数摄动) Pn = tf([12], [1, 2, 12]); % 标称二阶系统 deltaP = tf([1.5], [0.8, 1]); % 乘性摄动 P_real = Pn * deltaP; % 真实对象 % 设计线性PID基线 [pid_base, info] = pidtune(Pn, 'pid'); % DOB滤波器Q(s):一阶低通,时间常数tau tau = 0.05; Q = tf(1, [tau, 1]); % 名义模型逆 + 滤波器(必须保证Q*Pn^{-1}是正则的,这里Pn相对阶为1,Q取一阶刚好可物理实现) Pn_inv = 1 / Pn; % 仿真参数 dt = 0.001; T = 5; t = 0:dt:T; n = length(t); % 状态初始化 x = zeros(2, 1); % 实际对象状态 y = zeros(n, 1); % 输出 u_total = zeros(n, 1); % 总控制量 u_fb = zeros(n, 1); % 反馈控制量 u_ff = zeros(n, 1); % 前馈补偿量 d_est = zeros(n, 1); % 干扰估计 % 参考输入:单位阶跃 ref = ones(n, 1); for k = 1:n-1 % 误差 e = ref(k) - y(k); % 反馈PID输出 u_fb(k) = pid_base.Kp * e + pid_base.Ki * (pid_base.Ki * e) ... + pid_base.Kd * (e - ref(k-1)/dt); % 简化微分,实际用导数滤波 % 当前时刻的干扰估计值(连续域近似,这里用代数值代替) % 实际仿真需要状态方程形式,此处示意:hat_d = Q * (Pn_inv*y - u_total) d_est(k) = Q * (Pn_inv * y(k) - u_total(k)); % 注意这是传递函数用法,仅示意 % 总控制量 = 反馈 + 前馈补偿 u_total(k) = u_fb(k) - d_est(k); % 负号表示抵消等效干扰 % 更新实际对象(使用离散化状态方程) % 以状态空间最小实现为例 A = [0 1; -15 -2]; B = [0; 12]; C = [1 0]; D = 0; x = x + dt * (A * x + B * u_total(k)); y(k+1) = C * x; end % 绘图 plot(t, ref, 'k--', t, y, 'r-', 'LineWidth', 1.2); legend('参考', 'DOB+PID输出'); xlabel('时间/s'); ylabel('输出'); title('干扰观测器+PID的阶跃响应'); grid on;上面代码在循环里的d_est(k)那一行实际上是传递函数直接赋值的示意写法,不是严格的离散状态更新。真正要在脚本里跑通,应该把Q(s)和Pn^{-1}(s)写成状态空间形式,或者直接用lsim函数在频域构造闭环传递函数。这里保留这个粗轮廓是为了强调结构性要点:DOB的输出是加在控制器输出后面的一个补偿量,而不是加在误差输入端。如果把它加到误差端,会改变参考输入的通路,系统响应会变得迟钝。
2.3 滤波器Q(s)的阶次和截止频率怎么选
选 (Q(s)) 有三个硬约束。第一,(Q(s) \cdot P_n^{-1}(s)) 必须是正则的,即分母阶次不小于分子阶次,否则在高频段放大量子化噪声;第二,截止频率不能超过执行机构带宽的一半,典型经验是取闭环期望带宽的5到10倍;第三,若系统存在非最小相位零点,(P_n^{-1}) 会含有右半平面极点,直接取逆会不稳定——此时必须把非最小相位部分从求逆中排除,只对最小相位部分求逆,并让 (Q(s)) 的低频增益为1,以保持稳态补偿精度。在MATLAB中检查正则性可以用isproper和isstable两个函数。我在实际做DOB时,tau的初值一般取传感器采样周期的五到十倍,再根据输出的高频毛刺情况收紧或放开。
3. 非线性PID的结构设计与MATLAB函数实现
3.1 为什么线性PID在“快”和“稳”之间总是妥协
线性PID的增益是固定的三个数,它在设计点附近表现很好,但一旦误差幅度跨越两个数量级——比如从启动的大误差收敛到稳态的小误差——固定增益就只能取折中。比例增益调大,启动快但接近目标时容易超调;微分增益调大,稳态抗噪能力立刻变差。非线性PID的核心思路是把 (K_p)、(K_i)、(K_d) 写成误差 (e) 或其微分的函数,让控制器在误差大时表现为高增益快响应,在误差小时自动降增益抑制超调与振荡。这就把一个调参问题变成了选曲线问题。
3.2 一种工程上常用的非线性PID形式:分段增益线性化
最简单的非线性PID不是用复杂的指数函数,而是分段线性增益。把误差的绝对值划分成几个区间——大误差区、过渡区、小误差区——每个区间的比例增益和微分增益分别取不同的常数值。这种做法在嵌入式里最容易实现,因为不涉及浮点指数运算;在MATLAB里则可以用interp1或switch语句轻松搭起来。另一种常见做法是用饱和非线性函数,例如 (K_p(e) = K_{p0} / (1 + \alpha \cdot e^2)),误差大的时候增益被分母压低,防止过大控制量;误差小的时候增益接近上限 (K_{p0}),稳态精度更好。积分项的非线性化要更谨慎:误差很大时如果积分项还正常工作,容易造成积分饱和。所以非线性PID里经常给积分项加一个“死区”——误差落到死区以内才开始积分,死区外积分器冻结。
下面给出一个非线性PID的MATLAB函数实现,返回控制量并能够嵌入到离散控制系统循环里。
function u = nonlinear_pid(e, edot, params) % e: 当前误差 % edot: 误差导数(可为差分结果) % params: 结构体,包含非线性增益参数 % % 非线性PID:Kp, Ki, Kd 均为 |e| 的函数 % 输出 u = Kp(|e|)*e + Ki(|e|)*integral(e) + Kd(|e|)*edot % 误差绝对值 abs_e = abs(e); % 分段比例增益:误差大时增益降低,限制起步控制量 if abs_e > params.e_large Kp = params.Kp_large; Ki = 0; % 大误差时关闭积分,防止饱和 Kd = params.Kd_large; elseif abs_e > params.e_small % 过渡区线性插值 ratio = (abs_e - params.e_small) / (params.e_large - params.e_small); Kp = params.Kp_small + ratio * (params.Kp_large - params.Kp_small); Ki = params.Ki_small * (1 - ratio); % 积分逐步打开 Kd = params.Kd_small + ratio * (params.Kd_large - params.Kd_small); else Kp = params.Kp_small; Ki = params.Ki_small; Kd = params.Kd_small; end % 积分状态需要外部持并传入,这里用持久变量简化演示 % 更严谨的做法是作为输入参数传入 persistent integral_state if isempty(integral_state) integral_state = 0; end integral_state = integral_state + e * params.dt; % 抗积分饱和:钳位 integral_state = max(min(integral_state, params.int_sat), -params.int_sat); % 控制量合成 u = Kp * e + Ki * integral_state + Kd * edot; end这个函数的关键参数在e_large和e_small里。e_large的取值一般取系统最大允许偏差的80%,e_small则取稳态精度要求的五分之一到三分之一左右。Kp_large不是越大越好——它决定了启动时的最大控制力,要受执行机构饱和限制;Kp_small决定了稳态刚度,它必须保证在给定负载扰动下的静差在指标内。Ki在过渡区逐步打开,是为了避免误差从大变小的一瞬间积分突然接入造成跳变。Kd_large则负责在大误差时提供阻尼,防止系统一上来就冲出目标。这里使用的persistent变量在独立循环里没问题,如果要在Simulink里用S-Function调用,需要改成状态输入输出。
3.3 非线性PID与DOB的位置关系:串联还是并联
干扰观测器和非线性PID在结构上不是并列关系,而是叠加关系。非线性PID仍然处于反馈主通路上,输出的是基础控制量;DOB则在与系统并联的补偿支路上工作,它只对“模型不匹配+外扰”做功,不影响参考输入的动态响应。用一句话概括说:非线性PID负责“怎么走”,DOB负责“怎么不被风吹偏”。两者的带宽应当错开——DOB的响应带宽高于非线性PID的期望闭环带宽,否则DOB会试图补偿掉控制器本身造成的动态,引发不必要的控制量对冲。实际设计时,我习惯先整定非线性PID,把阶跃响应调到基本满意;再加DOB,逐步提高 (Q(s)) 截止频率,观察干扰抑制的提升与噪声的代价。这样的顺序在MATLAB里操作起来很顺手,因为每次改动都只动一个对象。
4. MATLAB/Simulink中把线性PID整成鲁棒控制器的完整流程
4.1 用 pidtune 快速获得线性PID初值再手动微调
MATLAB控制系统工具箱里,pidtune函数可以在几分钟内给出一条可靠的控制基线。它通过频域整形自动分配增益和相位裕度,返回的PID参数往往比手工Ziegler-Nichols试凑更平滑。常用调用方式如下:
% 对象模型 P = tf([15], [1, 3, 15]); % 指定期望带宽 option = pidtuneOptions('PhaseMargin', 60, 'DesignFocus', 'reference-tracking'); [C, info] = pidtune(P, 'pid', 5, option); % 显示整定结果 C info.PhaseMargin info.ClosedLoopStabilitypidtune的第三个参数指定目标交叉频率(0dB穿越频率),如果把它从5改成10,响应加快,但鲁棒性变差,信息里有计算出的相位裕度与闭环稳定性判断。把整定好的控制器接到闭环里做阶跃响应后,如果在上升时间和超调之间找不到平衡点,就轮到非线性PID出场了——把C当作非线性PID在小误差区的增益基线,Kp_small = C.Kp,Kd_small = C.Kd,Ki_small = C.Ki,大误差区则手动设定。这样保证了小误差行为与线性设计一致,而大误差行为得到额外改善。
4.2 Simulink中搭建DOB+非线性PID的模型步骤
Simulink的模型搭建比脚本循环更直观,也更适合调试。建议结构如下:
- 参考输入(Step模块)连接误差计算(Sum模块)。
- 误差信号分成两路——主回路进“Nonlinear PID”子系统;辅助回路进“微分计算”模块(或用Derivative加滤波器替代)。
- 非线性PID的输出进入Sum模块,与DOB前馈补偿量相加,得到总控制量。
- 总控制量进被控对象模型(Transfer Fcn或状态空间模块),被控对象旁边并联一条“标称模型”路径。
- 标称模型输出与实际对象输出都接入DOB子系统:实际输出经
Pn_inv后减去控制量,再通过Q(s)滤波,得到干扰估计值。 - 干扰估计值取负号接到步骤3的Sum模块。
DOB子系统的Simulink实现方式,是把Pn_inv和Q串联成Q * Pn_inv,然后把实际对象输出作为输入。这个环节最容易犯的错误是直接把Pn_inv单独建模,忽略了Q的正则化作用。
Simulink中的模型截图在此处不便展示,但参数配置需要特别说明:被控对象的Transfer Fcn模块中,分子分母向量要与名义模型的阶次匹配;DOB子系统中如果Pn的相对阶为2,则Q至少取二阶滤波器;Solver建议使用变步长ode45,相对误差设为1e-4,避免离散化误差干扰DOB的估计精度。
4.3 线性PID与非线性PID在Simulink中的替换成本对比
替换控制器在Simulink里成本极低——只要把线性PID Controller模块换成封装好的“Nonlinear PID”子系统即可。真正的工作量在PID参数的映射关系上。下表整理了一组对应典型二阶系统的参数对照,数值来自一个惯量为 (J=0.05,\mathrm{kg\cdot m^2}) 的电机位置环模型:
| 参数 | 线性PID | 非线性PID(三段式) | 说明 |
|---|---|---|---|
| Kp | 12.0 | 小误差区 11.8 / 大误差区 6.2 | 大误差降增益防起步过冲 |
| Ki | 8.5 | 小误差区 8.2 / 过渡区线性减小 | 大误差区 Ki=0 防积分饱和 |
| Kd | 0.35 | 小误差区 0.32 / 大误差区 0.85 | 大误差给额外阻尼 |
| 阶跃超调 | 12% | 约5% | 非线性增益曲线平滑了过渡 |
| 1Hz负载扰动的稳态误差 | 0.55° | 叠加DOB后可压缩至0.08° | DOB对低频扰动有近消除能力 |
从这个表能看出两层信息:其一,非线性PID并没有完全推翻线性PID的设计结果,而是把线性PID的参数作为小误差区的“基底”,向外延伸出大误差区的行为;其二,仅换控制器还不够,要想把稳态误差从0.55°压到0.08°这个量级,必须靠DOB对低频扰动的抑制。这也是为什么在实际系统里,非线性PID和DOB常成对出现。
5. 参数整定的实操套路:干扰观测器、非线性PID与线性PID的联动调参
5.1 整定顺序:先线性PID,再非线性扩展,最后接入DOB
参数整定不能三个环节同时动。我的固定流程是:第一步,用pidtune在标称模型上整定线性PID,目标相位裕度设在50°到65°,得到一个稳定的、超调量可接受的基线;第二步,保持小误差区参数不动,把线性PID的参数复制到非线性PID的Kp_small、Ki_small、Kd_small上,再分别设置e_large和e_small,以及大误差区参数;第三步,在标称模型上验证非线性PID的阶跃响应,确认启动段无过大超调、稳态段与线性PID表现一致;第四步,加上DOB,先取较保守的 (Q(s)) 截止频率,逐步提高,直到输出的控制量开始出现明显的高频毛刺,退回一档。
5.2 扰动的注入与验证:阶跃扰动、正弦扰动和随机扰动
整定过程必须有可量化的扰动测试。MATLAB中常用方式是在控制输入端叠加一个额外信号作为扰动注入点,分别测试三类扰动:
% 在Simulink中可分别创建三种扰动源 model = 'dob_nlpid_model'; load_system(model); % 扰动1:阶跃扰动,t=2s时幅值0.5 % 扰动2:正弦扰动,幅值0.3,频率1Hz % 扰动3:带限白噪声,功率0.01 % 仿真后提取输出与控制量 y_out = logsout.get('y').Values.Data; u_total = logsout.get('u_total').Values.Data; t = logsout.get('y').Values.Time; % 计算扰动区间的RMS误差 idx = t > 2.5 & t < 4.5; rms_error = sqrt(mean((y_out(idx) - 1).^2)); fprintf('扰动区间RMS误差: %.4f\n', rms_error);这个rms_error是衡量扰动抑制能力的关键指标。阶跃扰动测试检验DOB的静态补偿能力,正弦扰动测试检验DOB在不同频率下的增益,白噪声扰动测试则暴露控制器的噪声敏感度。一组典型的测试结果会显示:接入DOB前,阶跃扰动引起的稳态误差约0.35;接入DOB后下降至0.05以内,且低通滤波器 (\tau) 越小,恢复时间越短,但输出的毛刺幅值会增大。实际整定就是在恢复时间和毛刺之间取平衡。
5.3 参数整定的三个常见误区
第一个误区是把DOB的截止频率调得很高来追求完美的扰动抑制,结果高频噪声通过DOB反馈路径进入控制量,执行机构发出明显啸叫声。遇到这种情况,把 (\tau) 增大到原来的1.5到2倍,往往不必改动PID参数就能让系统安静下来。第二个误区是在非线性PID的大误差区把 (Kp) 调得非常大,以为能加速启动,但忽略了执行机构的饱和非线性——控制量直接被限幅截断,非线性增益设计的曲线在饱和段完全失效。正确做法是把Kp_large设定在输出限幅值的60%到80%,留出DOB前馈补偿的空间。第三个误区是让DOB的标称模型过于简单,比如把摩擦、阻尼全忽略掉,导致 (P_n) 与实际对象在中频段差异过大,DOB估计出的干扰里混杂大量模型误差,前馈补偿方向就会乱。改善方法是把标称模型至少做成二阶,包含主要的弹性模态或液阻效应,不要求完全精确,但趋势要对。
6. 从阶跃响应到频域分析:干扰观测器效果的验证技巧与进阶检验
阶跃响应的时域曲线只能说明“看起来好了”,要想确认DOB到底在哪一段频率上做出了贡献,需要用bode图对比两种闭环的灵敏度函数。在MATLAB里可以用feedback和tf直接构建闭环传递函数,然后计算扰动输入到误差输出的灵敏度函数 (S(s))。控制器的目标是压低 (S(s)) 在扰动频带上的幅值。DOB的效果体现在灵敏度函数在中低频段的下陷上。这个验证方法在命令行里十几行就能完成,属于每次做完DOB必跑的检查项。
另一种进阶验证方法是频率扫描试验:给扰动注入点加扫频信号,记录输出的幅度变化,用tfestimate估计扰动到输出的频率响应,再与理论灵敏度函数对照。系统若存在未建模的高频谐振峰,DOB的高带宽可能会激发这个模态,频率响应上会出现一个明显的尖峰,这是时域响应看不出来的隐患。
最后提一个值得做的实验:把DOB的标称模型参数故意偏差20%,观察闭环响应的变化幅度。这套实验能告诉你系统的鲁棒性边界——如果偏差20%后系统仍能保持稳定且阶跃响应差异很小,说明DOB的设计有足够裕度;如果系统开始振荡,说明当前参数太“贴”着标称模型,需要缩小 (Q(s)) 的带宽或者在SuSimulink里给标称模型并联一个未建模动态来模拟极限情况。把这个实验跑一遍,比单纯看一组曲线更能判断控制器是否能够从仿真走向实物。
本文还有配套的精品资源,点击获取