4. 数据采集与预处理:采样频率选择、噪声滤波、异常值剔除、时间戳对齐
各位工程师朋友,这一章我们聊聊数据采集与预处理。说实话,这块内容看起来基础,但往往是整个电池安全预警系统的“地基”。地基没打好,后面算法再花哨也是白搭。我在多个量产项目中吃过这方面的亏,今天把这些经验掰开了揉碎了讲给你听。
核心观点:数据预处理不是“洗数据”那么简单,它直接决定了阈值标定的可信度和算法响应的实时性。预处理做得好,后续工作事半功倍。
4.1 采样频率选择:不是越高越好
采样频率怎么选?很多人第一反应是“越高越好”。其实不然。我见过一个项目,工程师把采样频率设到了100Hz,结果数据量巨大,存储和通信都扛不住,而且很多高频噪声也被采了进来。
我个人习惯遵循奈奎斯特采样定理,但实际工程中会留2~5倍的余量。对于电池安全预警,关键信号的频率范围大致如下:
| 信号类型 | 有效频率范围 | 推荐采样频率 |
|---|---|---|
| 电压(单体/总压) | 0~10 Hz | 10~20 Hz |
| 电流 | 0~50 Hz(含纹波) | 50~100 Hz |
| 温度(NTC) | 0~1 Hz | 1~5 Hz |
| 绝缘电阻 | 0~0.5 Hz | 0.5~2 Hz |
为什么会这样?你想想看,电池的热惯性很大,温度变化很慢,你用10Hz去采温度,除了浪费资源,没有任何好处。而电流信号因为包含负载突变和纹波,需要相对高一些的采样率。
我的经验:对于BMS主控芯片资源有限的情况,我建议采用“多速率采样”策略——电压电流用较高频率,温度用较低频率。这样既保证了关键信号的实时性,又不会把MCU跑死。
4.2 噪声滤波:中值滤波 vs 卡尔曼滤波
原始信号里一定有噪声,这是物理世界的常态。滤波的目的不是把噪声完全消除(那不可能),而是把信噪比提升到可接受的水平。
4.2.1 中值滤波:简单粗暴但有效
中值滤波的原理很简单——取一个滑动窗口内的中间值。它对脉冲噪声(比如传感器偶尔跳变)效果特别好。
我在项目中遇到过一个问题:某个电芯的电压采集偶尔会跳变0.5V,用均值滤波根本压不住,但中值滤波一下就搞定了。代码实现也很简单:
// 中值滤波示例(窗口大小=5)
#define WINDOW_SIZE 5
float median_filter(float new_value) {
static float buffer[WINDOW_SIZE] = {0};
static uint8_t index = 0;
float temp[WINDOW_SIZE];
// 更新环形缓冲区
buffer[index] = new_value;
index = (index + 1) % WINDOW_SIZE;
// 拷贝并排序
memcpy(temp, buffer, sizeof(temp));
// 冒泡排序(实际工程中建议用快速选择算法)
for (int i = 0; i < WINDOW_SIZE - 1; i++) {
for (int j = 0; j < WINDOW_SIZE - i - 1; j++) {
if (temp[j] > temp[j + 1]) {
float t = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = t;
}
}
}
return temp[WINDOW_SIZE / 2];
}
注意:中值滤波的窗口大小很关键。窗口太小,滤波效果差;窗口太大,信号延迟严重。我一般取5~9个点,具体要看你的采样频率和信号变化速度。
4.2.2 卡尔曼滤波:状态估计的利器
卡尔曼滤波听起来高大上,其实说白了就是“用模型预测+测量修正”的方式,不断逼近真实值。它特别适合处理高斯噪声,而且能给出状态的不确定性估计。
对于电池电压和SOC估计,卡尔曼滤波几乎是标配。但要注意,它需要你建立相对准确的系统模型。模型不准,滤波效果反而更差。
// 一维卡尔曼滤波简化实现
typedef struct {
float x; // 状态估计值
float p; // 估计误差协方差
float q; // 过程噪声协方差
float r; // 测量噪声协方差
} kalman_1d_t;
float kalman_update(kalman_1d_t *kf, float measurement) {
// 预测
float p_pred = kf->p + kf->q;
// 卡尔曼增益
float k = p_pred / (p_pred + kf->r);
// 更新
kf->x = kf->x + k * (measurement - kf->x);
kf->p = (1 - k) * p_pred;
return kf->x;
}
我的建议:如果信号噪声类型比较单一(比如只有脉冲噪声),用中值滤波就够了,别折腾卡尔曼。如果信号变化规律且噪声复杂(比如电流信号),卡尔曼滤波值得投入。我曾经在一个项目中,用卡尔曼滤波把电流信号的SNR从15dB提升到了35dB以上。
4.3 异常值剔除:别让一个坏点毁了整个算法
异常值,也叫野点或离群点。它们可能来自传感器故障、通信干扰、或者电池本身真的出了异常。我们的目标是:剔除明显错误的测量值,但保留真实的异常事件。
常用的方法有三种:
- 3σ准则:假设数据服从正态分布,超出均值±3倍标准差的值视为异常。简单,但对非正态分布效果差。
- 箱线图法(IQR):基于四分位数,不受极端值影响。适合非对称分布。
- 滑动窗口法:实时计算窗口内的均值和标准差,判断当前值是否合理。
我个人最常用的是滑动窗口法,因为它适合实时嵌入式系统。代码示例如下:
// 滑动窗口异常值检测
#define WINDOW_SIZE 10
bool is_outlier(float new_value) {
static float buffer[WINDOW_SIZE] = {0};
static uint8_t count = 0;
float sum = 0, mean = 0, std = 0;
// 更新缓冲区
if (count < WINDOW_SIZE) {
buffer[count++] = new_value;
return false; // 数据不足,暂不判断
}
// 滑动更新(先进先出)
memmove(buffer, buffer + 1, (WINDOW_SIZE - 1) * sizeof(float));
buffer[WINDOW_SIZE - 1] = new_value;
// 计算均值和标准差
for (int i = 0; i < WINDOW_SIZE; i++) {
sum += buffer[i];
}
mean = sum / WINDOW_SIZE;
for (int i = 0; i < WINDOW_SIZE; i++) {
std += (buffer[i] - mean) * (buffer[i] - mean);
}
std = sqrt(std / WINDOW_SIZE);
// 判断:超出均值±3σ视为异常
return (fabs(new_value - mean) > 3 * std);
}
避坑指南:我曾经犯过一个错误——把电池真实的热失控信号当成了异常值剔除掉。后来我加了一个规则:如果连续3个点都超出阈值,就不再剔除,而是触发预警。这样既滤掉了传感器偶发故障,又不会漏掉真实危险。
4.4 时间戳对齐:多源数据的“统一时钟”
BMS系统里,电压、电流、温度来自不同的采集芯片,它们的时间基准可能不同。如果不做时间戳对齐,你算出来的内阻、SOC、SOP全都是错的。
时间戳对齐的核心思路是:选择一个主时钟源,其他所有数据都向它对齐。
具体做法:
- 每个数据帧都带上硬件时间戳(最好是微秒级)
- 在软件层面,用线性插值或最近邻插值,把不同频率的数据映射到统一的时间轴上
- 对于电流和电压,必须严格对齐,因为内阻计算依赖两者的同步性
// 时间戳对齐示例:将电流数据对齐到电压时间轴
typedef struct {
uint32_t timestamp_us;
float value;
} data_point_t;
// 线性插值对齐
float align_to_timestamp(data_point_t *current_data, int len, uint32_t target_ts) {
// 找到目标时间戳前后的两个点
for (int i = 0; i < len - 1; i++) {
if (current_data[i].timestamp_us <= target_ts &&
current_data[i + 1].timestamp_us >= target_ts) {
// 线性插值
float ratio = (float)(target_ts - current_data[i].timestamp_us) /
(current_data[i + 1].timestamp_us - current_data[i].timestamp_us);
return current_data[i].value + ratio *
(current_data[i + 1].value - current_data[i].value);
}
}
return current_data[len - 1].value; // 边界情况
}
我的经验:如果硬件不支持高精度时间戳,可以在软件里用定时器中断打时间戳。精度能做到毫秒级,对于大部分BMS应用已经够用了。但要注意中断响应延迟的补偿。
4.5 本章知识体系总览
下面这张图总结了数据采集与预处理的完整流程和关键决策点。你可以把它当作一个检查清单,做项目时对照着来。
嗯,这一章的内容就到这里。数据预处理看起来琐碎,但每一步都关系到后续算法的成败。我个人觉得,花30%的时间在预处理上,一点都不为过。下一章我们会进入核心算法部分,但前提是——你的数据已经准备好了。
本章要点回顾:
- 采样频率根据信号带宽选择,不是越高越好
- 中值滤波适合脉冲噪声,卡尔曼滤波适合高斯噪声
- 异常值剔除要平衡“滤除坏点”和“保留真实异常”
- 时间戳对齐是多源数据融合的前提,必须做