传感器数据采集:温湿度传感器(SHT30)、气压传感器(BMP280)、风速风向传感器、雨量传感器接口与驱动

各位同学,咱们今天聊聊传感器采集这块。说实话,气象站好不好用,一半看传感器驱动写得怎么样。我见过太多项目,硬件选型没问题,结果驱动写得稀烂,数据飘得没法看。今天我把四种核心传感器的接口和驱动经验,一次性讲透。

一、温湿度传感器 SHT30:I2C 总线的老朋友

SHT30 是 Sensirion 家的经典款。我个人习惯把它当作 I2C 传感器的入门标杆——简单、稳定、精度够用。

1. 硬件接口

SHT30 使用标准 I2C 接口,地址有两种:

  • ADDR 引脚接 GND:设备地址 0x44
  • ADDR 引脚接 VDD:设备地址 0x45

嗯,这里要注意:很多开发板默认把 ADDR 拉低了,你读不到数据时先查查地址对不对。我在项目中遇到过三次,都是因为地址搞错了,折腾了半天。

2. 驱动核心:单次测量模式

我个人推荐使用单次测量模式,而不是周期模式。为什么?因为周期模式会一直占用总线,你想想看,如果系统里还有气压传感器、风速传感器,总线冲突就麻烦了。

// SHT30 单次测量命令(高重复性)
#define SHT30_CMD_MEAS_HIGH 0x2C06

// 发送测量命令
uint8_t cmd[2] = {0x2C, 0x06};
i2c_write(0x44, cmd, 2);

// 等待 15ms(数据手册要求)
delay_ms(15);

// 读取 6 字节数据
uint8_t buf[6];
i2c_read(0x44, buf, 6);

// 计算温湿度
uint16_t raw_temp = (buf[0] << 8) | buf[1];
uint16_t raw_humi = (buf[3] << 8) | buf[4];

float temperature = -45.0f + 175.0f * raw_temp / 65535.0f;
float humidity = 100.0f * raw_humi / 65535.0f;
避坑指南:我曾经在批量生产时发现,SHT30 的 CRC 校验不能省。虽然大部分时候数据是对的,但偶尔会有 1-2 个 bit 跳变。加上 CRC 校验,能过滤掉 99% 的异常数据。

二、气压传感器 BMP280:精度与功耗的平衡

BMP280 是博世的产品,支持 I2C 和 SPI 两种接口。我个人更倾向 I2C,因为少两根线,布线简单。

1. 校准系数:绕不开的坑

BMP280 出厂时内部存了校准系数,你必须先读出来,才能算准气压值。说白了,没有校准系数,你算出来的数据就是错的。

// 读取校准系数(24 字节)
uint8_t calib[24];
i2c_read(0x76, 0x88, calib, 24);

// 解析校准系数
uint16_t dig_T1 = (calib[1] << 8) | calib[0];
int16_t  dig_T2 = (calib[3] << 8) | calib[2];
int16_t  dig_T3 = (calib[5] << 8) | calib[4];
// ... 还有气压校准系数
注意:BMP280 的校准系数是每个芯片独有的。你不能用一个芯片的校准系数去算另一个芯片的数据。我曾经见过有人偷懒,结果气压数据差了 200Pa,相当于 20 米的海拔误差。

2. 采样模式选择

BMP280 有四种采样模式:

模式 功耗 噪声 适用场景
Ultra Low Power 2.8 μA 3.3 Pa 电池供电
Standard 3.7 μA 2.6 Pa 日常使用
High Resolution 5.2 μA 2.0 Pa 气象站
Ultra High Resolution 7.7 μA 1.6 Pa 精密测量

我个人建议气象站用 High Resolution 模式。功耗多不了多少,但数据稳定很多。

三、风速风向传感器:脉冲信号与模拟量

风速风向传感器种类很多,我主要讲两种最常见的:脉冲输出型和模拟电压输出型。

1. 风速传感器:脉冲计数法

大部分风速传感器输出的是脉冲信号。风速越快,脉冲频率越高。说白了,就是测频率。

// 使用定时器捕获脉冲
volatile uint32_t pulse_count = 0;

void EXTI_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        pulse_count++;
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

// 每秒计算一次风速
float calc_wind_speed(void) {
    uint32_t count = pulse_count;
    pulse_count = 0;
    // 假设每 Hz 对应 0.1 m/s
    return count * 0.1f;
}
经验之谈:我曾经在风大的地方测试,发现脉冲信号有抖动。后来加了一个 10ms 的软件消抖,数据就稳了。你想想看,机械触点抖动是常态,不加消抖就是给自己挖坑。

2. 风向传感器:模拟电压转角度

风向传感器通常输出 0-5V 模拟电压,对应 0-360 度。这里有个坑:0 度和 360 度是同一个方向,但电压可能不一样。

// 读取 ADC 值
uint16_t adc_value = adc_read(ADC_CHANNEL_0);

// 转换为电压
float voltage = adc_value * 5.0f / 4095.0f;

// 转换为角度(假设 0V=0度,5V=360度)
float angle = voltage * 360.0f / 5.0f;

// 处理边界情况
if (angle >= 360.0f) {
    angle = 0.0f;
}

四、雨量传感器:翻斗式与电容式

雨量传感器我主要用翻斗式的。结构简单,成本低,精度够用。电容式的虽然没机械部件,但受温度影响大,我一般不推荐。

1. 翻斗式雨量计:中断计数

翻斗每翻一次,干簧管就闭合一次。说白了,就是数脉冲。但这里有个关键点:每次翻斗代表多少毫米降雨量?

// 雨量中断处理
volatile uint32_t rain_count = 0;

void RAIN_EXTI_IRQHandler(void) {
    // 消抖处理
    if (debounce_check()) {
        rain_count++;
    }
}

// 计算降雨量(假设每斗 0.5mm)
float calc_rainfall(void) {
    uint32_t count = rain_count;
    rain_count = 0;
    return count * 0.5f;
}
重要提醒:翻斗式雨量计有个问题——大雨时翻斗翻得太快,会漏掉一些雨水。我曾经在暴雨天测试,发现实际降雨量比传感器读数多了 15%。解决办法是加一个雨量修正系数,根据雨强动态调整。

2. 电容式雨量传感器:模拟量采集

如果你非要用电容式的,记住一点:电容值受温度影响很大。你需要做温度补偿。

// 电容式雨量传感器读取
float read_capacitive_rain(void) {
    uint16_t raw = adc_read(ADC_CHANNEL_1);
    float temp = read_temperature();  // 从 SHT30 获取
    
    // 温度补偿
    float compensated = raw * (1.0f + 0.002f * (temp - 25.0f));
    
    // 转换为降雨量
    return (compensated - DRY_VALUE) * RAIN_SCALE;
}

五、传感器数据融合:时间戳与对齐

四种传感器采集频率不一样。SHT30 和 BMP280 可以每秒采一次,风速风向可能需要 0.5 秒一次,雨量则是事件触发。怎么对齐?

我个人习惯的做法是:

  1. 统一时间基准:所有数据都带上系统时间戳
  2. 异步采集,同步输出:每个传感器独立采集,但输出时按时间戳对齐
  3. 数据缓存:用环形缓冲区存最近 10 秒的数据
// 传感器数据结构
typedef struct {
    uint32_t timestamp;
    float temperature;
    float humidity;
    float pressure;
    float wind_speed;
    float wind_direction;
    float rainfall;
} sensor_data_t;

// 环形缓冲区
#define BUFFER_SIZE 100
sensor_data_t data_buffer[BUFFER_SIZE];
uint8_t head = 0, tail = 0;

核心要点:传感器驱动不是简单的读寄存器。你要考虑时序、校准、消抖、温度补偿、数据对齐。我做了十年嵌入式,踩过的坑比你们走过的路还多。但记住一点:先把基础驱动写稳,再谈优化。

好了,这四种传感器的驱动就讲到这里。下一章咱们聊聊数据滤波和异常处理——说白了,就是怎么把脏数据洗干净。到时候我会分享一个我压箱底的卡尔曼滤波简化版,保证你们用得上。