ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于STM32的多传感器实验室消防预警系统设计与实现

2026/9/27 10:40:57 拓冰建站 浏览量
基于STM32的多传感器实验室消防预警系统设计与实现 1. 为什么我要用STM32做一套实验室消防预警系统实验室这个场景跟普通的办公室、住宅有本质区别。普通场所着火大概率是电线老化或者明火引燃实验室里可能同时存在酒精灯、乙醚、氢气钢瓶、锂电池充放电测试台甚至还有学生半夜跑数据忘了关的加热台。火源类型复杂燃烧速度可能极快而且很多试剂燃烧产生的烟雾是有毒的。等烟大到人眼能看见再报警基本已经晚了。我读研那会儿隔壁材料实验室就出过一次小事故——一台老化测试箱的温控模块失效内部温度从80度飙到200多度塑料外壳开始冒烟好在当时有人通宵做实验闻到了味道。事后复盘大家一致认为如果有一套能同时监测温度、烟雾浓度、并且能在异常时自动断电声光报警远程通知的系统风险会小很多。这就是这个项目的出发点。整套系统基于STM32F103C8T6最小系统板搭建核心功能包括多传感器融合检测DHT11采集环境温湿度MQ-2烟雾传感器检测可燃气体和烟雾浓度火焰传感器作为辅助确认。分级预警逻辑不是简单的“超阈值就响”而是根据温度变化速率、烟雾浓度绝对值、以及两者是否同时异常来做分级判断减少误报。自动断电控制通过继电器模块控制实验台插座电源确认火情后直接切断供电避免电气火灾扩大。声光报警与本地显示蜂鸣器LED闪烁OLED屏幕实时显示各项数据。仿真验证在Proteus里完成电路仿真验证逻辑正确后再打板焊接。关键词里提到的“原理图”“仿真”“代码”三件套我都会在下面逐一展开。这套东西适合电子类专业的学生做课程设计或毕业设计也适合实验室安全管理人员做低成本改造参考。整套BOM成本控制在80元以内代码全部开源。注意消防预警系统属于安全相关设备本文分享的是教学和原型验证级别的方案不能直接替代经过消防认证的专业设备。实际部署请务必以专业消防系统为准。2. 硬件选型为什么是STM32F103C8T6而不是别的2.1 主控芯片的取舍逻辑市面上做这类项目常见的主控选择有51单片机、Arduino、STM32F103、ESP32这几种。我选STM32F103C8T6的理由很具体51单片机比如STC89C52RC确实便宜但它的ADC精度只有8位DHT11的时序对晶振频率敏感51的机器周期导致微秒级延时不好做读温湿度经常出错。而且51的RAM太小想跑一个稍微复杂点的分级预警状态机就很吃力。Arduino UnoATmega328P开发快但同样存在ADC位数和RAM的限制而且成本比STM32最小系统板还高。最关键的是Arduino的生态偏向快速原型工业场景下大家更认STM32。ESP32功能强自带WiFi和蓝牙但功耗偏高而且对于这个项目来说WiFi不是必须的——实验室环境不一定有可用的无线网络而且无线模块会增加调试复杂度。如果后期确实需要远程通知加一个ESP-01S模块通过串口通信就行没必要一开始就上ESP32。STM32F103C8T6的优势在于72MHz主频、64KB Flash、20KB RAM、12位ADC、多个定时器和USART接口价格只要10块钱左右。它的定时器可以精确产生微秒级延时读DHT11毫无压力12位ADC读MQ-2的模拟输出足够细腻USART可以接蓝牙模块做调试输出。这个配置做消防预警系统绰绰有余而且资料多、社区活跃遇到问题容易找到答案。2.2 传感器选型的实际考量DHT11是入门级温湿度传感器精度±2℃、±5%RH采样周期1秒。有人会问为什么不用DS18B20或者SHT30。DS18B20是单总线数字传感器精度更高±0.5℃但它只测温度不测湿度。SHT30精度好、I2C接口但价格是DHT11的5倍以上。对于消防预警来说温度的绝对精度不是最关键的温度变化的趋势和速率才是判断火情的重要依据。DHT11的1℃分辨率足够捕捉到异常升温。MQ-2是半导体式可燃气体传感器对液化气、丙烷、氢气、烟雾都有响应。它的输出是模拟电压浓度越高电压越高。需要注意的是MQ-2需要预热——冷启动时读数不稳定通常需要预热20秒以上才能得到可靠数据。这一点在代码里必须处理否则上电初期会误报。火焰传感器我选的是那种带比较器输出的模块检测到火焰时输出低电平。它的检测角度大约60度有效距离1米左右。这个传感器只能作为辅助确认不能单独作为报警依据因为它对非火焰的强光源也可能有反应。2.3 继电器与电源方案继电器模块选的是5V驱动的单路继电器触点容量10A/250VAC。实验台插座的火线串进继电器的常闭触点正常情况下继电器不吸合插座有电报警触发后STM32输出高电平让继电器吸合常闭触点断开插座断电。这里有个安全设计细节我用的是常闭触点而不是常开触点。原因是如果系统本身断电了比如STM32死机或者电源被拔继电器失电常闭触点保持闭合插座仍然有电——这看起来好像不安全但实际上如果系统完全断电说明整个预警系统已经失效此时保持插座供电反而不会造成“系统误动作导致实验中断”的问题。而如果火情确认后需要断电STM32主动吸合继电器即可。这个逻辑在代码里要配合状态机来设计。电源方面STM32最小系统板通过USB供电或者外部5V适配器供电传感器和继电器都从5V取电。MQ-2的加热丝电流大约150mA继电器吸合电流大约70mA加上STM32本身和其他传感器总电流在300mA左右一个5V/2A的适配器完全够用。3. 原理图设计从模块连接到嘉立创画图实操3.1 整体连接框架原理图设计我是在嘉立创EDA里完成的也可以用Altium Designer或者OrCAD。整体连接关系如下STM32F103C8T6最小系统板作为核心引出5V、3.3V、GND、以及各个GPIO。DHT11数据脚接PA0VCC接3.3VGND接地。数据脚需要接一个4.7kΩ上拉电阻到3.3V。MQ-2模拟输出接PA1ADC1_IN1VCC接5VGND接地。模块自带电位器可以调节灵敏度。火焰传感器数字输出接PA2VCC接3.3VGND接地。继电器模块控制脚接PA3VCC接5VGND接地。OLED屏幕SSD1306I2C接口SCL接PB6SDA接PB7VCC接3.3VGND接地。蜂鸣器接PA4通过一个NPN三极管S8050驱动基极串1kΩ电阻。LED指示灯绿色LED接PA5正常状态红色LED接PA6报警状态各串220Ω限流电阻。3.2 画图时的几个关键细节DHT11的上拉电阻不能省。DHT11的数据线是开漏输出没有上拉电阻的话总线空闲时无法拉高STM32读到的全是0。我一开始在面包板上搭电路时忘了接上拉调试了半天以为是时序问题后来用示波器看波形才发现数据线一直是低电平。这个坑很典型画原理图时一定要把上拉电阻画上。MQ-2的模拟输出要接在STM32的ADC通道上。STM32F103C8T6的PA0~PA7对应ADC1的通道0~7PA1就是ADC1_IN1。在代码里配置ADC时要对应好通道号否则读出来的数据是错的。继电器的控制逻辑要确认。市面上很多继电器模块是低电平触发也就是控制脚给低电平时继电器吸合。但也有一些是高电平触发。画原理图时不用管这个但在代码里要定义好宏方便切换。我用的模块是高电平触发所以代码里RELAY_ON定义为GPIO_SetBits。OLED的I2C地址。SSD1306的I2C地址通常是0x787位地址或0x3C8位地址左移一位。不同厂家的模块可能不一样画图时不用管但写代码时要确认。我用的模块地址是0x78。电源去耦。每个芯片的VCC和GND之间都要加0.1μF的陶瓷电容MQ-2和继电器模块的电源脚附近再加一个100μF的电解电容防止继电器吸合时电流突变导致STM32复位。3.3 从原理图到PCB的注意事项画完原理图后生成PCB时要注意几点MQ-2的加热丝电流较大走线宽度至少20mil不要用细线。继电器的强电部分和弱电部分要隔离PCB上强弱电之间保持至少3mm的爬电距离。晶振尽量靠近STM32走线短而直下面不要走其他信号线。ADC输入线远离数字信号线避免数字噪声耦合到模拟信号上。如果只是做课程设计或者验证可以直接用最小系统板模块的方式在洞洞板上焊接不一定要打PCB。但如果是毕业设计需要展示完整的作品建议打一版PCB看起来更专业。4. 代码架构分级预警状态机是怎么跑起来的4.1 主循环与定时器节拍整个代码基于一个1ms的定时器节拍来调度。我用的是TIM2配置为1ms中断一次。在中断里维护几个计数器volatile uint32_t tick_1ms 0; volatile uint8_t flag_1s 0; volatile uint8_t flag_5s 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); tick_1ms; if (tick_1ms % 1000 0) flag_1s 1; if (tick_1ms % 5000 0) flag_5s 1; } }主循环里根据这些标志位来调度任务每1秒读一次DHT11和MQ-2每5秒更新一次OLED显示每100ms检查一次火焰传感器。这样做的目的是避免在主循环里用delay阻塞让系统能及时响应火焰传感器的中断信号。4.2 DHT11的读取时序与容错DHT11是单总线协议时序要求比较严格。STM32的主频是72MHz一个机器周期约13.9ns用delay_us函数可以精确控制微秒级延时。读取流程是STM32拉低数据线至少18ms然后拉高20-40μs等待DHT11响应。DHT11拉低80μs再拉高80μs表示数据即将开始。之后每一位数据以50μs低电平开始高电平持续26-28μs表示0持续70μs表示1。代码里我用了一个简单的状态机来读取40位数据8位湿度整数8位湿度小数8位温度整数8位温度小数8位校验和。校验和等于前四个字节之和的低8位。容错处理很关键。DHT11偶尔会读失败返回全0或者校验错误。我的做法是连续读3次如果3次都失败才认为传感器故障在OLED上显示“DHT ERR”。如果只是偶尔一次失败就沿用上一次的有效数据。这样避免了因为一次读取失败就触发误报。4.3 MQ-2的ADC采样与滑动滤波MQ-2的输出电压随烟雾浓度升高而升高。STM32的ADC是12位的参考电压3.3V所以ADC值0~4095对应0~3.3V。我用了滑动平均滤波维护一个长度为10的数组每次采样后替换最旧的数据然后求平均值。#define FILTER_LEN 10 uint16_t mq2_buf[FILTER_LEN] {0}; uint8_t mq2_idx 0; uint16_t mq2_get_filtered(void) { mq2_buf[mq2_idx] ADC_GetConversionValue(ADC1); mq2_idx (mq2_idx 1) % FILTER_LEN; uint32_t sum 0; for (int i 0; i FILTER_LEN; i) sum mq2_buf[i]; return sum / FILTER_LEN; }滤波之后还要做基线校准。上电后前20秒MQ-2处于预热阶段读数会从高到低变化。我在代码里让前20秒不进行报警判断同时记录这20秒内的最低值作为基线。之后的报警阈值设为基线值加上一个偏移量比如300而不是用一个固定的绝对值。这样能适应不同环境下的传感器差异。4.4 分级预警状态机的设计这是整个代码的核心。我把系统状态分为四级状态触发条件动作NORMAL温度40℃且烟雾阈值绿灯亮蜂鸣器不响WARNING温度40~60℃或烟雾超阈值黄灯闪烁蜂鸣器间歇响ALARM温度60℃或烟雾严重超标红灯亮蜂鸣器长响继电器断电FIRE火焰传感器触发且温度50℃红灯快闪蜂鸣器长响继电器断电状态之间的切换不是瞬时的而是带有迟滞和确认时间。比如从NORMAL到WARNING需要连续3次采样都满足条件才切换从WARNING回到NORMAL需要连续5次采样都正常才恢复。这样避免了数据抖动导致的频繁切换。火焰传感器的响应是中断方式一旦触发立即进入FIRE状态但会先确认温度是否也异常。如果火焰传感器触发但温度正常可能是强光干扰系统会进入WARNING而不是FIRE。4.5 继电器控制的安全逻辑继电器控制我加了一个双重确认机制只有当状态机进入ALARM或FIRE并且持续超过2秒才真正吸合继电器断电。这样做是为了防止瞬时干扰导致误断电。毕竟实验室里跑着实验突然断电可能造成数据丢失或者样品损坏。另外代码里还加了一个手动复位功能按下连接在PB0的按键可以强制将状态机复位到NORMAL继电器恢复常闭。这个功能是给实验人员确认安全后手动恢复供电用的。5. Proteus仿真在没有硬件的情况下验证逻辑5.1 仿真环境的搭建Proteus 8.9以上版本支持STM32F103C8T6的仿真。搭建步骤在Proteus里新建工程放置STM32F103C8T6芯片。添加DHT11、MQ-2、OLED、继电器、LED、蜂鸣器等元件。Proteus的元件库里有DHT11和SSD1306的模型MQ-2可以用一个电位器模拟模拟输出。连接电路和原理图一致。加载编译好的hex文件设置晶振频率为8MHz外部晶振或72MHz内部PLL。5.2 仿真中的几个坑DHT11的仿真模型响应速度。Proteus里的DHT11模型不是实时的它的温湿度值需要在属性里手动设置或者通过脚本动态修改。我一开始以为它会像真实传感器一样自动变化结果仿真跑起来读数一直不变。后来在DHT11的属性里设置了初始值并且用Proteus的脚本功能定时修改才模拟出温度上升的场景。MQ-2的模拟输出。Proteus里没有MQ-2的专用模型我用了一个电位器分压来模拟。电位器的中间抽头接STM32的PA1通过调整电位器来改变电压模拟烟雾浓度的变化。这个方法虽然简单但足够验证ADC采样和报警逻辑。OLED的显示。Proteus里的SSD1306模型可以显示I2C通信的内容但刷新速度比真实硬件慢。仿真时不要频繁刷新OLED否则会拖慢整个仿真速度。我设置的是每5秒刷新一次仿真跑起来还算流畅。继电器的仿真。Proteus里的继电器模型有吸合时间默认是10ms左右。仿真时可以看到继电器状态的变化但听不到声音。蜂鸣器可以用一个LED代替来观察状态。5.3 仿真验证的测试用例我在仿真里设计了几个测试场景场景一温度从25℃缓慢上升到70℃观察状态机是否按NORMAL→WARNING→ALARM的顺序切换继电器是否在ALARM状态持续2秒后吸合。场景二温度正常但MQ-2电压突然升高到3V观察是否进入WARNING以及是否在持续超标后进入ALARM。场景三火焰传感器触发但温度只有30℃观察是否进入WARNING而不是FIRE。场景四报警后按下复位按键观察状态机是否回到NORMAL继电器是否恢复。这四个场景跑通后基本可以确认逻辑没有问题。仿真通过后再打板焊接成功率会高很多。6. 调试过程中踩过的坑和解决方法6.1 DHT11读数一直是0这个问题我遇到过两次。第一次是因为上拉电阻没接数据线无法拉高。第二次是因为延时函数不准确——我用的delay_us是基于SysTick的但SysTick的配置被其他库函数修改了导致延时偏短。后来我改用TIM2做微秒延时问题解决。排查方法用示波器或者逻辑分析仪看数据线的波形。正常情况应该能看到STM32拉低18ms、拉高20μs、DHT11响应拉低80μs的波形。如果波形不对就是时序问题如果数据线一直是低电平就是上拉电阻的问题。6.2 MQ-2上电后一直报警MQ-2冷启动时加热丝从室温升到工作温度需要时间这期间传感器的输出电阻不稳定读数会偏高。我一开始没做预热处理上电后MQ-2的ADC值直接超过阈值系统进入ALARM状态。解决方法在代码里加一个20秒的预热期前20秒不进行报警判断同时用这段时间采集基线值。预热期结束后用基线值偏移量作为动态阈值。6.3 继电器吸合导致STM32复位这个问题很典型。继电器吸合瞬间电流突变如果电源滤波不好5V电压会瞬间跌落导致STM32复位。我一开始以为是代码问题后来用示波器看5V电源线发现继电器吸合时电压从5V跌到3.8V持续了大约5ms。解决方法在继电器模块的电源脚附近加一个100μF的电解电容和一个0.1μF的陶瓷电容同时在STM32的电源脚也加0.1μF去耦电容。另外继电器的控制信号线和电源线分开走避免耦合。6.4 OLED显示乱码OLED显示乱码通常是I2C通信问题。我遇到的原因是I2C的时钟频率太高SSD1306跟不上。STM32的I2C默认是100kHz但我配置成了400kHz导致数据传输出错。解决方法把I2C时钟降到100kHz或者在每次发送数据后加一个小延时。另外SSD1306的初始化序列要严格按照数据手册来特别是对比度、扫描方向、显示模式这些寄存器。6.5 火焰传感器误触发火焰传感器对打火机的火焰很敏感但对日光灯、白炽灯也可能有反应。我在实验室测试时发现用手机闪光灯照一下传感器它也会触发。解决方法在代码里加确认逻辑——火焰传感器触发后先检查温度是否也异常。如果温度正常只进入WARNING状态不触发断电。同时火焰传感器的数字输出可以加一个RC滤波减少瞬时干扰。7. 这套系统还能怎么扩展7.1 加蓝牙模块做远程通知STM32F103C8T6有两个USART其中一个用来烧录程序另一个可以接HC-05蓝牙模块。报警时通过蓝牙发送一条消息到手机配合手机端的串口助手APP就能实现远程通知。代码里只需要在状态机切换时调用USART_SendString发送预设的消息即可。7.2 加SD卡模块做数据记录实验室事故调查往往需要回溯数据。加一个SD卡模块通过SPI接口每隔1分钟把温度、湿度、烟雾浓度写入CSV文件。这样即使系统没有联网也能在事后分析数据。SD卡模块很便宜代码也不复杂用FatFS文件系统就能实现。7.3 多节点组网一个实验室可能有多个实验台每个实验台放一个预警节点通过RS485总线或者CAN总线连接到中央监控屏。STM32F103C8T6自带CAN控制器加一个CAN收发器比如TJA1050就能组网。中央节点用一个STM32触摸屏显示所有节点的状态。7.4 低功耗改造如果实验室没有常电供应可以用电池供电。STM32F103C8T6有睡眠模式DHT11和MQ-2也可以间歇供电。不过MQ-2的加热丝功耗较大低功耗改造需要换用低功耗的烟雾传感器比如MQ-7或者专用的光电式烟雾传感器。8. 开源资料说明与复现建议整套资料包括原理图嘉立创EDA格式包含STM32最小系统、传感器接口、继电器驱动、OLED接口。代码Keil MDK工程基于标准外设库包含DHT11驱动、MQ-2 ADC采样、OLED驱动、状态机逻辑。仿真Proteus工程文件包含电路和测试脚本。BOM清单所有元器件的型号、数量、参考价格。复现建议先跑仿真确认逻辑正确后再买硬件。硬件焊接时先焊电源部分测好5V和3.3V电压后再焊STM32和传感器。调试时先用串口打印数据确认每个传感器都能正常读数再调状态机逻辑。我在实际调试中最大的体会是不要相信任何一个传感器的单次读数。温度、烟雾、火焰任何一个传感器都可能因为干扰、老化、环境变化而出现异常值。只有通过多传感器交叉验证、滑动滤波、状态机迟滞这些手段才能做出一个误报率低、可靠性高的预警系统。这套代码我前后改了三个版本第一版误报频繁第二版响应太慢第三版才在灵敏度和可靠性之间找到平衡。如果你也在做类似的项目建议把状态机的参数做成可配置的宏定义方便根据实际环境调整。