第三章 多传感器数据采集:从硬件到软件的完整链路
大家好,我是这次课程的主讲工程师。今天我们来聊聊多传感器数据采集这个环节。说实话,我在安全预警系统里踩过最大的坑,就是数据采集这块。传感器选型不对、接口协议不匹配、软件框架设计不合理——任何一个环节出问题,整个系统都得返工。
这一章,我会从硬件方案、接口协议、软件框架三个维度,把我这些年积累的经验和教训都摊开来讲。你想想看,一个安全预警系统,前端传感器要是数据都采不准,后面云边协同再牛也是白搭。
3.1 数据采集硬件方案
先说说硬件选型。我个人习惯把传感器分为三类:环境感知类(温度、湿度、烟雾)、物理量感知类(振动、压力、位移)、以及特殊感知类(红外、微波、激光雷达)。
在安全预警场景下,我建议采用模块化设计。什么意思呢?就是每个传感器做成独立的小板子,通过统一的接口连接到主控板上。这样做的好处是——哪个传感器坏了,直接换模块,不用动整个系统。
核心硬件选型建议:
- 主控芯片:STM32F4系列或ESP32,前者稳定,后者带WiFi/蓝牙
- 传感器模组:DHT22(温湿度)、MPU6050(六轴姿态)、MQ系列(气体检测)
- 通信模块:LoRa(远距离)、ZigBee(组网)、NB-IoT(蜂窝)
- 电源管理:TPS5430(降压)、MAX17048(电量监测)
我在一个化工厂的项目里,曾经用过24个传感器同时采集。当时选的是STM32F407,主频168MHz,外设接口够多。但有个教训——电源纹波。传感器对电源质量很敏感,尤其是模拟输出的传感器。后来我加了LC滤波和独立LDO,问题才解决。
3.2 传感器接口协议详解
接口协议这块,说白了就是传感器和主控之间怎么「说话」。常见的就四种:I2C、SPI、UART、GPIO。每种协议都有自己的脾气,选错了,数据就乱套了。
3.2.1 I2C(两线制)
I2C只有两根线:SCL(时钟)和SDA(数据)。它最大的特点是多设备共用总线。你可以在一条总线上挂几十个传感器,每个设备有自己的地址。
我的经验:I2C适合短距离(1米以内)、低速(400kHz以下)的场景。我曾经在一条I2C总线上挂了8个传感器,结果发现总线电容太大,信号变形了。解决办法是降低时钟频率到100kHz,或者加总线缓冲器。
// I2C读取温湿度传感器示例(伪代码)
void read_sensor_i2c(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len) {
i2c_start(); // 起始信号
i2c_write(addr << 1); // 设备地址 + 写位
i2c_write(reg); // 寄存器地址
i2c_stop();
i2c_start(); // 重复起始
i2c_write((addr << 1) | 1); // 设备地址 + 读位
for (int i = 0; i < len; i++) {
data[i] = i2c_read(i == len-1 ? NACK : ACK);
}
i2c_stop();
}
3.2.2 SPI(四线制)
SPI比I2C快得多,最高能跑到几十MHz。它用四根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。每个设备需要独立的CS引脚。
注意:SPI没有标准的设备地址,全靠CS片选。如果你要接多个传感器,主控的GPIO引脚要够用。我有个项目接了6个SPI传感器,结果GPIO不够,最后用了一个I2C转SPI的桥接芯片才搞定。
// SPI读取加速度计示例
uint8_t spi_read_byte(uint8_t reg) {
uint8_t data;
CS_LOW(); // 拉低片选
spi_transfer(reg | 0x80); // 发送寄存器地址(读标志)
data = spi_transfer(0x00); // 读取数据
CS_HIGH(); // 拉高片选
return data;
}
3.2.3 UART(串口)
UART是异步通信,不需要时钟线。它用TX和RX两根线,加上GND共地。很多GPS模块、激光雷达都用UART接口。
UART有个好处——距离远。RS232能传15米,RS485能传1200米。我在一个大型仓库的安全预警项目里,传感器分布在各个角落,就是用RS485总线把所有UART设备连起来的。
避坑指南:我曾经在UART通信中忽略了波特率匹配的问题。传感器默认是9600,我代码里配成了115200,结果读出来的全是乱码。排查了整整半天才发现。所以,一定要先确认传感器的数据手册,波特率、数据位、停止位、校验位,一个都不能错。
3.2.4 GPIO(通用输入输出)
GPIO是最简单的接口,就一个引脚,要么高电平要么低电平。很多开关量传感器(比如门磁、红外对射)就用GPIO。
但GPIO也有讲究。比如上拉/下拉电阻,我见过有人把GPIO直接悬空,结果电平乱跳,系统误报了一整夜。正确的做法是:内部上拉或者外部加10kΩ上拉电阻。
// GPIO读取门磁状态
#define DOOR_SENSOR_PIN GPIO_PIN_0
#define DOOR_PORT GPIOA
uint8_t read_door_status() {
return HAL_GPIO_ReadPin(DOOR_PORT, DOOR_SENSOR_PIN);
// 返回0:门关闭(安全),返回1:门打开(报警)
}
3.3 数据采集软件框架
硬件选好了,协议搞定了,接下来就是软件框架。我见过太多人上来就写业务逻辑,结果后面要加传感器、改采样率,代码改得想哭。
我个人推荐分层架构。说白了,就是把代码分成三层:驱动层、抽象层、应用层。
为什么分层?你想想看,如果今天把DHT22换成SHT30,驱动层代码要改,但抽象层和应用层不用动。这就是解耦的好处。
3.3.1 驱动层(HAL)
驱动层直接和硬件打交道。每个传感器一个驱动文件,里面封装了读写寄存器、初始化、校准等操作。
// 驱动层接口示例(sensor_dht22.h)
#ifndef __SENSOR_DHT22_H__
#define __SENSOR_DHT22_H__
typedef struct {
float temperature;
float humidity;
uint8_t crc_ok;
} dht22_data_t;
int dht22_init(GPIO_TypeDef *port, uint16_t pin);
int dht22_read(dht22_data_t *data);
#endif
3.3.2 抽象层(Sensor Abstraction)
抽象层把不同传感器的差异抹平。比如温度传感器,不管你是DHT22、DS18B20还是BME280,抽象层都提供一个统一的接口:float get_temperature()。
// 抽象层接口
typedef struct {
int (*init)(void);
int (*read)(sensor_data_t *data);
void (*deinit)(void);
} sensor_ops_t;
// 注册传感器
void sensor_register(sensor_type_t type, sensor_ops_t *ops);
// 统一读取
int sensor_read(sensor_type_t type, sensor_data_t *data);
3.3.3 应用层(Application)
应用层只管业务逻辑。比如:每5秒采集一次数据,如果温度超过60度就报警。它不关心底层用的是I2C还是SPI。
// 应用层代码
void safety_monitor_task(void *param) {
sensor_data_t data;
while (1) {
if (sensor_read(SENSOR_TEMP, &data) == 0) {
if (data.temperature > 60.0f) {
alarm_trigger(ALARM_OVERHEAT);
log_warning("温度过高: %.2f°C", data.temperature);
}
}
vTaskDelay(pdMS_TO_TICKS(5000)); // 5秒采集一次
}
}
3.4 本章知识体系总览
下面这张图,是我用SVG画的本章核心逻辑。你可以看到从硬件选型到接口协议,再到软件框架的完整链路。
3.5 本章小结
数据采集是整个安全预警系统的地基。地基不稳,上面云边协同再牛也没用。
- 硬件方案:模块化设计,注意电源纹波和接口数量
- 接口协议:I2C适合多设备短距离,SPI适合高速,UART适合远距离,GPIO适合开关量
- 软件框架:分层架构,驱动层、抽象层、应用层各司其职
嗯,这一章就到这里。记住一句话:采集做不好,后面全是白忙活。下一章我们会聊传感器数据预处理,包括滤波、校准、异常检测——这些才是让数据真正「能用」的关键步骤。