4. 数据采集与信号处理:从模拟到数字的必修课

数据采集这活儿,说白了就是跟信号打交道。你传感器再好,信号调理再到位,到了ADC这一步如果没处理好,前面全白搭。我做了十几年嵌入式,见过太多人栽在采样率设置和滤波上——不是采出来的数据没法用,就是CPU被滤波算法拖死。

这一章咱们就聊聊数据采集里最核心的几个概念:采样定理、抗混叠滤波、数字滤波,还有数据校准。嗯,都是实战里绕不开的硬骨头。

4.1 采样定理(Nyquist):别让你的信号“说谎”

先问个问题:一个1kHz的正弦波,你用多快的速度去采,才能把它还原出来?

答案是至少2kHz。这就是奈奎斯特采样定理的核心——采样频率必须大于信号最高频率的两倍。低于这个值,就会出现一个很要命的现象:混叠(Aliasing)。

核心公式:

fs > 2 × fmax

其中 fs 是采样频率,fmax 是信号中的最高频率分量。

混叠是什么感觉?我举个例子。你拿手机拍风扇,有时候会看到扇叶好像倒着转,甚至不转。这就是时间域上的混叠——采样率(帧率)跟不上风扇转速,视觉系统就被骗了。

在数据采集里,混叠更危险。一个100Hz的噪声信号,你用150Hz去采,采出来会变成一个50Hz的假信号。你以为是真实数据,其实是噪声伪装成的。我曾经在一个振动监测项目里遇到过这问题,折腾了两天才发现是采样率设低了。

避坑指南:

我曾经在工业现场吃过亏——传感器输出有高频噪声,我按信号基频的2倍设采样率,结果数据全是乱的。后来才明白:采样率要按信号中最高频率分量来算,不是按你关心的频率算。

实际工程中,我一般取3~5倍,留足余量。

4.2 抗混叠滤波:采样前的最后一道防线

光提高采样率够吗?不够。因为真实世界里,信号里总有你意想不到的高频成分——电源噪声、电机干扰、射频耦合……这些频率你根本不知道,也没法提前避开。

所以,ADC之前必须加一个低通滤波器,把高于 fs/2 的成分干掉。这就是抗混叠滤波器。

滤波器类型 特点 适用场景
一阶RC低通 简单、便宜、-20dB/dec 对阻带要求不高的低速信号
二阶有源低通 -40dB/dec,截止特性好 大多数工业采集场景
高阶椭圆滤波器 过渡带极陡,但有通带纹波 高精度、窄过渡带需求

我个人习惯:低速信号(几百Hz以内)用二阶巴特沃斯有源滤波器,通带平坦,相位失真小。高速信号(几十kHz以上)直接用集成抗混叠滤波器芯片,省心。

实战技巧:

抗混叠滤波器的截止频率一般设在 fs/2 的 80%~90%。比如采样率 1kHz,截止频率设在 400~450Hz。留点余量,别卡在边界上。

4.3 数字滤波:软件层面的信号净化

模拟滤波做完了,数据进到MCU里,是不是就干净了?不一定。传感器本身的噪声、量化噪声、环境干扰,还会混进来。这时候就需要数字滤波上场了。

数字滤波的好处是灵活——改个参数就行,不用动硬件。我常用的有三种:移动平均、中值滤波、卡尔曼滤波。各有各的脾气。

4.3.1 移动平均滤波

最简单,也最常用。把最近N个采样值加起来求平均,输出一个平滑值。

// 移动平均滤波 - 循环队列实现
#define WINDOW_SIZE 10

float moving_average(float new_sample) {
    static float buffer[WINDOW_SIZE] = {0};
    static int index = 0;
    static float sum = 0;
    static int count = 0;

    sum -= buffer[index];
    buffer[index] = new_sample;
    sum += new_sample;
    index = (index + 1) % WINDOW_SIZE;

    if (count < WINDOW_SIZE) count++;
    return sum / count;
}

优点:计算量小,平滑效果好。缺点:对突变信号反应慢,而且会引入相位延迟。窗口越大,延迟越大。

我在做温度采集时常用这个——温度变化慢,延迟几秒无所谓,平滑最重要。

4.3.2 中值滤波

对付脉冲噪声(比如静电干扰、开关噪声)特别有效。原理很简单:取N个采样值,排序,取中间那个。

// 中值滤波 - 冒泡排序取中值
#define MEDIAN_WINDOW 5

float median_filter(float new_sample) {
    static float buffer[MEDIAN_WINDOW] = {0};
    static int index = 0;
    float temp[MEDIAN_WINDOW];
    int i, j;

    buffer[index] = new_sample;
    index = (index + 1) % MEDIAN_WINDOW;

    // 拷贝并排序
    for (i = 0; i < MEDIAN_WINDOW; i++) temp[i] = buffer[i];
    for (i = 0; i < MEDIAN_WINDOW - 1; i++) {
        for (j = 0; j < MEDIAN_WINDOW - 1 - i; j++) {
            if (temp[j] > temp[j+1]) {
                float t = temp[j];
                temp[j] = temp[j+1];
                temp[j+1] = t;
            }
        }
    }
    return temp[MEDIAN_WINDOW / 2];
}

你想想看,如果采样值里突然蹦出一个异常大的数,移动平均会被拉偏,但中值滤波完全不受影响。我做过一个压力传感器项目,现场有气动阀开关,每次动作都会产生一个尖峰干扰,中值滤波一把就解决了。

注意:

中值滤波窗口一般取奇数(3、5、7)。窗口太大,计算量上去了,而且会丢失细节。我一般不超过9个点。

4.3.3 卡尔曼滤波

这个稍微复杂点,但效果是真的好。卡尔曼滤波不是简单平滑,它是在估计系统状态——结合测量值和预测值,给出一个最优估计。

一维卡尔曼滤波的五个核心公式:

// 一维卡尔曼滤波
typedef struct {
    float Q;  // 过程噪声协方差
    float R;  // 测量噪声协方差
    float x;  // 估计值
    float P;  // 估计误差协方差
    float K;  // 卡尔曼增益
} Kalman1D;

float kalman_update(Kalman1D *kf, float measurement) {
    // 预测
    kf->P = kf->P + kf->Q;

    // 更新
    kf->K = kf->P / (kf->P + kf->R);
    kf->x = kf->x + kf->K * (measurement - kf->x);
    kf->P = (1 - kf->K) * kf->P;

    return kf->x;
}

卡尔曼滤波的厉害之处在于:它能自适应。信号平稳时,它信任预测值;信号突变时,它信任测量值。我做过一个无人机高度计融合项目,气压计和超声波的数据就是靠卡尔曼滤波融合的,效果比单纯平滑好太多。

参数调优经验:

Q和R的比值决定了滤波器的响应速度。Q/R越大,滤波器越“相信”测量值,响应越快但噪声也多。我一般先设Q=0.01、R=1,然后根据实际波形微调。

4.4 数据校准与归一化:让数据说“真话”

滤波做完了,数据看起来平滑了。但还有一个问题:传感器出来的原始值,跟真实物理量之间往往有偏差。这就是校准要做的事。

4.4.1 校准:消除系统误差

校准分两步:零点和满量程。

  • 零点校准:传感器在无输入时,输出应该是多少?比如压力传感器在通大气时,输出应该是0。但实际可能有个偏移量,记下来,后面减掉。
  • 满量程校准:给一个已知的标准输入,看传感器输出是多少。比如给100kPa压力,输出是2.5V,那增益系数就是 100 / 2.5 = 40 kPa/V。
// 两点校准
float calibrate(float raw_value, float zero_offset, float gain) {
    return (raw_value - zero_offset) * gain;
}

我习惯在系统初始化时做一次自动校准。如果环境温度变化大,还得做温度补偿——这个后面章节会细讲。

4.4.2 归一化:统一量纲

归一化就是把不同量纲、不同范围的数据,映射到同一个区间(通常是0~1或-1~1)。为什么要做?因为很多算法(比如神经网络、PID控制)对输入范围敏感。

最常用的归一化方法:

// 最小-最大归一化
float normalize_minmax(float value, float min_val, float max_val) {
    if (max_val == min_val) return 0.5;
    return (value - min_val) / (max_val - min_val);
}

举个例子:一个温度传感器输出范围是0~3.3V,对应温度-40~85°C。归一化后,电压值变成0~1,方便后续处理。

实战建议:

归一化的min和max最好用实际采集到的范围,而不是理论值。理论值往往偏大,导致归一化后数据集中在0.2~0.8之间,浪费了动态范围。

4.5 本章知识体系总览

说了这么多,咱们用一张图把整个数据采集与信号处理的流程串起来。从传感器原始信号,到最终可用的校准数据,每一步都不能少。

数据采集与信号处理流程 传感器原始信号 模拟量(电压/电流) 抗混叠滤波 低通滤波(硬件) ADC采样 fs > 2fmax 数字滤波 移动平均/中值/卡尔曼 数据校准 零点/满量程补偿 归一化 映射到0~1或-1~1 可用数据 真实、平滑、归一化的物理量 采样率不足 → 混叠 抗混叠滤波是必须的 滤波 ≠ 校准 两步都要做

这张图把咱们这一章的核心流程串起来了。从传感器出来的模拟信号,先过抗混叠滤波器,再进ADC采样,然后做数字滤波、校准、归一化,最后得到可用的数据。每一步都有坑,每一步也都有技巧。

嗯,这一章的内容就到这儿。数据采集这事儿,理论不难,难的是现场的那些“意外”。多留个心眼,多留点余量,数据就不会骗你。