1. 为什么要在Simulink里折腾“观测器”:无传感器控制的现实困境
做电机控制的人应该都有过这样的纠结:想省掉编码器或者旋转变压器,但又怕速度估计不准,系统转着转着就失步。特别是做电动汽车驱动、工业伺服这类场景,编码器不但占空间,还存在线缆磨损、振动失效、安装精度要求高等一堆麻烦。这时候大家都会想到“无速度传感器控制”,而其中工程化程度最高、数学上最干净的一条路,就是标题里提到的龙伯格观测器(Luenberger Observer)。
Luenberger观测器本质上是一个“状态重构器”:电机运行时你能实实在在测到的只有三相电流和母线电压,但控制器真正需要的是转子角度和速度。角度和速度藏在反电动势里,不能直接用电压表量出来,于是我们就用状态方程把“看不见的量”估计出来。用Simulink做这件事有几个天然优势:一是模型可视化,积分器、增益、反馈环在框图里一目了然,方便学生和工程师理解;二是参数扫描和波形分析方便,调一个增益就能立刻看到收敛速度的变化;三是这套搭建思路可以直接生成嵌入式代码,从仿真到上机是一脉相承的。
我见过不少初学者在Simulink里搭好模型后,波形不是发散就是震荡,最后怀疑算法本身有问题。其实大部分问题出在一件事:观测器增益怎么取、反馈修正项怎么加。这篇我想把从电机方程到Simulink模型,再到调参实测的完整路径讲清楚,尤其是公式和框图之间怎么对应,以及几个很容易踩的坑。
如果你已经在做FOC或者矢量控制,那这篇内容可以直接补上“无速度传感器”这一环;如果你是刚接触Simulink的新手,我建议先把dq坐标系下的电机模型复习一下,再来读这篇文章,会顺畅很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 建模前必须想清楚的几个前提:从电机方程到可观测性
2.1 为什么选静止坐标系而不是旋转坐标系
很多做FOC的同学习惯在dq旋转坐标系下思考问题,因为那里能直接看到直轴电流、交轴电流和磁链关系。但Luenberger观测器通常不放在旋转坐标系里,而是放在αβ静止坐标系里。原因很直接:dq坐标系下的方程里明显带着转子位置θe,如果位置本身都不知道,模型里的变量就不完整,根本没法直接写状态方程。
静止坐标系下的电压方程是比较清爽的。以表贴式永磁同步电机为例,通常可以认为直轴电感和交轴电感相等,写成:
code复制v_α = R_s * i_α + L_s * di_α/dt + e_α
v_β = R_s * i_β + L_s * di_β/dt + e_β
其中e_α、e_β就是反电动势在αβ轴上的分量。如果把反电动势再看细一点:
code复制e_α = -ω_e * ψ_f * sin(θ_e)
e_β = ω_e * ψ_f * cos(θ_e)
这里ω_e是电角速度,ψ_f是永磁体磁链,θ_e是电角度。从这两个式子就能看出:只要估计出e_α和e_β,角度和速度信息就在里面。这就是整个转速估计的物理基础。
2.2 把反电动势扩成状态变量
如果我们只把电流当作状态,那反电动势就成了未知扰动,没法直接设计观测器。所以常用做法是把e_α、e_β当成两个新增的状态变量,组成一个四阶状态向量:
code复制x = [i_α, i_β, e_α, e_β]^T
输入量就是电机端电压和电流变换后的静止轴分量:
code复制u = [v_α, v_β]^T
y = [i_α, i_β]^T
于是状态方程可以写成:
code复制dx/dt = A * x + B * u
y = C * x
其中:
code复制A = [ -R_s/L_s 0 -1/L_s 0
0 -R_s/L_s 0 -1/L_s
0 0 0 -ω_e
0 0 ω_e 0 ]
B = [ 1/L_s 0
0 1/L_s
0 0
0 0 ]
C = [ 1 0 0 0
0 1 0 0 ]
这里最关键的一步是用了ω_e近似恒定的假设,也就是认为在一个控制周期内转速变化不大,所以反电动势的导数可以写成e_α关于ω_e的旋转关系。仿真里只要速度变化不是极端剧烈,这个假设基本成立;但像电机急加减速或低速带载这类工况,这个假设会带来误差,后面调参部分我会专门讲怎么处理。
2.3 为什么要考察可观测性
观测器设计的前提是系统可观测。通俗说,就是根据测量到的输出能够反推出所有状态变量。对这个四阶系统,我们用可观测性矩阵来判断:
code复制O = [C; C*A; C*A^2; C*A^3]
计算后会发现,当ω_e=0时,反电动势状态和电流状态之间的耦合消失,可观测性会变差,甚至不可观。这就从数学上解释了为什么很多无速度传感器控制方案在零速或极低速段无法正常工作,需要在启动时用开环或高频注入等方式先拖起来,等到有一定转速再切入观测器。这不是某个软件实现的局限,而是系统本身的物理性质决定的,理解这一点对后续做启动策略非常有帮助。
3. 观测器增益设计:L矩阵怎么定,带宽和噪声如何权衡
3.1 观测器的标准形式
Luenberger观测器的结构其实一句话就能说清楚:我用模型算出一个预测结果,再用测量值和预测值之间的误差去修正模型。写成方程就是:
code复制dx̂/dt = A * x̂ + B * u + L * (y - C * x̂)
其中x̂是估计状态,L是增益矩阵,它决定了预测误差对状态修正的影响强度。如果模型参数和真实电机一致,并且L选择得当,那么估计误差会随着时间收敛到零,收敛速率就取决于矩阵“A - L*C”的特征值。
3.2 极点配置的正确姿势
设计L矩阵最直接的方法是极点配置。观测器的期望动态速度要比控制器电流环快,通常选为电流环带宽的3到10倍,但又不能快到把测量噪声放大得不可收拾。比如电流环带宽约200Hz,那么观测器闭环极点可以设在600到2000Hz左右,对应角频率约4000到12000 rad/s。具体位置可以根据仿真效果微调。
在MATLAB里,用place函数是基于连续系统设计的:
code复制L = place(A', C', p)';
这里的p是你希望配置的四个极点。注意place对重极点比较敏感,如果设置四个相同的实极点可能会报警告,建议稍微错开一点,比如:
code复制p = [-1000, -1050, -800+1j*600, -800-1j*600];
别看这四个极点是随手写的,实际调的时候它们对收敛速度的影响立竿见影。模值越大,估计值收敛越快,但噪声放大越明显;模值越小,波形越平滑,但动态跟踪越迟钝。本质上就是带宽和噪声的博弈。
3.3 不想手调极点,就用LQR试一圈
如果你觉得用手工试极点太粗暴,更稳健的办法是把观测器设计看成Kalman滤波的对偶问题,用LQR来解。简单说,你给状态估计误差和测量噪声各设一个权重矩阵,然后让算法自动算出最优L矩阵。举个例子:
code复制Q = diag([1, 1, 1e-3, 1e-3]);
R = diag([0.01, 0.01]);
L = lqr(A', C', Q, R)';
Q里的第三、四个元素对应反电动势状态,一般设得比较小,因为反电动势本身变化平缓,不需要用大增益去猛追。R是对电流测量噪声的惩罚,如果电流采样噪声大,适当增大R,所得L会更平滑。
这个“Q和R怎么选”看着玄,其实处理起来有套路:先把Q的前两个元素设为1,后两个元素设为反电动势幅值的倒数分之一,然后仿真看电流噪声是否明显、速度波形是否发毛。用LQR的好处在于,电机参数变化时重算L很方便,不用重新手工定极点。
3.4 设计完成后必须做的一个动作
不管你用哪种方法定出L矩阵,设计完以后一定回到eig(A - L*C)看一眼闭环极点是否落在左半平面,并且留出足够的稳定裕度。我遇到过有人把符号写反,增益矩阵符号错了但极点看似稳定,结果上机转起来噪声放大到无法忍受。这个动作十秒钟,却值千金。
4. Simulink模型搭建全过程:从状态方程到可跑通的仿真框图
4.1 先用连续积分器搭原型
我在Simulink里搭Luenberger观测器时,喜欢先用连续积分器模块验证算法,波形漂亮,调试直观。整体思路就是把x̂的微分方程拆成四个积分器,每个积分器对应一个状态。
观察状态方程:
code复制dx̂/dt = A * x̂ + B * u + L * (y - C * x̂)
你会发现右边就是三个部分相加:模型自身的动态、输入的作用、修正项。我给初学者一个非常直观的模块映射:
- 使用Constant模块存放A、B、C、L矩阵;
- 使用Gain模块计算Ax̂和Cx̂;
- 使用Add模块把Ax̂ + Bu + L*(y-C*x̂)合并;
- 四个积分器Integrator输出x̂;
- 输出后从x̂里提î_α、î_β、ê_α、ê_β。
搭好之后的反馈会把观测器包成一个闭环结构,注意这里的反馈来源是实际测量的电流,而不是观测器自己输出的电流。很多新手在这里容易连错线,把y接到了x̂上,结果修正项恒等于零,观测器退化成纯开环模型。
4.2 换成离散观测器:更接近上机代码
连续模型能验证思路,但上机必须离散化。离散化方式有几种,我推荐足够简单并且工程上常用的欧拉法。设采样周期为T_s,则状态矩阵离散化后可近似写作:
code复制A_d = eye(4) + A * T_s
B_d = B * T_s
离散观测器方程就是:
code复制x̂[k+1] = A_d * x̂[k] + B_d * u[k] + L_d * (y[k] - C * x̂[k])
增益L_d可以先用连续L乘以T_s近似,也可以在离散系统上用dlqe或place重新设计。一个值得注意的细节是,当采样周期在微秒级、状态矩阵元素比较小时,欧拉近似完全够用;但当系统动态明显快过采样速率,就得用更精确的c2d。你的采样时间最好是电流环控制周期的一半到十分之一,这样才能保证观测器输出不成为控制环的短板。
4.3 MATLAB Function模块写离散逻辑
我很推荐在Simulink里用一个MATLAB Function模块承载观测器逻辑。你是可以从基础模块搭起来的,但用代码维护矩阵运算更直观。
以离散欧拉法为例:
matlab复制function [i_alpha_hat, i_beta_hat, e_alpha_hat, e_beta_hat] = luenberger_step(...)
persistent x_hat;
if isempty(x_hat)
x_hat = zeros(4,1);
end
A_d = [1 - Rs*Ts/L, 0, -Ts/L, 0; ...
0, 1 - Rs*Ts/L, 0, -Ts/L; ...
0, 0, 1, -omega_e*Ts; ...
0, 0, omega_e*Ts, 1];
B_d = [Ts/L, 0; 0, Ts/L; 0, 0; 0, 0];
C = [1 0 0 0; 0 1 0 0];
y = [i_alpha; i_beta];
u = [v_alpha; v_beta];
x_hat = A_d * x_hat + B_d * u + L_d * (y - C * x_hat);
i_alpha_hat = x_hat(1);
i_beta_hat = x_hat(2);
e_alpha_hat = x_hat(3);
e_beta_hat = x_hat(4);
end
这里有个非常实用的经验:初始化时一定要把x_hat设为[0;0;0;0],而实际电机在零速时反电动势也是零,这样能避免启动瞬间的过大跳变。
4.4 从反电动势到转速和角度的提取
好,现在你手里已经有ê_α和ê_β两个估计值,剩下的工作是提取速度ω_e和角度θ_e。最常见的做法是直接用atan2:
code复制θ_e_hat = atan2(-e_α_hat, e_β_hat)
这个式子的正负号和你的坐标变换定义有关,很多人在这里栽跟头。我建议拿到波形后,把估计角度和真实角度放在一个scope里对比,如果转速输出正常但角度符号反了,说明atan2参数顺序要调整。
然而直接用atan2有一个缺点:角度对噪声非常敏感,而速度又要通过对角度求导得到,求导会再次放大噪声。所以工程上更推荐用PLL锁相环结构来提取角度和速度。做法也很简单:把ê_α、ê_β的包络归一化,然后构造一个角度误差,再通过一个PI调节器输出估计的ω_e,积分后就得到估计的θ_e。这个PLL本身就是一个小闭环,内部带宽通常放在50到200Hz。这样做出来的速度波形平滑,还天然滤掉了很多高频干扰。
我在仿真模型里通常把观测器核心和PLL分开写成两个子系统,前者输出ê_α、ê_β,后者输出速度和角度。这样出问题时能快速定位:是转矩修正不行,还是PLL跟踪不好。
4.5 整个仿真模型的信号连接顺序
一个完整可跑通的无速度传感器FOC仿真模型,信号链一般是:
- 电机模型模块(自带真实速度和角度);
- 三相电流采样和Clarke变换,得到i_α、i_β;
- PWM指令或逆变器输出电压重构,得到v_α、v_β;
- 电流电压送入观测器子系统,得到ê_α、ê_β;
- PLL子系统输出ω̂_e、θ̂_e;
- 控制器用θ̂_e做Park变换,用ω̂_e做速度反馈。
一定要注意的是第3步。很多人在仿真里直接用电机模型端口的电压值做输入,看似很合理,但实际系统中电压是从直流母线电压和占空比重构出来的,和理想电压存在一个逆变器死区的影响。仿真阶段如果直接使用理想电压源,观测器性能可能会比实机好很多,等你上机时就发现问题。最稳妥的方式是仿真时也加入逆变器环节的开关平均电压重构,哪怕用最简单的平均模型,也能提前暴露一部分误差。
5. 调参与实测:从波形异常到收敛性能的判断
5.1 增益过小:估计滞后
当你第一次把模型跑起来,如果发现估计转速总是一段滞后地贴在真实转速后面,大概率是L矩阵太小,也就是观测器带宽不够。这种情况下,观测器太依赖模型预测,对测量电流误差不敏感。你只需要把L矩阵整体放大几倍,或者把极点模值调大,滞后就会明显改善。
但有个细节容易忽略:滞后还可能是PLL带宽不足造成的。观测器输出ê_α、ê_β本身已经跟上了,但PLL的PI调节器输出速度过慢。怎么区分?很简单,在PLL之前把ê_α、ê_β和电机模型输出的真实e_α、e_β放在一起对比。如果反电动势估计波形重叠得很好,问题就在PLL参数;如果反电动势都跟丢了,问题才在观测器增益。
5.2 增益过大:噪声毛刺和震荡
相反,如果你发现估计转速波形像刺猬一样满是毛刺,或者稳态时出现小幅振荡,甚至系统发散,那就是L矩阵太大了。过大的增益会把电流采样噪声直接通过修正项灌进状态估计里。
解决办法不是单纯调小一个系数,而是要把L矩阵的每一项按物理意义分开考虑。具体说,电流状态对应的增益项(L第一、二行)对噪声最敏感,应该适当减小;反电动势状态对应的增益项(L第三、四行)影响收敛速度,可以和PLL带宽配合起来调。手动调节时一次只动一个量,避免矩阵里多个元素乱改后找不到原因。
5.3 模型参数不准时会发生什么
我在某个项目中曾故意把电机电阻设为真实值的1.2倍,看看观测器会怎样。结果发现:稳态速度误差不大,但动态过程中估计角度会出现固定偏差,而且载荷越大偏差越明显。原因很直观,电阻误差会直接影响电流方程里的压降项,而这个误差和电流大小成正比。
如果同时把电感设偏,问题会更明显,因为反电动势方程的斜率和瞬态响应都会受到影响。工程上比较有效的策略是:把观测器中模型参数的和控制中用的参数保持一致,不要单独只改观测器。很多高级方案会加在线参数辨识,但那是后话;初学者首先要保证模型参数和电机铭牌参数一致,至少数量级不能错。
5.4 低速和零速工况的救场手段
前面说过,零速时反电动势为零,Luenberger观测器无法正常工作。实际项目里常见的做法是“开环启动”然后“切入闭环”:刚开始用I/f控制或者压频控制把电机拖到一定转速,比如额定转速的5%到10%,等到观测器输出的角度波动收敛稳定,再把控制权从开环切换到闭环。
我在Simulink里会专门做一个使能模块:当估计速度超过阈值且角度误差小于几度时,切换开关才动作,把θ_real换成θ_est。切换瞬间如果处理不好,可能会引起电流尖峰,所以通常的做法是在切换前短暂使用斜坡过渡,让矢量控制的角度从一个初始值平滑地滑向估计角度。这个细节在仿真里很容易被忽略,却是上机成败的关键之一。
5.5 用仿真结果验证观测器性能的指标
调完之后不要只看转速波形像不像。我习惯看三个指标:第一,估计电角度和真实电角度的差值,稳态时应该小于几度电角度;第二,估计转速在阶跃加载后恢复到稳态的时间,这个反映了动态跟踪能力;第三,电流波形中高频毛刺的幅值,这反映了噪声抑制能力。把这三个曲线放在一张图中对比,很容易判断一次调参是好是坏。
6. 排查链路记录:为什么我的模型波形总是不对
搭过几版Simulink模型之后,我发现绝大多数学员会栽在同样几个地方。这里给出一份排查链路,你可以按顺序检查。
一是代数环。如果你把观测器中的修正项直接用K增益接到积分器输入,而电流测量信号又经过了一个同样的计算环节,可能会出现代数环。Simulink在编译时可能提示,也可能不提示但仿真会有大量警告。解决办法是在反馈路径上插入一个Unit Delay或Memory模块,破掉这个环。延迟一个周期对观测器影响远小于不稳定的代数环。
二是数值类型和采样时间的冲突。固定步长仿真里,电气时间常数往往很小,如果你的采样时间设成了1e-5,但电机电感参数导致的动态频率很高,模型可能根本无法收敛。我建议先跑一个简单的开环仿真,确认电机模型本身稳定,再接入观测器。否则你分不清发散是算法问题还是数值问题。
三是积分器初始值不一致。观测器的初始状态应该和电机的初始状态一致。观察电机模型从静止开始启动时,反电动势初始为零,观测器状态初始也应设为零。但在我调试的很多项目里,大家会把观测器状态初始值随手设为一个随机向量,结果启动瞬间出现惊人的过冲。解决办法就是在观测器子系统里加一个Init Reset逻辑,每次使能开始时清零或者设为预设值。
四是坐标变换正负号不一致。αβ坐标变换有两种常见约定,角度都差着90度。观测器和Park变换必须使用同一套约定。我常常看到一个项目里电机模型用A相轴线对齐α轴,控制器却在角度上加了一个固定偏移,导致观测器工作在错误坐标系下。这类问题不好查,但只要把θ_real和θ_est放入同一scope,很快就能发现它们之间存在一个固定差角。
五是速度滤波和观测器带宽混在一起。很多人看到估计转速太毛刺,直接上一阶低通,滤波时间常数调到几十毫秒。这种做法会让速度环看起来稳定,但会引入明显的相位滞后,动态性能变差。更好的做法是先限制观测器带宽,再用PLL内置的PI调节器做平滑,而不是额外叠加一个大惯性滤波环节。
讲到最后,我个人的经验是:Luenberger观测器在Simulink里实现的难点从来不是公式推导,而是状态方程和框图结构的一一对应,以及对“修正增益到底该多大”这个分寸感的理解。你如果能把一个仿真模型从发散调到稳定,再调到动态跟手、噪声可控,那你在无传感器控制这条路上已经超过大多数只背公式的初学者了。之后再去接触扩展卡尔曼滤波器、滑模观测器,会发现它们本质上都是“模型预测+误差修正”这个思想在增益和鲁棒性上做了不同的升级。
