STM32环境监测系统实战:从传感器选型到数据可信的完整指南
2026/9/17 3:23:07 网站建设 项目流程

简介:这份基于STM32的室内环境监测系统程序源码,面向单片机初学者和课程设计开发者,完整实现DHT11温湿度采集、阈值比较、LED指示灯报警与数码管显示等功能。项目以STM32F103C8T6最小系统为核心,使用Keil5编写,代码结构清晰,能帮助理解传感器驱动、外设初始化及主循环调度等典型知识点。压缩包共156个文件,大小仅2.71MB,以c源码和h头文件为主,同时包含uvprojx工程文件、hex烧录文件、map映射文件及大量编译中间产物,构成可直接打开学习和烧录验证的完整Keil工程。已有1313人浏览学习。对于需要快速搭建室内环境监测原型或借鉴温湿度控制逻辑的开发者,这份源码可以节省从零编写外设驱动的时间,通过阅读代码还能掌握DHT11单总线时序读取、阈值报警和数码管动态扫描等实用技巧,便于在此基础上继续扩展显示或通信功能。

1. 编译通过不等于数据可信:STM32环境监测系统的真实工作量

拿到一套“基于STM32的室内环境监测系统程序源码”,最容易犯的错是直接烧录进去,看到串口有数据就以为完工。OLED 上有温度、湿度、PM2.5,网页上也能刷出曲线,看起来一切正常,直到你把传感器读数跟标准仪器并排放一天,才发现温差 2°C、湿度差 8%RH,而这个误差不是换一颗传感器能解决的。室内环境监测系统的难点从来不是“读 I2C 寄存器”,而是怎样把传感器裸数据转换成可信的工程值,再经过通信链路送到上位机而不丢字节、不产生假数据。这套系统的技术栈说高不高——STM32F103 或 F401、SHT30 或 AHT21、SGP30 或 CCS811、GP2Y1010 或 SDS011,加上串口或 Wi-Fi 模块——但把它做成“准仪器”而不是“电子积木”,差距全在校准、滤波、通信协议和低功耗这几层。这篇文章就按一线工程师做这类项目的完整顺序,把硬件选型、驱动设计、数据链路和现场排查讲透,目标是你照着搭出来的系统,能直接拿去做 72 小时连续监测,而不是只在桌面上亮屏。

2. STM32 环境监测系统的硬件架构与传感器信号链设计

2.1 传感器选型:不是越贵越好,而是接口和量程匹配

ST 的 MCU 做环境监测,主流方案有两条路线。一条是全数字接口路线,温湿度用 SHT30(I2C,精度 ±0.3°C / ±2%RH),CO₂ 用 SGP30(I2C),PM2.5 用 PMS5003(UART,输出已校准的质量浓度)。另一条是模拟量路线,PM2.5 用夏普 GP2Y1010(模拟电压输出),甲醛用电化学传感器加运放。两条路线在代码工作量上差距极大:数字传感器只需要写寄存器读写,模拟传感器要先做基准电压标定、运放偏置补偿,再在 MCU 内部做 ADC 均值滤波和曲线插值。选型时有一个实际原则:可靠性优先于成本,数字传感器优先于模拟传感器。同样测 PM2.5,PMS5003 的 UART 输出直接给 μg/m³,GP2Y1010 的电压值还需要根据粉尘特性拟合曲线,而这个拟合在真实环境里会漂。如果项目是毕业设计或产品原型,优先选带数字接口的传感器,把 ADC 通道留给真正必须模拟采样的物理量。

2.1.1 I2C 总线上挂多颗传感器的地址冲突与电平匹配

SHT30 的 I2C 地址是 0x44(ADDR 引脚接低)或 0x45(接高),SGP30 的地址固定在 0x58,两路总线时序都要求上拉电阻。实际项目里最容易出问题的是传感器工作电压不一致:SHT30 允许 2.4V~5.5V,但 SGP30 最高只能 1.95V,如果直接挂在同一个 3.3V 上拉的总线上,SGP30 的 I2C 引脚存在过压风险。常见做法是用 PCA9306 电平转换芯片或分压电阻把 SGP30 的 SCL/SDA 从 3.3V 降到 1.8V,代码上无需任何改动。另一个隐蔽问题是总线电容过大:STM32 的 I2C 外设标准模式支持 100kHz,但室内监测系统常用的排线长度超过 20cm 后,上升沿变缓,通信出现随机 NACK。这时候优先把 I2C 时钟降到 50kHz,而不是换传感器。

2.2 STM32 端 ADC 采样与均值滤波的参数设计

模拟量传感器需要 ADC 采样,STM32 的 12 位 ADC 在 3.3V 参考电压下,LSB 约 0.806mV。以 GP2Y1010 为例,其输出电压范围 0.9V~3.6V,对应粉尘浓度的变化约从 0 到 500μg/m³。这时要做两件事:第一是硬件上并联一个 0.1μF 的电容到 ADC 引脚,并在 P0 引脚加 150Ω 限流电阻、电容 220μF 做脉冲电流支撑;第二是在代码里做滑动均值滤波,窗口大小推荐 16~32 个采样点,太小滤不掉 100Hz 工频干扰,太大会拖慢系统对烟雾等突发污染的反应。具体到代码,我一般会开一个 32 点的环形缓冲区,每 200ms 采样一次,持续累计并剔除最大最小值后再求平均,这样比直接均值更抗脉冲干扰。

2.3 供电与参考电压:环境监测系统最容易被忽略的误差源

室内监测系统通常是 5V USB 供电,板上再用 AMS1117-3.3 降压给 MCU 和传感器供电。这里有一个真实的坑:SHT30 的湿度测量受供电电压纹波影响明显,如果 3.3V 上并联了舵机或继电器等大电流负载,湿度读数会周期性跳变。系统里如果有 Wi-Fi 模块(ESP8266),其发射峰值电流可达 300mA,足以拉低 3.3V 导致 ADC 参考漂移。做法是电源分割:模拟部分和数字部分各自用磁珠加上 10μF 电容去耦,ADC 参考电压不从 LDO 直取,而是用 TL431 做独立基准。如果预算有限,至少做到在代码里采集一个固定分压点做实时补偿,以抵消电源波动带来的测量偏移。

3. STM32 工程结构与驱动代码组织:从裸机轮询到时间片调度

3.1 HAL 库 vs 标准库:项目源码阅读与二次开发的关键差异

拿到环境监测源码,第一件事是确认它基于哪种固件库。HAL 库是 ST 官方主推,代码抽象层厚,更新活跃,CubeMX 生成的工程里外设初始化代码自动生成,缺点是中断回调和延迟函数嵌套层级深,排查问题时要多跳几层。标准库已经停止更新,但代码直白,寄存器操作透明,很多老工程师维护的源码仍在使用。如果你的源码是标准库写的,想移植到 HAL 库工程,工作量主要在 GPIO 初始化、I2C 读写函数、定时器中断回调这几部分。以 I2C 为例,标准库的I2C_SendData()和 HAL 的HAL_I2C_Master_Transmit()不仅 API 不同,错误处理机制也不同,后者依赖超时和状态机,前者是阻塞等待。推荐做法是:传感器驱动自己做一层抽象接口,底层用宏切换 HAL 或标准库实现,这样源码可以同时兼容两种工程。

3.1.1 驱动层抽象:一套传感器驱动兼容多款 MCU 的写法

我在做驱动层时通常会定义统一的读接口。例如温湿度传感器的标准接口是int temp_humi_read(float *temp, float *humi),PM2.5 的标准接口是int pm25_read(uint16_t *pm25),CO₂ 是int co2_read(uint16_t *co2),上层逻辑只调用这组函数,不关心底层是 I2C 还是 UART。这样换 MCU——比如把 F103C8T6 换成 F401CCU6——只需要重写底层的 I2C 读写和 UART 接收部分,传感器的寄存器配置和校验逻辑全部保留。这个设计对源码阅读者也友好:你可以先读app_layer里的用户逻辑,再回头查driver目录下某颗具体的传感器实现。

3.2 时间片轮询调度:不跑 RTOS 的多任务写法

室内环境监测系统通常有周期性任务:温湿度采样每 2s 一次,PM2.5 每 5s 一次,LCD 或 OLED 刷新每 500ms 一次,串口上报每 1s 一次。如果全部用HAL_Delay()串联,最直接的后果是某一次 PM2.5 读取卡住(UART 超时等待),整个循环被拖慢,所有任务都出现延迟。正确做法是一个 1ms 的 SysTick 中断维护一个 tick 计数器,主循环里用非阻塞方式检查每个任务的到期时间。下面是一个可用的调度骨架:

volatile uint32_t g_tick_ms = 0; // 在 SysTick_Handler 或 HAL_IncTick 回调中调用 void systick_isr(void) { g_tick_ms++; } typedef struct { uint32_t interval; uint32_t last_run; void (*task_func)(void); } task_t; task_t tasks[] = { {2000, 0, temp_humi_task}, // 每 2s 读一次温湿度 {5000, 0, pm25_task}, // 每 5s 读一次 PM2.5 {1000, 0, uart_report_task}, // 每 1s 上报一次数据 {500, 0, oled_refresh_task}, // 每 500ms 刷新显示 }; void main_loop(void) { uint32_t now; uint8_t i; for (;;) { now = g_tick_ms; for (i = 0; i < sizeof(tasks) / sizeof(tasks[0]); i++) { if (now - tasks[i].last_run >= tasks[i].interval) { tasks[i].last_run = now; tasks[i].task_func(); } } } }

这段代码用时间差now - tasks[i].last_run而不是now > tasks[i].last_run + interval来判断,避开了uint32_t溢出回绕带来的比较错误。所有任务都在主循环上下文执行,中断里只维护一个 tick 计数器,不存在临界区问题。如果某个任务执行时间超过自身的调度间隔,会导致后续任务被延迟,所以任务函数内严禁用阻塞式的等待,传感器读取出错时直接返回错误码,下次周期再重试。注意这里的systick_isr是示意写法,在标准库工程中对应SysTick_Handler,在 HAL 库中对应HAL_IncTick(),移植时直接挂接即可。tasks数组的初始化顺序即优先级顺序,排在前面的任务先被执行,像 OLED 刷新这类耗时短的可以排前,PM2.5 这种偶尔需要等待 UART 数据的排后。

3.2.1 传感器读取超时:UART 和 I2C 的阻塞陷阱

UART 读取 PM2.5 数据时要严防阻塞。PMS5003 每 200ms~800ms 主动上报 32 字节,如果程序一直等待“接收完整包”,一旦传感器进入睡眠或线缆接触不良,程序会被卡死在等待状态。我在实际项目中用状态机接收:串口中断每收到一个字节推进状态机,完整收到 32 字节且帧头帧尾校验通过才置位数据就绪标志,主循环里只是查询标志位。对 I2C 同样不能裸等,HAL 库的HAL_I2C_Master_Transmit()默认超时是 1000ms,如果总线上某颗传感器拉低 SCL,MCU 会在这里耗掉大量时间。正确做法是把超时缩短到 20ms~50ms,超时后调用HAL_I2C_DeInit()重新初始化外设,这比无限等待更能保证整个系统的实时性。这也是为什么时间片调度和传感器超时机制必须成对出现——只做调度不做超时保护,系统照样会卡死。

3.3 数据滤波与校准:滑动平均、限幅滤波和温漂补偿

环境监测数据存在两种噪声。第一种是高频随机噪声,比如 ADC 采样时电源纹波带来的跳动,用滑动平均就能压住。第二种是尖峰脉冲,比如有人从传感器旁边走过带动空气流动,PM2.5 值会瞬间冲高,这种应该用限幅滤波:如果当前值比上次值偏差超过阈值,就丢弃本次采样,而不是参与平均。两个滤波器我都会同时启用,顺序是限幅在前、滑动平均在后。限幅阈值根据物理量设定,PM2.5 变化率通常设为 100μg/m³/s,温湿度则不需要限幅,因为热惯性本身就是低通滤波器。SGP30 还有一个特性:刚上电的 12 小时内读数持续漂移,它的基准零点需要自动校准。做法是在源码里维护一个“长期基线”变量,当传感器持续运行且在稳定环境(温度变化小于 1°C、湿度变化小于 5%RH)超过 30 分钟时,把当前读数逐步并入基线,用基线修正输出值。这部分逻辑属于典型的“源码全但你不知道它有什么用”的代码,读的时候要特别留意。

4. 串口与 Wi-Fi 通信链路:环境监测数据上报的完整实现

4.1 串口协议设计:帧格式、校验和数据对齐

环境监测系统的串口上报不能裸发字符串,上位机解析时会遇到断帧、粘包和字符编码问题。我一般会在源码里定义一版固定长度二进制帧,帧头 2 字节0xAA 0x55,紧接着是数据长度、设备地址、命令字、有效数据和 CRC16 校验。有效数据用大端序放温湿度、PM2.5、CO₂ 的原始值,单位在帧格式文档里约定好——比如温度放大 10 倍用 int16 传输,接收端除以 10 还原,避免浮点传输的解析误差。帧格式必须是结构体对齐的,但直接用memcpy发送 C 结构体在跨平台时会有字节对齐和大小端问题,建议在发送代码里逐字节赋值:

uint8_t frame[16]; uint8_t idx = 0; frame[idx++] = 0xAA; // 帧头1 frame[idx++] = 0x55; // 帧头2 frame[idx++] = 0x0C; // 数据长度 frame[idx++] = 0x01; // 设备地址 frame[idx++] = 0x10; // 命令字:主动上报 frame[idx++] = (int16_t)(temp * 10) >> 8; // 温度高字节 frame[idx++] = (int16_t)(temp * 10) & 0xFF; frame[idx++] = (int16_t)(humi * 10) >> 8; // 湿度高字节 frame[idx++] = (int16_t)(humi * 10) & 0xFF; frame[idx++] = pm25 >> 8; // PM2.5 高字节 frame[idx++] = pm25 & 0xFF; frame[idx++] = co2 >> 8; // CO2 高字节 frame[idx++] = co2 & 0xFF; uint16_t crc = crc16_modbus(frame, idx); // 从帧头到数据结尾做 CRC frame[idx++] = crc >> 8; frame[idx++] = crc & 0xFF; HAL_UART_Transmit(&huart1, frame, idx, 100);

这段代码把传感器数据转换成字节流发送,温度和湿度各占 2 字节,PM2.5 和 CO₂ 各占 2 字节,有效数据 8 字节,加上帧头和 CRC 共 16 字节。CRC16_MODBUS 覆盖从帧头到最后一个数据字节,上位机收到数据后先算 CRC,再决定是否丢弃,能有效防止串口干扰导致的假数据。HAL_UART_Transmit的最后一个参数是超时时间,这里给 100ms,如果发送队列满或 DMA 未完成,函数返回HAL_TIMEOUT,上层代码应当做错误计数而不是重试,避免阻塞调度。

4.1.1 波特率选择:9600 还是 115200、室内环境监测的取舍

波特率的选择会影响传输稳定性和功耗。9600bps 在 10m 线缆下容错性最高,但传一帧 16 字节需要约 17ms,如果系统还接了 ESP8266 做透传,它的默认波特率通常是 115200,两边不匹配就需要在代码里加一层波特率转换。实际项目中我遇到过因为杜邦线质量差导致 115200 下误码率升高、9600 下完全正常的情况。折衷方案是板间通信用 9600 或 38400,MCU 与 Wi-Fi 模块之间的串口用各自匹配的最高稳定速率——不是所有 ESP8266 模块都能稳定跑 115200,查验模块的 Flash 容量和固件版本后,把 AT 固件重设为 9600 也不丢人。数据量本身很小,每秒一帧 16 字节,任何波特率都绰绰有余,稳定压倒一切。

4.2 数据上云:ESP8266 AT 指令与 MQTT 的最小实现

室内环境监测的现实场景是人在家里看数据,或者通过网页远程查看。STM32 端最常见的上网方式是串口外挂 ESP8266,用 AT 指令走 TCP 或 MQTT。AT 指令的坑在于每次上电后模块状态不确定,源码里必须在初始化阶段做完整的握手流程:发AT,等待OK;设AT+CWMODE=1,等待OK;连 Wi-Fi 用AT+CWJAP="SSID","PASSWORD",这里会遇到连接超时。大部分源码在 5 秒内没收到WIFI CONNECTED就直接报错重试了,但真实路由器的 DHCP 分配可能要 8~15 秒,重试间隔至少要 20 秒。连接时会收到WIFI GOT IP,代表拿到了局域网地址。之后用AT+CIPSTART="TCP","x.x.x.x",1883建连,MQTT 协议如果是自研轻量版可以直接用 TCP 裸传,如果对接标准 MQTT Broker(比如 EMQX 或 Mosquitto),则需要实现 MQTT 的 CONNECT/PUBLISH 报文,源码里应当内置一个精简版 MQTT 客户端,只做连接、心跳、发布数据三个动作,不订阅主题:

uint8_t mqtt_connect_packet(char *client_id, uint8_t *buf) { uint8_t idx = 0; buf[idx++] = 0x10; // CONNECT buf[idx++] = 0x00; // remaining length 占位,稍后回填 // 协议名 "MQTT" buf[idx++] = 0x00; buf[idx++] = 0x04; buf[idx++] = 'M'; buf[idx++] = 'Q'; buf[idx++] = 'T'; buf[idx++] = 'T'; // 协议级别 4 / 连接标志 / keepalive buf[idx++] = 0x04; buf[idx++] = 0x02; // clean session buf[idx++] = 0x00; buf[idx++] = 0x3C; // keepalive 60s // Client ID 长度 + 内容 buf[idx++] = 0x00; buf[idx++] = strlen(client_id); memcpy(&buf[idx], client_id, strlen(client_id)); idx += strlen(client_id); buf[2] = idx - 2; // 回填 remaining length return idx; }

这段代码构造了一帧 MQTT CONNECT 报文,协议级别固定为 4(MQTT 3.1.1),连接标志只有 Clean Session 置位,keepalive 60 秒,意味着 Broker 如果在 60 秒内没收到报文会断开连接,所以应用层定时器至少每 50 秒发一次 PINGREQ 或 PUBLISH。注意 remaining length 用的是单字节编码,这里报文长度不超过 127 字节所以可以直接回填,如果 Client ID 很长就要用变长编码方式。这个报文用串口发给 ESP8266 后,模块会用AT+CIPSEND透传,代码里要注意 ESP8266 收到的回显>SEND OK的处理时序。

4.2.1 Wi-Fi 断线自动重连:状态机的状态迁移表

Wi-Fi 模块在长时间运行后可能断开,源码里必须有重连状态机。状态至少包括:未初始化、已配置、已连 Wi-Fi、已连 TCP、MQTT 已连接。每个状态下处理不同事件:收到WIFI DISCONNECT就回退到已配置状态,然后重新发AT+CWJAP;TCP 连接断开就只重发AT+CIPSTART;MQTT 连接断开则重发 CONNECT 报文。重连间隔要指数退避,1 分钟、2 分钟、4 分钟……最大 10 分钟,避免模块反复重启导致路由器踢掉设备。这块逻辑对环境监测项目的可靠性至关重要,源码质量高低往往就体现在这里——轮询读传感器是个人都能写,但断网 24 小时后数据还能自动补传的系统才是真能用的系统。

5. 环境监测系统现场调试:I2C 死锁、传感器漂移、数据异常排查

5.1 排查 I2C 总线死锁的三种方法和一个预防性代码

I2C 死锁的典型特征是系统运行几小时或几天后,温湿度突然不再更新,复位后又正常。原因是某次通信中 SDA 被从设备拉低,而此时主设备已经放弃传输,总线被卡在低电平。排查方法第一步是示波器或逻辑分析仪抓 SCL 和 SDA,看是否 SDA 常低、SCL 还在翻转;第二步是量上拉电阻阻值,SHT30 的数据手册建议 2.2kΩ~10kΩ,如果用了 10kΩ 且总线电容较大,信号边沿太缓会导致误判;第三步是检查代码中的错误处理,HAL 库的HAL_I2C_Master_Transmit()返回HAL_BUSY后如果没有HAL_I2C_DeInit()和重新初始化,总线永远恢复不了。

预防性做法是在每轮读传感器前,检查总线状态并主动恢复:

void i2c_bus_recover(void) { GPIO_InitTypeDef gpio = {0}; // 将 SCL 和 SDA 配成开漏输出 gpio.Mode = GPIO_MODE_OUTPUT_OD; gpio.Pull = GPIO_PULLUP; gpio.Pin = SCL_PIN | SDA_PIN; HAL_GPIO_Init(SCL_PORT, &gpio); HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET); for (int i = 0; i < 9; i++) { HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET); delay_us(5); HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET); delay_us(5); } // 发出 9 个时钟脉冲后,从设备会释放 SDA // 随后重新初始化 I2C 外设 HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET); MX_I2C1_Init(); }

这段代码在每次传感器读取出错时调用,通过翻转 9 个 SCL 时钟脉冲,让卡在低电平的 SDA 释放,之后重新初始化 I2C 外设。9 个脉冲对应 I2C 协议中从设备状态机的最大步数,足够让任意从设备退出异常状态。注意在恢复过程中 SDA 也被配置成开漏输出,确保不会对外部设备造成电平冲突。恢复失败就应该递增错误计数,连续失败超过 5 次时重启传感器电源或整个 MCU,这是环境监测设备长时间无人值守运行的保底手段。delay_us是一个微秒级延时函数,可以由定时器或空循环实现,重点是 SCL 的低电平脉宽必须满足 I2C 协议中从设备的最小时钟低电平时间(通常 4.7μs 以上),所以这里用 5μs 是安全的。

5.2 传感器数据与标准仪器对不上的三个校准技巧

环境监测系统的数据要具备可信度,单靠传感器出厂标定是不够的。温度传感器因为 PCB 上 MCU 发热、LDO 发热,测出的环境温度通常比真实温度高 1~3°C。常见的做法是软件补偿:把温度读数减去一个固定偏差,比如 1.5°C,但这个方法只在固定功耗下有意义。更好的做法是看源码里是否包含“温度补偿系数”的配置项,把传感器放在通风良好的机壳内,同时让 MCU 的低功耗模式在两次采样之间生效,减少自热影响。湿度传感器在长时间干燥环境下读数会偏高,可以用饱和盐溶液法校准:把传感器放在密封袋里和氯化钠饱和溶液共存 24 小时,环境湿度应为约 75%RH,读取传感器值后算出增益和偏移,写入 EEPROM 或 Flash 的校准参数区。PM2.5 传感器出厂前已经对齐标准粉尘,但如果进风口被灰尘堵住读数会偏低,维护周期按使用环境定为 3~6 个月,每次维护后记录零点和满量程点的读数变化。

5.2.1 数据跳变的实时判断:中值滤波和变化率报警

数据跳变和真实污染要区分。有人抽烟,PM2.5 数值在 30 秒内从 50 升到 300,这是合理的。但如果数值在 1 秒内从 50 跳到 300 又跳回,大概率是传感器串口丢帧或校验失败后填充了错误值。处理办法是每个上报周期做变化率检查:如果当前值相对上次采样变化超过物理极限,则标记为无效采样,不参与平均值计算,并将异常计数加一。连续出现多次异常时,自动降低该传感器的采样频率,并在日志中记录时间戳,方便事后定位是干扰还是传感器劣化。这套逻辑虽然写在代码里只有几十行,却是系统在现场“看起来稳不稳”的关键。

6. 还有人这么玩:OLED 曲线、历史日志与离线标定参数注入

环境监测系统的源码里通常藏着比“读传感器上报”更进一步的东西。最实用的一块是 OLED 或 TFT 上直接绘制趋势曲线,而不只是显示当前数值。做法是维护一个环形数组存放最近 120 个数据点,每 10 秒采样一次,就能显示 20 分钟的变化曲线。画线算法不需要完整实现 Bresenham,STM32 的 128x64 OLED 屏用逐列像素填充的方式,效率足够:把数组里的数值映射到屏幕高度,相邻两个点之间用等距插值补点,重点是在刷新时用I2C_Mem_Write只更新变化的区域,而不是全屏重绘。全屏重绘一帧要 1ms 以上,会拖慢主循环。源码里如果看到了pagebuf之类的数组名,基本就是这个设计。

另一个值得关注的功能是历史日志。环境监测系统的数据只上云不行,本地还应该存一份。STM32 内置 Flash 的擦写寿命在 1 万次左右,做高频日志会很快磨损,正确做法是外挂 SPI Flash(W25Q64 这类)或用 SD 卡。日志格式按二进制记录,每条记录包含时间戳、各传感器数值、设备状态位,每次写入 16 字节对齐,读出来可以转成 CSV 分析。如果源码里没有日志功能但有 SD 卡驱动,你可以加一个任务,把串口收到的数据帧原样落盘,代价极小,价值很大。

最后一个高级用法是参数注入。源码编译进固件的校准系数无法在设备现场调整,每次改系数都要重新烧录。我通常在 UART 上实现一个命令通道:上位机发送CAL:TEMP 0.5CAL:HUMI -2.0,STM32 解析后把新系数写入外部 EEPROM 或 Flash 的独立扇区,重启后生效。这样做的好处是设备装到用户家里后,售后只需要一条串口命令就能完成温度偏差修正,不用拆机、不用换传感器。命令解析放在串口接收中断的上下文中实现,帧格式沿用 4.1 节的 CRC 校验,只是命令字不同,也不影响主循环的实时性。这套机制配合前面的断线重传,就能覆盖“采集、传输、校准、维护”所有环节——拿到源码后从这四层读进去,比从main.c逐行看效率高得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询