十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ADRC自抗扰控制S函数实现与Simulink仿真调试指南

ADRC自抗扰控制S函数实现与Simulink仿真调试指南 简介ADRC自抗扰控制是应对系统不确定性、非线性与外部扰动的先进策略广泛应用于电力、机械、航空航天等领域。该资源面向自动控制学习者与工程师提供基于MATLAB/Simulink的ADRC控制器S函数实现包含扩展状态观测器ESO、跟踪微分器TD和非线性反馈控制律NLSEF等核心模块可帮助快速搭建完整仿真模型并验证控制效果。压缩包共4个文件含3个M脚本和1个Simulink模型.mdl整体仅7KB轻量易用M文件分别对应TD、ESO与NLSEF算法实现MDL模型则集成各模块构成完整控制系统便于理解ADRC的内部结构与参数调节。目前已有329人学习适合正在研究自抗扰控制原理或需要在Simulink中快速开展仿真的读者参考。1. 从 PID 调不动到 ADRC为什么还要自己写 S 函数做过伺服或逆变器控制的工程师大概率遇到过这种场景PID 参数在空载时调得挺好一加负载响应曲线立刻开始抖重新整定增益又是一个下午。ADRC自抗扰控制这套 1990 年代由韩京清提出的控制框架核心思想是把系统里说不清道不明的非线性、未建模动态和外部扰动打包成一个总扰动用扩张状态观测器实时估出来再在前向通道里补偿掉。这和 PID 靠误差积分来事后擦屁股的思路完全不同——ADRC 是主动估计、主动抵消所以在面对参数摄动和未知负载时鲁棒性要好很多。ADRC-func.rar 里包含的正是这样一套可直接运行的 MATLAB 实现td3.m 负责安排过渡过程eso3.m 是扩张状态观测器的核心nlsef3.m 做非线性状态误差反馈最终通过 ADRC-FUMC.mdl 在 Simulink 里闭环。对想把这套算法用在自己课题或产品上的工程师来说难点不在于理解那几张框图而在于搞清楚 S 函数里每个回调函数在仿真过程中到底被谁调用、调了几次以及参数为什么那样设置。这篇博文就按原理 → 代码 → 集成 → 排错 → 验证这条线把整包资源拆开讲透。2. 自抗扰三大件的分工跟踪微分器、扩张状态观测器与非线性反馈2.1 为什么需要跟踪微分器过渡过程的意义很多人第一次看到 ADRC 的框图最先注意到的往往不是 ESO而是最前面那个 TD跟踪微分器。它的作用通俗说就是让参考输入从阶跃变成一条平滑的斜坡给被控对象一个缓冲。直接给阶跃执行器会饱和超调也难压用 TD 消化一下这个问题在源头就缓解了。td3.m 的实现思路是离散差分典型代码如下function [x1, x2] td3(r, h, vk, x1_prev, x2_prev) % 跟踪微分器根据输入信号 vk输出跟踪值 x1 和其微分 x2 % r 为速度因子决定过渡过程快慢h 为积分步长 x1 x1_prev h * x2_prev; x2 x2_prev h * -r * sat(x1_prev - vk x2_prev*abs(x2_prev)/(2*r), 10*r); end跟踪微分器里面那个sat饱和函数不是随意加的它是为了限制加速度避免 x2 超出物理执行机构能承受的范围。r越大跟踪越快但代价是前馈信号的噪声会被放大。做电机控制时我习惯先设r为系统带宽的 35 倍再观察转速曲线的平滑度决定是否加大会引起震动。步长h必须和 Simulink 模型的固定步长一致否则离散化的相位误差会导致观测器输出毛刺。2.2 扩张状态观测器的核心机制把扰动变成第三个状态ESO 是整个 ADRC 的发动机。它的设计思路很直接把系统写成状态方程后把总扰动 f(x, w, t) 扩展成第三个状态 x3然后用状态观测器同时去估计 x1、x2 和 x3。这样一来扰动不再是看不见摸不着的东西而是一个可以被实时重构的状态量。eso3.m 的代码逻辑大致如下function [z1, z2, z3] eso3(beta1, beta2, beta3, u, y, z1_prev, z2_prev, z3_prev, h) % 扩张状态观测器输入为控制量 u 和系统输出 y % 输出三个状态估计z1 跟踪 yz2 跟踪 y 的微分z3 估计总扰动 e z1_prev - y; z1 z1_prev h * (z2_prev - beta1 * e); z2 z2_prev h * (z3_prev - beta2 * fal(e, 0.5, 0.01) b0 * u); z3 z3_prev h * (-beta3 * fal(e, 0.25, 0.01)); end这里用了 fal 非线性函数替代线性增益目的是实现误差大时用小增益、误差小时用大增益的变增益控制。注意 z3 的更新公式里没有 u这就是扩张状态观测器区别于普通观测器的地方——它不假设扰动模型而是把所有未被模型描述的内容都归入 z3。beta1/beta2/beta3 的整定历来是新手最头疼的部分常见的做法是采用带宽参数化设观测器带宽为 w0则 beta1 3w0beta2 3w0^2beta3 w0^3。这样只需调一个参数 w0大幅降低了调参难度。参数作用整定规律beta1状态 z1 的修正增益过大引发高频抖动过小估计滞后beta2状态 z2 的修正增益增大加快微分收敛但放大噪声beta3扰动估计 z3 的修正增益控制扰动跟踪速度过大会导致系统震荡b0控制增益的粗略估计偏差 30% 内可由 ESO 补偿偏差过大会引起闭环失稳2.3 非线性状态误差反馈用 kp 和 kd 之外的方式压制误差nlsef3.m 做的事情是把过渡过程信号与 ESO 输出的估计状态做差然后经过非线性组合生成控制量 u0。与线性 PD 的区别在于它用的是 fal 函数而非线性比例这使得控制器在误差大和误差小时表现出不同的增益特性。function u nlsef3(kp, kd, v1, v2, z1, z2) % 非线性状态误差反馈综合过渡过程信号与状态估计生成基础控制量 e1 v1 - z1; % 跟踪误差 e2 v2 - z2; % 微分误差 u0 kp * fal(e1, 0.5, 0.01) kd * fal(e2, 0.5, 0.01); end最终施加到被控对象上的控制量为 u u0 - z3/b0。z3/b0这一步就是扰动补偿项它把 ESO 估计到的总扰动折算成等价的控制量在前向通道直接抵消。因此控制器的设计本质上只依赖误差反馈而不依赖精确的被控对象模型。3. S 函数在 ADRC 实现中的角色回调函数与仿真时序3.1 为什么 ADRC 要用 S 函数而不写普通 m 文件Simulink 里做控制算法验证最常见的错误是把 ADRC 的三段代码分别放在三个 MATLAB Function 模块里结果运行速度极慢而且各个模块之间的采样时间不一致导致代数环报警。S 函数的优势在于它拥有明确的仿真时序——初始化、状态导数计算、输出计算被严格区分可以精确控制连续/离散状态还能在同一个模块里完成状态数据和参数传递。S 函数本质上是一个符合固定接口规范的 m 函数Simulink 在不同的仿真阶段用 flag 参数告诉它应该执行哪段逻辑。ADRC 控制器需要同时处理连续/离散状态TD/ESO 是离散差分但被控对象可能是连续模型用 S 函数可以避免手工处理采样时间同步的问题。function [sys, x0, str, ts] ADRC_sfunc(t, x, u, flag, r, h, w0, b0) % ADRC 主 S 函数整合 TD、ESO 和 NLSEF switch flag case 0 % 初始化 [sys, x0, str, ts] mdlInitializeSizes(h); case 2 % 离散状态更新用于 TD 与 ESO sys mdlUpdate(t, x, u, r, h, w0, b0); case 3 % 输出计算控制量 u sys mdlOutputs(t, x, u, w0, b0); case {1, 4, 9} % 连续导数、过零检测和结束阶段无需处理 sys []; otherwise error([Unhandled flag , num2str(flag)]); end3.2 初始化回调用途定义状态维度与采样时间初始化回调是仿真开始前必须执行的第一步。它的作用是告诉 Simulink 这个模块有多少个状态、输出什么维度、采样时间怎么设置。以 ADRC 为例TD 有两个状态 x1/x2ESO 有三个状态 z1/z2/z3总共五个离散状态输出是一个标量控制信号。function [sys, x0, str, ts] mdlInitializeSizes(h) sizes simsizes; sizes.NumContStates 0; % ADRC 内部全部用离散状态 sizes.NumDiscStates 5; % TD 2 个 ESO 3 个 sizes.NumOutputs 1; % 输出控制量 u sizes.NumInputs 1; % 输入为被控对象的输出 y sizes.DirFeedthrough 0; % 输入不直接出现在输出中避免代数环 sizes.NumSampleTimes 1; % 单采样时间 sys simsizes(sizes); x0 zeros(5, 1); % 状态初始值全为零 str []; ts [h 0]; % 采样周期 h0 表示固定步长DirFeedthrough这个参数值得一提。ADRC 的输出 u 依赖 ESO 状态 z3 和误差 e1/e2后者由上一时刻的状态推得不直接依赖当前时刻的输入 y因此可以安全设为 0。这能避免 Simulink 在闭环仿真中报代数环错误。参数h通过ts传递给了整个模块意味着 TD 与 ESO 内部的差分步长是统一的。3.3 离散状态更新与输出计算谁先谁后离散状态更新回调在每个采样时刻被调用它更新的是五个内部状态的值。这里注意一个微妙的地方离散 S 函数中mdlUpdate先于mdlOutputs被调用也就是说输出时刻使用的状态其实是刚刚被更新过的状态。function sys mdlUpdate(t, x, u, r, h, w0, b0) % x(1),x(2) 为 TD 状态x(3),x(4),x(5) 为 ESO 状态 beta1 3*w0; beta2 3*w0^2; beta3 w0^3; % 更新 TD e_td x(1) - u; % 注意这里假设 u 端口接入的是参考输入 vk x(1) x(1) h * x(2); x(2) x(2) h * (-r * sat(e_td, 10*r)); % 更新 ESO使用上一时刻的控制量因此需在内/外部保存 u_prev e_eso x(3) - y_prev_sig; % y_prev_sig 由外部工作区变量传入 x(3) x(3) h * (x(4) - beta1 * e_eso); x(4) x(4) h * (x(5) - beta2 * fal(e_eso, 0.5, 0.01) b0 * u_prev_sig); x(5) x(5) h * (-beta3 * fal(e_eso, 0.25, 0.01)); sys x; endESO 更新时用到u_prev_sig这个量必须在 S 函数的工作区里缓存上一时刻的控制量否则会出现用当前时刻的输出去计算当前时刻的状态的隐式方程问题。常见做法是在mdlOutputs里把 u 存入x的附加状态或全局变量需要注意 MATLAB 函数内部变量的生命周期更稳妥的方案是把 u_prev 作为离散状态追加进状态向量。S 函数实现过程中这种跨回调传数据的细节往往比算法本身更容易踩坑。4. 在 ADRC-FUMC.mdl 中集成模型搭建、参数注入与闭环整定4.1 模块结构拆解与信号线连接顺序ADRC-FUMC.mdl 的结构并不复杂但新手经常栽在信号线接错上。模型里核心的就三部分参考输入通常是阶跃信号、ADRC 控制器S-Function 模块和被控对象一般是传递函数或状态方程。建议自行搭建时的连接顺序从 Simulink 库拖入一个 Step 模块将阶跃时间设置为 1。添加一个 S-Function 模块模块名称填ADRC_sfunc参数栏填r, h, w0, b0的数值例如10, 0.001, 50, 20。把 Step 输出接 S-Function 的第一个输入端口同时把被控对象的输出接 S-Function 的第二个输入端口。这里ADRC 需要同时知道参考输入和被控对象输出因此 S 函数的输入不是 1 而是 2需要在mdlInitializeSizes里把NumInputs改为 2。S-Function 的输出接被控对象的输入被控对象的输出接 Scope同时反馈回 S-Function 的第二输入。提示如果 S-Function 输入输出端口数不匹配启动仿真时会直接报 Input port 2 of ADRC_sfunc/ADRC_FUMC is not connected 一类的错误。排查时优先确认mdlInitializeSizes里的维度定义是否与模型实际接线一致。4.2 从模型外部注入参数通过工作区变量或 Mask在模型里直接写死参数不利于批量调参。推荐的做法是在 MATLAB 脚本里定义参数后通过模型回调函数或 Mask 封装注入。% 初始化脚本setup_adrc_params.m % 定义 ADRC 全集参数一次性写入基础工作区 r 10; % 跟踪微分器的速度因子 h 0.001; % 固定步长秒 w0 80; % ESO 带宽 b0 25; % 控制增益估计值取决于被控对象 kp 60; % 非线性反馈比例增益 kd 8; % 非线性反馈微分增益 % 如果 S 函数中 kp、kd 需要动态调整可通过工作区变量传入 assignin(base, r, r); assignin(base, h, h); assignin(base, w0, w0); assignin(base, b0, b0);在 S-Function 参数栏填r, h, w0, b0这样每次修改脚本里的数值再重新运行Simulink 模型无需打开就能更新参数。对于 kp/kd 这类控制器增益建议在 S 函数内部通过u的第 2 输入端获取或者在输出的控制量里额外返回一个增益调节用的观测端口这样就不用频繁改动参数列表。4.3 被控对象设置与仿真步长选取被控对象可以是任意传函或状态空间模型例如一个二阶惯性系统% 被控对象传递函数G(s) 12 / (s^2 1.2s 8) obj_tf tf(12, [1 1.2 8]);在 Simulink 中用 Transfer Fcn 模块直接填写[12]和[1 1.2 8]即可。仿真步长必须与 S 函数中的 h 一致——在求解器设置中选择固定步长并填入0.001。若使用变步长S 函数的离散状态更新时间会不固定直接导致 ESO 的差分公式失效出现估计发散。被控对象特征建议步长说明机械系统惯量阻尼0.5~1 ms对应带宽 100~200 Hz电力电子逆变器/整流器0.05~0.1 ms开关频率的 1/100 左右精密电机伺服0.1~0.25 ms兼顾仿真速度与精度纯数值模型/低频热力系统10~50 ms系统带宽低步长可放宽4.4 闭环调试顺序先观测器后反馈调试时不要一上来就同时动所有参数。我的习惯是分三步第一步单独验证 ESO。将 S 函数的控制量输出断开用白噪声或阶跃扰动作为被控对象输入同时用 Scope 观察 z1 和实际 y 的追踪曲线。z1 应在 0.5 秒内跟上 yz3 能反映扰动幅值的大致变化。如果 z1 滞后严重加大 w0 到 100 甚至 200如果 z1 出现高频振铃说明 beta1 过大或步长太长。第二步验证 TD 的过渡过程。给阶跃信号后看 v1 和 v2 的曲线v1 应平滑逼近阶跃值v2 应是一个单峰脉冲。如果 v2 的峰值超过执行机构的承受上限降低 r。第三步闭合反馈。把 TD 输出和 ESO 输出同时接入 NLSEF控制量接入被控对象观察闭环阶跃响应。此时先调 kp 让输出上升时间符合预期再调 kd 抑制超调。如果输出静态误差明显不要急着加积分项——先检查 z3 是否收敛到了实际扰动量。5. 从仿真数据反推参数有效性扰动估计的收敛性验证技巧成熟度较高的 ADRC 调试最终都归结到一个验证动作观测 z3 的曲线形态。z3 是对总扰动的实时估计它的收敛精度直接决定了前馈补偿的效果。你可以设计一组对比实验来量化它给被控对象输入端叠加一个幅值为 5 的阶跃扰动用 Step 模块经过加法器叠加进去在仿真结束后用 MATLAB 脚本输出 z3 的终值与扰动幅值的差值。% 验证脚本validate_eso.m % 假设模型输出的 z3 存在 tout 与 z3_out 中 [t, y, z1, z2, z3] sim(ADRC_FUMC, 3); % 仿真 3 秒 perturbation 5; % 注入的扰动幅值 error abs(z3(end) - perturbation); fprintf(扰动估计稳态误差: %.4f\n, error); if error 0.1 disp(ESO 扰动估计收敛良好); else disp(ESO 欠估计或超调请增大 w0 后重试); endz3 的初始段通常会有一段瞬态波动这段时间的长短取决于 beta3 的整定。如果你看到的 z3 是一条缓慢爬坡、最后停在扰动值附近的曲线说明观测器带宽合适如果 z3 在扰动加入的瞬间就冲出一个尖峰再回落则是 beta3 偏大、进入了过冲状态。此外ESO 的收敛性还可以用观测误差 e z1 - y 来判断e 的稳态均值应当趋近于零它的振荡幅度间接反映了观测噪声水平。最后一个实用技巧基于 z3 补偿量的饱和限幅。实际工程中z3 可能因为噪声累积而输出一个很大的扰动估计如果直接除 b0 补偿控制量会瞬间饱和。在 S 函数的mdlOutputs里对 z3 做限幅处理输出sign(z3)*min(abs(z3), z3max)这样在大扰动冲击下算法不会因积分饱和失去控制能力。把补偿带宽闭环掉就是这套 S 函数从仿真走向实物的最后一步。本文还有配套的精品资源点击获取
返回列表