这阵子整理网盘的时候,翻出前年做的一个练手项目——图书馆环境监测系统。当时正好赶上工作室接了校内图书馆的局部改造需求,加上自己一直在折腾STM32,就顺手用STM32F103C8T6搭了一套能测温度、湿度、光照和烟雾浓度的环境监测装置,把原理图、源码和Proteus仿真一起打包开源了。
做这个项目的初衷很简单:图书馆自习区经常有人反映"靠窗位置夏天太晒""角落返潮有霉味""机房附近异味重",而馆方只能靠人工巡查,效率和覆盖面都有限。我当时想的是,能不能用一套低成本硬件,加上清晰的上位机逻辑和报警机制,把几个关键环境参数实时采集、显示、超限提醒,顺便还能联动通风设备。对于正在学STM32的人来说,这个项目正好把GPIO、ADC、I2C、定时器、中断、状态机这些核心外设全部串了一遍,是一个典型的"麻雀虽小五脏俱全"的嵌入式综合练习。
目前开源仓库里已经有不少人fork并做了改动,比如有人加了ESP8266联网把数据推到MQTT,有人把OLED界面重绘成曲线图,还有人把DHT11换成了SHT30。不过大部分问题都集中在仿真怎么跑起来、原理图某些器件为什么要这么接、代码里那几个配置到底是干什么的。这篇文章不打算写成一份PDF式教程,而是把我从立项、画图、写代码、调仿真、实测标定的全过程,以及踩过的坑和取舍逻辑,原原本本讲一遍。
1. 项目动因:图书馆场景对嵌入式系统提出的真实要求
1.1 为什么选"图书馆"而不是"智能家居"
刚开始我也想过直接套用常见的智能家居方案,但那类项目的问题在于需求太泛,最后做出来的东西常常只是"一堆传感器数据滚动显示在屏幕上",没什么实际约束力。图书馆不一样,它有明确的物理空间边界、固定的巡查盲区、特定的环境隐患点,这会让整个系统的设计目标变得非常聚焦。
具体到图书馆场景,我拆出了四个必须监测的参数:
- 温度:夏季靠窗区域阳光直射,温度可能比馆内平均温度高出5-8℃,长时间高温对古籍、纸质书籍的保存不利。
- 湿度:地下室密集书库和洗手间附近的过道湿度经常超标,湿度过高容易导致书籍发霉、纸张变脆。
- 光照:阳光直射会让书脊褪色,但室内照明不足又影响阅读体验,所以需要区分"自然光强度"和"馆内照明"。
- 烟雾/可燃气体:机房、配电间附近的烟雾隐患,需要在明火或线路过热冒烟阶段就提前报警。
这四个参数对应四种不同的采集方式,恰好覆盖了数字单总线(DHT11)、I2C(BH1750)、ADC模拟量(MQ-2烟雾传感器)、数字电平判断(火焰/烟雾二合一模块)这几种嵌入式最常用的传感器接口类型。也就是说,做完这个项目,你基本就把单片机读传感器的常见姿势都练了一遍。
1.2 主控和外设的选型逻辑
主控选择上我用的是STM32F103C8T6,也就是大家常说的"蓝板"核心板。选它的理由其实很现实:第一,价格便宜,十几块钱一片,烧了不心疼;第二,资料极其丰富,任何一个外设遇到问题都能搜到案例;第三,这个项目需要的资源它刚好够用——72MHz主频、20KB RAM、64KB Flash、3个USART、2个I2C、2个SPI、10个ADC通道,对一套环境监测系统来说性能完全溢出,后续想加联网模块也不用换主控。
传感器选择我参考了"够用就好"的原则:
| 传感器/模块 | 型号 | 接口 | 测量范围 | 精度 | 为什么选它 |
|---|---|---|---|---|---|
| 温湿度 | DHT11 | 单总线 | 20~90%RH,0~50℃ | ±5%RH,±2℃ | 便宜、常见、学时序正好 |
| 光照 | BH1750 | I2C | 1~65535 lux | ±20% | 直接输出光照度数值,不用自己算 |
| 烟雾/可燃气体 | MQ-2 | ADC模拟量 | 300~10000ppm | 粗略级 | 反应速度快,阈值可通过电位器调 |
| 火焰检测 | 红外火焰传感器 | 数字电平 | 0~1m可调 | 开关量 | 响应快,作为烟雾传感器的补充 |
显示和交互这边,我用了一块0.96寸OLED(I2C接口,SSD1306驱动),三个按键(菜单切换、加、减),一个无源蜂鸣器,一个继电器模块用来控制风扇。OLED选I2C版本纯粹是因为省引脚,整个显示只占PB8和PB9两个IO口。蜂鸣器用了三极管驱动的无源蜂鸣器,而不是开发板自带的那个有源蜂鸣器,原因后面在电路部分细说。
2. 硬件原理图设计:每一根走线背后的工程考量
2.1 电源与最小系统:稳定是一切传感器数据的前提
原理图设计的第一件事不是接传感器,而是把电源和最小系统画扎实。这套系统里有个非常容易翻车的细节:DHT11、BH1750、火焰传感器、OLED都可以在3.3V下工作,但MQ-2烟雾传感器的加热丝部分必须用5V供电,而它的模拟输出引脚接的是STM32的ADC,这就涉及电平匹配问题。
我的处理方式是:整个系统用USB的5V供电,5V直接给MQ-2的VCC和继电器线圈,同时经过一颗AMS1117-3.3稳压芯片降压到3.3V给MCU和其余传感器用。MQ-2的AO输出引脚直接进STM32的PA1,很多人担心3.3V单片机读5V传感器的模拟量会不会烧引脚,实际上MQ-2的AO输出在洁净空气中大约只有0.1~0.3V,浓度很高时也就3V左右,它的分压电路决定了输出不会是满幅5V。不过为了稳妥,我还是在AO与PA1之间串了一个1kΩ电阻,并且在PA1对地并联了一个100nF电容做滤波,这比直接飞线可靠得多。
电源去耦这件事,新手容易忽略,但恰恰是ADC采样值稳定性的关键。我在原理图中给每个IC的电源引脚都就近放置了100nF的陶瓷电容,在USB输入处放了10μF钽电容和100nF电容并联。实测下来,如果不加这些电容,MQ-2的ADC采样值会有明显的周期性波动,大概是风扇或继电器动作瞬间的电流冲击耦合到了模拟参考电压上。
最小系统部分,STM32F103C8T6我用了常见的八脚配置:BOOT0通过10kΩ电阻下拉到地,确保从Flash启动;NRST对地接一个100nF电容;OSC_IN和OSC_OUT接8MHz晶振,两个负载电容用20pF;VDDA引脚串一个10Ω电阻后接3.3V,并且对地接一个1μF电容——这个VDDA的滤波电容官方手册里特别强调要接,它对ADC精度影响很大。
2.2 传感器接口电路:上拉、滤波与电平匹配
传感器接口部分的原理图看起来就是几个排针,但每个引脚的处理都有说法。
DHT11的数据引脚是开漏输出,必须外接上拉电阻才能保证通信时序正确。我用的是4.7kΩ上拉到3.3V。这个阻值其实是个经验值:太小了会让总线上的低电平时间变长,影响时序容限;太大了则上升沿变缓,单片机在窄脉冲采样时容易误判。4.7kΩ是DHT11数据手册和大部分参考设计共同推荐的,实测在20cm杜邦线长度下很稳定。
BH1750和OLED挂在同一条I2C总线上,I2C同样需要上拉电阻。我选了4.7kΩ上拉到3.3V。这里有个小细节:BH1750的地址引脚ADDR直接接地,所以它的7位地址是0x23;OLED的SSD1306地址默认是0x3C,两者不冲突。同一总线上两个不同地址的I2C设备,省了一组IO口。
MQ-2的电路相对复杂一点:模块上自带一个电位器用来调节比较器阈值,同时有两个输出——AO是模拟输出,DO是数字输出。我两个都接了:AO接PA1做连续浓度采集,DO接PA0做快速中断判断。这样系统可以在浓度超过DO阈值时立刻报警,同时通过AO读取具体浓度值,用户界面上的"浓度进度条"就是靠这个模拟量驱动的。
火焰传感器模块输出的是数字开关量信号,默认输出高电平,检测到火焰时输出低电平。我在PA2引脚上配置了内部上拉,因为模块输出的高电平其实是OC门(集电极开路)形式的,外部需要上拉才能保证高电平是确定的3.3V。如果忘了使能内部上拉,这个引脚在无火焰状态下会读到不确定的浮动电平,偶尔会误报警。
2.3 执行机构与报警电路:驱动能力这件事
报警和联动部分,新手最容易犯的错是试图用单片机的IO口直接驱动继电器或蜂鸣器。STM32的GPIO在推挽模式下最大输出电流也就20mA左右,而一个5V继电器线圈的吸合电流可能需要30~70mA,蜂鸣器虽然电流小但反向电动势会倒灌。
我的方案是:蜂鸣器接在PB12引脚上,通过一颗S8050三极管做开关驱动。PB12输出高电平时三极管导通,蜂鸣器发声;低电平时截止。蜂鸣器两端并联了一个1N4148二极管,方向是负极接5V、正极接三极管集电极,用来吸收关断瞬间的反向电动势——这个二极管不加的话,蜂鸣器停止发声时会有一个负电压尖峰打在集电极上,虽然不一定立刻损坏三极管,但长期运行会缩短寿命,而且可能干扰同一供电电源上的其他电路。
继电器模块则直接用了市面上常见的一路5V低电平触发光耦隔离模块,IN引脚接PC13。为什么要用光耦隔离模块而不是三极管直接驱动继电器?因为继电器线圈在吸合和释放瞬间会产生较大的电流突变,如果和MCU共地共电源,这个突变会沿着地线窜回MCU,表现为ADC采样值跳变、OLED偶发花屏。光耦隔离模块把控制侧和负载侧的电源彻底分开,虽然成本多了一两块钱,但系统的稳定性会上一个台阶。
原理图里还有一个不起眼但很重要的设计:OLED和传感器的电源引脚都单独引出一个100nF电容到地。这类模块在插拔瞬间会产生较大的电压毛刺,有了电容缓冲,插拔时MCU不容易复位。
3. 代码架构与核心逻辑:从轮询到下位机状态的完整实现
3.1 分层文件组织与模块封装
写这套代码的时候我给自己定的规矩是:每个外设一个C文件和对应的H文件,不把逻辑全堆在main.c里。最终的工程结构是这样的:
├── Core/ │ ├── main.c │ ├── stm32f1xx_it.c ├── Drivers/ │ ├── BSP/ │ │ ├── bsp_dht11.c/h │ │ ├── bsp_bh1750.c/h │ │ ├── bsp_oled.c/h │ │ ├── bsp_mq2.c/h │ │ └── bsp_flame.c/h │ ├── App/ │ │ ├── app_monitor.c/h │ │ └── app_menu.c/hmain.c里只做三件事:初始化各外设、打印启动信息、进入while(1)主循环。具体的业务逻辑在app_monitor.c里,它维护一个系统状态机,每隔200ms做一次数据采集和刷新。这样做的最大好处是排错容易——如果OLED显示乱了,直接查bsp_oled.c,不影响其他模块;如果采集数据异常,单独测试对应的传感器模块就行。
3.2 DHT11时序采样的关键细节
DHT11是这套系统里唯一需要自己严格掐时序的外设,也是最容易让新手崩溃的地方。它的通信协议是单总线:主机拉低总线至少18ms,然后释放,DHT11会响应一个80μs的低电平,然后进入40bit的数据传输阶段。每一位数据的时间长度不一样,50μs的低电平之后,如果跟着26~28μs的高电平则代表"0",如果跟着70μs的高电平则代表"1",最后还有一个50μs的结束低电平。
我之前看到很多教程用简单的延时函数来读取这段时序,实际上不太可靠,因为DHT11的时序余量很小,而且它要求主机在采样时总线忙等,任何中断干扰都可能导致读到错误的电平宽度。我最后采用的方案是:把DHT11的数据引脚挂在PA6上,读取时先关闭该引脚的中断(项目里PA6没开中断,但保险起见还是加了__disable_irq这一行),然后使用一个高精度的延时循环配合读取引脚电平:
uint8_t DHT11_ReadByte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) { while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == RESET); // 等待低电平结束 DELAY_US(40); if (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == SET) { data |= (0x80 >> i); } while (GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == SET); // 等待高电平结束 } return data; }这段代码里最关键的是"等待低电平结束"这句话,它让程序自动跳过前导信号,在数据位传输到当前bit时才做采样。比固定延时然后读引脚的方式更抗干扰。实测下来,在系统开启蜂鸣器报警、继电器吸合的电磁干扰环境下,这个读取方式依然能稳定读到数据,没有出现一位错乱或校验失败的情况。
另外,DHT11读取频率不能太高,两次读取间隔至少要1s。我在代码里加了时间戳判断,距离上一次采集不足800ms时直接返回上次结果,避免了连续读取导致的时序冲突。数据校验用的是DHT11自带的8bit校验和机制,读到的40bit中前四个字节是湿度和温度,最后一个字节必须是前四字节之和的末8位,校验不过就丢弃本次数据,保留上一次有效值——这个机制在长时间运行中非常有用,偶尔一次采集中断不会让屏幕上的温湿度突然变成0或乱码。
3.3 多传感器融合:简单但有效的环境判定规则
环境监测系统真正有"智商"的部分,在于怎么把多个传感器的数值综合成一个合理的判断。我的判定逻辑放在app_monitor.c里,用一个全局结构体保存所有传感器的最新值和对应的标志位:
typedef struct { float temperature; float humidity; uint16_t light; uint16_t smokeValue; uint8_t flameFlag; uint8_t smokeAlarm; uint8_t tempAlarm; } EnvData_t;判定的核心规则是:
- 温度超过38℃或低于5℃触发温度报警,蜂鸣器短鸣两声,并在OLED上显示"TEMP HIGH"。
- 湿度超过75%RH触发湿度报警,同时自动开启继电器风扇,提示通风。
- 烟雾浓度持续超过阈值——注意是"持续",我在代码里用了一个计数器,连续3次采集(约600ms)都超标才真正触发烟雾报警,这能防止炒菜般短暂的烟雾尖峰引起误报。
- 火焰传感器输出低电平时立即报警,优先级最高,无论其他参数是否正常,OLED直接切换到"FIRE!!"警示页。
- 光照值只做记录和显示,不参与自动控制,但会在OLED上通过一个进度条显示当前光照强度,方便馆员判断是否需要拉开窗帘或调亮照明。
规则设计上我刻意没有引入复杂的模糊控制或PID,因为环境监测系统的核心价值是"准确告知环境状态",而不是"自动把所有参数调节到最优"。风扇联动只有在湿度和烟雾同时超标时才开启,这是为了避免冬天湿度没到阈值但温度低的情况下风扇误启动,让读者挨冻。
3.4 显示与交互:OLED驱动与按键状态机
OLED部分用的是SSD1306驱动芯片的经典I2C写法,先发控制字节(0x00表示后续是命令、0x40表示后续是数据),再发送具体的寄存器配置序列。初始化序列是从Adafruit的例程里精简出来的,去掉了很多用不到的功能,保留了显示开启、电荷泵开启、内存地址模式设置为页模式这几个必要步骤。
UI设计上我做了三个页面:第一页显示温湿度和环境状态图标(用简单的中文字库点阵画了"温""湿""光""烟"四个字),第二页显示烟雾浓度和光照强度的数值进度条,第三页是阈值设置页,可以按键调整温度和湿度的报警阈值。三个页面通过菜单状态机切换,状态定义如下:
- MENU_MAIN:默认显示页,按确认键进入菜单列表
- MENU_TEMP_SET:温度阈值设置页,按加/减修改,长按确认键保存并退出
- MENU_HUMI_SET:湿度阈值设置页,逻辑同上
- MENU_ABOUT:版本信息页,显示固件版本和开源仓库地址
菜单状态机的实现用了一个switch-case结构,按键扫描放在定时器中断里,通过消抖逻辑(连续两次扫描间隔20ms以上且电平稳定)来确认按下事件,这样主循环里读取到的永远是一个稳定的"事件",而不是电平状态。这个设计虽然简单,但避免了按键抖动导致的一次按击被识别成多次操作的经典问题。
4. 仿真搭建与踩坑实录:Proteus仿真STM32的真实经历
4.1 仿真环境的搭建:固件烧录与器件库问题
很多初学者以为Proteus仿真和实物开发一样,直接把生成的HEX文件拖进单片机就行。实际上Proteus的STM32模型要求你先把固件加载到MCU,再运行仿真。我的具体步骤是:
- 在Proteus中放置STM32F103C8T6元件,双击打开属性对话框。
- 在"Program File"一栏选择MDK工程编译出的HEX文件路径。
- 晶振频率设置为8MHz,程序执行方式选"Debug"运行而不是"Run",这样可以在仿真中观察GPIO电平变化。
器件库里容易出问题的是DHT11,Proteus自带的器件列表里其实没有完全匹配DHT11的模型,但有个叫"AM1001"的温湿度传感器模型,引脚定义和DHT11基本一致,代码里稍作映射就能用。BH1750在Proteus里也没有现成模型,我的处理方式是用一个I2C调试探针代替,或者在仿真里干脆用滑动变阻器模拟光照的ADC电压,把光照数值的ADC部分单独拉出来验证,这样至少能确认ADC配置和数值计算公式是没问题的。
如果你要把整个系统完整跑仿真,我建议用Proteus 8.9以上版本,早期的Proteus 8.0对STM32F103的I2C外设支持相当粗糙,BH1750这种需要I2C时序读写的器件经常挂在总线通信上,仿真结果和实物完全对不上。
4.2 仿真与实物的差异:哪些能信,哪些必须实测
这是我觉得整个过程里最有价值的认知收获:仿真各有能信和不能信的地方。
能信的是逻辑流程。比如菜单状态机切换、报警标志位置位、OLED页面的文字切换、按键消抖处理,这些纯逻辑层面的东西在仿真里跑起来和实物是完全一致的。通过Proteus的虚拟终端和示波器,还能很直观地看到I2C总线上每个字节的时序对不对,这在实物上用逻辑分析仪才能做到,对新手来说门槛低了很多。
不能信的是模拟量的绝对精度和时序余量。Proteus的ADC模型是把输入的模拟电压线性映射到0~4095,它不会模拟传感器的响应迟滞、温漂、供电纹波这些真实特性。MQ-2在仿真里给一个0~5V电压源,读出来的是理想线性结果,但实物里MQ-2预热阶段电压会漂移、不同浓度下响应时间也不同。同样,DHT11在仿真里读取时序只要逻辑对就能出结果,但实物上因为线缆电容、供电噪声和温度变化,同样的代码可能偶尔读零。
所以我建议的流程是:先在仿真里确认代码逻辑正确,再烧录到实物上,把仿真阶段用不到的校准环节补齐。我这个项目的仿真文件主要用于展示系统架构和运行流程,而不是用来精确模拟传感器数据的。
4.3 一次"灵异现象"的排查:ADC数值跳变的真相
仿真阶段我遇到过一个特别典型的坑,值得单独拿出来说。当时我在仿真里把MQ-2的AO引脚接到一个可调电压源,模拟不同烟雾浓度,发现PA1读到的ADC值在电压源显示1.2V时稳定在900左右,这没问题,但当我同时打开继电器模块(仿真里是一个LED+电阻模拟)时,ADC值突然跳到了1100,而且跳得不规律。
排查的过程:先怀疑程序问题,仔细检查了ADC配置代码,初始化顺序、采样通道选择、采样时间设置都没问题。又怀疑是ADC参考电压不稳,但仿真里VREF是理想3.3V。最后试着把继电器模块的地线在仿真原理图上单独拉了一根"地线回路",ADC值立刻恢复正常了。
原因其实很简单:在Proteus里,如果多个模块的地符号都直接用"GROUND"这个元件,它们之间是共地的。当继电器动作时,电流冲击会在共享的地回路上产生微小的电压降,而这个压降恰好叠加到了模拟信号源的参考地上,导致ADC输入端的有效电压发生了偏移。换成实物其实也是同一个道理——所以我在实物布局时把继电器模块的电源和地单独走线,在核心板附近一点接地,就是反推这个仿真经验。仿真帮我提前暴露了一个在实物上很难定位的问题,这算它的额外价值。
5. 实测数据与参数标定:让系统真正可用的最后一步
5.1 温湿度校准:DHT11的误差修正
DHT11出厂标称精度是±2℃和±5%RH,这个精度在精确气象站面前是上不了台面的,但作为环境监测的提示系统完全够用。我在实测阶段做了一件事:把DHT11和水银温度计、毛发自记湿度计放在同一个环境里,连续记录48小时,对比数据后发现DHT11的温度读数平均偏高0.8℃,湿度读数平均偏低6%RH左右。
这个偏差在DHT11的允许误差范围内,但可以通过软件修正:温度减去0.8℃,湿度加上6%RH。修正后与标准仪器误差控制在±1℃和±4%RH以内。具体的校准偏移量每个批次可能略有不同,我在代码里定义了两个可调参数:
#define TEMP_CAL_OFFSET (-0.8f) #define HUMI_CAL_OFFSET (6.0f)建议拿到传感器后先和室内温度计对比一两天,根据自己的硬件修正这两个值。不同供应商的DHT11一致性差异较大,直接抄我的偏移值不一定适用。
5.2 烟雾传感器阈值整定:避免误报的科学方法
MQ-2传感器的输出特性是:洁净空气中输出电压较低且相对稳定,遇到烟雾或可燃气体时电压上升。但它的"零点"会随着环境温度和湿度漂移,我实测同一块模块在湿度30%的空调房和湿度80%的雨天的零点电压能差出0.2V左右,反映到ADC上就是几十个码值的偏移。
阈值到底设多少合适?我的做法是:上电后让系统先运行10分钟预热(MQ-2内部加热丝需要时间达到稳定工作温度),然后程序自动读取100次ADC值取平均,记为baseline。报警阈值设置为baseline + 100(ADC码值,对应大约0.08V压差)。这个偏移量是通过点燃一小片纸靠近传感器实测确定的——烟雾浓度刚引起人注意时,ADC大概比baseline高出60~80码值;明显呛人时高出200以上。阈值设在100,既不至于在轻微烟味时误报,也不会让浓烟产生时无法触发。
同时,我将ADC采样做了一阶低通滤波:每次新采样值x_new,更新smokeFiltered = 0.8 * smokeFiltered + 0.2 * x_new。这个滤波不是简单地平均,而是让响应既平滑又有足够的反应速度。实测中,连续吹一口电子烟(约2秒)后,滤波后的数值大约在1秒内爬升到峰值的70%,足够触发报警;而环境中偶然的干扰尖峰则被抑制掉了。
5.3 功耗与长时间运行稳定性
系统在5V供电下实测整机电流约180mA,其中MQ-2加热丝占了约150mA,单片机加传感器约30mA。如果连续运行一个月,大概会消耗130度电——对于图书馆这种24小时供电的场所不算什么,但如果想用电池供电,MQ-2的大电流就是必须解决的问题。
我做了两个优化实验:一是让MQ-2间歇工作,每10分钟通电1分钟采集一次,功耗降为原来的十分之一,但代价是烟雾响应的实时性变差;二是改成低功耗方案,用STM32的STOP模式,定时RTC唤醒采集一次后继续睡眠。这两个扩展方向我在仓库的README里都写了思路,实测数据也列了,后续想改项目的人可以直接参考。
长时间运行稳定性方面,我连续跑过一个月,出现两个现象:一是DHT11偶尔会间隔几天出现一次"校验失败",代码里的保留上次有效值机制让这种偶发错误无感;二是OLED屏幕出现轻微残影,这是OLED正常的老化现象,不影响使用,但如果屏幕固定显示同内容,建议增加屏幕保护机制,比如2分钟无操作后降低亮度。
6. 开源资料的使用方法:代码+原理图+仿真,怎么快速跑起来
6.1 工程目录结构与文件说明
开源仓库的核心目录在项目主页就能看到,我按"硬件""固件""仿真""文档"四个维度做了划分:
LibraryEnvMonitor/ ├── Hardware/ │ ├── Schematic_LibraryEnvMonitor.pdf │ └── PCB_LibraryEnvMonitor/ ├── Firmware/ │ ├── MDK-ARM/ │ ├── Core/ │ └── Drivers/ ├── Simulation/ │ ├── LibraryEnvMonitor.pdsprj │ └── Libraries/ └── Docs/ ├── README.md ├── UserManual.pdf └── CalibrationGuide.md需要特别提醒的是Simulation/Libraries目录,Proteus工程打开如果提示找不到器件,十有八九是库文件路径没关联。打开Proteus后先在"系统→系统设置→仿真器/调试器"里确认版本,再到"库→库管理"把Libraries文件夹添加进去,然后重新打开工程。Proteus版本尽量不低于8.9,低版本打开高版本工程会缺失部分器件模型。
6.2 一步一步:从代码下载到板子跑通
如果你是第一次接触这类项目,按照下面的顺序操作成功率最高:
先用Keil MDK打开Firmware/MDK-ARM目录下的工程文件,按F7编译,确认没有error。如果用的是Keil 5.25以下版本,打开时可能会提示"device pack版本过低",去Keil官网装一下对应版本的STM32F1xx_DFP即可。
编译成功后在Output目录生成HEX文件。我用的是STM32F103C8T6,其他型号如果Flash或RAM容量不够,直接换高配型号改一下芯片选择就行,代码不需要动。
烧录用ST-Link或串口ISP都行。ST-Link接线方式是SWDIO接PA13、SWCLK接PA14、GND接GND、3.3V接3.3V,四根线就够。串口ISP需要BOOT0拉高再上电,用FlyMcu选择HEX下载。
下载代码后会看到OLED先显示开机Logo,2秒后进入主界面显示温湿度数据。如果没有显示,先测一下I2C地址是不是0x3C,有时候OLED模块地址是0x3D,改一下bsp_oled.c里宏定义就行。
然后测试报警和联动:用手捏住DHT11探头(温度上升)或者往MQ-2附近哈气(烟雾浓度上升),应该能看到OLED界面变化、蜂鸣器响、继电器吸合。不响不动作的话,优先检查蜂鸣器管脚PB12和继电器管脚PC13的电平时序,用Debug模式看代码有没有跑到报警分支。
仿真验证:打开Simulation目录下的工程,加载HEX,点运行。注意仿真里没有真正的DHT11模型,我用的是替代器件,如果修改了代码中DHT11的引脚定义,仿真里也要同步改。
6.3 扩展方向:这个项目还能怎么改
仓库里我已经预留了几个扩展点,都是实测验证过的思路:
- 联网化:在USART1上外接ESP8266模块,将采集数据通过AT指令上报到MQTT服务器或B站大屏数据接口。我测试过用USART1的DMA+空闲中断接收AT回调,波特率115200,数据帧格式是JSON,约1.2KB一条,稳定性没问题。
- 多节点组网:用485总线把多套监测系统串起来,上位机用Modbus协议轮询。需要加一个MAX3485收发器,STM32的USART2加方向控制引脚,代码里把Modbus从站协议栈移植过来就能用。
- 数据记录:加一片W25Q64 Flash,每5分钟记录一条带时间戳的数据,用一个简单的环形缓冲区写入,掉电不丢失。读者拿这些数据做数据分析或画趋势图都很方便。
- 升级传感器:把DHT11换成SHT30,I2C接口,精度提升一个量级,代码只需要替换bsp_dht11.c模块,其他逻辑不用动。BH1750也可以换成VEML7700,环境光响应更平滑,但寄存器配置需要重新调。
我个人在实际操作中的体会是,这个项目最难的部分不是任何一个单独的模块,而是把多个模块组合在一起后,怎么让它们稳定协作。比如继电器动作和ADC采样的地线干扰问题,不长期跑根本发现不了;代码里沿用"校验失败保留上次数据"的思路,在任何一个传感器项目里都值得推广。你现在下载的这套代码,至少已经被几十个人在自己板子上验证过了,遇到问题先在Github Issues里搜一下,大概率有人踩过同样的坑。如果真找不到解决办法,欢迎开个issue把现象和原理图发上来,我看到都会回。