信号与噪声基础:时域与频域的基本概念

做运动控制这些年,我最大的体会就是——你永远躲不开噪声。不管你的电机多精密,编码器多高级,信号里总有些「不干净」的东西。今天我们就来聊聊,这些噪声到底长什么样,以及怎么从时域和频域两个角度去看它们。

时域:信号在时间轴上的样子

时域,说白了就是「信号随时间怎么变」。你拿示波器看一个波形,横轴是时间,纵轴是幅值,这就是时域视角。

举个例子。一个理想的正弦波,在时域里就是一条光滑的曲线。但实际采集到的电机速度信号呢?

// 模拟一个带噪声的速度信号
float true_speed = 100.0;          // 真实速度 100 RPM
float noisy_speed = true_speed + 2.0 * sin(2 * PI * 50 * t) + 0.5 * randn();

你看,这里叠加了50Hz的高频振动和随机噪声。在时域里,你只能看到一条抖动的曲线,很难分清哪些是真实信号,哪些是噪声。

关键点:时域直观,但信息有限。你看到抖动,却不知道抖动的「成分」是什么。

我在项目中遇到过一件事。有次调试一个伺服系统,速度曲线总是有周期性波动。时域波形看起来乱七八糟,我盯着示波器看了半天也没头绪。后来切换到频域分析,一眼就看出是机械共振频率被激发了。嗯,这就是时域的局限性。

频域:换个角度看问题

频域是什么?它告诉你信号里「有哪些频率成分」,以及「每个频率的强度有多大」。

你想想看,任何复杂的时域信号,都可以分解成一系列不同频率、不同幅值的正弦波叠加。这就是傅里叶变换的核心思想。

我习惯用FFT(快速傅里叶变换)来分析运动控制中的噪声。代码很简单:

// 伪代码:对采集到的位置信号做FFT
float signal[1024];   // 采集1024个点
float freq_spectrum[512]; // 频谱结果

FFT(signal, freq_spectrum, 1024, SAMPLE_RATE);

// 找到幅值最大的频率成分
float max_magnitude = 0;
int max_freq_index = 0;
for (int i = 0; i < 512; i++) {
    if (freq_spectrum[i] > max_magnitude) {
        max_magnitude = freq_spectrum[i];
        max_freq_index = i;
    }
}
float dominant_freq = max_freq_index * SAMPLE_RATE / 1024.0;

这段代码跑完,你就能知道信号里最主要的噪声频率是多少。是50Hz的工频干扰?还是200Hz的机械振动?一目了然。

我的习惯:做运动控制滤波之前,先做一次FFT分析。看看噪声集中在哪些频段,再决定用什么滤波器。这叫「对症下药」。

运动控制中常见的三种噪声

根据我的经验,运动控制系统的噪声大致可以分为三类。我画了一张图,帮你快速理解:

运动控制常见噪声类型 白噪声 频域:平坦,所有频率都有 时域:随机跳动,无规律 典型来源 • 传感器电子噪声 • ADC量化噪声 • 电缆热噪声 处理方法 • 低通滤波 • 均值滤波 低频漂移 频域:集中在低频段 时域:缓慢变化,像「爬坡」 典型来源 • 温度变化导致零漂 • 机械磨损 • 电源缓慢波动 处理方法 • 高通滤波 • 带通滤波 高频振动 频域:集中在特定高频 时域:快速周期性波动 典型来源 • 电机换向纹波 • 机械共振 • PWM开关噪声 处理方法 • 陷波滤波器 • 低通滤波

白噪声:无处不在的「背景音」

白噪声在频域里是平坦的——所有频率都有,强度差不多。在时域里,它看起来就是随机跳动,没有规律。

我记得第一次做编码器信号采集时,看到数据在±1个脉冲之间随机跳,还以为是硬件坏了。后来才知道,这就是白噪声,任何电子系统都逃不掉。

避坑指南:白噪声虽然看起来烦人,但它的均值为零。所以用均值滤波或者滑动平均,效果往往不错。我曾经犯过一个错误——看到白噪声就想用高阶滤波器,结果把有用信号也滤掉了。后来学乖了,先看幅值,再决定滤波强度。

低频漂移:慢慢「溜走」的零点

低频漂移,说白了就是信号缓慢地偏离真实值。你想想看,一个温度传感器,早上开机时输出0V,到了中午变成0.1V——这就是漂移。

在运动控制中,低频漂移特别讨厌。它会让位置积分越偏越大,速度估计越来越不准。我做过一个AGV(自动导引车)项目,里程计积分误差越来越大,最后发现是加速度计的低频漂移在作怪。

处理低频漂移,我个人的经验是:用高通滤波器或者带通滤波器,把直流和极低频成分滤掉。但要注意截止频率不能设太高,否则会把真实的运动信号也滤掉。

高频振动:机械系统的「共振」

高频振动,通常是机械系统本身的特性。比如电机换向时产生的纹波,或者某个机械部件的共振频率被激发。

这类噪声在频域里很「干净」——就集中在几个特定的频率点上。时域里看起来就是叠加在信号上的小波纹。

对付高频振动,陷波滤波器(Notch Filter)是首选。它能精准地干掉某个频率,而对其他频率影响很小。

// 一个简单的陷波滤波器实现
typedef struct {
    float b0, b1, b2;
    float a1, a2;
    float x1, x2, y1, y2;
} NotchFilter;

// 初始化陷波滤波器,中心频率50Hz,采样率1000Hz
void NotchFilter_Init(NotchFilter *f, float freq, float Q, float fs) {
    float w0 = 2 * PI * freq / fs;
    float alpha = sin(w0) / (2 * Q);
    
    f->b0 = 1;
    f->b1 = -2 * cos(w0);
    f->b2 = 1;
    f->a1 = -2 * cos(w0);
    f->a2 = 1 - alpha;
    
    float norm = 1 + alpha;
    f->b0 /= norm;
    f->b1 /= norm;
    f->b2 /= norm;
    f->a1 /= norm;
    f->a2 /= norm;
    
    f->x1 = f->x2 = f->y1 = f->y2 = 0;
}

float NotchFilter_Process(NotchFilter *f, float x) {
    float y = f->b0 * x + f->b1 * f->x1 + f->b2 * f->x2
              - f->a1 * f->y1 - f->a2 * f->y2;
    
    f->x2 = f->x1;
    f->x1 = x;
    f->y2 = f->y1;
    f->y1 = y;
    
    return y;
}

实用技巧:用陷波滤波器之前,一定要先做FFT分析,确认共振频率到底是多少。我曾经凭经验猜了一个频率,结果滤波效果很差,反而引入了相位滞后。后来老老实实做频谱分析,一次搞定。

时域与频域:两个视角,缺一不可

做运动控制滤波,你不能只看时域,也不能只看频域。两个视角要结合起来。

时域告诉你「信号抖得有多厉害」,频域告诉你「为什么抖」。我习惯的流程是:

  1. 采集一段信号,先看时域波形,有个直观印象
  2. 做FFT分析,看噪声的频谱分布
  3. 根据频谱特征,选择合适的滤波器类型和参数
  4. 在时域里验证滤波效果

这个流程我用了很多年,基本没出过大问题。

噪声类型 时域特征 频域特征 推荐滤波器
白噪声 随机跳动,无规律 频谱平坦 低通滤波、均值滤波
低频漂移 缓慢变化,趋势性 集中在低频段 高通滤波、带通滤波
高频振动 快速周期性波动 集中在特定频率 陷波滤波、低通滤波

好了,这一章的内容就到这里。理解时域和频域,是做好滤波的第一步。下一章我们会深入具体的滤波算法实现,到时候见。


公众号:蓝海资料掘金营,微信deep3321