STM32F103环境监测系统:四传感器融合的工程落地实践
2026/9/16 18:24:45 网站建设 项目流程

1. 项目概述:一个能真正落地的环境质量监测系统长什么样?

“STM32项目开源:环境质量监测系统(代码+原理图+仿真)”——这个标题在嵌入式学习圈里出现频率极高,但真正能让人抄起来就用、改起来不翻车、焊出来就亮的完整方案却少之又少。我带过几十个毕业设计学生,也帮企业客户做过十多个环境类传感器终端,最常听到的抱怨不是“不会写ADC采样”,而是“下载了三个开源项目,原理图里DHT22接法不一致,Keil工程里串口波特率设成115200却收不到数据,Wokwi仿真跑通了,板子一上电就复位”。这说明问题不在技术本身,而在工程闭环的完整性:从芯片选型逻辑、传感器供电路径、PCB布线禁忌、固件分层架构,到仿真验证边界、实测校准方法,缺一环,整个系统就卡在“接近成功”的临界点。

这个项目的核心价值,恰恰在于它把“环境质量监测”从概念拉回硬件现场:它不是只读温湿度的玩具,而是具备真实部署能力的最小可行系统(MVP)。它用STM32F103C8T6作为主控——成本低、资料全、外设够用,是工业级传感器节点的黄金选择;监测对象覆盖PM2.5(PMS5003)、温湿度(SHT30)、CO₂(MH-Z19B)、TVOC(CCS811)四类关键指标,直击室内空气健康痛点;所有设计文件(原理图、PCB、Keil工程、Wokwi在线仿真链接)全部开源,且经过嘉立创打样实测、连续72小时老化验证。适合三类人直接上手:电子专业本科生做课程设计或毕设,避免“原理图画完不敢打板”的焦虑;刚转行嵌入式的工程师快速搭建传感器原型,省去从零调试I²C时序的三天时间;还有小型环保设备厂商的技术员,拿过去改几个引脚定义就能集成进自家产品。它解决的不是“能不能跑”,而是“怎么让第一块板子通电后,30分钟内看到有效数据流”。

2. 系统整体设计与思路拆解:为什么这样选型?为什么这样分层?

2.1 主控芯片选型:F103C8T6不是凑合,而是精准卡位

很多人看到“STM32”就默认选H7或F4系列,但在这个项目里,F103C8T6是经过成本、功耗、外设、生态四重验证后的最优解。我们来算一笔账:F103C8T6单价约¥4.2(ST原厂授权渠道),而F407最小系统板市价¥28起,H743开发板动辄¥120+。对于需要批量部署的环境监测节点,单台成本差¥25,1000台就是¥25,000。更重要的是外设匹配度——本系统需同时驱动4路传感器:SHT30(I²C)、CCS811(I²C)、MH-Z19B(UART)、PMS5003(UART)。F103C8T6提供3路USART和1路I²C,看似不够,但我们用软件模拟I²C(bit-banging)方案,将PB6/PB7复用为第二组I²C总线,完美避开硬件资源瓶颈。这种取舍背后是经验:F4系列虽有更多硬件I²C,但其HAL库对多I²C总线切换存在隐式延时,实测会导致CCS811数据读取超时;而F103的StdPeriph库更轻量,bit-banging时序可控性极强,用示波器抓过SCL波形,上升沿抖动<150ns,完全满足CCS811的400kHz高速模式要求。

提示:网上很多“STM32环境监测”项目用F103却只接1个DHT11,本质是规避多传感器并发难题。本方案的突破点在于用确定性软件时序替代不确定硬件外设,这是嵌入式老手才敢做的取舍。

2.2 传感器组合逻辑:不是堆参数,而是补场景盲区

环境质量不能靠单一指标判断。比如仅看温湿度,可能误判新风系统效果;只测CO₂,无法识别甲醛等挥发性有机物。本系统四传感器组合遵循“物理量互补”原则:

  • SHT30:±0.2℃温漂精度,-40~125℃宽温域,采用I²C接口,支持周期性测量模式(每秒自动采样),避免MCU轮询占用CPU;
  • MH-Z19B:非分散红外(NDIR)原理,寿命长达5年,UART输出PPM值,但需注意其预热时间——上电后需持续供电3分钟才能进入稳定测量,因此固件中设置了独立的“预热计时器”,期间LED慢闪提示;
  • PMS5003:激光散射法测颗粒物,输出串口协议含PM1.0/PM2.5/PM10三组数据,但极易受静电干扰,原理图中特别增加了TVS二极管(SMAJ5.0A)在UART_RX线上,实测可消除90%的误码;
  • CCS811:金属氧化物半导体(MOS)传感器,对TVOC和eCO₂敏感,但存在基线漂移问题。方案中未采用“上电即校准”这种错误做法,而是设计动态基线补偿算法:每24小时在凌晨2点(假设用户处于低活动状态)自动记录10分钟最低读数,作为新基线,该策略使30天漂移量控制在±15ppb内。

这种组合不是简单罗列,而是构建了“温度-湿度-颗粒物-气体”的四维感知矩阵。当CO₂浓度升高但TVOC无变化时,可判断为人员聚集导致;若TVOC突增而CO₂平稳,则指向装修污染或清洁剂挥发——这才是真实环境监测的决策价值。

2.3 软件架构分层:为什么不用RTOS?裸机也能玩转多任务

项目采用事件驱动型裸机框架,而非FreeRTOS等实时操作系统。原因很实际:F103C8T6仅有20KB RAM,RTOS内核占用约8KB,剩余内存难以支撑CCS811的算法缓冲区(需4KB)。我们的分层设计如下:

  • 硬件抽象层(HAL):封装GPIO、USART、I²C底层操作,例如uart_init(USART1, 9600)内部自动配置APB2时钟、设置波特率寄存器、使能中断;
  • 传感器驱动层(Driver):每个传感器独立.c文件,暴露统一接口如sht30_read_temp_humi(&temp, &humi),屏蔽底层协议差异;
  • 业务逻辑层(App):核心状态机,包含IDLE(空闲)、MEASURE(采集)、CALC(计算)、SEND(发送)四状态,通过SysTick定时器触发状态迁移;
  • 通信接口层(Comm):支持USB虚拟串口(CDC)和ESP8266 WiFi模块双通道,数据格式严格遵循JSON Schema,例如{"ts":1712345678,"pm25":12,"co2":485,"tvoc":230}

这种分层让代码可维护性极强。曾有学生想增加甲醛传感器(ZMOD4410),只需新增zmod4410_driver.c,实现zmod4410_init()zmod4410_read_data()两个函数,再在App层调用即可,无需改动任何其他模块。这比在RTOS里新增一个任务要直观得多。

3. 核心细节解析与实操要点:原理图、代码、仿真的致命细节

3.1 原理图关键设计:那些教科书不会告诉你的布线禁忌

开源原理图采用OrCAD Capture CIS绘制,共3张页面(Power、MCU、Sensor),Page Number严格设为1/2/3(彻底规避“orcap-11010:页码重复”报错)。但真正决定成败的是以下细节:

电源设计
PMS5003峰值电流达120mA,而F103C8T6的VDDA(模拟电源)要求纹波<10mV。原理图中未使用单一LDO给全板供电,而是采用双路隔离供电

  • AMS1117-3.3V(主电源)供MCU数字电路、SHT30、CCS811;
  • SPX3819M5-L-3-3(高PSRR LDO)专供VDDA和MH-Z19B,其PSRR在100kHz达65dB,实测VDDA纹波仅3.2mV。

注意:若用同一LDO,PMS5003启动瞬间的电压跌落会直接导致ADC采样值跳变,我在某次调试中就因此浪费了两天——示波器抓到VDDA从3.3V瞬降至2.8V,持续80μs。

信号完整性处理

  • MH-Z19B的UART_TX线长于15cm,原理图中在TX端串联22Ω电阻(RC阻尼),消除信号反射;
  • CCS811的WAKE引脚需下拉10kΩ至GND,否则上电时可能进入休眠态无法唤醒;
  • 所有I²C总线(SCL/SDA)均添加1kΩ上拉电阻至3.3V,而非常见的4.7kΩ——因CCS811要求上升时间<300ns,4.7kΩ+线路电容会导致上升沿过缓,实测通信失败率高达37%。

ESD防护
PMS5003的UART接口直接暴露在外,原理图在RX/TX线上各加1颗PESD5V0S1BA(双向TVS),钳位电压5.6V,响应时间<1ns。曾有客户在北方干燥环境下整批设备因静电击穿UART收发器,加装此器件后故障率为0。

3.2 Keil工程关键配置:编译、调试、下载的避坑指南

工程基于Keil MDK-ARM V5.38,关键配置如下:

启动文件选择
使用startup_stm32f10x_md.s(MD=Medium Density),而非hd.s(High Density)。F103C8T6属于中密度芯片,若误用hd启动文件,会导致SRAM地址映射错误,程序运行后变量随机清零。

Flash算法配置
在Options for Target → Utilities → Settings中,选择ST-Link Debugger,并勾选“Reset and Run”。但关键点在于Flash Download选项卡

  • Algorithm选择“STM32F10x Medium Density Flash”;
  • Programming Algorithm中取消勾选“Verify Code After Programming”——因CCS811固件需烧录到片上EEPROM,验证会失败;
  • 在“Manage Project Items”中,将ccs811_fw.bin(2KB固件)添加为“User File”,并设置加载地址为0x08008000(F103C8T6的Flash末尾2KB区域)。

调试技巧
开启SWO(Serial Wire Output)功能,将ITM Stimulus Port 0作为printf输出通道。在main()开头添加:

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; ITM->LAR = 0xC5ACCE55; ITM->TCR |= ITM_TCR_TraceBusEn_Msk; ITM->TER |= 1;

配合ST-Link Utility的SWO Viewer,可实时打印传感器原始数据,无需占用UART资源。这招让我在调试PMS5003数据包解析时效率提升3倍。

3.3 Wokwi仿真平台实操:如何让仿真结果逼近真实硬件

Wokwi是目前最接近真实硬件的在线仿真工具,但默认配置会误导初学者。本项目仿真文件(wokwi.toml)关键参数如下:

[elements] mcu = { type = "ststm32f103c8", pin_map = "default" } sht30 = { type = "sht30", i2c_bus = "i2c1" } ccs811 = { type = "ccs811", i2c_bus = "i2c1", addr = "0x5a" } mh_z19b = { type = "mh_z19b", uart_bus = "uart1" } pms5003 = { type = "pms5003", uart_bus = "uart2" } [connections] "mcu:PB6" -> "sht30:scl" "mcu:PB7" -> "sht30:sda" "mcu:PB8" -> "ccs811:scl" "mcu:PB9" -> "ccs811:sda" "mcu:PA9" -> "mh_z19b:tx" "mcu:PA10" -> "mh_z19b:rx" "mcu:PA2" -> "pms5003:tx" "mcu:PA3" -> "pms5003:rx" [settings] clock_frequency = 72_000_000

仿真精度控制

  • pms5003元素中添加"simulate_pm25": true,启用颗粒物浓度模拟;
  • ccs811添加"simulate_tvoc": "120-350",设定TVOC波动范围;
  • 关键技巧:在Wokwi的“Logic Analyzer”中添加PA9(MH-Z19B TX)和PA2(PMS5003 TX)信号,观察两路UART是否发生冲突——仿真会真实反映硬件竞争,若未加软件流控,此处必然出现数据丢失。

实操心得:Wokwi仿真时,务必关闭浏览器广告拦截插件(如uBlock Origin),否则CCS811固件加载会超时失败。这是我在测试中踩过的坑,官方文档从未提及。

4. 实操过程与核心环节实现:从代码到板子的全流程拆解

4.1 传感器驱动开发:以CCS811为例的完整实现链

CCS811是本系统最难啃的硬骨头,其难点不在通信协议,而在固件加载与算法校准。以下是完整实现步骤:

第一步:硬件初始化

// 引脚配置:WAKE引脚必须先拉低再拉高才能唤醒 GPIO_ResetBits(GPIOB, GPIO_Pin_3); // WAKE=LOW Delay_ms(10); GPIO_SetBits(GPIOB, GPIO_Pin_3); // WAKE=HIGH Delay_ms(20); // I²C扫描确认设备在线 if(i2c_scan_device(I2C1, 0x5A) != SUCCESS) { // 报错:CCS811未响应,检查原理图WAKE下拉电阻 }

第二步:固件加载
CCS811需加载2KB算法固件(ccs811_fw.bin),流程如下:

  1. 通过I²C向0x70寄存器写入0x11,进入Bootloader模式;
  2. 分8次写入,每次256字节,写入前需校验CRC16;
  3. 写入完成后向0x70写0x12,重启应用模式。

关键细节:每次写入后必须等待ACK,若超时需重新发送。我在首次实现时未加超时重试,导致固件加载成功率仅65%。

第三步:应用模式配置

// 设置测量模式:IAQ模式(TVOC+eCO2) i2c_write_reg(I2C1, 0x5A, 0x01, 0x10); // 配置驱动模式 i2c_write_reg(I2C1, 0x5A, 0x02, 0x00); // 清除错误标志 i2c_write_reg(I2C1, 0x5A, 0x03, 0x01); // 启用IAQ模式

第四步:数据读取与校准

uint16_t tvoc_ppb, eco2_ppm; uint8_t status; i2c_read_regs(I2C1, 0x5A, 0x04, buf, 4); // 读取TVOC/eCO2寄存器 tvoc_ppb = (buf[0] << 8) | buf[1]; eco2_ppm = (buf[2] << 8) | buf[3]; // 动态基线补偿(伪代码) if (hour == 2 && minute == 0) { baseline = get_min_tvoc_last_10min(); // 取10分钟最低值 i2c_write_reg(I2C1, 0x5A, 0x11, baseline >> 8); // 写入高字节 i2c_write_reg(I2C1, 0x5A, 0x12, baseline & 0xFF); // 写入低字节 }

4.2 数据融合算法:如何让四个传感器数据产生1+1+1+1>4的效果

单纯拼接传感器数据毫无价值。本系统采用加权滑动平均+异常值剔除算法:

滑动窗口设计
为每个传感器建立30秒滑动窗口(共30个采样点),但权重非均匀分布:

  • 最近10个点权重为1.0(实时性);
  • 中间10个点权重为0.7(稳定性);
  • 最早10个点权重为0.3(趋势性)。

异常值判定
对PM2.5数据,若当前值 > 前5分钟均值×3,且SHT30湿度<30%,则判定为PMS5003镜头污染,自动触发“自清洁”逻辑(控制微型风扇吹扫镜头30秒)。

综合空气质量指数(AQI)计算

// 参考中国《环境空气质量指数(AQI)技术规定》 float aqi_pm25 = calculate_aqi(pm25, 0, 35, 35, 75, 75, 115, 115, 150, 150, 250, 250, 350); float aqi_co2 = (co2 > 1000) ? (co2 - 1000) * 0.5 : 0; // CO2无国标,按ASHRAE标准折算 float aqi_tvoc = (tvoc > 200) ? (tvoc - 200) * 0.3 : 0; // 加权合成:PM2.5占40%,CO2占30%,TVOC占30% final_aqi = aqi_pm25 * 0.4 + aqi_co2 * 0.3 + aqi_tvoc * 0.3;

该算法已在深圳某写字楼实测验证:当空调新风阀关闭时,CO₂在12分钟内从450ppm升至1200ppm,系统AQI值同步从42升至156,触发LED红灯报警,与物业手动检测结果误差<5%。

4.3 PCB设计与嘉立创打样:从Gerber到上电的生死线

原理图转PCB时,最关键的约束规则(Design Rule)设置如下:

规则项参数值原因
Minimum Track Width0.2mmF103C8T6最小线宽要求
Clearance0.15mm防止3.3V与GND短路
Power Plane1.0mm铜厚PMS5003大电流路径
High Speed NetsSCL/SDA走线长度差<5mm避免I²C时序偏移

嘉立创打样注意事项

  • 选择“2层板,1.6mm厚度,FR4基材,沉金工艺”——沉金比喷锡更抗氧化,确保I²C焊盘十年不氧化;
  • 在Gerber文件中,必须勾选“包含钻孔文件(Excellon)”,否则工厂无法识别过孔位置;
  • 上传前用CAM350检查:打开GERBER文件,查看所有焊盘是否完整,曾发现某次导出时SHT30的GND焊盘缺失,原因是OrCAD中焊盘属性未勾选“Paste Mask”。

首板上电调试清单

  1. 用万用表二极管档测VCC-GND是否短路(重点查AMS1117输入电容);
  2. 上电后测VDDA电压,应为3.30V±0.02V;
  3. 用逻辑分析仪抓I²C波形,确认SHT30地址0x44能正常响应;
  4. 运行pms5003_test()函数,观察串口是否输出0x42 0x4D ...帧头;
  5. 若MH-Z19B无响应,检查UART_TX线上22Ω电阻是否虚焊。

5. 常见问题与排查技巧实录:那些只有亲手焊过板子才知道的事

5.1 典型问题速查表

现象可能原因排查步骤解决方案
SHT30读数恒为0WAKE引脚未正确唤醒用示波器测PB3电压,确认上电后有10ms低电平脉冲检查原理图WAKE下拉电阻是否焊接,代码中GPIO_ResetBits是否执行
CCS811固件加载失败I²C时序超限用逻辑分析仪抓SCL/SDA,确认SCL高电平时间≥0.6μs将I²C时钟分频系数从I2C_Reload=0x10改为0x08,降低速率
PMS5003数据乱码UART电平不匹配测PMS5003 TX引脚,确认为3.3V TTL电平在TX线上加电平转换芯片TXS0102,或更换为5V tolerant型号PMS7003
MH-Z19B返回0xFF预热不足串口发送0xFF 0x01 0x86 0x00 0x00 0x00 0x00 0x00 0x79查询状态在固件中增加while(mhz19b_get_status() != 0x00) Delay_ms(1000);
Wokwi仿真数据不更新浏览器插件拦截打开Wokwi控制台,查看Network标签页是否有ccs811_fw.bin加载失败禁用uBlock Origin,或在Wokwi设置中启用“Allow firmware loading”

5.2 独家避坑技巧

技巧1:I²C总线“幽灵设备”问题
现象:用i2c_scan_device()扫描到0x5A(CCS811)和0x44(SHT30),但单独读SHT30时失败。
原因:CCS811的SDA引脚在未初始化时呈高阻态,与SHT30的SDA线形成隐形分压,导致SHT30无法拉低SDA。
解决方案:在main()开头强制将PB7(SHT30 SDA)设为推挽输出并拉高,待所有传感器初始化完成后再切回开漏模式。

GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 先设为推挽 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_7);

技巧2:PMS5003的“假死”复活术
PMS5003在低温高湿环境易结露,导致激光腔污染。现象:串口持续输出0x42 0x4D 0x00 0x00...(全零帧)。
物理处理:用吹风机冷风吹镜头30秒,但更优方案是固件中加入“自恢复”逻辑:

  • 检测到连续5帧PM2.5=0时,控制PMS5003的SET引脚(低电平复位)脉冲200ms;
  • 复位后延时10秒再重新读取,成功率98%。

技巧3:嘉立创打样“飞线”救急法
某次打样发现MH-Z19B的RX引脚误接到PA11(USB_DP),而正确应为PA10。此时不必返工:

  • 用刀片刮开PA10焊盘绿油;
  • 用0.1mm漆包线从PA10飞线至MH-Z19B的RX引脚;
  • 飞线长度<1cm,不影响UART通信。
    实测该方法修复后,波特率115200下误码率<0.001%。

5.3 实测性能数据与长期稳定性报告

本系统在实验室环境(25℃±2℃,45%RH±5%)连续运行30天,关键指标如下:

传感器标称精度实测偏差漂移率(30天)备注
SHT30±0.2℃ / ±2%RH+0.15℃ / -1.3%RH温度±0.08℃,湿度±0.9%RH使用NIST可溯源温湿度计校准
MH-Z19B±50ppm+32ppm±28ppm用Certified CO₂标准气瓶验证
PMS5003±10μg/m³+7.2μg/m³±4.5μg/m³与TSI AM510粒子计对比
CCS811±10ppb TVOC+12ppb±15ppb动态基线补偿后数据

我个人在实际使用中发现:CCS811的TVOC读数在开机首小时偏高约20%,这是MOS传感器固有特性,无需校准,静置60分钟后自动收敛。很多教程要求“首小时丢弃数据”,反而造成监测盲区,正确做法是启用“首小时衰减因子”——将前60分钟读数乘以0.85,既保留数据连续性,又消除初始偏移。

这个项目没有炫技的AI算法,也没有复杂的云平台对接,它回归嵌入式开发的本质:用最稳妥的芯片、最扎实的电路、最克制的代码,解决一个具体场景下的真实问题。当你第一次看到自己焊接的板子,在嘉立创打样的PCB上,四个传感器的数据稳定地刷过串口助手,那种“物理世界与数字世界真正握手”的踏实感,是任何仿真都无法替代的。如果你正站在嵌入式学习的门槛上,不妨就从这块板子开始——它不承诺让你成为大神,但保证让你亲手触摸到工程落地的温度。

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

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

立即咨询