传感器数据采集:温湿度传感器(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;
二、气压传感器 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];
// ... 还有气压校准系数
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;
}
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;
}
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 秒一次,雨量则是事件触发。怎么对齐?
我个人习惯的做法是:
- 统一时间基准:所有数据都带上系统时间戳
- 异步采集,同步输出:每个传感器独立采集,但输出时按时间戳对齐
- 数据缓存:用环形缓冲区存最近 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;
核心要点:传感器驱动不是简单的读寄存器。你要考虑时序、校准、消抖、温度补偿、数据对齐。我做了十年嵌入式,踩过的坑比你们走过的路还多。但记住一点:先把基础驱动写稳,再谈优化。
好了,这四种传感器的驱动就讲到这里。下一章咱们聊聊数据滤波和异常处理——说白了,就是怎么把脏数据洗干净。到时候我会分享一个我压箱底的卡尔曼滤波简化版,保证你们用得上。