4、数据采集:使用微控制器读取温湿度数据,实现定时采样与数据格式化

在TinyML异常检测系统中,数据采集是连接物理世界与模型推理的桥梁。温湿度传感器输出的模拟信号或数字协议数据,必须经过微控制器的正确读取、时间规整和结构化处理,才能成为后续阈值判断与模型推理的有效输入。本章将围绕微控制器读取温湿度数据的完整链路,讲解硬件接口原理、定时采样机制、数据格式化方法,并通过一个实际工程案例展示从传感器原始输出到标准化数据帧的完整转换过程。

温湿度数据采集与格式化链路 温湿度传感器 SHT30 / DHT22 I2C / 单总线 温度±0.3°C 湿度±2%RH 信号调理 电平匹配/上拉电阻 滤波电容/ESD保护 I2C地址选择 微控制器 (MCU) ESP32 / STM32 I2C外设 / GPIO 定时器触发采样 数据格式化 JSON/CSV/二进制 时间戳+传感器ID 校验和/单位标注 定时采样机制 定时器中断 → 采样标志位 → 主循环读取 采样周期可配置:1s / 5s / 10s / 60s 数据帧结构示例 {"id":"sht30_01","ts":1699000000, "temp":25.3,"hum":58.7} 时间戳(Unix) + 温度(°C) + 湿度(%RH) 模拟/数字信号 I2C总线通信 结构化输出

上图展示了温湿度数据从传感器到格式化输出的完整链路。整个系统分为四个核心模块:传感器感知、信号调理、微控制器读取与数据格式化。其中定时采样机制决定了数据的时间分辨率,而数据格式化则直接影响后续TinyML模型的输入质量。下面我们将逐一深入每个环节的实现细节。

4.1 核心原理:传感器接口与微控制器读取机制

温湿度传感器与微控制器之间的通信方式决定了数据读取的基本流程。目前主流传感器主要采用两种接口协议:I2C(如SHT30、SHTC3)和单总线(如DHT22)。I2C协议使用SCL(时钟线)和SDA(数据线)两根线进行同步通信,支持多设备挂载,通信速率可达400kHz;单总线协议仅需一根数据线,但时序要求严格,适合对引脚资源敏感的场景。

以I2C接口的SHT30传感器为例,其读取流程包含以下关键步骤:

  1. 总线初始化:配置微控制器的I2C外设,设置时钟频率(通常为100kHz或400kHz),并使能上拉电阻。
  2. 设备寻址:SHT30的7位地址默认为0x44(ADDR引脚接地)或0x45(ADDR引脚接VCC),发送起始条件后,先发送设备地址加写位(0x44<<1 | 0),等待ACK应答。
  3. 发送测量命令:向传感器发送2字节命令,例如0x2C 0x06表示开启高重复性测量,传感器将执行一次温湿度采集。
  4. 等待测量完成:根据重复性设置,等待约2-15ms,期间可执行其他任务或使用延时函数。
  5. 读取数据:发送设备地址加读位(0x44<<1 | 1),连续读取6字节数据(温度高字节、温度低字节、CRC校验、湿度高字节、湿度低字节、CRC校验)。
  6. 数据校验与转换:对读取的原始值进行CRC校验,然后通过公式转换为实际物理量:温度 = -45 + 175 × (原始值 / 65535),湿度 = 100 × (原始值 / 65535)。

工程经验:在批量读取传感器时,建议将I2C通信封装为独立函数,并加入超时机制。例如使用i2c_read_timeout替代阻塞式读取,避免传感器无响应时导致系统卡死。同时,对于DHT22这类单总线传感器,必须严格遵循其时序要求——主机拉低总线至少18ms再释放,传感器会拉低80μs再拉高80μs表示响应,随后按50μs低电平+26~28μs高电平表示"0",50μs低电平+70μs高电平表示"1"。

4.2 定时采样机制:从定时器中断到数据缓存

定时采样是TinyML系统获取时间序列数据的基础。微控制器通常采用硬件定时器产生精确的中断信号,在中断服务函数(ISR)中设置采样标志位,主循环检测到标志位后执行传感器读取和数据格式化操作。这种设计避免了在ISR中执行耗时的I2C通信,保证了系统的实时性和稳定性。

采样周期的选择需要根据应用场景权衡:

采样周期适用场景数据量/天异常检测灵敏度
1秒快速温度变化环境(如冷库门开关)86,400条极高,可捕捉瞬时异常
5秒一般工业环境监测17,280条高,适合多数场景
10秒仓库、机房等稳定环境8,640条中等,可检测缓慢漂移
60秒长期趋势分析1,440条低,仅适合宏观异常

在实现定时采样时,推荐使用环形缓冲区(Ring Buffer)暂存采样数据。环形缓冲区可以避免频繁的动态内存分配,同时支持生产者(定时器中断)与消费者(主循环)之间的解耦。缓冲区大小建议设置为采样周期的10-20倍,例如5秒采样周期下,缓冲区可存储100条数据(约8分钟),为后续的滑动窗口异常检测提供足够的历史数据。

// 定时采样核心代码示例(ESP32 + SHT30)
#define SAMPLE_INTERVAL_MS 5000  // 5秒采样周期
#define BUFFER_SIZE 100          // 环形缓冲区大小

typedef struct {
    float temperature;
    float humidity;
    uint32_t timestamp;
} sensor_data_t;

sensor_data_t data_buffer[BUFFER_SIZE];
volatile uint8_t head = 0, tail = 0;
volatile bool sample_flag = false;

// 定时器中断服务函数(1ms周期)
void IRAM_ATTR timer_isr() {
    static uint32_t counter = 0;
    if (++counter >= SAMPLE_INTERVAL_MS) {
        counter = 0;
        sample_flag = true;  // 置位采样标志
    }
}

void loop() {
    if (sample_flag) {
        sample_flag = false;
        sensor_data_t data;
        data.timestamp = millis() / 1000;  // Unix时间戳(简化)
        read_sht30(&data.temperature, &data.humidity);  // I2C读取
        // 写入环形缓冲区
        data_buffer[head] = data;
        head = (head + 1) % BUFFER_SIZE;
        if (head == tail) tail = (tail + 1) % BUFFER_SIZE;  // 覆盖最旧数据
        format_and_send(&data);  // 数据格式化与发送
    }
}

风险提示:在ISR中仅设置标志位,切勿在ISR内直接调用I2C读取函数。I2C通信需要等待传感器响应,耗时可能达到毫秒级,会阻塞中断导致系统时钟漂移。此外,环形缓冲区满时采用覆盖策略,需根据应用决定是丢弃最旧数据还是停止采样,避免数据丢失影响异常检测的连续性。

4.3 数据格式化:从原始字节到结构化数据帧

微控制器读取到的温湿度原始值通常是16位无符号整数,必须经过格式转换和结构化封装,才能被后续的TinyML模型或上位机正确解析。数据格式化包含三个层次:物理量转换、数据帧封装、序列化输出。

物理量转换是将原始ADC值或传感器寄存器值映射为实际物理单位。对于SHT30,温度转换公式为T = -45 + 175 × (raw_temp / 65535),湿度为RH = 100 × (raw_hum / 65535)。转换后的浮点数需要根据精度要求进行舍入,通常保留1位小数即可满足异常检测需求。

数据帧封装需要定义统一的结构,包含以下字段:

  • 设备标识:传感器ID或节点ID,用于多设备场景下的数据区分。
  • 时间戳:Unix时间戳(秒级)或毫秒级时间戳,用于时间序列对齐。
  • 温度值:浮点数,单位°C。
  • 湿度值:浮点数,单位%RH。
  • 状态标志:传感器错误、CRC校验失败等异常状态。
  • 校验和:对数据帧进行CRC16或简单累加校验,确保传输完整性。

序列化输出根据下游需求选择格式:

格式优点缺点适用场景
JSON可读性强,易于调试解析开销大,占用Flash开发调试、云端对接
CSV简洁,适合日志存储缺乏类型信息SD卡记录、PC分析
二进制体积小,解析快可读性差,需协议文档低功耗无线传输
MessagePack类JSON但更紧凑需要额外库支持资源受限的TinyML设备
// JSON格式化示例(使用ArduinoJson库)
#include <ArduinoJson.h>

void format_and_send(sensor_data_t *data) {
    StaticJsonDocument<128> doc;
    doc["id"] = "sht30_01";
    doc["ts"] = data->timestamp;
    doc["temp"] = serialized(String(data->temperature, 1));  // 保留1位小数
    doc["hum"] = serialized(String(data->humidity, 1));
    char buffer[128];
    serializeJson(doc, buffer);
    Serial.println(buffer);  // 输出到串口或无线模块
}

经验建议:在资源受限的微控制器上,避免使用动态JSON库(如DynamicJsonDocument),改用静态分配(StaticJsonDocument)可防止内存碎片。对于二进制格式,建议使用memcpy将结构体直接打包发送,但需注意字节序(小端/大端)对齐问题。

4.4 实战案例:冷链运输环境监测系统的数据采集

我们为一个疫苗冷链运输箱设计温湿度监测系统。约束条件:使用ESP32微控制器,SHT30传感器,采样周期10秒,数据通过WiFi上传至云端,同时本地SD卡备份。初始现象:运输过程中温度波动较大,需要实时监测并触发异常报警。

关键参数设定:

  • 采样周期:10秒(兼顾电池续航与监测精度)
  • 温度报警阈值:2°C ~ 8°C(疫苗存储标准)
  • 湿度报警阈值:30%RH ~ 70%RH
  • 数据帧格式:JSON,包含设备ID、时间戳、温度、湿度、电池电量
  • 环形缓冲区:存储60条数据(10分钟历史),用于滑动窗口检测

实施步骤:

  1. 初始化I2C总线(频率400kHz),配置SHT30为高重复性测量模式。
  2. 使用ESP32的硬件定时器(Timer0)产生1ms中断,累计10000次后置位采样标志。
  3. 主循环检测到标志后,读取传感器数据,进行CRC校验和物理量转换。
  4. 将数据封装为JSON格式,同时写入SD卡(CSV格式)和通过MQTT发送至云端。
  5. 在本地维护一个60条数据的环形缓冲区,每10秒计算一次滑动窗口内的均值与标准差,若超出阈值范围则触发LED报警。

结果对比:系统连续运行72小时,共采集25,920条数据。实际测试中,当打开箱门导致温度从5°C骤降至1°C时,系统在20秒内(2个采样周期)检测到异常并触发报警。与纯阈值判断相比,结合滑动窗口的均值漂移检测将误报率降低了约35%。

风险与代价:高频率采样(1秒)会导致电池续航从72小时缩短至18小时,因此最终选择10秒采样周期。此外,JSON格式在WiFi传输中占用的带宽约为二进制格式的3倍,但在实验环境中WiFi信号稳定,未造成明显延迟。

复盘经验:定时采样中,时间戳的同步至关重要。ESP32的millis()在长时间运行后可能溢出(约49天),建议使用NTP时间同步或记录相对时间戳。另外,SD卡写入操作耗时约30ms,在写入期间应暂停采样或使用双缓冲机制,避免数据丢失。

4.5 常见误区与总结复盘

在数据采集环节,开发者常犯以下错误:

  • 忽略传感器上电稳定时间:SHT30上电后需要至少1ms稳定时间,DHT22则需要1秒。若上电后立即读取,可能返回错误数据。
  • 采样周期与中断优先级冲突:当系统同时使用WiFi和传感器时,WiFi中断可能抢占定时器中断,导致采样周期抖动。解决方案是使用ESP32的Core 1专门处理WiFi,Core 0处理传感器采样。
  • 数据格式化时丢失精度:将浮点数直接转换为整数(如(int)temp)会丢失小数部分,导致异常检测灵敏度下降。应使用round()函数或保留固定小数位。
  • 未处理传感器通信故障:I2C总线可能因干扰导致通信失败,应加入重试机制(如连续3次失败则标记传感器错误),并设置看门狗定时器防止系统死循环。

本章核心要点:数据采集是TinyML系统的"感知层",其质量直接决定异常检测的准确性。定时采样应基于硬件定时器而非软件延时,数据格式化需兼顾可读性与传输效率,环形缓冲区是连接采样与处理的高效桥梁。在实际部署中,务必根据应用场景权衡采样周期、数据格式与功耗,并通过充分的故障处理机制保证系统的鲁棒性。