ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于STM32的实验室消防预警系统:从传感器到报警联动的完整开源方案

基于STM32的实验室消防预警系统:从传感器到报警联动的完整开源方案 实验室消防预警这件事我在学校里折腾了大半个学期。起因很简单学院实验室里堆着各种加热设备、化学试剂和连夜运行的仪器而传统消防报警主机一套下来价格不低布线又麻烦很多实验室其实并没有做到位。所以我就用STM32从头搭了一套消防预警控制系统把烟雾检测、火焰识别、温度监测和报警联动全部整合在一块板子上并且把代码、原理图和Proteus仿真完整开源出来。这套方案不仅适合做课程设计、毕业设计也适合真正想给实验室或者小型工作间加一道安全防线的人参考。整个项目的核心思路并不复杂传感器采集环境数据STM32做阈值判断和状态管理蜂鸣器、LED和继电器负责报警输出与联动控制再加上OLED显示当前状态所有逻辑都可以在Proteus里先仿真验证再移植到实物。下面我按从方案设计到落地复现的顺序把这个项目的关键细节完完整整地拆开讲。1. 实验室消防预警为什么值得用STM32从头撸一套1.1 传统消控方案的痛点很多人一提到消防报警第一反应是买现成的烟雾报警器。确实家用独立式烟雾报警器几十块钱一个装上就能用。但放到实验室场景它有几个问题第一独立式报警器只能本地响没法把信号联动到风扇、电磁阀或者远程通知第二实验室里常见的酒精、有机溶剂挥发某些传感器容易误报而真正需要关注的火焰和异常升温反而没有监测第三报警阈值固定不可调不同实验室的底噪环境差很多固定阈值要么天天误报要么真出事了不响。所以这个项目的定位不是替代专业消防主机而是做一套低成本、高灵活、可二次开发的预警控制系统。它要解决三件事多种传感器融合判断减少单一传感器误报分级报警不同严重程度对应不同动作把关键状态可视化方便现场人员快速判断。1.2 系统整体架构与工作流程整个系统采用STM32F103C8T6作为主控这是最常见也最便宜的选择蓝色Pill板十几块钱就能买到资源完全够用。传感器方面选了三个MQ-2烟雾传感器检测可燃气和烟雾浓度火焰传感器检测红外火焰特征DHT11温湿度传感器监测环境温度。输出设备包括有源蜂鸣器、红绿双色LED、一个继电器模块继电器可以接排风扇或者电磁阀做联动此外还有一个0.96寸OLED显示屏显示实时数据。工作流程可以这样理解STM32通过ADC读取烟雾传感器的模拟电压通过GPIO读取火焰传感器的数字信号通过单总线协议读取DHT11的温度值。然后主控里跑一个简单的状态机把所有输入综合判断输出到报警设备和显示设备。判断逻辑不是单一超限就报警而是做了分级温度偏高但烟雾正常时提示注意但不鸣笛烟雾浓度超标或者检测到火焰时立即进入报警状态如果温度和烟雾同时异常则进入最高级报警并触发继电器联动。1.3 代码、原理图、仿真三个部分的分工开源资料里我把这三个部分分开是有原因的。原理图解决的是硬件怎么连接的问题你照着接线就能搭出一套实物仿真解决的是逻辑对不对的问题在Proteus里不需要真实硬件就能调试状态机和阈值逻辑代码则是连接两者的桥梁。我建议的复现路线是先看原理图理解硬件连接然后用仿真跑一遍代码逻辑最后再动手焊实物。这样做的最大好处是最难的调试环节被前置到了仿真阶段实物阶段基本就是验证性的工作不容易烧板子。2. 原理图设计传感器、电源与接口的核心细节2.1 传感器选型与接口设计原理图里最关键的是传感器接口设计这里每一路都有讲究。MQ-2烟雾传感器我用的模块是带模拟输出的那种四根引脚VCC、GND、AO模拟输出、DO数字输出。在原理图里我把AO直接接到STM32的PA0引脚用ADC1的通道0来采集。MQ-2的模拟输出在洁净空气中大概0.1V到0.3V浓度越高电压越高接近5V。但这里有个细节MQ-2模块的AO输出范围是0到5V而STM32的ADC只能接受0到3.3V。很多新手直接接上去ADC采到的值永远只有4095就是因为没做电平匹配。解决方式有两种。一种是在AO输出和PA0之间加分压电阻用两个10K电阻串联分压把0到5V映射到0到2.5V这已经低于3.3V安全了。另一种更优雅的方式直接把MQ-2模块供电改为3.3V但这样传感器的加热丝电压不足灵敏度会明显下降我不推荐。所以原理图里我用了分压方案并且在分压节点加了100nF电容滤波抑制电源纹波对ADC精度的影响。火焰传感器这里用的是一个简单的红外火焰探测模块检测波长760nm到1100nm的红外光。模块输出DO数字信号有火焰时输出低电平。这个模块的接口设计有个容易踩的坑它内部比较器的参考电压是固定的不同环境下灵敏度差异巨大。所以我在原理图上额外预留了一个电位器接口用10K多圈精密电位器微调灵敏度这是实物调试时的关键调节手段。DHT11温湿度传感器用单总线协议数据引脚接PA1。原理图上务必在数据线和3.3V之间接一个4.7K上拉电阻否则时序不稳定读取经常失败。DHT11的供电我单独从3.3V取不和5V混在一起因为它的逻辑电平是3.3V标准如果供电5V再配上不正确的上拉信号可能读不到。2.2 电源与复位电路最容易被忽略的不稳定源这套系统的供电我设计了两个方案USB 5V供电和DC接口供电通过一个自恢复保险丝进入系统。5V进来之后分成两路一路直接给蜂鸣器、继电器、MQ-2加热丝供电另一路通过AMS1117-3.3稳压到3.3V供给STM32、OLED、DHT11和火焰传感器。这里要重点说一个坑继电器模块不能用STM32的GPIO直接驱动。继电器线圈工作电流在70mA左右而STM32单个GPIO最大输出能力只有20mA直接驱动轻则继电器不动作重则烧毁引脚。原理图里我加了S8050三极管做驱动GPIO接1K限流电阻到三极管基极继电器线圈接在三极管集电极和5V之间线圈两端并联一个1N4007续流二极管。这个续流二极管非常重要继电器断开瞬间线圈会产生反向电动势没有二极管的话这个高压尖峰很容易击穿三极管。复位电路用的是经典的10K上拉电阻加100nF电容到地NRST引脚在低电平时复位。虽然STM32内部有复位电路但外部RC电路能提高抗干扰能力特别是在实验室这种电机、加热设备启停频繁的电磁环境里外部复位电路更可靠。2.3 下载调试接口与扩展预留原理图里我还预留了三个非常实用的接口。首先是SWD下载接口只用SWDIO和SWCLK两根线加地线配合ST-Link就能下载和在线调试。不要用JTAG占用的引脚太多了SWD足够用。其次是串口接口USART1的TX和RX引出方便通过串口把采集到的数据发到上位机查看调试阈值时非常有用。第三是预留的IO口PA2和PA3引出来作为扩展接口后续如果要加Wi-Fi模块或者LoRa模块不需要改板子直接飞线接上就行。2.4 原理图绘制工具与封装检查经验原理图我是用嘉立创EDA画的免费、上手快、器件库全生成的BOM清单可以直接用来买元件。画图时有几个经验值得分享第一电源和地网络一定要用全局网络标号不要画长长的导线横穿整个图纸第二每个芯片的电源引脚旁边都加一个100nF去耦电容这比只在一个地方放大电容效果要好得多第三画完一定要做电气规则检查特别是有没有引脚悬空、网络命名冲突这类基础错误。封装检查这块是很多新手栽跟头的地方。我的建议是要用哪个封装就下单前把封装图打印成1:1拿实物芯片对比一下引脚间距。像MQ-2这种传感器模块它本身是插针引出封装反而简单。但DHT11有直插和贴片两种封装引脚顺序完全不同买零件之前一定要确认型号对应的封装。我自己就吃过这个亏画完PCB才发现DHT11买的是贴片版引脚定义和原理图对不上最后只能飞线解决。3. Proteus仿真不烧一片板子先把逻辑跑通3.1 仿真器件选型与元件库匹配Proteus仿真的第一步是解决元件库问题。STM32F103C8T6在Proteus里的型号是STM32F103C8可以直接在元件库搜到。MQ-2在Proteus里没有完全一致的模型通常用MQ-2这个气体传感器模型它有两个输出模拟电压和数字阈值。DHT11也有现成模型但要注意Proteus里的DHT11模型需要给它一个时钟信号才能正确输出温湿度数据不然读出来的永远是0。火焰传感器没有标准模型我用一个可变电阻分压电路代替模拟输出或者直接用按钮模拟数字电平翻转这完全够验证逻辑用。在Proteus里放置STM32之后第一件事不是连电路而是双击芯片设置固件文件。Proteus的老版本需要把编译好的HEX文件手动关联到芯片上新版本支持加载外部HEX文件。我用的流程是Keil编译生成HEX然后在Proteus里双击STM32芯片在Program File选项卡里选择这个HEX文件。这一步搞错了仿真跑起来芯片不会执行任何代码。3.2 传感器信号模拟ADC电压怎么注入仿真里最灵活的部分就是模拟传感器信号。MQ-2的模拟输出在仿真里可以直接用电位器来模拟一端接5V一端接地滑动端接STM32的PA0这样旋转电位器就能改变ADC输入电压模拟烟雾浓度从正常到超标的完整过程。DHT11的读数在仿真里是通过模型参数设置的右键点击DHT11器件在属性里可以修改温度和湿度的初始值。这样调试状态机时非常方便不用真的去加热或者点烟直接改参数就行。火焰传感器的数字信号在仿真里我用两种方式模拟一种是按钮按下接地表示检测到火焰另一种是信号发生器输出矩形波可以模拟火焰闪烁时信号抖动的情况。我强烈建议用按钮方式先跑通逻辑再用信号发生器测试火焰检测的抗抖动滤波逻辑因为真实的火焰传感器输出并不是稳定的低电平而是随着火焰跳动不断变化的如果主控程序里没有做消抖很容易出现误报或者漏报。3.3 仿真结果验证与边界测试仿真阶段我做了三个层次的功能测试。基础测试是确认每个传感器单独报警时蜂鸣器和LED的行为是否正确联动测试是确认当温度和烟雾同时超标时继电器是否正确吸合边界测试则是把阈值设在临界点附近观察是否有频繁翻转的临界抖动。边界测试尤其重要因为真实环境里信号不会像理想值一样稳定烟雾浓度可能在阈值附近来回波动。我在代码里引入了迟滞比较的思路报警阈值设为2.0V复位阈值设为1.8V也就是进入报警状态后需要电压降到1.8V以下才会解除报警。这个0.2V的迟滞窗口让系统不会因为信号微小的波动而频繁切换状态。在Proteus里我把电位器调到接近2.0V的位置然后微调电压观察系统是否稳定这个测试暴露了最初版本没有迟滞逻辑时的状态抖动问题修复后才算通过。3.4 仿真与实物之间的差距提醒仿真能验证逻辑但别天真地以为仿真通过实物就一定没问题。Proteus里MQ-2模型的响应曲线和真实传感器差异很大真实MQ-2在通上电之后需要预热几分钟输出电压才会稳定而模型瞬间就有输出。DHT11的时序在仿真里也偏理想化实物的单总线时序对延时非常敏感通常需要反复调整延时参数才能可靠读取。所以我的经验是仿真阶段重点验证状态机和阈值逻辑的正确性实物的传感器特性和时序问题在调试阶段单独处理。仿真能帮你省掉80%的逻辑调试时间剩下20%的硬件调优才是真正考验耐心的地方。4. 代码模块化设计与核心逻辑实现4.1 工程结构与模块划分代码组织我尽量做到了模块化每个人的工程习惯不同但我觉得这种划分方式最适合这种多传感器项目Core/ ├── main.c 主函数与状态机 ├── adc.c 烟雾ADC采集与滤波 ├── dht11.c 温湿度读取 ├── flame.c 火焰信号检测 ├── alarm.c 报警控制逻辑 ├── oled.c 显示驱动 └── relay.c 继电器控制每个模块只干一件事接口通过头文件暴露。比如adc.c只负责返回当前烟雾浓度对应的电压值不关心这个值该怎么判断alarm.c只负责接收传感器数据和报警状态不关心数据是怎么来的。这样一个模块出问题不会影响其他模块调试时也能快速定位。4.2 ADC采集与滤波为什么平均值滤波还不够烟雾传感器的ADC采集是这套系统里数据质量的关键。我最初用最简单的平均值滤波连续采10次取平均但实测效果很一般。原因在于MQ-2的输出噪声不是白噪声里面有低频漂移简单平均无法有效抑制。而且一旦出现瞬间尖峰平均值会被拉得很高造成误报。后来我改用中位值平均滤波也就是去掉最大值和最小值之后再对剩余数据取平均。这个算法对尖峰脉冲的抑制效果非常好代价只是多消耗一点CPU时间对STM32来说完全无所谓。核心代码如下uint16_t ADC_GetMedianAverageValue(void) { uint16_t buf[10]; uint16_t temp; int i, j; uint32_t sum 0; // 连续采集10次 for (i 0; i 10; i) { buf[i] ADC_ReadSingleChannel(); delay_ms(10); // 每次间隔10ms让采样点分散 } // 冒泡排序实际数据量小排序开销可忽略 for (i 0; i 9; i) { for (j i 1; j 10; j) { if (buf[j] buf[i]) { temp buf[i]; buf[i] buf[j]; buf[j] temp; } } } // 去掉最大值和最小值对中间8个值取平均 for (i 1; i 9; i) { sum buf[i]; } return (uint16_t)(sum / 8); }这里有个细节每次采样间隔10ms是刻意加的。ADC采样本身是微秒级的如果连续快速采10次采到的很可能是同一时刻的信号滤波效果等于没有。把采样点分散到100ms的时间窗口里才能反映真实信号的变化趋势。4.3 阈值判断与状态机设计状态机是整个控制逻辑的核心我设计了三种状态正常状态、预警状态和报警状态。转换条件如下状态触发条件动作正常温度50℃烟雾电压2.0V无火焰绿灯常亮无报警预警温度≥50℃ 或 烟雾电压≥2.0V黄灯闪烁蜂鸣器短鸣3次报警温度≥60℃ 且 烟雾电压2.5V或检测到火焰红灯闪烁蜂鸣器连续鸣叫继电器闭合设计成状态机而不是简单的if-else堆叠好处是状态转换的管理清晰。比如从报警状态恢复时需要同时满足温度降到55℃以下和烟雾电压降到1.8V以下而不是某一个条件满足就立刻恢复这个恢复迟滞逻辑在if-else里写容易混乱。核心状态机代码大致如下void FSM_Update(void) { switch (current_state) { case STATE_NORMAL: if (flame_detected || smoke_voltage 3.0f || temp_c 65.0f) { current_state STATE_ALARM; } else if (smoke_voltage 2.0f || temp_c 50.0f) { current_state STATE_WARNING; } break; case STATE_WARNING: if (flame_detected || smoke_voltage 3.0f || temp_c 65.0f) { current_state STATE_ALARM; } else if (smoke_voltage 1.8f temp_c 45.0f) { current_state STATE_NORMAL; } break; case STATE_ALARM: // 报警状态的解除需要满足更低的阈值形成迟滞 if (!flame_detected smoke_voltage 2.2f temp_c 55.0f) { current_state STATE_WARNING; } break; } }4.4 报警输出与联动逻辑报警输出模块相对简单但有三个实践经验值得说明。蜂鸣器使用有源蜂鸣器接在PB12引脚上通过NPN三极管驱动。有源蜂鸣器内部自带振荡源只要给高电平就会响程序里不需要生成PWM控制逻辑简化很多。无源蜂鸣器虽然音调可变但对这个项目没必要反而增加复杂度。LED指示用红绿双色共阴LED红色接PB13绿色接PB14。正常状态亮绿灯预警状态亮黄灯实际上是绿灯熄灭红灯闪烁利用视觉暂留效果报警状态红灯快速闪烁。用LED状态来指示系统工作状态比单纯的蜂鸣器更直观。继电器控制逻辑我加了一个安全设计只有在报警状态并且确认持续时间超过3秒后继电器才闭合。这个3秒的确认时间是为了防止瞬间的干扰信号导致排风扇或者电磁阀误动作。真实场景里消防联动设备一旦误动作可能会造成实验中断甚至是损失所以联动要保守报警可以果断。4.5 OLED显示与按键设置阈值0.96寸OLED用的是SSD1306驱动芯片I2C接口接到STM32的I2C1上。显示内容分为三块第一行显示烟雾电压值和状态图标第二行显示温度和湿度第三行显示当前状态文字和继电器状态。刷新频率设为500ms太快没必要还增加CPU负担。阈值参数我在代码里做了宏定义默认值如下#define SMOKE_WARNING_THRESHOLD 2.0f // 烟雾预警阈值 (V) #define SMOKE_ALARM_THRESHOLD 2.5f // 烟雾报警阈值 (V) #define TEMP_WARNING_THRESHOLD 50.0f // 温度预警阈值 (°C) #define TEMP_ALARM_THRESHOLD 60.0f // 温度报警阈值 (°C)如果不想改代码重新编译我预留了三个按键接在PB0、PB1、PB10可以在运行时调整预警阈值。长按按键进入阈值设置模式用OLED显示当前值短按加减再长按确认退出。这个功能在实物调试阶段非常有用不用每次调阈值都重新烧录程序。5. 实物调试记录误报、漂移和阈值校准5.1 烟雾传感器上电漂移问题实物调试第一个遇到的坑就是MQ-2的预热漂移。刚上电时MQ-2的内部加热丝需要时间把敏感层加热到工作温度这个过程中输出电压会从高到低逐渐变化有时候需要3到5分钟才能稳定。如果系统上电后立刻用初始值做判断很大概率会误触发报警。解决办法是在代码里增加一个初始化校准过程系统上电后前10秒不进行报警判断只采集数据记录当前环境的基线电压值之后所有的判断都用当前电压减去基线电压这个差值来执行。简单说就是做了一次自动零点校准。这个改动看似简单却直接把上电误报率降到了零。5.2 火焰传感器的方向性与干扰光火焰传感器的方向性很强它的红外接收管正前方灵敏度最高侧面基本无响应。安装时要注意朝向应该覆盖实验室的主要风险区域比如加热设备或者试剂架。测试时我用打火机在30cm距离处实验传感器正面朝火源时响应灵敏但稍微偏一点角度就可能漏报。另一个问题是红外干扰光。白炽灯、太阳光中的红外成分都会引起误报尤其是阳光直射的时候传感器可能一直输出有火焰信号。我这个模块本身不带滤光片所以比较怕强光干扰。解决策略有两个硬件上在传感器前面加红外滤光片只让火焰特征波段的红外光通过软件上增加一个确认机制连续检测到火焰信号超过1秒才确认为真实火焰瞬间的信号尖峰直接忽略。5.3 阈值应该怎么设分区分级策略阈值设置不是拍脑袋定的我总结了一套实用的方法论。先让系统在实验室正常环境下运行半天记录烟雾传感器的基线电压波动范围。我的实测结果是正常环境下烟雾传感器基线电压在0.15V到0.35V之间波动偶尔有0.5V的瞬态尖峰。所以预警阈值设在2.0V是比较合理的这个电压对应的烟雾浓度已经明显高于正常环境但又没到剧烈燃烧的程度。报警阈值2.5V则对应更明显的烟雾浓度。温度阈值方面实验室正常温度在25到30度之间我把预警阈值设为50度这个温度已经远超正常环境但又低于危险范围。报警阈值60度意味着环境已经异常升温很有可能是设备过热或者初期火灾。这里要特别提醒的是每个实验室的环境底噪不一样。如果你的实验室通风条件差或者经常有酒精挥发烟雾传感器基线可能就是0.5V甚至更高这时候阈值要相应抬升。最好的办法就是把自动校准得到的基线电压值在OLED上显示出来调试时观察一段时间再确定阈值。5.4 联动测试与功耗考量继电器联动测试我做了两种模式。模式一是直接带负载测试接一个小风扇模拟排烟设备报警时风扇启动预警解除后风扇停止。模式二是模拟真实场景用酒精灯产生烟雾测试烟雾传感器从响应到报警再到继电器闭合的完整链路。实测下来从烟雾浓度超标到继电器动作大约需要3秒其中1秒是传感器响应时间2秒是软件确认时间。这个响应速度对实验室场景来说完全够用初期火灾不会在三秒内就失控。功耗方面整个系统在正常状态下大约120mA其中MQ-2加热丝占了将近100mASTM32和OLED加起来不到30mA。如果实验室供电不稳定建议用5V 1A以上的电源适配器不要用USB口的手机充电器勉强供电因为继电器吸合瞬间会有电流尖峰供电不足会导致STM32复位。6. 开源资料复用指南拿到代码原理图仿真怎么上手6.1 文件目录与复现顺序开源包里我按照使用顺序整理了文件结构。拿到资料后第一步不要急着打开工程文件先看根目录的说明文档里面写了硬件连接表和引脚分配。第二步打开原理图PDF对照引脚分配表理解每个模块的连接关系。第三步打开Proteus仿真工程加载HEX文件跑起来。第四步再打开Keil工程看代码。这个顺序能让你从整体到局部逐步理解不会一头扎进代码细节出不来。引脚分配表是经过反复确认的列出核心部分如下模块引脚STM32引脚说明MQ-2 AOPA0ADC1_IN0烟雾模拟电压DHT11 DATAPA1GPIO输入单总线数据火焰传感器 DOPA2GPIO输入低电平有效蜂鸣器PB12GPIO输出高电平鸣叫红色LEDPB13GPIO输出报警指示绿色LEDPB14GPIO输出正常指示继电器PB15GPIO输出通过三极管驱动OLED SCLPB6I2C1_SCLI2C时钟OLED SDAPB7I2C1_SDAI2C数据按键1/2/3PB0/PB1/PB10GPIO输入阈值调节6.2 从仿真到实物的移植步骤如果你打算照着实物的方式做一套我的建议是分四步走。第一步按照引脚分配表把核心模块接好先不接传感器只烧录一个测试程序确认OLED能正常显示蜂鸣器和LED能控制。第二步单独接DHT11读取温湿度数据并和手机天气App对比确认数值合理。第三步接烟雾传感器和火焰传感器用打火机和酒精蒸汽做测试调整阈值。第四步接继电器和联动设备做完整的联动测试。每一步都要验证通过再进行下一步不要一次性把全部模块接上再调试出了问题很难定位。移植过程中最常见的坑是传感器模块的供电电压不一致。MQ-2模块要5V供电才能保证加热效率火焰传感器和DHT11我用3.3V供电OLED也用3.3V。如果全部接5VOLED和DHT11大概率会冒烟。如果全部接3.3VMQ-2灵敏度会下降。所以供电分配一定要按照原理图来不能图省事统一电压。6.3 二次开发思路加远程报警和节点扩展这套系统本身是一个很好的基础平台二次开发的方向很多。我目前正在做的扩展方向是加ESP8266 WiFi模块把报警状态实时推送到手机。思路很简单串口1和ESP8266对接STM32在状态发生变化时通过AT指令发送一个HTTP请求调用Server酱或者钉钉机器人Webhook实现手机推送。这样人在办公室实验室里出事了也能第一时间知道。另一个方向是组网扩展。用RS485总线把多套预警节点接到一个总控上每个节点负责一个实验室总控汇总所有节点的状态。STM32F103C8T6本身有USART支持RS485通信再加一个MAX485芯片就能实现。这种方案对于整栋实验楼的安全管理来说比部署传统消防主机更灵活成本也更低。还有一个更细节的优化点给系统加一个自检功能。每次上电时自动检查传感器是否在线比如DHT11读不到数据或者烟雾传感器输出恒定为0就在OLED上提示传感器故障。实验室设备管理人员能通过这个功能快速判断是哪一路传感器坏了不用打开外壳逐个排查。整个项目做下来我最深的体会是消防预警系统的价值不在控制器本身而在传感器的选型、布局和阈值校准这些细节上。STM32只是把逻辑跑起来真正决定系统靠不靠谱的是你能不能理解传感器在真实环境中的表现。这也是为什么我把原理图、仿真和代码全部开源出来的原因——任何一个环节缺失这套资料的价值都会大打折扣。如果你打算在自己的实验室部署这套系统我建议你重点花时间在阈值校准和传感器位置调整上这两项工作做到位系统的可靠性会远超预期。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进