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 Uno(ATmega328P)开发快,但同样存在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:数据脚接PA0,VCC接3.3V,GND接地。数据脚需要接一个4.7kΩ上拉电阻到3.3V。
- MQ-2:模拟输出接PA1(ADC1_IN1),VCC接5V,GND接地。模块自带电位器可以调节灵敏度。
- 火焰传感器:数字输出接PA2,VCC接3.3V,GND接地。
- 继电器模块:控制脚接PA3,VCC接5V,GND接地。
- OLED屏幕(SSD1306,I2C接口):SCL接PB6,SDA接PB7,VCC接3.3V,GND接地。
- 蜂鸣器:接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~7,PA1就是ADC1_IN1。在代码里配置ADC时要对应好通道号,否则读出来的数据是错的。
继电器的控制逻辑要确认。市面上很多继电器模块是低电平触发,也就是控制脚给低电平时继电器吸合。但也有一些是高电平触发。画原理图时不用管这个,但在代码里要定义好宏,方便切换。我用的模块是高电平触发,所以代码里RELAY_ON定义为GPIO_SetBits。
OLED的I2C地址。SSD1306的I2C地址通常是0x78(7位地址)或0x3C(8位地址左移一位)。不同厂家的模块可能不一样,画图时不用管,但写代码时要确认。我用的模块地址是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和传感器。调试时先用串口打印数据,确认每个传感器都能正常读数,再调状态机逻辑。
我在实际调试中最大的体会是:不要相信任何一个传感器的单次读数。温度、烟雾、火焰,任何一个传感器都可能因为干扰、老化、环境变化而出现异常值。只有通过多传感器交叉验证、滑动滤波、状态机迟滞这些手段,才能做出一个误报率低、可靠性高的预警系统。这套代码我前后改了三个版本,第一版误报频繁,第二版响应太慢,第三版才在灵敏度和可靠性之间找到平衡。如果你也在做类似的项目,建议把状态机的参数做成可配置的宏定义,方便根据实际环境调整。