恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于STM32F103C8T6的家庭环境监测系统:原理图、代码与Proteus仿真全开源
首页
资讯中心
/
基于STM32F103C8T6的家庭环境监测系统:原理图、代码与Proteus仿真全开源
基于STM32F103C8T6的家庭环境监测系统:原理图、代码与Proteus仿真全开源
发布时间:2026/9/11 7:42:34
从决定做这个项目到把整套资料打包开源前后花了我两个多月。这套基于STM32F103C8T6的家庭环境监测系统硬件上就是一块最小系统板加几个常见传感器——DHT11温湿度、MQ-2烟雾检测、0.96寸OLED屏外加一个蜂鸣器听起来不复杂但要把原理图、代码、仿真工程三样东西全部对齐、跑通、整理干净工作量比想象中大不少。现在项目已经完整开源代码可以直接编译烧录原理图按图接线就能复现Proteus仿真工程也调好了从零开始也能一步步做出来。这篇文章我把整个设计和踩过的坑拆开讲清楚适合准备做课设、入门嵌入式或者想给家里做个简单监测装置的读者参考。1. 项目思路与方案设计1.1 需求拆解家庭环境监测到底要监测什么动手画原理图之前我先把需求一条条列出来。市面上的家庭环境监测产品很多但自己用STM32做一套的意义在于完全可控——想加什么传感器加什么传感器想怎么报警怎么报警数据怎么处理也能自己决定。这套项目我锁定了三个最实用的监测点温湿度家里是否潮湿、空调温度是否合适DHT11一个传感器就能同时解决。烟雾/可燃气体浓度厨房燃气泄漏、室内烟味都可以通过MQ-2的模拟电压变化反映出来这也是家庭安全里最值得关注的一点。设备运行状态指示用OLED实时显示当前读数超阈值时蜂鸣器报警同时通过串口把数据帧发出去方便后续接ESP8266之类的WiFi模块做远程查看。需求定下来之后整个项目的功能边界就清楚了采集、处理、本地显示、报警、串口输出。共五条线每条线都对应硬件上的一个模块。这里我刻意没有上复杂的操作系统而是用“主循环 定时器分时调度”的方式管理任务原因很简单——传感器采集周期都在百毫秒到秒级单片机主频72MHz单线程轮询完全够用上RTOS反而增加理解成本。1.2 器件选型对比为什么是F103C8T6加这些传感器很多新手会问我为什么不用ESP32或者STM32F407这里我把自己当时的选型思路分享出来。主控方面STM32F103C8T6有两个不可替代的优势一是资料多到溢出不管是中文手册还是B站视频遇到问题基本一搜就有答案二是价格低拆机片几块钱一片学习成本几乎可以忽略。ESP32虽然自带WiFi蓝牙但配置外设的复杂度更高第一次做项目容易被网络协议栈拖住精力。F407性能强但封装更大、引脚更多对初学者画PCB并不友好。所以这个项目定位就是“用最经典的芯片把整个流程跑通”F103C8T6是最合理的选择。传感器选型我做过一张对比表现在贴出来给大家参考功能选型备选选择理由温湿度DHT11DHT22、SHT30DHT11精度够用±2℃、±5%RH单总线协议适合学习气体检测MQ-2MQ-135、SGP30MQ-2对丙烷/烟雾敏感输出模拟量ADC处理简单显示0.96寸OLEDI2CLCD1602OLED显示内容丰富I2C只占两个IO功耗更低报警有源蜂鸣器无源蜂鸣器有源蜂鸣器高低电平就能驱动代码省事DHT11和MQ-2这两个器件我用了一个很俗但准确的类比DHT11像一个只报整数温度的电子温度计精度不高但稳定MQ-2像一个打火机气体探测器它不告诉你具体浓度但能告诉你“味儿大不大”。理解了这个你就知道代码里数据处理该怎么做——DHT11读出来直接用MQ-2的ADC值做阈值判断不需要精确换算成ppm浓度。1.3 系统整体架构与数据流向设计系统架构我用一张逻辑图在心里先画清楚用文字描述就是这种流向传感器温湿度/气体→ STM32 GPIO/ADC采集 → 软件滤波与阈值判断 → OLED显示 蜂鸣器报警 UART发送任务调度上我设置了一个1ms的软件定时器作为时间基准然后做标志位每100ms读一次ADC并做滤波每2秒读一次DHT11每500ms刷新一次OLED。为什么DHT11要2秒才读一次因为DHT11本身的采样周期就是1秒读太快只会拿到旧数据而且频繁发起始信号反而容易让数据线卡死。这个“给传感器留喘息时间”的思路在整个项目里帮我省了很多事。2. 硬件原理图设计与打板要点2.1 最小系统电路晶振电容计算、复位、下载接口STM32F103C8T6虽然市场上有现成的核心板卖但自己做项目我还是建议把最小系统画一遍哪怕最后打板直接贴核心板模块。原因很简单最小系统是所有STM32项目的底子晶振、复位、Boot、下载电路这四件事搞懂了后面换任何型号都不慌。晶振部分是我这次特意想聊清楚的。外部8MHz晶振要配两个负载电容很多原理图上直接抄20pF但实际上电容值跟晶振的负载电容参数有关。计算逻辑是这样的CL (C1 × C2) / (C1 C2) Cparasitic其中CL是晶振手册里标明的负载电容Cparasitic是引脚和PCB走线的寄生电容一般取3到5pF。假设常用晶振的CL是18pF寄生电容取4pF那么C1和C2相等时(C × C) / (2C) 4 18解得C约等于28pF实际取值22pF到30pF都可以。我最后选了22pF实测系统时钟用逻辑分析仪校准误差在0.1%以内完全够用。复位电路用的是最经典的10k上拉电阻配合100nF电容到地上电瞬间电容充电NRST引脚保持短暂低电平完成复位。BOOT0直接下拉到GND让芯片从Flash启动这样就不会出现下载完程序不运行的情况。下载接口我强烈建议预留一个4针的SWD排针SWDIO、SWCLK、3.3V、GND。SWD只需要两根数据线比J-Link的JTAG节省IO。第一次画板子的人容易漏掉这个口结果程序烧不进去我只能说这种坑踩一次就长记性了。2.2 传感器接口与信号调理电路DHT11的接线非常考究并不是直接把数据脚接到单片机上就行。数据脚和STM32之间需要接一个5.1kΩ上拉电阻到3.3V这是为了让数据线空闲时保持高电平。如果省略上拉电阻长线传输时DHT11的数据很容易受干扰误码率会明显上升。另外DHT11供电我单独用3.3V实测比5V供电稳定虽然DHT11的说明书说3.3到5.5V都可以但3.3V供电时输出的高电平正好是3.3VSTM32的GPIO识别起来最干净。MQ-2这边要啰嗦几句。它的加热丝需要5V供电所以这部分要从USB的5V取不能直接挂在3.3V上。传感器有四个引脚H-H是加热丝A-B是测量电极实际接线是把A端接5VB端通过一个10kΩ负载电阻接地然后从B端取电压送入STM32的ADC引脚。这就是经典的分压测气体浓度方案。负载电阻的取值不是随便定的。MQ-2在洁净空气中的内阻大约是几十kΩ有烟雾时内阻会下降分压点的电压就会上升。10kΩ这个值能保证在大多数浓度范围内ADC读数有较好的分辨率。如果你想更灵敏可以在信号输出端加一级运放跟随比如LM358但这种场景下没必要直接ADC读取完全够用。OLED这边用的是I2C接口SDA和SCL各接一个4.7kΩ上拉到3.3V。蜂鸣器我选了有源的用S8050三极管做开关驱动单片机引脚输出高电平时通过1kΩ限流电阻驱动基极蜂鸣器导通发声。为什么加三极管因为蜂鸣器工作电流通常要20到30mASTM32的GPIO直接驱动虽然也能响但会拉低引脚电平长期使用对芯片不友好。三极管在这里就是个小开关成本几分钱作用却很重要。2.3 电源设计与功耗优化经验供电方案我采用了“USB 5V进AMS1117-3.3稳压”的方式。AMS1117的压差大约1.1V5V输入稳定输出3.3V没问题。输入侧接一个10uF电解电容和100nF陶瓷电容输出侧同样接一个10uF和一个100nF作用是滤除低频纹波和高频噪声。画原理图时一个容易忽略的点是传感器分区域供电。DHT11和OLED用3.3VMQ-2加热丝用5V两者必须共地。如果不共地ADC读到的电压就是浮空的数值会乱跳。我在PCB上把数字地和模拟地分成两片然后用一颗0欧电阻在一点连接这样能减少数字信号对模拟采样的干扰。功耗方面实测整套系统在正常工作时电流大约是80mA其中MQ-2的加热丝占了差不多一半。如果后面想升级成电池供电有两个思路一是给MQ-2加一个MOS管开关平时关掉每两分钟开机加热采样一次二是STM32进入Stop模式用RTC闹钟唤醒。这两种方案我都预留了接口但基础版本先把功能跑通不折腾复杂的低功耗逻辑。3. 仿真环境搭建与调试实践3.1 仿真平台选择Proteus和Wokwi怎么取舍仿真这块我纠结了挺久最后两个平台都用了结论是各有分工。Proteus 8是目前最主流的单片机仿真软件库里直接有STM32F103C8T6模型可以搭建完整的原理图级别仿真连DHT11、LCD、蜂鸣器这些外设都有对应的仿真模型。它的优势是贴近真实硬件能够验证原理图是否正确。缺点是DHT11这种单总线传感器在Proteus里时序比较敏感一旦模型时钟跑得不够快就可能出现读不到数据的现象。Wokwi则是个轻量级在线仿真平台浏览器里就能写代码、搭电路对STM32的支持也不错。我自己的习惯是先用Wokwi快速验证代码逻辑比如DHT11的驱动和OLED显示算法再回到Proteus把整个电路连起来做系统的综合性验证。如果你只是为了验证某段代码的算法逻辑Wokwi确实能帮你省下大量搭建工程的时间。建议顺序是先在Wokwi里调通所有传感器驱动再把这个驱动原封不动地搬到Proteus工程里。这样分层验证的好处是出了问题你能清楚地知道是代码逻辑的锅还是纯仿真环境造成的问题。3.2 Proteus下搭建仿真电路的完整步骤Proteus里的搭建流程我走了一遍算是把完整的操作步骤记录下来第一步新建工程从元件库搜索并放置STM32F103C8T6。Proteus在Device下拉框里直接输入型号就能找到选中后放置到原理图区域。注意它默认带一个时钟源需要在芯片属性里把晶振频率改为8MHz。第二步放置DHT11。Proteus的传感器库里能搜到DHT11模型它的引脚有三个VCC、DATA、GND注意把DATA脚接上5.1kΩ上拉电阻到VCC并连接到STM32的一个GPIO比如PA0。第三步放置一个虚拟终端Virtual Terminal连接STM32的USART1收发脚PA9和PA10波特率设成115200。虚拟终端在仿真的作用就相当于电脑上的串口助手程序里通过串口打印的所有调试信息都能从这里看到。第四步放置一个LM016L液晶代替OLED。Proteus自带的OLED模型很少我用LCD1602代替。虽然显示方式不同但验证“显示的数值是否正确”这个核心逻辑完全够用。第五步放置电位器和按钮配合ADC调试。电位器可以模拟MQ-2的模拟电压变化这是我在仿真阶段发现的一个特别好用的调试手法转动电位器旋钮就能让ADC读数连续变化不用真的去改传感器参数。3.3 仿真联调与故障注入技巧仿真调试里我最常用的工具是Proteus的变量监视窗口和波形窗口。打开Debug菜单下的Variables窗口可以实时查看程序中全局变量的值。比如我设了一个adc_value变量存储MQ-2的读数仿真运行时转动电位器就能看到这个变量在0到4095之间变化比用串口打印变量还要直观。故障注入是仿真最大的价值所在。我可以通过直接修改仿真模型参数来模拟传感器的异常状态比如把温度设定值调到50度观察蜂鸣器是否按预期触发。这种“人为制造故障”的调试方式在真实硬件上做起来很麻烦在仿真里改一个参数就行所以在仿真阶段把报警阈值逻辑调得越透实际打板后的返工率就越低。仿真时一个非常常见的坑是LCD1602乱码或者完全不显示。排查方向一般是两个一个是检查程序里是否正确配置了GPIO的输出模式另一个是检查里时钟树配置。Proteus的STM32模型对时钟频率比较敏感如果代码里配置的系统时钟跟仿真模型里的晶振频率不一致外设的时序就会乱。我遇到过一次串口乱码排查了半天发现是仿真模型里写的是8MHz实际上我配置成了HSE直通没有锁相环倍频这个大家要注意。4. 代码工程结构解析4.1 工程目录与模块划分代码工程我是基于STM32标准外设库写的目录结构如下STM32_EnvMonitor/ ├── Core/ │ ├── main.c │ └── stm32f10x_it.c ├── Hardware/ │ ├── dht11.c / dht11.h │ ├── mq2.c / mq2.h │ ├── oled.c / oled.h │ └── buzzer.c / buzzer.h ├── App/ │ ├── monitor.c / monitor.h │ └── data_filter.c / data_filter.h ├── User/ │ └── delay.c / delay.h └── Project/ ├── Keil工程文件 └── Proteus仿真工程我把驱动层和应用层分开驱动层只负责“把传感器的寄存器/时序读出来”应用层负责“这些数据怎么处理”。这样有个很直接的好处以后从DHT11升级到DHT22只需要改Hardware里的驱动App层的报警和显示逻辑完全不用动。这也是我建议所有嵌入式初学者养成的习惯——写代码先想好哪里是“会变的”把它隔离出去。main.c里的主循环比较简洁int main(void) { SystemClock_Config(); Delay_Init(); USART1_Init(115200); OLED_Init(); Buzzer_Init(); MQ2_ADC_Init(); DHT11_GPIO_Init(); OLED_Clear(); OLED_ShowString(0, 0, Env Monitor v1.0); while(1) { if (timer_100ms_flag) { timer_100ms_flag 0; mq2_value MQ2_Read_Average(10); } if (timer_2s_flag) { timer_2s_flag 0; dht11_ok DHT11_ReadData(humidity, temperature); } if (timer_500ms_flag) { timer_500ms_flag 0; Monitor_Update(); OLED_Update(); } } }主循环里做的事情很少就是检查标志位、执行对应任务。有人可能觉得这种写法太简单但对于这种采样周期远大于指令周期的场景它就是最可靠、最容易调试的架构。你不需要去琢磨任务之间的优先级关系只要保证每个任务在自己的时间片内执行完就行。4.2 DHT11单总线时序驱动解析DHT11的驱动是整个项目代码里技术含量最高的部分因为它是单总线协议读一个字节的每一位都要严格控制时序。完整读取过程分三步主机发起起始信号、DHT11响应、主机读取40位数据。起始信号是这样的主机把数据线拉低至少18ms然后释放。DHT11检测到这个低电平后会先拉低80us再拉高80us作为响应信号然后开始连续发送40位数据。每位的时序是先拉低50us再拉高高电平持续26到28us表示“0”持续70us表示“1”。代码里判断这个脉宽用的是延时采样法——在拉低结束后延时30us再去读引脚电平如果读到高就是“1”读到低就是“0”。核心代码uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { uint8_t data[5] {0}; uint8_t i, j; DHT11_IO_OUT(); DHT11_DQ_LOW(); Delay_Ms(20); DHT11_DQ_HIGH(); Delay_Us(30); DHT11_IO_IN(); if (DHT11_DQ_READ() 0) { while (DHT11_DQ_READ() 0); while (DHT11_DQ_READ() 1); for (i 0; i 5; i) { for (j 0; j 8; j) { while (DHT11_DQ_READ() 0); Delay_Us(30); if (DHT11_DQ_READ() 1) { data[i] (data[i] 1) | 1; } else { data[i] (data[i] 1); } while (DHT11_DQ_READ() 1); } } if ((uint8_t)(data[0] data[1] data[2] data[3]) data[4]) { *humidity data[0]; *temperature data[2]; return 1; } } return 0; }这段代码有几个关键点需要说明。首先延时函数必须用定时器或者系统时钟做校准不能用空循环简单模拟否则换个编译优化等级就不准了。我用的Delay_Us是基于SysTick实现精度可以到1us量级。其次每次操作数据线之前要切换GPIO方向这是单总线协议最容易出问题的地方一旦忘了切方向读到的一直是同一个值。最后就是校验位DHT11发完4字节数据后会再发一个校验字节等于前四个字节的累加和这个机制能在一定程度上过滤掉时序错误导致的脏数据。4.3 数据处理、滤波与报警消抖MQ-2的ADC原始值直接拿来做判断会有抖动特别是刚上电的几分钟里传感器预热过程中读数会缓慢漂移。我加了一个简单的滑动平均滤波连续采样10次去掉最大值最小值再求平均uint16_t MQ2_Read_Average(uint8_t times) { uint16_t buf[10]; uint8_t i, min_idx 0, max_idx 0; uint32_t sum 0; for (i 0; i times; i) { buf[i] ADC_Read(); if (buf[i] buf[min_idx]) min_idx i; if (buf[i] buf[max_idx]) max_idx i; } for (i 0; i times; i) { if (i min_idx || i max_idx) continue; sum buf[i]; } return (uint16_t)(sum / (times - 2)); }滤波后再做阈值判断同时加了“连续越限计数”防抖逻辑。简单说就是被测数据需要连续3次超过阈值系统才会报警。这个设计是为了避免偶然的脉冲干扰触发蜂鸣器比如炒菜时一瞬间的油烟浓度升高连续采样3次都超标才说明真的有持续泄漏。报警触发后我让蜂鸣器以1Hz频率间歇鸣叫同时OLED上显示“ALARM”字样直到数据恢复到阈值以下并且持续5秒才消警。4.4 OLED显示与按键交互OLED驱动用的是经典的SSD1306 I2C协议网上现成代码一大堆我主要改了显示布局。屏幕分辨率是128x64分成四行第一行显示温度和湿度第二行显示烟雾ADC值第三行显示当前时间戳第四行显示报警状态。因为I2C传输整屏数据需要一点时间我做了局部刷新只在数值变化时才更新对应区域这样能减轻总线负担。按键交互做的是最简单的红外感应模式其实我只加了两个按钮一个切换显示页面一个静音报警。按键消抖用延时20ms的软件消抖虽然简单但很实用。这里有个细节按键检测放在主循环里但静音标志位被中断函数引用时要注意加volatile修饰不然编译器优化后可能出现“改了半天没反应”的情况。5. 常见问题与排查技巧实录5.1 问题速查表把项目过程中遇到的所有印象深刻的问题整理成一张速查表方便大家直接对照排查。现象可能原因解决办法下载时提示no stm32 target foundST-Link驱动未装或接线错误检查SWD四线按住复位键点击下载再松开DHT11一直读到0或数据不变化起始信号时间不足上拉电阻缺失将拉低时间提到20ms确认5.1k上拉到3.3VOLED黑屏I2C地址不对或SCL/SDA接反检查0.96寸屏的I2C地址是0x3C还是0x3DMQ-2读数满量程4095ADC输入悬空或负载电阻开路测量分压点对地电压确认10k负载焊好蜂鸣器声音沙哑有源蜂鸣器当成无源驱动有源蜂鸣器用直流电平驱动无源需要PWM方波Proteus仿真LCD乱码晶振频率与代码配置不一致把仿真模型晶振设为8MHz检查锁相环配置5.2 印象最深的三个调试经历第一个是DHT11数据线卡死的问题。前几版代码里如果DHT11没有响应数据线可能会被传感器长期拉低程序会卡在while循环里出不来。解决方法是给等待循环加超时计数超过一定时间直接返回读取失败而不是无限等下去。这个经验后来我用到了所有带while等待的代码中算是吃一堑长一智。第二个是ST-Link下载失败的问题。那段时间换了台电脑重新装完Keil和驱动后怎么都连不上芯片报的就是no stm32 target found。折腾了很久发现是Debug设置里没有勾选Reset and Run导致程序下载后没有自动运行看起来就像“连不上”。后来我养成了一个习惯新环境第一件事就是把Debug选项卡下的几项配置截图存起来省得每次重装都重新踩一遍。第三个是Proteus仿真卡顿。DHT11模型在Proteus里跑起来非常吃资源因为它的VSM模型要按时序模拟电平变化。解决办法是把仿真运行速度调低一点同时在代码里增加延时采样点的精度让每个位的解析更稳定。仿真环境的怪问题往往不是代码错而是模型限制这时候要分清主次别在仿真里死磕这种跟硬件无关的东西。5.3 实操补充代码和资料怎么高效复用既然项目是开源的最后说点资料使用的小建议。整套项目包括三样东西原理图源文件、Keil工程代码、Proteus仿真工程。我的建议是复现时不要一上来就打开完整工程而是按照“仿真验证→硬件接线→烧录调试”的顺序走。先跑Proteus仿真把系统逻辑理清楚再根据原理图接真实硬件这样即使接线出错你也能快速判断是硬件问题还是代码逻辑问题。硬件接线方面我强烈建议先用面包板搭一套快速原型。STM32最小系统板、DHT11模块、OLED模块、蜂鸣器模块都有现成的用杜邦线连接基本半小时就能搭好。芯片先不要焊在洞洞板上等所有功能都验证OK了再画PCB这个习惯能省掉很多重复焊接的时间。如果你打算自己做PCB我开源包里给的原理图可以直接导入立创EDA稍微整理一下封装就能转PCB。代码复用方面我的建议是驱动层和应用层拆开存成独立文件夹。比如DHT11的驱动、OLED的驱动这些是“几年都不会大变”的代码可以直接存进自己积累的代码仓库。下次做别的项目要显示温湿度直接把这两个文件拖进去改一下引脚宏定义就能用。长期下来这个“个人代码库”给你节省的时间比你现在学的任何框架都实惠。做到这一套完整的STM32家庭环境监测系统就算从原理图到实物全部落地了。回看整个项目我最有感触的一点是“开源”不等于“把代码扔出去”真正麻烦的是把原理图、仿真环境、代码结构、踩坑记录全部对齐让拿到资料的人不需要再来问“这个引脚接哪”。我在整理资料的过程中也重新梳理了一遍自己写过的逻辑删掉了很多冗余代码这本身就是一种提升。如果你在复现过程中卡在哪一步建议先对照问题速查表排查还是解决不了的话打开仿真工程对比一下参数设置多半是时钟配置或者上拉电阻这种细节没对齐。嵌入式这东西只要硬件没焊错、时序没乱系统就一定会按你想的方式跑起来。