4、氧浓度传感器驱动开发:电化学传感器原理、I2C通信协议实现、数据滤波算法
好,咱们进入第四章。这一章我打算聊聊氧浓度传感器的驱动开发。说实话,在麻醉机里,氧浓度这个参数是真正的“生命线”。你想想看,病人吸入的氧气浓度如果偏差太大,要么是缺氧,要么是氧中毒,哪个都受不了。所以这一章,我会把电化学传感器的原理、I2C怎么玩、还有数据滤波那点事儿,掰开了揉碎了讲清楚。
4.1 电化学传感器原理:它到底是怎么测氧的?
电化学氧传感器,说白了就是一个“会发电的小电池”。它内部有阳极、阴极,还有一层电解质膜。当氧气分子穿过透气膜进入传感器内部,在阴极上发生还原反应,产生电流。这个电流的大小,和氧气浓度是成正比的。
我记得第一次接触这玩意儿,是在一个老款的麻醉机上。当时我还在想,这玩意儿怎么这么娇气?后来拆开一看,里面就三个引脚:一个工作电极、一个对电极、一个参比电极。嗯,其实没那么神秘。
核心公式(简化版):
I = k × [O₂]
其中 I 是输出电流(μA),k 是传感器灵敏度常数,[O₂] 是氧气浓度(%)。
实际项目中,这个 k 值会随着温度、湿度、老化程度发生变化。所以你不能指望它永远准。我一般会在产品出厂前做一次两点校准:用 21% 的空气和 100% 的纯氧做标定。
4.2 I2C通信协议实现:怎么把数据读出来?
现在的电化学传感器模块,很多都集成了 ADC 和 I2C 接口。比如我常用的 City Technology 的 4OXV 系列,它内部已经把电流信号转成了数字量,你只需要通过 I2C 去读寄存器就行。
I2C 协议本身不复杂,但有几个坑我得提醒你。
避坑指南:
我曾经在一个项目中,I2C 时钟线 SCL 和数据线 SDA 的上拉电阻选错了。选了 10kΩ,结果总线电容太大,波形都变成正弦波了。后来换成 4.7kΩ,问题解决。记住:上拉电阻一般选 2.2kΩ ~ 4.7kΩ,具体看总线长度和从机数量。
下面是我常用的 I2C 读取代码片段,用的是 STM32 HAL 库:
#include "i2c.h"
#define O2_SENSOR_ADDR 0x70 // 7位地址,左移一位后是0xE0
#define O2_DATA_REG 0x00 // 数据寄存器地址
uint16_t O2_ReadConcentration(void)
{
uint8_t reg = O2_DATA_REG;
uint8_t buf[2] = {0};
uint16_t raw_value = 0;
// 先写寄存器地址
HAL_I2C_Master_Transmit(&hi2c1, O2_SENSOR_ADDR << 1, ®, 1, 100);
// 再读两个字节
HAL_I2C_Master_Receive(&hi2c1, O2_SENSOR_ADDR << 1, buf, 2, 100);
raw_value = (buf[0] << 8) | buf[1];
return raw_value;
}
这里有个细节:I2C 地址是 7 位的,但 HAL 库的传输函数需要 8 位地址(左移一位,最低位表示读写方向)。我第一次用的时候忘了左移,结果读出来的数据全是 0xFF。嗯,这种低级错误,犯过一次就不会再犯了。
4.3 数据滤波算法:原始数据太“脏”怎么办?
电化学传感器的输出,其实挺“毛躁”的。因为气流波动、泵的振动、甚至病人呼吸都会造成干扰。如果你直接把原始数据送给控制算法,那麻醉机可能会疯掉——一会儿报氧浓度过高,一会儿报过低。
我个人习惯用两种滤波算法:滑动平均和中值滤波。它们各有各的适用场景。
4.3.1 滑动平均滤波
滑动平均,说白了就是取最近 N 个采样点的平均值。它适合滤除高频噪声,但对突发性的尖峰干扰效果不好。
#define FILTER_WINDOW_SIZE 10
static uint16_t buffer[FILTER_WINDOW_SIZE] = {0};
static uint8_t index = 0;
static uint8_t count = 0;
uint16_t MovingAverageFilter(uint16_t new_sample)
{
uint32_t sum = 0;
uint8_t i;
buffer[index] = new_sample;
index = (index + 1) % FILTER_WINDOW_SIZE;
if (count < FILTER_WINDOW_SIZE) count++;
for (i = 0; i < count; i++) {
sum += buffer[i];
}
return (uint16_t)(sum / count);
}
你想想看,窗口大小怎么选?选太小了,滤波效果差;选太大了,响应延迟大。我一般选 8 到 16 之间。在麻醉机上,氧浓度变化不会特别快,所以 10 个点是比较折中的选择。
4.3.2 中值滤波
中值滤波就厉害了。它把窗口内的数据排序,取中间那个值。这个算法对付“野点”特别有效。比如传感器偶尔蹦出一个 200% 的离谱值,滑动平均会被拉偏,但中值滤波直接把它扔掉了。
#define MEDIAN_WINDOW_SIZE 5
static uint16_t sort_buffer[MEDIAN_WINDOW_SIZE];
uint16_t MedianFilter(uint16_t new_sample)
{
uint8_t i, j;
uint16_t temp;
// 把新数据插入到排序缓冲区
for (i = 0; i < MEDIAN_WINDOW_SIZE - 1; i++) {
sort_buffer[i] = sort_buffer[i + 1];
}
sort_buffer[MEDIAN_WINDOW_SIZE - 1] = new_sample;
// 冒泡排序(窗口小,无所谓效率)
for (i = 0; i < MEDIAN_WINDOW_SIZE - 1; i++) {
for (j = 0; j < MEDIAN_WINDOW_SIZE - 1 - i; j++) {
if (sort_buffer[j] > sort_buffer[j + 1]) {
temp = sort_buffer[j];
sort_buffer[j] = sort_buffer[j + 1];
sort_buffer[j + 1] = temp;
}
}
}
return sort_buffer[MEDIAN_WINDOW_SIZE / 2];
}
我的经验:
在实际项目中,我经常把两种滤波组合起来用。先做一次中值滤波,把明显的野点干掉,再做一次滑动平均,把剩下的毛刺磨平。这样既保证了响应速度,又保证了数据平滑度。你可以试试看。
4.4 总结一下
这一章我们聊了三件事:
- 电化学传感器原理:电流和氧浓度成正比,但要注意温度补偿和老化校准。
- I2C 通信实现:地址要左移一位,上拉电阻别选太大,时序要严格按 datasheet 来。
- 数据滤波算法:滑动平均去噪声,中值滤波去野点,组合使用效果更佳。
下一章我会讲怎么把这些数据整合到麻醉机的安全监控系统中。到时候你会看到,一个简单的氧浓度值,背后有多少道“安检”工序。嗯,咱们下章见。