STM32室内环境监测系统设计:从传感器选型到数据上报

📅 发布时间:2026/10/15 12:48:57
STM32室内环境监测系统设计:从传感器选型到数据上报
简介基于STM32的室内环境监测系统完整工程资源面向嵌入式学习者、毕业设计或智能家居项目开发者。项目以STM32F103ZET6为核心集成光敏、火焰、烟雾、声音、DHT11温湿度等多种传感器并支持红外遥控与LCD实时显示覆盖数据采集、处理、告警与交互全流程。资源共237个文件包含40个C源码、40个头文件及编译生成的o/hex/axf等固件文件另有工程备份文件与烧录配置适合直接参考或二次开发。压缩包约5.14MB已吸引10682人学习下载。通过这套资源可掌握STM32外设配置、多传感器融合、中断与定时器应用、LCD界面设计以及工程管理细节也可借鉴其模块化代码结构完成类似环境监测项目的搭建与调试。1. 为什么室内环境监测系统值得用 STM32 自己做把市面上几百块的“智能空气检测仪”拆开你大概率会看到一块 STM32 主控、两三个传感器芯片、一个小屏幕电路说不上复杂。基于 STM32 的室内环境监测系统设计本质上是把温湿度、PM2.5、有害气体这些物理量变成能看到、能报警、能上传的数据链路。自己动手做这件事最大的收获不是省那几百块而是把传感器选型、驱动移植、数据处理、人机交互到联网上报的完整链路走一遍这套能力在后续做任何智能硬件项目时都能复用。这篇笔记面向三类人正在做嵌入式课程设计或毕业设计的学生、想给家里添一套监测设备的开发者、以及想在简历里放一个完整STM32项目的从业者。2. 系统架构与选型先定传感器再定主控别一上来就焊板子2.1 温湿度、PM2.5、有害气体怎么选一张表把接口和量程定下来室内环境监测到底测什么最常见的组合是“温湿度 PM2.5 有害气体VOC/甲醛”。温湿度决定居住舒适度PM2.5 决定空气污染水平有害气体传感器则负责给衣柜、厨房这类局部空间做危险预警。对于第一个版本我建议不要把参数搞得太多三到四路传感器足够覆盖绝大多数需求也方便调试时快速定位问题。测量对象常用传感器类型接口方式供电要求典型注意事项温湿度单总线数字传感器 / I2C 数字传感器单总线或 I2C3.3V~5V单总线时序要求严格I2C 需要上拉电阻PM2.5红外散射粉尘传感器模拟电压输出5V峰值电流较大需要 PWM 驱动内部 LED电路要去耦有害气体半导体气体传感器旁热式模拟电压输出加热丝 5V信号输出模拟量上电预热时间长基线漂移明显可扩展项气压、光照、噪声I2C / 模拟量3.3V按需求添加不影响主架构选型时有三个筛选项第一是接口数字接口I2C、单总线比模拟接口少搭运放电路开发省心第二是供电传感器需要 5V 还是 3.3V直接决定你的电源方案第三是功耗如果是插电使用可以忽略但如果你想以后改造成电池供电就必须优先选低功耗型号。我一般会先把传感器清单定下来再去看主控够不够用不然很容易出现“焊好了板子才发现引脚不够”的尴尬局面。2.2 主控选型为什么 STM32F1 系列依然是当前最稳妥的选择主控采用 STM32F103C8T6 这类 F1 系列芯片几乎是室内环境监测系统设计里最常见的选择。原因很直接它对 I2C、单总线、ADC、PWM、UART 都有硬件外设支持处理三路传感器加一块 OLED 屏幕绰绰有余社区参考设计多网上能找到大量可对照的驱动源码遇到问题不容易卡死成本可控做坏了也不心疼。从资源占用角度看STM32F103C8T6 有 64KB Flash 和 20KB RAM。系统固件里包含标准外设库或 HAL 库、三路传感器驱动、一个简易显示协议栈、串口打印和上报逻辑估算下来 Flash 占用大约在 30KB 到 45KB 之间RAM 占用主要是各个传感器的缓冲区、显示缓冲区预留 5KB 做动态数据足够。如果你选的是带 Wi-Fi 模组透传的方案注意只要用串口通信不需要额外的协议栈资源对主控的压力很小。这里要强调一点STM32F1 系列不是性能最强的但它的稳定性和资料丰富度决定了它最适合做“既要学原理、又要尽快跑通”的项目。如果你后续想扩展人脸识别、本地语音控制这类重负载功能才需要考虑升级到更高主频系列但那是后话。2.3 系统级设计感知层、主控层、交互层怎么划分拿到一个系统级的标题最容易犯的错误是上来就写代码。我习惯先把系统拆成四层感知层、主控层、交互层、通信层。感知层包含所有传感器负责把物理量变成电平、数字或模拟信号。主控层就是 STM32负责定时唤醒传感器、读取数据、做滤波和校准、执行报警判断。交互层包含 OLED 显示屏、按键、蜂鸣器和指示灯负责把数据呈现给用户。通信层用串口和 Wi-Fi 模组把数据上报到局域网内的服务器或者调试终端。这四层的物理划分方式建议在面包板或开发板上先把感知层单独一组、交互层单独一组再用排线或者杜邦线接到主控板。这样做的直接好处是调试时可以用“替换法”定位问题屏幕上没数据先手电筒照一下传感器有没有供电数据乱码先断开通信层再确定是主控问题还是上位机问题。分层越清晰后面每一步能照着复现的把握越大。3. 传感器驱动与数据采集把真实数据从引脚上读出来3.1 接线设计分清 3.3V 与 5V 电源域别把模拟输出直接灌进 ADC硬件接线是整个系统第一个坑点。STM32F103 的 ADC 输入范围是 0 到 3.3V而很多粉尘传感器和气体传感器的模拟输出范围可能达到 0 到 5V。一旦输出超过 3.3V轻则读数削顶重则烧坏引脚。所以接线之前必须查清楚每一路传感器的输出范围必要时加电阻分压。下面是一套我常用的接线表主控以 STM32F103C8T6 为例STM32 引脚外设说明3.3VOLED、数字传感器供电负载电流小稳压器直接供5V粉尘传感器、气体传感器加热丝独立供电避免影响 3.3V 域GND所有模块地线采用星型接地方式PB6OLED SCLI2C 时钟需要 4.7kΩ 上拉到 3.3VPB7OLED SDAI2C 数据需要 4.7kΩ 上拉到 3.3VPB8单总线温湿度传感器 DATA数据线需 4.7kΩ 上拉到 3.3VPA0粉尘传感器模拟输出ADC 输入通道确认不超过 3.3VPA1气体传感器模拟输出ADC 输入通道需分压时加分压电阻两个执行细节值得注意第一I2C 的上拉电阻不能省多数 OLED 模块板载了上拉电阻但如果买的模块没有必须在 SCL 和 SDA 上各接一个 4.7kΩ 电阻到 3.3V第二粉尘传感器和气体传感器的模拟输出地线要尽量靠近主控 ADC 的地线避免长线引入噪声我一般会把传感器模块的 GND 直接接到主控板的模拟地平面区域。3.2 I2C 温湿度读取阻塞式主机读写与 CRC 校验选一款 I2C 接口的温湿度传感器如 SHT3x 系列可以完全避开单总线的微秒级时序问题代码写起来也干净。I2C 读取的核心代码骨架如下这里以 STM32 的 HAL 库为例static uint8_t sht3x_read(float *temp, float *hum) { uint8_t cmd[2] {0x24, 0x00}; /* 周期测量命令2.0MHz 采样 */ uint8_t buf[6]; /* 0x44 是传感器地址左移 1 位后才是 HAL 库需要的 8 位地址格式 */ if (HAL_I2C_Master_Transmit(hi2c1, 0x44 1, cmd, 2, 100) ! HAL_OK) return 0; HAL_Delay(30); /* 等待芯片完成内部测量 */ if (HAL_I2C_Master_Receive(hi2c1, 0x44 1, buf, 6, 100) ! HAL_OK) return 0; uint16_t raw_t (buf[0] 8) | buf[1]; uint16_t raw_h (buf[3] 8) | buf[4]; *temp -45.0f 175.0f * ((float)raw_t / 65535.0f); *hum 100.0f * ((float)raw_h / 65535.0f); return 1; }这段代码的执行逻辑是先向传感器发送测量命令延时等待内部测量完成后读取 6 字节数据其中前两字节是温度、后两字节是湿度原始值需要按芯片手册给出的线性公式换算成物理量。注意 0x24 0x00 是“周期测量 2.0MHz”命令如果是低功耗场景可以改成功率相关命令但代价是转换时间变长。三个参数要重点说明第一100 是 I2C 超时时间毫秒实际执行时如果总线上有设备没应答通讯会阻塞到超时这个值设成 100 足够第二30ms 延时取决于芯片的测量周期不要随意缩短否则读回来的数据是上一次的缓存值第三0x44 是器件的 7 位地址如果芯片 ADDR 引脚拉高地址会变成 0x45接线时确认清楚。代码里我注释了 CRC 校验生产中必须补上读出来的 6 字节中第 2、第 5 字节是 CRC校验失败时应该丢弃本次数据而不是继续使用。3.3 单总线温湿度读取把微秒级时序从中断里抢回来如果你手头已经有单总线温湿度传感器如 DHT22 系列可以用下面这段代码理解单总线的时序挑战static int dht22_read(float *temp, float *hum) { uint8_t data[5] {0}; /* 主机先把总线拉低至少 18ms触发传感器开始测量 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); /* 释放总线后等待 20us然后进入接收应答 */ delay_us(20); /* 等待传感器的低电平应答信号 */ while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_SET); while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_SET); /* 连续读取 40 bit高电平位宽 26~28us 表示 070us 表示 1 */ for (int i 0; i 40; i) { while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_RESET); uint32_t t 0; while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_SET) { t; } data[i / 8] (data[i / 8] 1) | (t 30); } /* 5 字节中前 4 字节是数据最后 1 字节是校验和 */ if ((uint8_t)(data[0] data[1] data[2] data[3]) ! data[4]) return -1; *hum ((data[0] 8) | data[1]) / 10.0f; *temp (((data[2] 0x7F) 8) | data[3]) / 10.0f; if (data[2] 0x80) *temp -*temp; /* 负温度标志位 */ return 1; }这段代码里最坑的是两个 while 循环它们在做微秒级时间测量而不是毫秒级延时。HAL_Delay 的最小分辨率是 1ms完全无法用于单总线时序所以代码里用了 delay_us 和纯循环计数。变量 t 每次循环的执行周期数不是固定时间跟编译器优化等级和主频有关用 t 30 判断“0 和 1”之前需要先实测一轮把空循环校正到手册规定的时间阈值上。另一个教训是中断。如果在读取 DHT22 的过程中发生串口中断或者定时器中断微秒级时序会被拉长读到的位可能从 0 变 1表现为“数据偶发错误”。常见做法是读取前关闭全局中断读完打开中断或者把这段读取逻辑放到一个高优先级中断里。如果你觉得这套时序太折腾换 I2C 传感器是最省心的后悔药。3.4 粉尘传感器模拟量采集连续采样与电压换算PM2.5 粉尘传感器的输出是模拟电压且要求 MCU 提供一路 PWM 驱动内部红外 LED。读取代码骨架如下void dust_read(float *voltage, float *duty) { /* 假设定时器已经输出 1kHz、占空比 0.32% 的 PWM 到传感器 LED 引脚 */ uint32_t sum 0; /* 连续采样 32 次降低单次读数的随机波动 */ for (int i 0; i 32; i) { HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { sum HAL_ADC_GetValue(hadc1); } HAL_Delay(2); } float v (sum / 32.0f) * 3.3f / 4095.0f; *voltage v; *duty v / 3.3f; /* 占空比换算供后续查浓度曲线 */ }为什么采样 32 次因为粉尘传感器的输出是脉冲式的颗粒物在检测腔内飘过时输出电压会瞬时升高单次采样可能正好错过一个脉冲也可能正好采到峰值导致跳变很严重。连续采样取平均后数据才适合显示和报警。2ms 的采样间隔不会拖慢主循环因为整个 32 次循环只耗时约 64ms。使用 ADC 时特别注意参考电压芯片 VDDA 必须是干净的 3.3V如果 VDDA 有纹波采样值整体会忽高忽低。PCB 或面包板上我一般会在 VDDA 脚放一个磁珠加一个 10uF 钽电容采样前再用软件做一次滑动平均效果比单纯加大采样次数好得多。4. 数据处理与交互滤波、校准与 OLED 报警面板4.1 滑动平均怎么选窗口温湿度用 5 点粉尘用 20 点传感器裸读数直接上屏幕是不行的你会看到温度和湿度小数点后一直在跳粉尘数据更明显。常见做法是用滑动平均滤波器代码很轻量#define WIN 20 /* 窗口长度按通道不同使用不同的值 */ static float ring[WIN]; static uint8_t idx 0; float smooth_filter(float new_val) { ring[idx] new_val; idx (idx 1 WIN) ? 0 : idx 1; float sum 0; for (int i 0; i WIN; i) { sum ring[i]; } return sum / WIN; }窗口长度的选择有讲究。温湿度变化本身较慢5 点窗口既能让读数稳定又能保证室内开着空调时 30 秒内看到明显变化粉尘数据噪声大20 点窗口更合适。但窗口太长会带来滞后比如你打开空气净化器浓度已经降下来了屏幕上数值还在慢慢爬这种体验也很糟糕。如果遇到这种情况可以改用一阶低通滤波out alpha * (in - out)alpha 取 0.2 左右代码量更小响应也更自然。4.2 两点校准把传感器的“玄学偏差”掰成线性关系很多温湿度传感器出厂时标称精度是正负 2% 到 3%但实际装进机壳后由于主控芯片发热、环境气流不通畅读数会整体偏高或偏低。我做过的一个模拟项目里装上外壳后温度比裸片读数高了 3℃ 多这就是热风道没做好导致的系统性偏差。两点校正是解决这个问题最实用的手段。校准点参考仪器读数传感器读数低温点15.0℃16.8℃高温点35.0℃38.2℃用这两组数据算出比例增益和偏移量在固件里维护两个 float 常数。实际显示值时真正的温度等于原始读数乘以增益再加偏移量。校准操作流程是把传感器和一台基础款温湿度计放在同一个环境里稳定 30 分钟记一次数据再换一个明显不同的环境比如开空调的房间和阳台稳定后记录第二次数据求解线性关系。校准之后再用实际环境回测一次如果误差在一个可接受范围内这套参数就可以写死在固件里了。4.3 OLED 显示与报警回差阈值让蜂鸣器不再疯狂抖动OLED 显示本身不难I2C 驱动库一般都能直接画字符和数字。容易出问题的是刷新策略。OLED 的显存 128x64全屏刷新一次 I2C 要传 1KB 数据在 400kHz 总线频率下也要几十毫秒频繁刷新会导致显示闪烁和主循环卡顿。我一般会让数值变化超过 0.1℃ 或 0.1%RH 时才更新对应区域屏幕只在“数值有意义变化”时工作。报警逻辑是另一个新手翻车点。如果只设置一个阈值比如 PM2.5 超过 80 就报警数值在 78 到 82 之间抖动时蜂鸣器会拼命开关这种体验会让人直接关掉报警功能。正确的做法是设置“回差阈值”/* 进入报警使用高阈值退出报警使用低阈值 */ #define PM_ON 80.0f #define PM_OFF 65.0f static uint8_t alarm_state 0; void alarm_update(float pm25) { if (alarm_state 0 pm25 PM_ON) { alarm_state 1; beep_on(); } if (alarm_state 1 pm25 PM_OFF) { alarm_state 0; beep_off(); } }这个设计的关键在于触发报警的阈值和解除报警的阈值之间留了 15 的滞回区间。数据在 80 附近抖动时只要没有低于 65报警状态就不会频繁切换。同理适用于温度上限、湿度过高等场景回差通常取报警阈值的 10% 到 20% 之间效果最好。5. 调试上云与常见问题排查串口协议、Wi-Fi 上报与四个翻车点5.1 printf 重定向与串口调试调板上没有屏幕时的眼睛硬件跑通后第一件事不是接 Wi-Fi而是先把串口打印打通。在 STM32 的标准开发环境里重定向 printf 到 UART1代码非常简单#include stdio.h int fputc(int ch, FILE *f) { /* 重定向标准输出到串口 1 */ HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 100); return ch; }在 Keil 环境下记得在魔术棒选项卡里勾选 MicroLIB否则 printf 会占用大量代码空间甚至无法链接在 GCC 环境下则需要实现 _write 函数。之后代码里就可以直接写printf(temp%.1f hum%.1f pm2_5%.1f\r\n, temp, hum, pm25);串口调试的核心价值在于它能暴露程序卡在哪个环节。比如打印放到读取函数前后如果只有前一条没有后一条说明读取函数卡死在 I2C 超时或者单总线的 while 循环里如果数据总是偶发性乱码优先怀疑串口波特率误差和中断优先级。把调试信息分成 ERROR、WARN、INFO 三级用宏开关控制开发期全开发布时只留 ERROR这样不会把串口带宽占满。5.2 私有协议帧怎么拼帧头、长度、校验位在裸机上怎么实现如果你打算把数据传给上位机或 Wi-Fi 模组至少要设计一个简单的协议帧避免接收方无法区分字节边界。我常用的最小帧格式如下typedef struct { uint8_t head[2]; /* 固定为 0xAA 0x55 */ uint8_t type; /* 帧类型1周期数据2报警事件 */ uint8_t len; /* payload 长度 */ uint8_t payload[8]; /* 真正的数据内容 */ uint8_t crc; /* 校验字节 */ } frame_t; uint8_t calc_crc(uint8_t *buf, uint8_t len) { uint8_t crc 0; for (uint8_t i 0; i len; i) { crc ^ buf[i]; /* 先做异或校验够用后换 CRC8 */ } return crc; }发送端拼好这个结构体后通过 UART1 直接发出即可。接收端用状态机匹配先等 0xAA 0x55 两个字节再收 type 和 len接着按 len 收 payload最后收 CRC 并比对。这个协议设计在裸机上非常可靠同时能把数据包长度控制在十几个字节以内不会给 Wi-Fi 模组的串口缓冲带来压力。发送频率我一般只用 1Hz室内环境数据本身变化很慢高频发送只会暴露串口缓冲和 Wi-Fi 吞吐的问题。5.3 Wi-Fi 模组上报通过串口 AT 指令把数据推到局域网服务器给系统加 Wi-Fi 上报能力常见做法是接一个 ESP 系列串口透传模组。这种方式对主控的压力很小主控只需要通过串口发 AT 指令。模组启动后配置为 STA 模式连接路由器再建立一个 TCP 连接到局域网内自己搭的服务端下面是一个典型的 AT 指令序列ATCWMODE1 ATCWJAP你的SSID,你的密码 ATCIPSTARTTCP,192.168.1.100,8080 ATCIPMODE1 ATCIPSEND每条 AT 指令都要等待模块返回 OK 再发下一条。ATCIPSTART 的 IP 地址是运行在局域网内 PC 或开发板上的 TCP 服务端地址端口保持 8080 或者其他空闲端口。调通之后主控每隔一秒往串口写一行 JSON 或者私有协议帧数据就到了服务端手机在同一局域网内订阅即可查看。如果你有本地部署的 MQTT Broker可以把 AT 指令换成 MQTT 透传版本订阅关系更灵活。这里强调一句这套方案的全部通信都发生在你自己的局域网内不依赖外部平台数据归属完全自己掌控。串口 AT 模式最大的坑是波特率不匹配和流控引脚接线错误模组默认波特率通常是 115200主控串口配置要对齐否则屏幕上只会出现一堆乱码。5.4 翻车现场四个最容易踩的坑与排查路径第一条I2C 读取返回 0xFFFF 或者总是超时。现象是温湿度数据一直是 5 位数字乱跳。原因九成是 I2C 总线上没有上拉电阻或者模块板载上拉焊盘没接。解决方法是先量 SCL 和 SDA 对地电压正常空闲时应该都是 3.3V 高电平。如果是 0V补上 4.7kΩ 上拉电阻到 3.3V问题立刻消失。第二条DHT22 数据时而正确时而错得离谱。现象是温度湿度偶尔变成两位数甚至负数。原因大多数是读取过程中串口中断打断了微秒级时序或者连续两次读取间隔太短传感器未准备好。解决方法是读取期间关中断并在两次读取之间保证至少 1 秒间隔如果项目里中断较多建议直接换 I2C 传感器单总线在这种场景下太脆弱。第三条ADC 采样值整体偏高且电压值不稳定。现象是粉尘电压在 1V 左右大幅跳动。原因通常是模拟电源和数字电源没有分开或者传感器输出阻抗太高。解决办法是在传感器电源端并联一个 10uF 电解电容和 0.1uF 陶瓷电容去耦同时把 ADC 采样周期设置到最长档让采样保持电容有足够时间充电。第四条粉尘传感器输出电压几乎为零完全没数据。现象是 ADC 数值固定在一个极小的值附近怎么吹气都不变。原因几乎都是没有给传感器内部的 LED 提供正确的 PWM 驱动。先拿示波器或万用表量传感器 LED 驱动引脚确认有没有 1kHz 左右的方波。如果没有回去配定时器通道这路 PWM 不输出传感器就是瞎子。6. 进阶验证数据准不准拿什么说话系统能跑、能显示、能上报只完成了一半。真正让这套系统“可信”的验证方法第一是标定对比把传感器与参考设备放在同一个房间同一高度连续运行 30 分钟记录两个设备读数的差值误差稳定在允许范围内才算合格。第二是动态响应测试用线香在距传感器一米外轻轻扇两下观察 PM2.5 数值是否在一到两分钟内明显上升随后逐步回落用湿毛巾靠近温湿度传感器观察湿度是否快速上升移开后逐渐下降。第三是长稳测试连续通电 72 小时每小时记录一次串口日志观察数据是否有慢漂移如果漂移方向恒定多半是传感器自身发热导致需要在结构上加散热设计和风道隔离。我自己的习惯是任何传感器参数改动后都会把校准数据记录在固件注释里包括校准日期、参考设备读数、传感器读数和拟合系数这样半年后拿回设备重新校准时还能对照历史数据找到规律。之前做一个模拟项目时就是漏了风道隔离导致机壳内设备温度比房间实际温度高了 4℃业主现场看了数据直接怀疑产品坏了。从那以后每台设备出厂前都会和参考表并排读 30 分钟做一次校准这套流程看起来繁琐却省掉了后续大量“玄学定位”的时间。数据准了系统才有继续投入的价值。希望帮到你。本文还有配套的精品资源点击获取