简介:NESO.rar压缩包内含一套基于Simulink的非线性扩展状态观测器(ESO)仿真模块,面向自动控制、无人系统、机器人及伺服驱动领域的研究者,用于解决系统不可测状态的实时估计问题。压缩包共3个文件,均为MATLAB脚本,整体仅1KB,分别为NESO.m、Plant.m与tuning.m;Plant.m定义被控对象的非线性动态模型,NESO.m实现扩张状态观测器核心算法,tuning.m提供参数整定与调试流程,三者在Simulink中可组合替换,方便复现和二次开发。已有337人学习下载。借助这套仿真模型,读者能够掌握ESO的建模思路与调参规律,观察不同非线性强度下观测器的估计精度与收敛速度,并可将模型迁移至电机控制、无人机姿态估计、电力电子等实际工程场景,是理解主动抗扰控制与状态观测技术的高效工具。
1. NESO在Simulink里到底在解决什么
如果你在 MATLAB 的搜索框里敲过ESO或NESO,大概率是从自抗扰控制(ADRC)那边摸过来的。拿到的模型往往是一个打包好的.rar,里面躺着simulink的.slx或.mdl文件,打开一看,里面除了被控对象,最显眼的是一坨连着积分器的反馈结构,注释写着 "ESO" 或者 "Nonlinear ESO"。这个结构就是整个 ADRC 里最值钱的部分——扩张状态观测器。它做的事情很朴素:把系统内部未建模动态和外部扰动捏成一个"总扰动",当成一个新的状态变量去在线估计。NESO 和 ESO 的区别在反馈误差的处理方式上:ESO 常用线性增益,NESO 用非线性函数fal(),在小误差区域放大增益、大误差区域压低增益,从而在扰动估计精度和噪声抑制之间拿到更好的平衡。这篇文章面向的是做电机控制、飞行器控制、电力电子或者一般非线性系统仿真的工程师和研究生。读完你能在 Simulink 里自己搭一个 NESO,知道参数怎么设、仿真发散了怎么查、估计效果怎么验证。
2. ESO 与 NESO 的数学基础:从线性扩张到非线性修正
2.1 ESO 的核心思想:把不确定性扩张成状态
对一个二阶非线性被控对象,可以写成这样的形式:
[ \ddot{x} = f(x, \dot{x}, w, t) + b u ]
其中x是系统状态,u是控制输入,b是控制增益,f(·)包含了内部动力学、未建模动态和外部扰动w的总和。ESO 的思路是不去建模f的具体表达式,而是把它当作第三个状态变量:
[ x_3 = f(x, \dot{x}, w, t) ]
于是原系统被扩张成一个三阶系统:
x1' = x2 x2' = x3 + b·u x3' = h这里h = f'假定有界。对应地,设计三阶观测器:
e = z1 - y z1' = z2 - β1·fal(e, α1, δ) z2' = z3 - β2·fal(e, α2, δ) + b0·u z3' = -β3·fal(e, α3, δ)其中z1跟踪系统输出y,z2跟踪速度状态x2,z3跟踪扩张状态x3,也就是总扰动的实时估计值。b0是控制增益b的估计值。这个结构里没有用到被控对象的精确模型,这正是 ESO 的工程价值所在。
2.2 线性 ESO 的带宽参数化
如果直接把fal(e, α, δ)换成线性函数e,就得到线性扩张状态观测器(LESO):
e = z1 - y z1' = z2 - β1·e z2' = z3 - β2·e + b0·u z3' = -β3·eLESO 的参数可以用带宽法统一配置,减少整定维度。令观测器带宽为ω0,则:
| 参数 | 表达式 | 典型值(ω0 = 30) |
|---|---|---|
| β1 | 3·ω0 | 90 |
| β2 | 3·ω0² | 2700 |
| β3 | ω0³ | 27000 |
这个配置来自观测器特征多项式(s + ω0)³的展开系数。ω0越大,观测器收敛越快,但噪声放大也越明显。线性增益在误差大和误差小时用的是同一个比例系数,导致一个矛盾:误差大时希望增益高、收敛快,误差小时希望增益低、抗噪好。线性结构无法同时满足这两点。
2.3 NESO 的非线性机制:fal函数的行为拆解
NESO 把线性增益替换成韩京清提出的非线性函数fal(e, α, δ),定义如下:
fal(e, α, δ) = e / δ^(1-α) 当 |e| ≤ δ fal(e, α, δ) = |e|^α · sign(e) 当 |e| > δ其中α是幂次,取值范围一般在 0 到 1 之间;δ是线性段的区间宽度。这个函数的行为分两段看:
- 当
|e| > δ时,fal相当于一个压缩的符号函数。α越小,增益随误差增大而下降得越快,大误差时的输出幅值被压得越低,好处是初始误差很大时不会产生过大的控制冲击。 - 当
|e| ≤ δ时,fal退化为线性函数,增益是1/δ^(1-α)。δ越小,这个线性段增益越大,对微小误差的响应越灵敏。换句话说,误差小的时候观测器有高增益,误差大的时候增益自动降下来。
α和δ配合,让观测器在小误差时有高增益、大误差时有低增益,兼顾了收敛速度和噪声抑制。sign(e)是符号函数,|e|^α的幂次变换是为了让增益随误差平滑变化。
2.4 NESO 改在哪:从β·e到β·fal(e)
把 LESO 的误差项e换成fal(e, α, δ),就得到 NESO。这个改动带来一个直接效果:当估计误差很小时,fal的增益远大于线性增益,能更敏锐地捕捉到扰动起始时刻的微小变化;当估计误差很大时,fal的输出被压缩,观测器状态不会剧烈跳变,抗初始冲击能力更强。
在实际的 Simulink 仿真里,改动非常小,只是把乘法器前面的误差信号换成一个fal函数模块。但这个模块怎么实现,有不同的做法,下一章展开。
3. 在 Simulink 中搭建 NESO 模型:最小实现与模块选择
3.1 最小被控对象模型:二阶不确定系统
先搭一个被控对象。这里用最常见的二阶非线性系统:
x1' = x2 x2' = -25·x2 + 2·sin(x1) + w(t) + 5·u y = x1其中w(t)是外部扰动,-25·x2是阻尼项,2·sin(x1)是非线性项。控制增益b = 5,取估计值b0 = 4.5(故意偏差 10%,模拟模型不精确的情况)。Simulink 模型用积分器、增益、三角函数模块搭起来,注意两个积分器的初始值都设为零。
新建一个模型,拖入两个Integrator模块、一个Gain模块(增益设为 25,注意符号)、一个Trigonometric Function模块(选sin)、一个Add模块把各项加起来。w(t)用一个Signal Builder或Step模块注入。控制输入u先接常数 0,只观察观测器效果。
3.2 用 MATLAB Function 实现 NESO 的核心方程
NESO 本身不适合用零散模块搭,容易乱。我一般直接在 Simulink 里放一个MATLAB Function模块,把三阶观测器写成离散递推形式。双击模块,输入以下代码:
function [z1, z2, z3] = neso_observer(y, u, dt, param) % NESO 离散化递推实现 % 输入: % y - 系统输出 (x1 的测量值) % u - 控制输入 % dt - 固定步长 % param - 参数向量 [b0, beta1, beta2, beta3, alpha1, alpha2, alpha3, delta] % 输出: % z1, z2, z3 - 扩张状态观测器三阶状态 persistent z1k z2k z3k if isempty(z1k) z1k = 0; % 观测器初值 z2k = 0; z3k = 0; end b0 = param(1); beta = param(2:4); alpha = param(5:7); delta = param(8); % 误差 e = z1k - y; % fal 函数计算 fal1 = fal(e, alpha(1), delta); fal2 = fal(e, alpha(2), delta); fal3 = fal(e, alpha(3), delta); % 一阶欧拉离散 z1_next = z1k + dt * (z2k - beta(1) * fal1); z2_next = z2k + dt * (z3k - beta(2) * fal2 + b0 * u); z3_next = z3k + dt * (-beta(3) * fal3); % 更新 z1k = z1_next; z2k = z2_next; z3k = z3_next; z1 = z1k; z2 = z2k; z3 = z3k; end function f = fal(e, alpha, delta) if abs(e) <= delta f = e / (delta^(1 - alpha)); else f = (abs(e)^alpha) * sign(e); end end逻辑说明:用persistent变量保存三个状态,避免使用全局变量。dt必须是 Simulink 模型里设置的固定步长,比如 0.001 秒。param是 8 维数组,前 4 个是b0和观测器增益,后 4 个是 NESO 特有的非线性参数。每次调用先计算误差e,再用欧拉法递推三个状态。这个实现方式有一个前提——必须用定步长求解器,因为递推式里用到固定的dt。如果你用的是变步长求解器,dt传进去的只是一个常量,实际步长会变,结果就是仿真发散或精度下降。
参数说明:b0是控制增益的估计值,欠估计会导致z3稳态偏差;beta1影响z1的收敛速度;beta2影响z2;beta3影响z3(扰动估计)的收敛速度。三者数量级通常相差很大,后面整定章节细说。
3.3 用基本模块搭建的等价方案
如果不习惯用 MATLAB Function,也可以用 Simulink 基本模块搭出同样的结构。做法是:
- 三个
Integrator模块串联,分别输出z1、z2、z3。 - 第一个积分器输入是
z2 - beta1 * fal(e),第二个是z3 - beta2 * fal(e) + b0*u,第三个是-beta3 * fal(e)。 fal函数用Fcn模块写:输入设为u(1)(即误差e),表达式写if(abs(u(1)) <= delta, u(1)/(delta^(1-alpha)), (abs(u(1))^alpha)*sign(u(1)))。注意Fcn模块里不能直接用abs之外的复杂函数,sign要用sign(),MATLAB 的Fcn支持这个语法。
用基本模块的好处是每个信号都拉线可见,适合教学演示;缺点是模型图会比较大,连线容易错。建议:2.2 节提到的参数beta1 = 3*omega0、beta2 = 3*omega0^2、beta3 = omega0^3在模型里用Gain模块的乘系数直接填表达式,Simulink 支持在增益框里写计算式。
3.4 求解器与步长设置
NESO 的仿真必须注意求解器配置。打开Configuration Parameters → Solver:
- 求解器类型选定步长(Fixed-step),不要用变步长。
- 求解器选
ode4(四阶龙格库塔)或ode3,两个都够用。ode4精度更高,ode3速度更快。 - 步长设
0.001秒,即 1 kHz。步长再大(如 0.01 秒)会导致观测器递推误差增大,特别是z3的估计值会明显滞后;步长再小(如 0.0001 秒)仿真时间会拉长 10 倍,一般没必要。 - 如果模型里有
MATLAB Function模块,定步长情况下 Simulink 会在每个步长调用一次该函数,dt传参和实际步长一致。
提示:步长和观测器带宽
ω0之间有关系。经验上要求dt * ω0 < 0.1,否则离散化误差会积累,最终表现为高频震荡或发散。
4. NESO 的六个关键参数与整定步骤
4.1 参数速查表
NESO 在实际调试中需要关注的参数一共有六个,再加一个b0总共七个。下表给出每个参数的作用和典型范围:
| 参数 | 含义 | 典型范围 | 对行为的主导影响 |
|---|---|---|---|
ω0 | 观测器带宽 | 5 ~ 100 | 收敛速度与噪声放大 |
b0 | 控制增益估计 | 0.5b ~ 1.5b | 扰动估计的稳态偏差 |
α1 | fal 第一个幂次 | 0.5 ~ 1.0 | z1跟踪误差的压缩程度 |
α2 | fal 第二个幂次 | 0.5 ~ 1.0 | z2对速度估计的灵敏度 |
α3 | fal 第三个幂次 | 0.1 ~ 0.5 | z3扰动估计的收敛速度 |
δ | fal 线性区间宽度 | 0.005 ~ 0.1 | 稳态误差与噪声抑制的权衡 |
h | 采样步长(固定) | 0.0001 ~ 0.01 | 离散化精度,不算可调参数但影响上限 |
上面的范围是针对一般二阶机械系统、仿真步长 1 kHz 的经验值。电机控制里ω0常取 20~50,飞行器姿态控制里取 10~30,具体要看传感器噪声水平。噪声大就调低ω0和调大δ。
α的取值逻辑:α1和α2可以取同一个值,一般 0.5 左右;α3要小一些,比如 0.25,这样可以加快对扰动变化率的跟踪,但代价是容易引入高频分量。
4.2 从带宽法出发的整定顺序
NESO 虽然有七个参数,但不要一上来就全部手动调。常见的做法是先用线性 ESO 把ω0和b0调好,再引入非线性项。具体顺序:
- 先把
fal换成线性,即令fal(e) = e。此时α和δ参数无效,只留β1 = 3ω0、β2 = 3ω0²、β3 = ω0³。从ω0 = 20开始,观察z1对测量信号y的跟踪误差。如果跟踪慢,逐步增大ω0到 40、60;如果z1出现高频毛刺或震荡,说明ω0太高或步长不够小,回调一档。 - 调
b0。给被控对象加一个已知的阶跃扰动(比如w = 1),看z3的稳态值。如果z3稳态值大于 1,说明b0偏小,扰动被放大;如果小于 1,说明b0偏大。调整b0使z3稳态值接近真实扰动幅值。 - 切换为 NESO。把误差通路换成
fal函数,α1 = α2 = 0.5,α3 = 0.25,δ = 0.01。保持β不变,观察z3的响应曲线。 - 精调
δ。如果z3在稳态时有明显噪声,逐步增大δ到 0.02、0.05,观察噪声是否被压下去。如果z3对扰动的响应出现明显滞后,逐步减小δ。δ每改动一次,至少跑完一个完整仿真周期再看,不要看瞬间波形。 - 微调
α3。如果z3对突变扰动的超调量太大,把α3从 0.25 降到 0.1;如果扰动变化快但z3跟不上,把α3提高到 0.4。
这套顺序的核心思想是:用带宽法锁定稳定性区间,再用非线性项改善性能,避免上来就跌入多维参数搜索。
4.3 仿真发散排查:模型刚性、代数环、初始值
NESO 仿真发散是最常见的问题,特别是在ω0调大之后。发散来源一般有三个:
第一个是模型刚性。观测器带宽ω0大意味着闭环响应快,系统动力学出现时间尺度差异。Simulink 的变步长求解器会发现步长被压得极小但误差仍然超限,最终报错或输出无穷大。排查方法是打开求解器的Max Step Size,手动设置为dt的 10 倍,看是否改善。如果仍然发散,很可能是ω0 * dt > 0.1,把ω0调低或减小步长。
第二个是代数环。当MATLAB Function模块的输出直接反馈回同一个模块的输入时,Simulink 会提示 Algebraic Loop。NESO 的方程是显式递推,理论上不会产生代数环,但如果把z3直接接回了被控对象模型的某个输入,而这个输入又决定y,就会形成环。排查方法是看诊断窗口有没有 Algebraic Loop 警告。解决方法是把反馈信号用Unit Delay模块延迟一个步长,或者把控制律放在同一个 MATLAB Function 里统一计算。
第三个是初始值不匹配。观测器的初始状态z1(0)、z2(0)和被控对象的真实初始状态偏差过大时,初始误差会经过β3放大,导致z3在仿真起始瞬间出现巨大的尖峰,尖峰又反馈到控制量,造成发散。处理方式有两种:一是把观测器初值设成与对象状态一致;二是在z3输出后加一个Saturation模块限幅,范围设为真实扰动可能最大值的 1.5 倍。
提示:如果你拿到手的模型一开始就跑不动,先检查是不是所有
Gain模块的增益都填了数值,而不是变量名。MATLAB Function里引用的变量如果不在param数组中,也会报未定义错误。
5. NESO 仿真验证技巧:三种检验方法与实用建议
5.1 零初值阶跃扰动估计验证
搭好模型后,第一个验证实验用阶跃扰动。给被控对象注入一个幅值 1.0 的Step信号作为w(t),控制输入u保持为 0。运行仿真后把z3和真实扰动w画在同一张图上。正确的行为是:z3在大约5/ω0秒内追上w的幅值,稳态误差不超过 5%。如果z3稳态值低于 1,检查b0是否偏大;如果高于 1,检查b0是否偏小。z1应该紧密贴合y,误差量级在1/(ω0²)左右,这是带宽法给出的理论精度。
5.2 正弦扰动跟踪对比
阶跃扰动验证的是稳态精度,正弦扰动验证动态跟踪能力。把w(t)换成幅值 0.5、频率 5 Hz 的正弦波。在 NESO 输入端加一组正弦波发生器,Simulink 里用Sine Wave模块,幅值 0.5,频率 2π×5 rad/s。观察z3对正弦扰动的跟踪幅值和相位差。NESO 相对 LESO 的优势这里会体现出来——同样ω0下,z3的相位滞后更小、幅值衰减更少。如果z3的波形明显畸变,把α3往小调,比如 0.1,畸变通常会缓解。
5.3 NESO 输出接入控制器的闭环验证:z3补偿方向测试
NESO 估计出总扰动z3后,最终要进入控制律做前馈补偿。常用的 ADRC 控制律是u = (u0 - z3) / b0,其中u0是反馈控制器(比如 PD)的输出。这里有一个特别容易出错的点:补偿符号。如果符号反了,z3会把扰动推得更大而不是抵消。测试方法:给系统加一个正向阶跃扰动,观察被控对象输出。若补偿方向正确,输出的偏移量会明显减小;若方向错误,输出会朝扰动方向加速跑偏。排查方向错了就在控制律里把减法改成加法,看波形是否恢复正常。
在把 NESO 模型用于生成嵌入式代码前,还要做两个检查:一是确认MATLAB Function里的所有运算都是定点和离散兼容的,persistent变量不能被代码生成器优化掉;二是检查dt是否与实际中断周期一致,这直接决定z3在硬件上的估计精度。Simulink 的Code Generation选项里有静态代码检查工具,跑一次能发现不少隐藏的类型不匹配问题。
本文还有配套的精品资源,点击获取