7AA7 Blog

传感器数据采集:采样率、滤波与校准

KernelPan UID:90
2026-09-17 20:06
40 2 1

ADC 读回来的数字只是”某个时刻的某个电压”,从它到”可信的物理量”之间还有采样、滤波、校准三段路。这三段各自有各自的坑,而且互相耦合——采样率定错了,后面再好的滤波器也救不回来。

奈奎斯特与为什么要过采样

奈奎斯特采样定理说的是:采样频率必须大于信号最高频率的两倍,才能无失真地重建信号。低于这个频率,高频成分会混叠成低频假信号——你在数据里看到的”缓慢波动”,可能根本不存在,只是混叠的产物。

定理给的是理论下界,工程上通常取 5~10 倍甚至更高,理由是:

抗混叠滤波器不可能是理想砖墙。真实滤波器有过渡带,要在采样率一半处把信号彻底衰减掉,需要留出过渡带。采样率越高,过渡带越宽裕,滤波器越好做。

相位延迟。滤波器阶数越高、截止频率相对采样率越低,延迟越大。提高采样率可以换来更小的相对延迟。

过采样换分辨率。在噪声近似白噪声的前提下,对同一个量做 4 倍过采样再平均,有效位数大约能提升 1 位;16 倍提升 2 位。这是用时间换精度的经典做法,代价是采样时间变长、功耗上升。很多 MCU 的 ADC 自带硬件过采样单元,比软件平均省事。

需要注意过采样换分辨率有个前提:输入信号上要有足够幅度且随机的噪声。如果信号太干净,量化误差是相关的,平均也白搭。

采样率的权衡

采样率不是越高越好,它直接牵动三件事:

  • 功耗:ADC 转换、传感器激励、MCU 唤醒都有成本。电池设备里,采样率是续航的一阶影响因素。
  • 存储与带宽:原始数据存储下来再处理,每秒 N 个点 × 通道数 × 位数,几天就能吃满 flash。
  • 处理量:滤波、FFT 都是按点算的。

反过来采样率过低会漏掉事件。一个只在几十毫秒内出现的冲击,用 1 Hz 采样是抓不到的。

实用的取法是:按要捕捉的最短事件定下界,按功耗和带宽预算定上界,然后在这个区间里选一个,再验证滤波后的延迟是否满足控制需求。对缓慢变化的量(温度、液位、土壤湿度),采样率在 1 Hz 甚至更低量级就够;对振动、声音、电流波形,往往是 kHz 甚至更高量级。

三种常用滤波

滑动平均:维护一个长度 N 的环形缓冲,输出算术平均。对白噪声抑制明显,代价是延迟约 N/2 个采样周期,且对脉冲干扰几乎无效——一个尖峰会被摊平成一个宽一点的小鼓包,不会消失。

#define N 8
static int32_t buf[N];
static uint8_t idx;
static int32_t sum;

int32_t moving_avg(int32_t x) {
    sum -= buf[idx];
    buf[idx] = x;
    sum += x;
    idx = (idx + 1) % N;
    return sum / N;
}

用 sum 增量维护避免每次求和,N 较大时差别明显。注意 sum 的溢出:N 乘以满量程要在 32 位范围内,或者用 64 位累加。

中值滤波:取窗口内的中位数。对脉冲干扰(偶发的跳变、毛刺)效果极好,且不像平均那样把阶跃抹平。代价是需要排序,N 通常取 3、5、7 这类小奇数。

int32_t median3(int32_t a, int32_t b, int32_t c) {
    int32_t t;
    if (a > b) { t = a; a = b; b = t; }
    if (b > c) { t = b; b = c; c = t; }
    if (a > b) { t = a; a = b; b = t; }
    return b;
}

中值滤波适合”偶尔蹦个野点”的场景,比如接近开关、超声测距、被电磁干扰的模拟量。

一阶低通(指数平滑):y[n] = α·x[n] + (1-α)·y[n-1]。只需保存一个状态,内存最省,计算最轻。α 越小越平滑但延迟越大。截止频率与 α、采样周期的关系可以近似成 fc ≈ α / (2π·T·(1-α)),够用来选型。

/* alpha 用整数定点:Q15 格式,alpha_q15 = alpha * 32768 */
int32_t lpf(int32_t x, int32_t prev, int32_t alpha_q15) {
    return prev + ((alpha_q15 * (x - prev)) >> 15);
}

用定点实现避免在无 FPU 的 MCU 上跑浮点。定点运算要注意中间结果溢出,alpha_q15 * (x - prev) 可能超过 32 位,用 int64_t 中转或者限制输入范围。

三者的适用场景可以粗略划分:随机噪声用平均或一阶低通;偶发毛刺用中值;既要去噪又要保留阶跃边缘,用中值打头、低通收尾的两级组合。

卡尔曼滤波的门槛

卡尔曼的直觉是”预测 + 观测的加权”:系统模型给出一个预测值,传感器给出一个观测值,两者的不确定度(方差)谁小就多信谁,权重由一个叫卡尔曼增益的系数动态决定。测量噪声大时更信模型,模型不确定度累积变大时更信测量。

一维情形下代码不长,难的是参数整定:过程噪声方差 Q 和测量噪声方差 R 决定了滤波器的行为。Q 大表示不相信模型、响应快但抖;R 大表示不相信传感器、平滑但迟钝。这两个数通常靠实测数据的统计特性来定,而不是拍脑袋。

多变量卡尔曼涉及矩阵运算和协方差传播,在 MCU 上要处理矩阵求逆的数值稳定性,代码量和内存占用都不小。如果系统模型本身不线性,还得上 EKF,线性化误差又会引入新的问题。

实际选型建议:如果只是为了平滑,一阶低通足够;如果需要融合多个传感器(比如加速度计和陀螺仪),卡尔曼才有明确收益。

两点校准

传感器输出的电压或码值到物理量之间是线性关系 物理值 = k · 码值 + b,k 和 b 会因器件离散性、温漂而变。两点校准就是在两个已知标准点读取码值,解出 k 和 b:

k = (P2 - P1) / (C2 - C1)
b = P1 - k · C1

选取两个标准点时有讲究:两点跨度越大,量程内的误差越小。用 0 ℃ 和 100 ℃ 标定温度计,比用 20 ℃ 和 30 ℃ 定出来的 k 准得多。同时两点要落在常用量程内,外推到量程之外误差会放大。

标定用的标准源精度必须高于被测传感器一个量级,否则校出来的系数只是把标准源的误差刻了进去。

实现上 k 和 b 存到 flash 或 EEPROM,上电加载。存之前加 CRC,避免写入失败导致校准系数变成垃圾值,让设备读数整体偏移。

温漂补偿

零点和灵敏度都会随温度漂移。补偿有两条路:

硬件上,把温度传感器放在尽量靠近敏感元件的位置,保证测到的温度是传感器自己的温度而不是板子另一头的温度。

软件上,建立温度与偏移量的关系。简单做法是分段线性:标定若干个温度点下的零点偏移,运行时插值。复杂一点拟合一条二次曲线。如果有温度传感器和被测量的联合标定数据,可以写成 P = (k0 + k1·ΔT) · C + (b0 + b1·ΔT) 的形式。

一个容易忽略的细节:上电自热。器件刚上电时温度还在爬升,这时候做的零点校准会把冷机状态的偏移记进去。通常让设备上电预热一段时间(量级在几十秒到几分钟,视器件而定)再采集。

异常值识别

滤波能压噪声,但识别”这个数据根本不能用”是另一件事。几种判据:

超量程:码值贴着 0 或满量程,通常是传感器断线、短路或者饱和。直接判无效,不要当成真值滤波。

斜率限幅:物理量有变化速率上限(温度不可能在 1 秒内跳 50 ℃)。相邻两次的差值超过阈值就判为跳变。注意区分真跳变和干扰——连续两拍都跳,可能是真实事件。

连续跳变计数:连续 N 次超阈值才报警,避免单次干扰误报。同时维护一个连续无效计数,超过上限就上报传感器故障。

合理性交叉验证:多个相关量互相印证,比如流量为 0 但压差很大,其中一个是错的。

识别出异常之后的处理策略要提前定:用上一次有效值填充、标记为无效不参与聚合、或者触发传感器故障上报。这三种选择对上层业务的含义完全不同,别让异常数据静悄悄地被平均掉。

827Views
40Likes
2Comments
2Participants
9 minread time

Comments (2)

  • Talen · 2026-09-17 22:32

    说实话第一次见有人把这块讲得这么不装。

    Reply
  • Pipeline 郑 · 2026-09-18 07:06

    看完去查了下我们的配置,果然也是错的。

    Reply