前段时间调试一台循环水泵的驱动电机,振动明显比以往大,电流波形也出现了一些不规则的毛刺。我一边抱着笔记本在现场采集数据,一边在MATLAB里做离线频谱分析,再用Python写在线监测脚本。那几天最大的感受就是:信号处理和电机控制这两个领域,MATLAB和Python确实是绕不开的“神器”。说神器不是营销话术,而是这两套工具几乎覆盖了从理论推导、离线分析到工程部署的完整链路。
这篇文章我就想从自己最近这段实测经历出发,把信号处理和电机控制里最常用的几个技术点拆开聊一聊,包括信号预处理、FFT故障特征频率定位、PI控制器的离散化实现,以及MATLAB与Python如何配合工作。每块都会穿插可直接运行的代码,代码不算复杂,但每段背后都有我踩过坑后总结的参数依据和注意事项。适合正在做电机故障诊断、驱动器调试、或者刚把信号处理与控制理论结合起来做项目的朋友参考。
1. 为什么我坚持MATLAB和Python双修:两套工具在信号处理与电机控制里的分工
很多初学者喜欢问一个问题:学MATLAB还是学Python?我的答案是两个都要用,但用在不同阶段。这个观点不是我一开始就有的,而是在一次完整的电机控制项目里被逼出来的。
1.1 MATLAB的强项:从工具箱到Simulink,适合把问题想透
MATLAB真正厉害的地方不是语法,而是它的工具箱体系。做信号处理有Signal Processing Toolbox,做控制系统有Control System Toolbox,做电机仿真有Simulink和Simscape Electrical。这意味着你想验证一个理论,不需要从零开始写算法,工具箱函数已经帮你把底层细节处理好了。
我常用MATLAB做三件事。第一是离线数据分析,比如采集一段振动信号,我会先用pspectrum或者fft快速看频谱,用findpeaks找峰值频率,整个过程非常直觉化。第二是控制系统设计,sisotool可以直接打开根轨迹和Bode图界面,拖一拖零极点就能看到阶跃响应变化,这对于整定电流环和速度环的PI参数帮助很大。第三是Simulink建模仿真,在把算法写到真实控制器之前,先用Simulink跑一遍模型在环仿真,能提前发现很多逻辑错误。
MATLAB的代码风格也比较接近数学表达,y = filter(b, a, x)、Y = fft(x)、[Kp, Ki] = pidtune(sys, 'pi')这种写法让工程师能把精力集中在“公式长什么样”而不是“数组怎么循环”。所以我认为MATLAB最适合用来把问题想清楚,做理论验证和参数整定。
1.2 Python的强项:脚本化、批量化和部署集成
Python在信号处理和电机控制里的角色,更多是工程化和自动化。比如我需要批量处理100个测试文件,用MATLAB虽然有dir和for循环也能做,但Python配合pathlib、pandas和scipy处理起来更自然,尤其是要从Excel或数据库里读取配置参数、把结果写到报表里的时候。
另一个优势是生态。数据分析用numpy和pandas,信号处理用scipy.signal,画图用matplotlib,机器学习诊断算法可以用scikit-learn,这些库之间的衔接非常顺滑。我在做电机振动异常分类时,用Python从原始信号提取特征,直接丢给随机森林模型训练,全程不需要换语言、导数据、做格式转换。
Python还有一个MIT的特别优势,就是免费且跨平台。在车间电脑、树莓派或者工控机上装一个Python环境就能跑采集脚本,而MATLAB在企业里往往面临授权问题。后来我做的在线监测系统,采集端和告警端全部用Python实现,MATLAB只负责前期的算法研究和参数标定。
1.3 我的双修工作流:什么时候用谁
经过几次项目磨合,我总结出一套相对固定的双修工作流,这里列出来供参考:
| 工作阶段 | 推荐工具 | 理由 |
|---|---|---|
| 算法调研与理论推导 | MATLAB | 工具箱全,公式表达直观 |
| 离线数据分析与特征提取 | MATLAB或Python | 数据量大时Python更快,快速出图用MATLAB |
| 控制参数整定 | MATLAB + Simulink | pidtune和仿真环境成熟 |
| 批量数据处理与自动化 | Python | 文件遍历、表格读写、脚本管理方便 |
| 实时采集与在线监测 | Python | 跨平台、易部署、社区库多 |
| 嵌入式代码生成 | MATLAB Coder / Simulink | 可直接生成C代码,减少手写错误 |
简单说,我习惯用MATLAB做“脑力活”,用Python做“体力活”。两者并不是竞争关系,而是互补关系。后面的内容,我也会在每个技术点里同时给出MATLAB和Python的代码,方便你对照着用。
2. 信号预处理三板斧:重采样、去趋势与滤波器的工程参数
拿到一段原始信号,我的习惯是不要急着做FFT或者训练模型,先做预处理。预处理做不好,后面的频谱分析会得到一堆没有物理意义的“虚假峰值”,控制回路也会被噪声带偏。信号预处理看起来基础,但工程参数选错了,结果差之千里。
2.1 多源数据对齐:重采样解决采样率不一致
我在那次水泵电机诊断中,同时采集了三路信号:驱动电机的电流、泵壳的振动加速度、还有转速计的脉冲信号。问题来了,振动传感器采样率设为10 kHz,电流采集是4 kHz,转速脉冲是1 kHz。如果直接把这三路数据按索引对齐,时间轴上完全是错位的,频谱里的特征频率会“漂移”。
解决办法是重采样,把所有信号统一到相同的采样率和时间基准。MATLAB里可以用resample函数,Python里用scipy.signal.resample_poly或scipy.signal.resample。
import numpy as np from scipy import signal # 假设振动信号 fs=10000,目标重采样到 fs_target=4000 fs_old = 10000 fs_target = 4000 vibration = np.random.randn(100000) # 示例数据 # 用多相滤波重采样,避免直接用np.interp带来的频谱混叠 vibration_resampled = signal.resample_poly(vibration, fs_target, fs_old)% MATLAB等效操作 fs_old = 10000; fs_target = 4000; vibration = randn(100000, 1); vibration_resampled = resample(vibration, fs_target, fs_old);注意,resample_poly的上采样因子和下采样因子必须互质,否则要先约分。如果两个采样率比值很怪,比如10000比4000,化简后是5比2,那直接用这组数就行。我踩过的坑是直接用了10000和4000,报错才发现scipy要求传的是最简比,这个细节很多人第一次都会遇到。
重采样之后,还要把三路信号的起始时刻对齐。最可靠的方式是用转速脉冲的上升沿作为同步基准,把所有数据都按第一个上升沿时刻截取对齐。否则哪怕采样率统一了,起始时刻差几毫秒,计算出的相位关系也是错的。
2.2 去直流与去趋势:别让基线把频谱带偏
电机振动信号里往往有一个很大的直流分量,也就是信号的均值。如果不做去均值直接做FFT,0 Hz处会有一个巨大的谱线,它会把周围低频成分的幅值“压”下去,看起来很不直观。另外,如果信号存在缓慢漂移,比如传感器温漂、放大器零点漂移,频谱的低频段会出现一个“斜坡”式的抬高,影响对低频特征频率的判断。
去直流最简单的方法就是直接减去均值,或者用高通滤波器。去线性趋势可以用detrend。
import matplotlib.pyplot as plt from scipy import signal # 原始信号含直流偏置和线性趋势 n = len(vibration_resampled) trend = 0.05 * np.arange(n) + 2.0 x_with_trend = vibration_resampled + trend x_detrend = signal.detrend(x_with_trend, type='linear') # 去均值可以一并由detrend完成,也可以手动减去 x_detrend = x_detrend - np.mean(x_detrend)x_detrend = detrend(x_with_trend, 'linear'); x_detrend = x_detrend - mean(x_detrend);这里有个工程判断:并非所有场景都适合去趋势。如果我要分析的是转速爬升过程中的低频变化,趋势本身就是有用信息,就不能简单detrend。我一般先画原始波形观察,确认是传感器漂移还是真实物理过程,再去决定是否去趋势。
2.3 数字滤波器设计:阶数、截止频率与相位响应
滤波器是整个预处理里最需要讲参数的部分。以振动信号为例,我要关注的特征频率在2 kHz以内,而振动传感器在高频段有很多结构共振噪声,这时就需要低通滤波器。但滤波器不是随便设一个截止频率就完事,有几个参数必须考虑。
首先是滤波器类型和阶数。我常用butterworth和ellip。巴特沃斯幅频特性平坦,相位响应在通带内有非线性,但阶数低时影响可控;椭圆滤波器过渡带窄,但阻带有纹波。对于离线分析,我倾向于巴特沃斯,因为它参数直观;对于实时控制环路上的滤波,后面会说,我会用更简单的一阶低通。
截止频率的选择要基于特征频率分布。我用的是fs_target=4000 Hz,如果目标特征频率最高是1200 Hz,那低通截止频率可以设在1600 Hz左右,留出过渡带。截止频率太高,噪声滤不干净;太低,会把有用的特征频率削掉,比如特征频率是1000 Hz,截止频率设为800 Hz,那就全没了。
阶数的确定,主要看过渡带要求。比如从通带1600 Hz到阻带2000 Hz,衰减40 dB以上,这需要4阶左右的巴特沃斯。工程上我会先用butter设计,画出幅频图,如果衰减不够就加阶数,而不是靠猜。
from scipy import signal b, a = signal.butter(4, 1600 / (fs_target/2), btype='low', output='ba') y_filtered = signal.filtfilt(b, a, x_detrend)[b, a] = butter(4, 1600/(fs_target/2), 'low'); y_filtered = filtfilt(b, a, x_detrend);2.4 MATLAB和Python的滤波器设计对照
我经常两套工具交替用,这里做一个简单对照,方便你按需选择。
| 设计功能 | MATLAB | Python |
|---|---|---|
| 巴特沃斯设计 | [b,a] = butter(n, Wn) | scipy.signal.butter(n, Wn) |
| FIR设计 | fir1(n, Wn, 'low') | scipy.signal.firwin(n+1, Wn) |
| 零相位滤波 | filtfilt(b,a,x) | scipy.signal.filtfilt |
| 实时滤波 | filter(b,a,x) | scipy.signal.lfilter |
| 功率谱密度 | pwelch(x, window, nfft) | scipy.signal.welch |
| 峰值查找 | findpeaks(x, 'MinPeakHeight', v) | scipy.signal.find_peaks |
用filtfilt的好处是零相位偏移,适合离线分析;但它的输出在信号两端会出现振铃,所以我在分析时会把首尾各切掉一段。如果用filter做实时滤波,相位滞后是必然的,后续做控制环路时必须补偿。这个差异我在最后一部分会专门展开讲。
预处理这块,我的建议是:先把采样率统一,再处理趋势和直流,最后滤波。顺序不要乱。如果你重采样后再滤波,效果会更好;如果先滤波再重采样,滤波器设计时要用原始采样率,容易算错归一化频率。
3. 用FFT定位电机故障特征频率:参数、公式与易错点
信号预处理做完,接下来就是频谱分析。FFT是电机故障诊断里最基础也是最高效的手段,没有之一。但FFT本身并不神秘,真正有价值的是理解它的参数设置和特征频率背后的物理意义。
3.1 为什么FFT是电机故障诊断的第一工具
电机运行时的振动、电流、噪声信号里都包含了丰富的频率成分。拿振动来说,正常电机在转频及其倍频处会有幅值较小的谱峰;一旦出现轴承磨损、转子偏心、不对中,某些特定频率处的幅值会明显抬升。FFT能把时域信号分解到频域,让我们直接看到“哪个频率分量有多大”,诊断逻辑就变成了:找到突出频率,对照特征频率表,判断故障类型。
我还记得自己第一次做FFT时犯的错:直接把时域波形丢进fft函数,然后看复数模值,结果发现0 Hz处幅值巨大,其他地方全是噪声。后来才明白是没去均值、没加窗、没归一化。FFT出图容易,出对图难。
3.2 故障特征频率估算:转频、轴承系数与边带
做频谱分析前,得先手动算一下可能出现的特征频率,做到心里有数。常用公式如下:
- 轴转频:
f_r = n / 60,n为转速(rpm)。 - 转子不平衡:主要出现在
1 × f_r。 - 不对中:常出现在
2 × f_r,有时也有1 × f_r成分。 - 滚动轴承外圈故障:
BPFO ≈ 0.4 × N × f_r。 - 滚动轴承内圈故障:
BPFI ≈ 0.6 × N × f_r。 - 滚动体故障:
BSF ≈ 0.2 × f_r × N × (D/d ± 1),近似值。 - 电机极通过频率:
f_p = 2 × f_line(工频供电时),用于检测气隙偏心。
其中N是滚动体数量,d是滚动体直径,D是节圆直径,α是接触角。工程上如果不知道精确几何参数,可以用0.4和0.6这两个经验系数乘上滚动体数量和转频做一个粗估,但最终还是要查轴承手册确认。
关键一点:机械故障往往不是单一频率,而是以特征频率为中心、以转频为间隔的一簇边带。比如内圈故障会出现BPFI ± n×f_r的边带,因为故障点随转频周期性通过载荷区。只看中心频率不看边带,很容易漏判。
3.3 一个完整的Python频谱分析示例
下面是我在现场跑过的一段Python代码,参数都按实际诊断场景设置。
import numpy as np from scipy.signal import welch, get_window fs = 4000 n_fft = 4096 overlap = 0.5 # x为预处理后的振动信号,这里用仿真信号替代演示 t = np.arange(n_fft) / fs fr = 29.5 # 模拟转频约等于30 Hz bpfo = 0.4 * 9 * fr # 9个滚动体,约106.2 Hz x = (0.5 * np.sin(2*np.pi*fr*t) + 0.2 * np.sin(2*np.pi*2*fr*t) + 0.15 * np.sin(2*np.pi*bpfo*t) + 0.05 * np.sin(2*np.pi*2*bpfo*t)) + 0.01 * np.random.randn(len(t)) # 方法一:直接FFT单帧 win = get_window('hann', n_fft) sp = np.fft.rfft(x * win, n=n_fft) freq = np.fft.rfftfreq(n_fft, 1/fs) amp = 2 * np.abs(sp) / np.sum(win) # 幅值修正 amp[0] = amp[0] / 2 # 直流分量修正 # 画图,找峰值 peaks, _ = signal.find_peaks(amp[:n_fft//2], height=0.03, distance=20) for p in peaks: print(f"峰频率: {freq[p]:.2f} Hz, 幅值: {amp[p]:.3f}")% MATLAB等效单帧FFT fs = 4000; n_fft = 4096; fr = 29.5; bpfo = 0.4 * 9 * fr; t = (0:n_fft-1) / fs; x = 0.5*sin(2*pi*fr*t) + 0.2*sin(2*pi*2*fr*t) ... + 0.15*sin(2*pi*bpfo*t) + 0.05*sin(2*pi*2*bpfo*t) + 0.01*randn(1,n_fft); win = hann(n_fft, 'periodic'); sp = fft(x .* win', n_fft); freq = (0:n_fft-1) * fs / n_fft; amp = 2 * abs(sp) / sum(win); amp(1) = amp(1) / 2;这里很多人会问:为什么2 * np.abs(sp) / np.sum(win)?因为rfft得到的是单边谱,负频率部分的能量折到了正频率,所以要乘2恢复真实幅值;除以窗函数之和是消除加窗带来的能量损失。已知信号幅值0.5,最终频谱图在30 Hz处应该读出约0.5的幅值。
3.4 频谱泄露、加窗与平均:三个影响判断的细节
FFT有三个细节,处理不好会让频谱图根本没法看。
第一是频谱泄露。当信号频率不是FFT频率分辨率的整数倍时,能量会泄漏到相邻的频点,形成“裙边”。比如目标频率是29.5 Hz,而频率分辨率是fs/n_fft=4000/4096≈0.977 Hz,29.5 Hz不是0.977的整数倍,就用矩形窗时会出现宽大的主瓣和很高旁瓣。解决办法是用汉宁窗或汉明窗。加窗的本质是让截断信号的边界变得平滑,降低旁瓣幅度。代价是主瓣变宽,频率分辨率会下降一些。
第二是频率分辨率。Δf = fs / n_fft。如果要区分两个相近的特征频率,比如转频29.5 Hz和极通过频率100 Hz,那0.977 Hz的分辨率够用。但如果要区分29.5 Hz和30.2 Hz,就必须增加n_fft,或者用welch方法做功率谱平均时选择更长的窗。傅里叶变换的测不准原理限制在那里,靠补零不会提高分辨率,补零只是插值平滑显示。
第三是平均。现场信号往往有随机噪声,单帧FFT的谱线会抖动。我习惯用scipy.signal.welch做分段平均,比如4段50%重叠,这样谱线稳定很多,但代价是频率分辨率下降。诊断故障时我一般先用welch看整体谱,再用长窗FFT对可疑频段做细化分析。
f, psd = signal.welch(x, fs=fs, nperseg=n_fft, noverlap=n_fft//2, window='hann')[pxx, f] = pwelch(x, hann(n_fft, 'periodic'), n_fft/2, n_fft, fs);频谱分析这一段,说到底是要建立“频谱峰-物理故障”的映射关系。FFT只是工具,特征频率公式和结构知识才是判断依据。我见过不少人把FFT谱图做得很漂亮,却不知道每个峰对应什么东西,那诊断就无从谈起。
4. 电机控制环路数字化:从PI调节器到坐标变换的原型验证
信号处理解决了“看得见”的问题,电机控制解决的是“动得稳”的问题。我在这块最常用到的技术就是电流环和速度环的PI控制,以及坐标变换。MATLAB和Python在原型验证上的侧重点很不一样,我结合代码讲讲。
4.1 电流环PI控制器的离散化与抗积分饱和
电机控制中,电流环一般工作在1 kHz到20 kHz的开关频率下,控制器的离散周期与PWM周期一致。经典PI控制器连续域公式是:
u(t) = Kp e(t) + Ki ∫e(τ)dτ
离散化之后,我通常用位置式:
u[k] = Kp e[k] + Ki Ts Σe[i]
或者增量式:
Δu[k] = Kp (e[k] - e[k-1]) + Ki Ts e[k]u[k] = u[k-1] + Δu[k]
位置式直观,但积分环节容易饱和。增量式不累积绝对积分项,天然有抗积分饱和效果,但需要保存上一拍输出。
我最常踩的坑是积分饱和。当电流给定超过控制器输出限幅时,积分项还在不断累加,导致误差恢复时输出长时间停留在饱和值,这就是“退饱和慢”,表现为电流过冲和振荡。解决办法是条件积分法:当输出达到限幅且误差与输出方向一致时,冻结积分累积。我在第6部分还会专门讲。
class PIController: def __init__(self, kp, ki, ts, out_min=-1.0, out_max=1.0): self.kp = kp self.ki = ki self.ts = ts self.out_min = out_min self.out_max = out_max self.integral = 0.0 self.prev_error = 0.0 def update_position(self, ref, feedback): error = ref - feedback # 先计算不含积分的比例输出,判断是否饱和 out_unclamped = self.kp * error + self.ki * self.integral if out_unclamped > self.out_max or out_unclamped < self.out_min: # 饱和时不更新积分 pass else: self.integral += error * self.ts out = np.clip(self.kp * error + self.ki * self.integral, self.out_min, self.out_max) return out def update_incremental(self, ref, feedback): error = ref - feedback delta = self.kp * (error - self.prev_error) + self.ki * self.ts * error out = np.clip(self.last_out + delta, self.out_min, self.out_max) self.prev_error = error self.last_out = out return out这段代码可以直接在Python里跑仿真。用一个电压源模拟电机模型,就能看到PI参数整定的效果。
4.2 反馈信号处理:编码器测速与电流采样滤波
控制环路里,反馈信号的质量决定了控制性能。我用过低成本增量式编码器,测速有两个常见方案:M法(测脉冲数)和T法(测脉冲间隔)。M法在高速时分辨率好,低速时脉冲太少,量化误差大;T法正好相反。所以很多驱动器是M/T法切换,低速用T法,高速用M法。
测速出来后的速度反馈信号还有量化噪声,我会在速度环前加一个一阶低通滤波器,截止频率设置要合理。这里有个矛盾:截止频率太低,速度环带宽上不去;太高,噪声又会影响PI输出。我一般从速度环带宽的5到10倍来定,比如速度环带宽20 Hz,反馈滤波截止频率设在100到200 Hz。
电流采样也需要注意。电流传感器出来的信号常常叠加PWM开关噪声,需要在ADC采样前做硬件RC滤波,采样时刻尽量避开PWM开关瞬间。软件里我还会对电流采样值做滑动平均或者一阶滤波,但要注意滤波带来的相位滞后可能影响电流环稳定性。这也是为什么电流环PI参数和滤波截止频率要一起整定,不能分开调。
4.3 用Python搭建一个可调参的控制原型
在写嵌入式C代码之前,我习惯用Python先搭一个控制原型,把PI参数跑通了再移植。这里用简化的永磁同步电机模型做演示,控制频率10 kHz,电流环比例积分先手动设一组初值。
import numpy as np import matplotlib.pyplot as plt # 电机简化模型参数 Rs = 0.5 # 定子电阻 Lq = 0.005 # q轴电感 Ke = 0.1 # 反电动势系数 J = 0.002 # 转动惯量 B = 0.001 # 粘滞摩擦 # 电流环PI参数 kp_i = 2.0 ki_i = 100.0 ts = 1e-4 # 仿真条件 sim_time = 0.1 steps = int(sim_time / ts) # 状态 iq = 0.0 omega = 0.0 iq_ref = 5.0 # 给定q轴电流 controller = PIController(kp_i, ki_i, ts, out_min=-12.0, out_max=12.0) iq_history = [] t_history = np.arange(steps) * ts for k in range(steps): # 电流环PI vq = controller.update_position(iq_ref, iq) # 电机电流动态(忽略反电动势耦合) diq = (vq - Rs*iq) / Lq iq = iq + diq * ts # 电机机械动态 torque = Ke * iq domega = (torque - B*omega) / J omega = omega + domega * ts iq_history.append(iq) plt.plot(t_history, iq_history, label='iq(A)') plt.axhline(iq_ref, color='gray', linestyle='--', linewidth=0.8) plt.xlabel('t/s') plt.ylabel('iq/A') plt.legend()这个原型虽然简化了很多(没有加Park变换、反电动势解耦),但足够验证PI参数整定的方向和抗积分饱和逻辑。直接在这上面把参数调到满意,再移植到Simulink或C代码里,效率会高很多。
4.4 Simulink验证控制参数:离线到在线一脉相承
Python原型验证的是控制逻辑,但真要评估参数性能,Simulink里通常跑得更细致。我会在Simulink里搭一个完整的FOC电流环,包含Clarke/Park变换、PID Controller模块、PWM调制器和电机模型,然后把Python原型里调好的Kp/Ki填进去。
Simulink里我经常用tunePID或者在PID Controller模块里直接用自动整定工具,它会根据回路模型自动给出参数。但自动整定给出的参数往往偏保守,我会再结合实际工况微调。这个过程里,MATLAB和Python配合的价值就体现出来了:用Python快速迭代逻辑,用Simulink精细评估稳定性,最后再用MATLAB Coder生成C代码。
这里也补充一点:不要把Simulink当作黑盒。我见过不少同学直接点击自动整定,看到漂亮的阶跃响应就完事,但从来没有关心过相位裕度、带宽和抗干扰性。真实驱动器里,负载突变、母线电压波动、死区时间都会影响性能。仿真只是验证的一部分,台架测试才是最终标准。
5. MATLAB离线分析与Python在线监测的联调协作
诊断和控制做完,还有一层很实际的工程问题:MATLAB做出来的分析结论,怎么变成一套能持续运行的监测系统?我自己的答案是让MATLAB负责“专业判断”,Python负责“长期值守”。
5.1 数据交换:MAT文件、CSV与时间戳对齐
两个工具之间协作的第一步是数据交换。MATLAB的.mat文件可以直接用Python读取,这是最方便的方式。
from scipy.io import loadmat data = loadmat('motor_data.mat') vibration = data['vibration'].flatten() current = data['current'].flatten() fs = data['fs'].item()% MATLAB生成数据 save('motor_data.mat', 'vibration', 'current', 'fs');如果数据量很大或者需要长期存储,我推荐用CSV加时间戳的方式,方便用pandas直接读取和处理。
import pandas as pd df = pd.DataFrame({ 'timestamp': timestamps, 'vibration': vibration, 'current': current, }) df.to_csv('motor_data.csv', index=False)时间戳对齐是这里的关键。我的做法是统一用Unix时间戳(秒)或者相对采集开始时刻的秒数,重采样后每一行都带时间列,后续处理和画图都以时间为基准,而不是索引。否则在不同采样率、不同起始时刻的数据合并时,索引对齐必然出错。
5.2 离线标定参数如何部署到在线程序
MATLAB离线分析得到三个东西:信号特征频率表、滤波器参数、控制PI参数。这些参数要部署到Python在线程序里,我通常写一个JSON配置文件。
{ "fs": 4000, "filter": {"type": "bandpass", "low": 20, "high": 1500, "order": 4}, "fault_freq": { "fr": 29.5, "bpfo": 106.2, "bpfi": 159.3 }, "warning_thresholds": { "rms": 0.05, "peak_factor": 3.5 } }在线程序启动时读取这个配置文件,不重新训练模型、不手动改代码,参数调整只改JSON。这样MATLAB里重新分析发现特征频率有变化,只要更新JSON,Python监测端就同步更新。
5.3 一个振动异常实时识别小例子
在线监测并不需要一开始就上深度学习。我常用的是滑动窗口特征提取加阈值判断,简单、可靠、可解释。这段代码可以跑在树莓派或者工控机上,实时读采集卡数据并计算RMS和峰值因子。
import numpy as np from collections import deque class SlidingWindowMonitor: def __init__(self, window_size=4000, fs=4000, rms_threshold=0.05, peak_threshold=3.5): self.window = deque(maxlen=window_size) self.rms_threshold = rms_threshold self.peak_threshold = peak_threshold def add_sample(self, sample): self.window.append(sample) if len(self.window) >= self.window.maxlen: x = np.array(self.window) rms = np.sqrt(np.mean(x**2)) peak = np.max(np.abs(x)) peak_factor = peak / rms if rms > 1e-6 else 0.0 if rms > self.rms_threshold or peak_factor > self.peak_threshold: return {"alarm": True, "rms": rms, "peak_factor": peak_factor} return {"alarm": False} monitor = SlidingWindowMonitor() for sample in stream_samples(): # 伪代码接口 result = monitor.add_sample(sample) if result["alarm"]: print("振动报警", result)这里补充一点工程经验:阈值不要一开始就设得很苛刻。先收集一段正常运行的数据,算出正常状态下RMS的均值和标准差,然后用“均值 + 3倍标准差”作为告警阈值,这叫统计过程控制思路。我最早直接用固定阈值,结果不同负载下频繁误报,后来改成相对阈值,误报率才降下来。在线监测比离线分析更考验容错能力,宁可漏报也不要用一堆误报把维护人员烦到关掉系统。 ## 6. 实测踩坑清单:采样率、频谱、滤波与控制环路的隐性陷阱 最后这部分,我想把几次项目里踩过的坑集中说一下。每一个坑都花了我不少时间排查,写出来希望你能少走弯路。 ### 6.1 采样率不匹配:合并数据时最容易犯的错 我之前做多通道同步采集,振动是10 kHz、电流是4 kHz,现场工程师把数据导出成Excel后,直接按Excel行号对齐了。结果FFT谱图上一堆莫名其妙的“伪峰”,比如真实特征频率是100 Hz,图上却在105 Hz冒出来一个尖峰。排查了很久,才发现是两路信号的时间轴没有重采样对齐,导致频率被“拉伸”或“压缩”了几赫兹。 后来我总结出一个检查方法:先找一段信号里非常明显的脉冲或上升沿,分别在两路数据里定位它的索引,计算两个索引的差值。如果差值随时间漂移,说明采样率不一致或者时钟不同步;如果差值恒定,说明只是起始时刻有偏移。恒定的偏移可以裁剪校正,漂移就必须用重采样。加上时间戳是最彻底的解决方案。 ### 6.2 滤波相位滞后:filtfilt和实时滤波不是一回事 我在做一个闭环控制仿真时,用`filtfilt`对速度反馈信号做滤波,仿真效果非常好,阶跃响应又快又稳。但移植到实际驱动器后,系统变得很容易振荡。后来检查发现,`filtfilt`是零相位滤波,它把数据先正向滤波再反向滤波,会引入未来的信息,这在离线分析里没问题,但在实时控制里根本不可用。实时控制只能用因果滤波,比如`lfilter`或者一阶低通递归形式,而因果滤波一定有相位滞后。 滞后太多时,反馈信号就像慢了一拍,控制环路的相位裕度会下降,甚至变正反馈。我的解决办法是:实时控制环路里,反馈滤波器的截止频率设得比控制带宽高5到10倍,尽量减少滞后;滤波引入的相位滞后,通过适当降低PI增益来补偿。不要为了追求反馈信号干净而把滤波器截止频率压得很低,那会得不偿失。 ### 6.3 索引与坐标符号:1-based和0-based导致的“玄学”故障 MATLAB的数组索引从1开始,Python的numpy从0开始,这三言两语说得轻巧,实际写代码时非常容易出问题。我有一次做Park变换,正余弦查表是从MATLAB代码转过来的,MATLAB里`sin(alpha)`的相位角按0到2π计算,转成Python后数组索引计算时多减了1,结果转子位置估算始终差一个很小的角度,导致电流环输出一直在震荡。 这类问题排查起来特别隐蔽,因为代码逻辑完全正确,公式也没错,纯粹是索引基准或者角度范围约定不一致。我的建议是:坐标系变换和查表操作必须用单元测试验证,比如输一个已知角度,手算期望的d/q轴电流,再对程序输出。还有就是注释里写明角度单位(弧度还是角度)和角度范围(-π到π还是0到2π),这个习惯能省掉很多“玄学”排错时间。 ### 6.4 积分漂移与饱和:PI参数调了很久不收敛的真凶 PI控制器里,积分环节让人又爱又恨。爱是因为它能消除稳态误差,恨是因为它天然容易饱和和漂移。我在调电流环时,给定5 A的电流阶跃,输出限幅是12 V,一开始PI参数设得很激进,结果电流冲到了8 A才回来,过冲30%。这就是积分饱和的典型症状。 后来我用了条件积分法,输出饱和时停止积分累积,过冲立刻压了下去。另一个案例是速度环在空载时正常,一加载转速就稳不住,排查之后发现积分项初始值没有随着负载变化进行前馈补偿。解决方式是在速度环输出叠加一个前馈力矩,或者将积分项上下限设得更紧一些。 我补充一个调PI的实用经验:先用纯P把Kp调到系统开始震荡的临界值,再取临界增益的0.5到0.6倍作为Kp初值;然后从零开始慢慢加Ki,观察阶跃响应的恢复速度。这个过程在Python原型或者Simulink里做都比较快,但一定要加负载扰动测试,只看空载阶跃响应往往会让你误以为参数已经调好了。 回到文章开头那次水泵电机诊断。我最终的排查发现,振动异常主要是轴承早期磨损叠加转子不平衡,特征频率和边带对上了,电流环的轻微振荡则是速度反馈滤波器截止频率过高放大了量化噪声。在MATLAB里离线确认故障机理,用Python部署长期监测阈值,再回到Simulink和台架上把控制参数重新整定,整个过程跑了将近一周,但每个步骤都走得清楚。这就是我为什么坚持双修的原因:没有一种工具能从头到尾高效完成所有事情,但组合好了,它们就能覆盖从频谱分析、故障诊断、控制整定到在线监测的完整链路。 最后再分享一个习惯:每次分析完一段信号,我会把原始数据、预处理脚本、当天使用的参数和结论整理成一个有日期命名的文件夹。以前总觉得这是多此一举,直到有次隔了两周回头查某个特征频率的来源,才发现没有记录根本无从考证。做信号处理和电机控制,技术能力是一方面,能把自己每一步的判断和依据追溯回去,是更难得的工程素养。希望这篇文章里的代码和思路,能帮你在自己的项目里少踩几个坑。