拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32多路火灾报警系统设计:从传感器选型到去抖滤波的工程复盘

STM32多路火灾报警系统设计:从传感器选型到去抖滤波的工程复盘

入秋之后帮人参谋了一个毕业设计项目:基于STM32的多路火灾报警系统。这标题在嵌入式和自动化类选题里属于常青树,每年都能见到好几个版本。但说实话,大部分交上来的方案只做到了“能响”——单片机检测到一个模拟量超过阈值,就推高蜂鸣器,演示流程走一遍就结束。实际做一套真正可用的多路火灾报警系统,远不是点个LED那么简单,从传感器选型、信号调理、电源隔离,到软件层面的去抖、滤波、迟滞判断,每一环都有能让人熬夜的坑。

这套系统的核心价值在于“多路”和“可靠”。单个烟雾报警器覆盖范围有限,厨房的水蒸气、客厅的香烟、卧室的空调热风会让单点传感器疯狂误报;而真正火灾初期的高温、浓烟特征,又可能因为传感器安装位置不当而漏报。STM32的多路ADC采集、多组GPIO控制、定时器调度能力,配合合理的外围电路,可以在一颗主控上完成多区域并行监控、智能判定、联动控制和故障自检。我这次就来完整复盘一套可落地的设计,侧重讲清楚那些文档里不会写明的取舍和实测经验。

1. 需求拆解与选型逻辑:为什么是多路,为什么是STM32

1.1 单点报警的天然缺陷

市面上一百多块的独立烟雾报警器,本质是一个MQ-2或离子式传感器接一个电压比较器,浓度超了就响。这类设备的误报率有多高,用过的都知道:厨房煎个牛排,警报响了;浴室的蒸汽散不出去,也响了。原因在于单传感器单阈值方案无法区分“烟雾浓度高”和“确实着火了”,更无法定位火源在哪个区域。

多路方案解决的问题有两个:空间覆盖和区域定位。在实验室、仓库、居民楼这类场景,四个典型监测点(比如厨房、卧室、客厅、楼道)分别布置烟雾、温度或火焰传感器,集中接入一台STM32主机。哪一路报的警,主机通过数码管或OLED显示号位,同时控制对应区域的声光报警器和联动阀门。这个逻辑用PLC能做,用51单片机勉强能做,但论外设丰富度、开发效率、资料成熟度,STM32是舒适区。

1.2 STM32在火灾报警场景的不可替代性

选STM32不是因为它跑得快,而是因为它在“中等集成度+低功耗+丰富外设”这个交叉点上太合适。

  • 多通道ADC:STM32F103系列有十几个ADC通道,完全可以覆盖8路以内的模拟量传感器,不需要外扩模拟开关或独立ADC芯片。
  • 多组定时器:一路定时器做系统心跳,一路做ADC触发采样,一路留作看门狗喂狗,调度不打架。
  • 高电平驱动能力:GPIO直接配合三极管或MOS管驱动蜂鸣器、继电器,省去额外扩展芯片。
  • 开发资料成熟:标准库和HAL库的例程铺天盖地,CubeMX图形化配置外设,学生和工程师都能快速上手,踩坑时有大量前车之鉴可查。

需要注意一点,STM32F103C8T6这种小容量芯片的Flash只有64KB,代码量控制不好容易爆。做这类报警系统,我建议全程用HAL库但不开CubeMX生成所有中间层代码,用不到的外设别初始化,编译优化选Level 2,能省出不少空间。

2. 硬件电路设计:传感器接口、电源与驱动的完整方案

硬件是整个报警系统最容易出“隐性问题”的地方。软件写错了顶多跑飞重启,硬件布线不合理会导致传感器数据漂得你怀疑人生。

2.1 传感器选型:烟感、温感、火焰检测各司其职

多路火灾报警不能全用同一种传感器,不同火灾阶段的物理特征不一样,传感器互补才是正解。

传感器类型检测对象输出信号响应速度典型型号
烟雾传感器早期阴燃产生的烟雾颗粒模拟电压(随浓度升高)慢,需预热MQ-2、MQ-7
温度传感器环境温度异常升高NTC分压/数字单总线中等NTC 10K、DS18B20
火焰传感器火焰发出的红外/紫外波段数字高低电平快红外火焰模块

MQ-2烟雾传感器是这类设计的绝对主力,它内部有一个加热电阻和二氧化锡气敏材料,遇到可燃气体或烟雾时电导率变化,输出电压随之变化。需要注意的是MQ-2模块有两种输出:数字量DO和模拟量AO。很多新手直接接DO口进GPIO,以为拧到灵敏度电位器就能用,实际测下来DO阈值的重复性很差,温度漂移严重。正确做法是读AO模拟量,在软件里做阈值判断。

NTC热敏电阻作为温度传感器,成本几毛钱,一个10K电阻分压就能工作。火灾报警场景不需要高精度体温计那样的分辨率,NTC足够。

火焰红外传感器模块输出的是比较器后的数字信号,低电平表示检测到火焰。它的响应速度极快,几毫秒就能拉低电平,适合做快速联动(比如第一时间切断燃气阀)。缺点是容易受阳光、白炽灯中的红外成分干扰,需要调整模块上的阈值电位器,配合遮光筒使用。

2.2 模拟信号采集电路:分压和滤波是基本功

STM32的ADC输入范围是0到3.3V,而MQ-2模块的AO输出范围是0到5V,直接接上去会烧引脚。正确做法是电阻分压,我用的是10K和20K串联,输出端对GND接20K,分压比例1/3,满量程5V降到3.33V,正好卡在ADC量程边缘。

NTC测温电路同样用电阻分压:10K NTC串联10K精密电阻,中间节点接ADC输入。温度升高时NTC阻值下降,分压值降低,软件端通过公式或查表换算温度。公式法用Steinhart-Hart方程,查表法要把NTC分度表做成数组,工程上查表更稳定,不依赖浮点运算。

硬性建议:**每一路模拟输入到ADC引脚之间串一个100欧到1K的电阻,再接一个0.1uF电容到GND,组成RC低通滤波。**这能滤掉传感器输出上的高频噪声和50Hz工频干扰。第一次做的时候我图省事没加,ADC采样用DMA循环采了100次做平均,数据依然在±50mV范围跳,后来加了104电容立刻干净了。

2.3 声光报警和联动控制驱动电路

报警输出我用了两路:一路蜂鸣器,一路继电器控制电磁阀或风机。

蜂鸣器驱动用有源蜂鸣器最简单,内部自带震荡源,通电就响。PB12引脚通过1K限流电阻接S8050三极管基极,发射极接地,集电极接蜂鸣器负极,蜂鸣器正极接5V电源。三极管饱和导通时集电极电流约30mA,完全带得动小蜂鸣器。如果要用无源蜂鸣器,需要TIM输出PWM驱动,音调可变,但代码复杂度上去了,报警场景没必要。

继电器驱动同样用三极管,但必须加续流二极管。继电器线圈是感性负载,断电瞬间会产生反向感应电动势,没有二极管泄放的话,这个尖峰能打穿三极管,甚至沿着电源线干扰MCU,导致瞬间复位。1N4007或1N4148反向并联在线圈两端,极性别接反。

注意继电器电源和传感器电源尽量分开走线。MQ-2传感器的加热丝工作电流约150mA,继电器吸合瞬间电流更大,如果共用一根细长电源线,MCU侧电压会被拉低,ADC参考电压也跟着波动,采集值直接失真。我的做法是外部12V适配器供电,经LM2596降到5V给蜂鸣器和继电器,再从5V经AMS1117降到3.3V给MCU和传感器,三个电压域地线单点接地。

3. 软件实现:CubeMX初始化、多路采集与报警判定

硬件焊好后,软件是重头戏。这部分我会把从CubeMX配置到最终烧录验证的完整链路讲清楚,直接照着做就能跑通。

3.1 CubeMX配置:时钟树、ADC和定时器

我用的是STM32F103C8T6 + HAL库 + Keil5环境,CubeMX版本6.x。配置项如下:

  • 时钟:外部8MHz晶振,PLL倍频到72MHz主频,APB1分频到36MHz,ADC时钟设为12MHz(ADC最高14MHz,别超)。
  • ADC1:开启4个通道(PA0/PA1/PA2/PA3对应IN0/IN1/IN2/IN3),扫描模式开启,连续转换关闭,用定时器触发而不是连续转换,这样能精确控制采样节奏。DMA开启,循环模式,数据宽度半字(16位),外设地址自动递增。DMA缓存数组设为4个uint16_t。
  • TIM2:设置为1ms中断,作为系统时基,软件里做计数器累加,每100ms启动一次ADC转换,每500ms读取一次DMA结果并处理报警逻辑。
  • GPIO:PB12推挽输出接蜂鸣器,PB13推挽输出接继电器,PB14/PB15接两个LED指示灯(运行灯+报警灯),火焰传感器数字量输入接PB0-PB3,上拉输入。

配置完成后,CubeMX会自动生成main.c、adc.c、dma.c等文件,但实际工程文件不需要全部保留,把gpio、tim、adc、dma这些核心文件拖进工程即可。

3.2 多路ADC采集:DMA循环模式与滤波

DMA在这里解决了“CPU反复进中断读ADC寄存器”的问题。配置成循环模式后,ADC每次转换完4个通道,DMA自动把结果搬运到内存数组,CPU无需干预。

#define ADC_CH_NUM 4 uint16_t adc_raw[ADC_CH_NUM]; // ADC初始化后启动DMA HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_raw, ADC_CH_NUM); // 在TIM2中断回调中,每100ms触发一次ADC采样 if (software_tick % 100 == 0) { HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_raw, ADC_CH_NUM); }

这里有个细节:采样值必须滤波,但滤波算法别选滑动平均。我试过直接对4个通道做10次滑动平均,虽然平滑了,但火灾报警场景需要快速响应,滑动平均会拖慢阶梯信号的爬升响应。正确做法是中值滤波:每通道连续采5次,排序后取中间值。这样既滤掉脉冲噪声,又不会显著增加响应延迟。

uint16_t median_filter(uint16_t *buf, uint8_t len) { uint8_t i, j; uint16_t tmp; for (i = 0; i < len - 1; i++) { for (j = i + 1; j < len; j++) { if (buf[j] < buf[i]) { tmp = buf[i]; buf[i] = buf[j]; buf[j] = tmp; } } } return buf[len / 2]; }

实际测试中,日照、电吹风这类突然出现的红外和气流噪声,中值滤波能压掉大部分单次脉冲干扰。

3.3 报警判定:去抖、迟滞与区域联动

判定逻辑是整个软件的核心,我设计了三级状态机:正常 -> 预警 -> 报警。

去抖环节很重要。MQ-2的模拟输出在火灾初期不会是平滑上升的直线,而是带波动的爬坡。直接拿单次滤波值和阈值比,结果就是蜂鸣器响一下停一下,比心虚的闹钟还烦人。我的做法是积分判定:连续5次采集(每次间隔500ms),如果至少4次超过阈值,才进入预警状态;再连续3次确认,才正式拉高报警输出。这样既过滤了瞬时波动,又保证了在2秒内能响应真实火情。

迟滞阈值是避免“报警阈值附近反复横跳”的关键。报警动作值和恢复动作值必须设置成两个不同值,中间留出不小于50mV的迟滞带。

#define ALARM_THRESHOLD_MV 1800 // 对应MQ-2输出约1.8V(分压后) #define RESET_THRESHOLD_MV 1500 // 低于1.5V才解除报警

意思是当烟雾浓度升高到1.8V触发报警后,浓度得跌回1.5V以下蜂鸣器才停。这样避免了临界状态下蜂鸣器反复通断的“弹振”现象。

区域联动逻辑上,每一路报警都对应独立的输出动作,而不是把所有路都做成“响一个大蜂鸣器”。我的设计:

  • 第1路(厨房):报警时蜂鸣器响,同时继电器动作切断燃气阀
  • 第2路(卧室):报警时蜂鸣器响,LED闪烁,继电器动作启动排烟风机
  • 第3路(客厅):报警时蜂鸣器响,LED亮,不联动设备
  • 第4路(楼道):报警时蜂鸣器响,同时触发所有路声光报警,表示需要疏散

这个逻辑在代码里就是一组if-else和位标志,重点在于用状态机把“当前处于什么阶段”理清楚,避免多路报警互相覆盖。比如锅炉房和卧室同时报警时,不能只显示最后触发的区域号,要支持多路状态同时显示。我用一个8位变量alarm_flags,每一位代表一路,按键切换OLED显示时轮询读取。

4. 实测中的误报问题排查与改善

系统的软件框架和硬件搭好后,真正的挑战才开始。以下是我实测过程中踩过的坑和对应的排查思路,这部分在整个项目里最有借鉴价值。

4.1 一次误报警的完整排查链路

第一版样机装好后,放在实验室角落,没烧火没点烟,某天凌晨突然开始报警。排查过程如下:

  • 检查报警记录(通过串口日志):第3路的值在20秒内从1.2V飙升到2.3V。
  • 检查传感器硬件:第3路是客厅的MQ-2,外观无异常,万用表测量AO输出稳定。
  • 用示波器观察第3路供电电源:发现12V输入经过LM2596后的5V输出有周期性跌落,跌落到4.5V后再回弹,周期约5秒。启动空调压缩机时跌落更深。
  • 进一步查指纹:MQ-2的AO是相对自身供电电源的比值输出,电源跌落时AO电压短暂上升,恰好穿越了阈值。

根因:传感器模拟输出对电源波动敏感,加热丝电流变化导致气敏电阻两端电压突变。解决办法分三步:

  1. 在MQ-2模块电源引脚并联100uF电解电容和104瓷片电容,吸收瞬时跌落;
  2. ADC参考电压源用MCU的3.3V LDO独立输出,不跟传感器共用;
  3. 软件上把单次采样扩展为“100ms窗口内采20次,取中值”,进一步抑制工频噪声。

改完后连续跑了三天,没有再出现空报。

4.2 传感器预热和基线校准

另外一个容易忽略的点是传感器预热。MQ-2断电重新上电的前3分钟,输出会严重漂移,加热丝还没把气敏元件加热到工作温度,此时读到的电压异常偏高。

我的处理是在主控上电后加一个120秒的“初始化预热”阶段,串口打印倒计时,OLED显示“Warming Up”。预热阶段不进行报警判定,预热结束后,读取当前各路ADC值作为基线偏移量,后续判定使用“当前值-基线值”和阈值比较。这一步解决了“为什么今天开机就报警、明天同样位置又不报警”的玄学问题。

提到基线就牵扯到长期老化问题。MQ-2使用3个月以上,零点输出电压会因气敏材料老化发生漂移。可做一个技巧:每24小时自动在凌晨3点记录一次基线值(此时无人活动,烟雾浓度最低),存入Flash。当然这个功能对毕业设计来说是加分项,实现起来不过是RTC+Flash存储+启动时读回而已。

4.3 火焰传感器误触发的红外干扰

火焰红外模块让我头疼了很长时间。某一路的数字量输出总是随机出现低电平,用示波器抓,频率不规律,持续几毫秒到几十毫秒不等,不像真实火焰的信号。

排查中发现,模块旁边正好是LED照明灯的电源线,LED驱动里的红外成分虽然不多,但近场耦合进模块的敏感PIN脚,比较器一旦判断超过阈值就输出低电平。处理方案:

  • 给火焰传感器模块加一个黑色遮光罩,只留一个朝向监测区域的窗口,缩小接收视角;
  • 调整模块电位器,提高触发阈值,让太阳光和灯光都达不到触发电压;
  • 软件层面对数字量输入做连续确认:连续读到10次低电平(每次间隔10ms)才认为有效报警,否则忽略。

这三步做完,火焰传感通道再没出现过误触发。遮光罩的细节很值得注意,双面胶固定时检查传感器开窗方向,位置装反了等于没遮。

5. 系统调试与扩展:从Demo到可交付的小系统

当报警系统的基础功能稳定之后,我从三个方向做了扩展和打磨,让这套东西从“能响的毕设”变成“像个正经产品”的程度。

5.1 OLED显示、按键阈值标定与本地调试

给系统加了一块0.96寸OLED(I2C接口,SCL接PB6,SDA接PB7),显示信息包括:

  • 当前日期时间(RTC芯片DS3231或STM32内部RTC)
  • 四路传感器实时电压值、当前状态(正常/预警/报警)
  • 报警历史记录(最近5条,含区域和时间)
  • 阈值参数菜单

菜单用三个按键操作:确认、切换、加减。调试时直接在界面上调整阈值,不用反复改代码烧录。这个交互设计其实是项目演示时的加分项,评审老师看到实物能直接调整阈值而不是看串口日志,印象分完全不一样。

5.2 通信扩展:CAN总线、RS485与远程监控

火灾报警系统在实际场景中往往需要接入上位机或消防控制主机。我给系统预留了两种通信接口:

  • RS485接口:使用SP3485收发器,Modbus-RTU从站模式,上位机通过485总线轮询各分机和传感器数值。多个报警分机可以挂在同一条总线上,实现楼宇级联网。
  • CAN接口:STM32F103C8T6没有CAN外设(C8T6的是精简版),需要换用STM32F103RCT6或加SPI转CAN芯片(MCP2515)。CAN适合实时性要求高的联动场景,类似消防广播和排烟系统的联动触发。

RS485的代码分包格式很简单:地址码+功能码+寄存器地址+数据+CRC16。我实测过Modbus-RTU轮询1200波特率下10个分机不掉线,稳定性足够毕业设计演示。

5.3 可靠性设计:看门狗、掉电存储与电路保护

报警系统最重要的属性是“长期可靠”。我加入了一套可靠性措施,这也是之前从实际项目学到的教训:

  • 独立看门狗IWDG:在主循环里周期性喂狗,一旦程序跑飞,自动重启。看门狗溢出时间设为2秒,不干扰正常的500ms判定逻辑。
  • EEPROM存储:使用AT24C02存储阈值、基线和报警记录。掉电不丢数据,重启后恢复现场。
  • 电源反接保护:电源输入串联1N5819肖特基二极管,反接时后级不会损坏。
  • TVS瞬态抑制管:在12V输入端口并联SMBJ15A,吸收雷击浪涌和感性负载通断产生的尖峰。

这套设计花在电路保护上的时间不多,但对于去过现场的老工程师来说,看到输入端口有TVS管和续流二极管,就知道这项目不是只会跑马灯的入门水平。

最后再聊一点个人体会。做火灾报警这类安全相关项目,和做普通物联网玩具最大的区别是:你必须假设传感器会坏、电源会波动、信号会被干扰,然后设计一套逻辑来拥抱这些不确定性。多路采集的意义不是让系统更复杂,而是让单路失效不至于导致整体瘫痪。这套系统固化下来后,我在四路传感器全都通电的情况下连续跑了将近一周,每天记录误报和漏报次数,最终确认无误后才敢往下传。希望这篇复盘能帮做类似题目的朋友少走几个弯路,尤其是正好卡在误报和去抖问题上的,那几段代码可以直接抄走用,理解比硬搬更重要。

返回列表