news 2026/9/27 1:47:05

STM32开源项目:实验室消防预警控制系统设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32开源项目:实验室消防预警控制系统设计与实现

实验室里的火灾风险,往往藏在最不起眼的细节里——酒精灯倾倒、电阻过热、电烙铁忘拔、锂电池短路,任何一次疏忽都可能酿成大祸。而市面上的独立烟雾报警器只会发出刺耳的蜂鸣,响完之后既没有现场浓度反馈,也没有任何联动处理能力。那段时间我和几个经常泡实验室的同学聊起来,大家共同的痛点很朴素:能不能自己动手做一套既能看到现场数据、又能自动排烟通风、还允许手动干预的消防预警装置?

这就是《STM32项目开源:实验室消防预警控制系统》这个项目的由来。整套系统以 STM32F103C8T6 为主控芯片,集成 MQ-2 烟雾传感器、DHT11 温湿度传感器、火焰传感器,配合 0.96 寸 OLED 显示屏、蜂鸣器、继电器和排风扇,实现了烟雾探测、温度监测、火焰检测、声光报警、自动排烟、阈值设置和串口日志输出。代码、原理图、仿真工程全部开源,文件组织成标准工程包,直接下载就能照着复现。对正在找 STM32 实战项目练手的开发者来说,这套系统的硬件规模不大、代码逻辑清晰,既可以作为入门级的综合项目来学习,也能直接当作课程设计或毕业设计的主课题。

下面我把整个项目的架构、电路设计、软件思路、仿真方案和实测校准经验逐块拆开讲,里面有不少东西是光看原理图和代码看不出来的。

1. 项目概况:这套消防预警系统到底做了什么

1.1 设计需求从哪来

先说清楚项目要解决的场景,这决定了整个系统的功能边界。普通实验室的环境特点是:空间通常不大,但电器设备多、化学试剂多、临时性操作多。市面上几十块钱的烟雾报警器大多是独立式的,内部一个单片机加一个蜂鸣器,检测到烟雾就响,没显示、没联动、没调节,而且阈值在出厂时写死,在粉尘大或试剂气味的实验室里特别容易误报,误报几次之后人就会对它麻木,真出事反而不理它。

这套系统要做的事可以归纳成四条:

  • 实时采集烟雾浓度、环境温度、火焰信号,综合判断火灾风险。
  • 在现场显示屏上直接看到浓度数据和当前状态,不再只能靠耳朵听响。
  • 根据风险等级自动联动启动排风扇,并支持手动消音复位和阈值调整。
  • 把运行数据通过串口输出,方便电脑端监控和后续扩展。

对比独立式报警器,这套系统的优势就是"看得见、可干预、能联动"六个字。这也是我在做系统架构时最坚持的几个基本点。

1.2 系统组成与核心器件

系统的整体结构可以这样理解:传感器端负责感知环境,主控端负责收集数据、做判断、跑状态机,执行端负责输出警告和处理动作,交互端负责显示和设置。虽然功能看着多,但器件选型都走大众化路线,全部是实验室里最容易找到、价格也压得很低的型号。

器件清单如下表所示:

模块型号/规格作用备注
主控芯片STM32F103C8T6数据采集、逻辑判断、外设控制72MHz,64KB Flash,20KB RAM
烟雾传感器MQ-2检测可燃气体和烟雾浓度模拟量输出,需要 ADC 采集
温湿度传感器DHT11采集环境温度和湿度单总线协议,20%-90%RH,0-50℃
火焰传感器红外接收管 + LM393检测火焰特征红外辐射数字量输出,阈值可调
显示屏0.96 寸 OLED,SSD1306显示浓度、温度、状态、阈值I2C 接口
报警输出有源蜂鸣器 5V声光报警的发声部分三极管驱动
联动执行继电器模块 5V控制排风扇通断线圈需续流保护
人机交互轻触按键 x3阈值加、减、确认/消音GPIO 读取,需消抖

这里再解释一下为什么烟雾传感器用 MQ-2 而不是更高端的数字式烟雾传感器。MQ-2 虽然精度一般,测不出具体的浓度值,但它对烟雾和可燃气的响应范围宽、灵敏度高、输出信号是连续模拟电压,适合用来做阈值判断和趋势观察。数字式的如 SGP30、SHT4x 精度是高了,但价格也高了几倍,而且火情感知类应用本来就只需要"浓度到了没到阈值"这个判断,用 MQ-2 是最务实的选择。

1.3 为什么主控选 STM32F103C8T6

选这款芯片几乎不需要犹豫。首先,它基本上是目前资料最全、案例最多、踩坑记录最丰富的 STM32 芯片,没有之一。实验室初学者遇到的大部分问题,搜索一下就能找到现成答案。其次,它的外设刚好覆盖本项目所有需求——一个 ADC 模块支持多通道采样、两个 I2C、一个 USART、充足的 GPIO,不需要外扩任何东西。第三是成本,芯片零售价也就几块钱,最小系统板十几块钱,做坏几块也不会心疼。

当然也有一个更实际的理由:这个项目最初就是面向教学场景设计的。用 F103 系列,Keil MDK 里新建工程、标准外设库或 HAL 库都有大量模板可以用,代码在 F103 上跑通之后,迁移到 F407 或 F4 系列也很方便,学习梯度平滑。

2. 硬件电路设计:原理图里那些容易出错的节点

2.1 MQ-2 烟雾传感器的 ADC 采样电路

MQ-2 是半导体气敏传感器,内部有一段加热丝和二氧化锡气敏层。通电后加热丝把敏感层加热到工作温度,当空气中可燃气体或烟雾浓度升高,气敏层的电导率就会变化,通过外部负载电阻分压后,输出电压也随之变化。这个电压信号就是判断烟雾浓度的原始依据。

这里有一个非常关键、很多新手在画原理图时容易踩的坑:MQ-2 模块的模拟输出电压范围是 0 到模块供电电压。市面上大部分 MQ-2 模块额定供电是 5V,所以 AO 引脚空载时最高能输出接近 5V,而 STM32F103 的 ADC 输入范围是 0 到 3.3V,直接怼进去会把 ADC 引脚打坏。解决办法有两个:

  • 给模块供 3.3V,AO 输出范围也就变成 0-3.3V,可以直连 ADC,但传感器灵敏度会有所下降。
  • 保持 5V 供电,在 AO 输出到 ADC 引脚之间加分压电阻。比如用两个 10k 电阻串联分压,把 5V 范围折半到 2.5V,留出安全余量。

我最后采用的是 5V 供电加分压的方案,因为实验环境里 5V USB 电源最容易获取,而且 MQ-2 的加热丝对电压比较敏感,5V 供电能保证传感器工作在标准状态,读数更稳定。分压电阻取值也做过实测,10k 对 10k 分压后,实际 ADC 采到的电压范围为 0-2.5V 左右,配合 12 位 ADC(4096 格),分辨率完全够用。

程序里计算电压值的公式很简单:

float voltage = (float)adcValue * 3.3f / 4096.0f * 2.0f; // 乘2还原5V分压前的电压

MQ-2 模块板上通常自带一个电位器,用于调节数字量输出 DO 的阈值。如果只用 AO 模拟量,这个电位器不影响模拟输出数值,不用管它。但如果对应的项目版本用到了 DO,就一定要注意逆时针和顺时针分别对应灵敏度的提高和降低,不同批次模块方向不一致,最稳妥的方法是拿万用表实测。

2.2 DHT11 与火焰传感器的接入细节

DHT11 是单总线传感器,数据线是开漏输出,因此外部必须接一个 4.7k 到 10k 的上拉电阻到 VCC。实际接线非常简单:VCC 接 3.3V 或 5V 都可以,GND 共地,DATA 接一个 GPIO,比如 PA0。在软件里把 PA0 配成开漏输出或推挽输出都行,但开漏模式配合外部上拉最符合协议规范,时序也更稳定。

火焰传感器模块的原理是红外光电二极管接收火焰燃烧时辐射的特征红外光,经 LM393 比较器处理后输出数字电平。模块上有两个输出:AO 模拟输出和 DO 数字输出。本项目只用 DO,接一个 GPIO(PA3)即可,有火焰时输出低电平(具体极性看模块说明)。这里要提醒大家注意一个实际问题:火焰传感器对太阳光、白炽灯等红外辐射强的光源也会响应,安装在实验室里很容易被阳光或热源干扰。我的处理方式是给传感器加了一个遮光罩,只留一个朝向监测区域的窗口,并且在软件里不把火焰信号作为唯一报警条件,而是作为烟雾和温度之外的辅助确认信号。

2.3 蜂鸣器、继电器和风扇的驱动电路

这部分是原理图里最考验硬件基本功的地方。STM32 的 GPIO 输出电流只有几毫安到二十毫安,直接驱动蜂鸣器或者继电器线圈是完全不现实的。蜂鸣器和继电器都需要通过三极管来放大驱动电流。

蜂鸣器驱动最简单:用一颗 S8050 NPN 三极管,GPIO 通过 1k 电阻接基极,集电极接蜂鸣器负极,蜂鸣器正极接 5V,发射极接地。GPIO 输出高电平时三极管导通,蜂鸣器通电发声。注意有源蜂鸣器内部自带振荡电路,只需要给直流电就会响,不需要用 PWM 输出特定频率。

继电器驱动这里有两个细节必须讲清楚。第一个是集电极开路驱动中,继电器线圈是一个大电感,断开瞬间会产生反向电动势,如果不加保护,这个反向高压会直接击穿三极管。解决办法是在继电器线圈两端反向并联一个续流二极管(1N4007 即可),正极接继电器供电端,负极接三极管集电极,把断开瞬间的感应电流泄放掉。第二个细节是,如果使用的是市售继电器模块而不是裸继电器,模块上一般已经带了驱动三极管和续流二极管,只需要用 GPIO 去触发的信号脚即可。但如果自己从零画 PCB,这两个元件必须手工加上去,漏了就等着元器件烧毁。

排风扇的接入位置也有讲究。我用的是 12V 小功率散热风扇,继电器只是控制风扇电源回路的通断,而不是直接让 F103 去控制风扇。这样风扇和主控电路之间就有电气隔离,风扇启停瞬间的电压波动不会干扰主控部分。如果实际应用中需要控制 220V 的排风设备,继电器要换成带隔离的交流继电器或固态继电器,并且强弱电之间要留足安全间距,这块千万不能乱接。

2.4 用嘉立创 EDA 画原理图过程中的几条经验

原理图是用嘉立创 EDA 画的,这里分享几点实际操作中积累的经验,能帮你少走弯路。

第一,原理图库里的 MQ-2 封装各家画的差异很大。立创商城自带库里的 MQ-2 封装标注为四脚通用封装,但不同厂家的模块引脚的间距不完全一致,画完原理图后一定要对着实物模块量一下引脚间距再打板或焊接,否则焊不上去。

第二,STM32F103C8T6 的引脚非常多,画原理图时建议把原理图按功能分区:电源区、主控区、传感器区、驱动区、交互区。每个区加一个矩形标注框,网络标签命名规范一点,比如 5V、GND、ADC_SMOKE、DHT11_DATA、RELAY_FAN。这样维护起来清楚很多,检查信号连接也会方便。

第三,电源去耦电容不能省。每个 VCC 引脚旁边都要放一个 100nF 陶瓷电容,靠近引脚放置。这主要是为了滤除高频噪声,防止芯片在继电器或蜂鸣器动作瞬间掉电复位,实物调试时很多莫名其妙的重启问题都是因为去耦电容没加够。

3. 固件代码逻辑:状态机、滤波与交互的实现路线

3.1 主程序框架:轮询为主、中断为辅

这套系统我不建议上 RTOS,因为功能规模不大,用实时操作系统反而引入任务调度和资源竞争的复杂性。程序结构采用经典的裸机超级循环加 SysTick 时基的方式。

主循环里按顺序执行四个任务:按键扫描、传感器读取、状态机运转、OLED 刷新。每个任务都是非阻塞的,不会出现一个传感器初始化失败就导致整个系统卡死的情况。SysTick 每 1ms 产生一次中断,维护一个全局 tick 计数,用于按键消抖、蜂鸣器节奏控制等需要"过一段时间再操作"的逻辑。

系统上电后的初始化顺序也很重要。我的经验是先把串口打开(方便输出调试信息)、再初始化 OLED 和 GPIO,等看到屏幕点亮了,再初始化 DHT11、设置 ADC,最后读取 MQ-2 的基线值。因为 MQ-2 有很长的预热期,上电后直接读数据会得到一个剧烈波动的不稳定值,所以程序初始化流程里加了 10 秒的预热延时,预热期间 OLED 会显示"Warming Up"并附一个倒计时。

主循环伪代码如下:

int main(void) { SysTick_Config(SystemCoreClock / 1000); // 1ms 系统时基 USART1_Init(115200); OLED_Init(); KEY_Init(); LED_Init(); BEEP_Init(); RELAY_Init(); FAN_Init(); DHT11_Init(); ADC1_Init(); OLED_ShowString(0, 0, "Fire Alarm System"); OLED_ShowString(0, 2, "Warming Up..."); Delay_Ms(10000); // MQ-2 预热 smokeBaseline = GetFilteredADC(); // 记录干净环境基线 warningThreshold = smokeBaseline + 200; // 默认预警阈值 alarmThreshold = smokeBaseline + 400; // 默认报警阈值 while(1) { KEY_Scan(); // 按键处理(非阻塞) Sensor_Read(); // 读取烟雾、DHT11、火焰 StateMachine_Run(); // 状态机判断与联动输出 OLED_Refresh(); // 刷新显示 UART_SendLog(); // 串口输出运行状态 } }

3.2 传感器数据采集与滤波

ADC 采集烟雾电压是整个系统响应速度的瓶颈之一。如果直接单次采样就拿来判断,数据跳变得很厉害,轻微的气流扰动都能让读数上下浮动几十个单位,很容易造成误报。我在代码里实现了中位平均滤波——连续采 5 次,去掉一个最大值和一个最小值,剩下 3 个取平均。这个算法实现简单,但对瞬时尖峰干扰的抑制效果很好,计算开销也很小,跑在 72MHz 的 F103 上毫无压力。

#define SAMPLE_COUNT 5 uint16_t GetFilteredADC(void) { uint16_t buf[SAMPLE_COUNT]; uint16_t max = 0, min = 0xFFFF; uint32_t sum = 0; for (int i = 0; i < SAMPLE_COUNT; i++) { buf[i] = ADC_ReadChannel(ADC_CHANNEL_SMOKE); if (buf[i] > max) max = buf[i]; if (buf[i] < min) min = buf[i]; sum += buf[i]; } return (uint16_t)((sum - max - min) / (SAMPLE_COUNT - 2)); }

DHT11 的读取要严格按单总线时序来,主机先把数据线拉低至少 18ms 发出启动信号,然后释放总线,传感器应答后按 40 位数据流输出。每一位都以 50us 低电平开头,后面高电平持续 26-28us 表示位 0,70us 表示位 1。用普通延时函数就能完成时序,但要特别注意读数据期间要关闭中断,否则一个串口中断进来就会破坏时序,导致读到的温湿度完全错误。代码里我是用__disable_irq()和__enable_irq()做了临界区保护,实测非常稳定。

火焰传感器就比较简单了,直接读取 GPIO 电平。为了排除瞬时干扰,我在程序里做了连续 3 次读取都为有效状态才确认有火情的逻辑,相当于一个硬件层的软件防抖。

3.3 两级报警状态机的设计思路

整个程序的核心是状态机。它把系统运行状态分成以下几类:

  • 正常状态:浓度和温度都在安全范围,绿灯常亮,蜂鸣器静音,风扇不转。
  • 预警状态:烟雾浓度超过预警阈值但还没到报警阈值,或温度超过 45℃。此时蜂鸣器间歇短鸣,黄灯闪烁,继电器吸合启动排风扇。
  • 报警状态:烟雾浓度超过报警阈值,或火焰传感器触发。此时蜂鸣器连续鸣叫,红灯常亮,风扇全速运转,等待手动复位。
  • 设置状态:通过按按键进入,可以调整预警和报警阈值,调整后的值保存到 STM32 内部 Flash,掉电不丢失。

状态机的核心判断逻辑是"浓烟为主、温度辅助、火焰确认"。单靠烟雾浓度到报警阈值就进入报警态,同时用火焰信号作为确认条件之一;温度超过阈值也能触发预警或报警,但温度报警阈值设置得比较高(比如 60℃),目的是尽量减少电热设备正常工作时带来的误报。

状态机代码简写如下:

void StateMachine_Run(void) { switch (currentState) { case STATE_NORMAL: LED_Set(LED_GREEN, ON); FAN_OFF(); if (smokeValue > warningThreshold || temperature > TEMP_WARNING) currentState = STATE_WARNING; break; case STATE_WARNING: LED_Set(LED_YELLOW, TOGGLE); BEEP_Pulse(2, 200); // 鸣2声,每声200ms FAN_ON(); if (smokeValue > alarmThreshold || flameDetected) currentState = STATE_ALARM; else if (smokeValue <= warningThreshold && temperature <= TEMP_WARNING) currentState = STATE_NORMAL; break; case STATE_ALARM: LED_Set(LED_RED, ON); BEEP_Continuous(); FAN_ON(); if (keyConfirmPressed) currentState = STATE_NORMAL; // 手动复位 break; case STATE_SETTING: OLED_ShowSettingMenu(); if (keyConfirmPressed) { SaveThresholdToFlash(); currentState = STATE_NORMAL; } break; } }

蜂鸣器工作时有一个细节值得注意。有源蜂鸣器在连续鸣叫时电流大约 30mA,虽然在三极管驱动范围内,但在报警状态中整个系统电流会明显增大。实测下来,在 5V USB 供电的情况下,系统稳定工作电流在 280mA 左右,蜂鸣器响起后上升到 500mA 以上,如果用的是劣质 USB 线或者充电器输出电流不够,电压会被拉低导致 OLED 闪烁甚至 MCU 复位。所以我在设计时把蜂鸣器的供电单独做了一个 100uF 电容储能,同时对报警逻辑做了节流控制——预警状态蜂鸣器不连续鸣叫,而是每 200ms 响 50ms,这样既保证提醒效果,又大幅降低平均电流。

3.4 OLED 显示与按键交互

OLED 用的是 SSD1306 驱动的 0.96 寸屏,I2C 接口。软件用现成的 SSD1306 库,把 I2C 引脚初始化好之后就可以直接操作显存刷屏。注意 I2C 总线速度不要太激进,软件模拟 I2C 的话 400kHz 的上限基本够用,硬件 I2C 则要确认 STM32 的 I2C 模块工作正常——F103 的硬件 I2C 在中断和 DMA 配合不好时容易卡死,很多项目最后都改回了软件 I2C。我给这个项目的建议是:直接用软件 I2C,用 PB6/PB7 两个引脚,代码简洁稳定,不折腾。

界面布局上,我把屏幕分成四个区域:

  • 第一行显示烟雾浓度百分比,直接显示"SMOKE:35%"。
  • 第二行显示温度"TEMP:26.5C"。
  • 第三行显示当前状态"NORMAL""WARNING""ALARM"。
  • 第四行显示当前浓度阈值:"THR:1200"。

按键一共三个:加、减、确认。正常运行时短按确认键可以消音复位,长按确认键进入阈值设置模式。设置模式下,加和减键调整阈值,确认键保存退出。阈值保存用 STM32 内部 Flash 的最后一页,写入前必须先擦除,写入时也要注意断电造成的数据损坏问题。在实际编码中我给 Flash 操作加了简单的双备份机制,就是同一个阈值写两份,读取时校验,不一致则用默认值,防止写入一半断电。

3.5 串口日志输出

串口输出在调试阶段作用巨大。USART1 配置成 115200 波特率,8N1,PA9 发送 PA10 接收。系统每秒输出一帧状态日志,格式定义为:

[S] smoke=1234 temp=26.5 flame=0 state=NORMAL thr=1200

这个格式让串口助手可以很直观地看到数据变化,后续如果想接上位机做可视化界面,也可以直接按这个格式解析。实测过程中我经常把串口日志接到电脑上持续跑几个小时,专门观察传感器数值的漂移情况,这对后面标定阈值非常有帮助。

4. 仿真验证:不烧一片芯片先把逻辑跑通

4.1 Wokwi 在线仿真:最适合快速验证状态逻辑

Wokwi 是我这两年用得比较多的在线仿真平台,它对 STM32F103 的支持很完善,可以直接在浏览器里搭出 MCU、OLED、LED、按键这些元件,然后加载编译好的固件运行。它的好处就是零成本,不用焊板子,不用接线路,改代码之后重新编译上传,几秒钟就能看到运行结果。

在这个项目中,我用 Wokwi 做两件事:第一件是把状态机逻辑跑起来,用代码里的计数器模拟烟雾浓度变化,而不是直接接一个传感器模型。这样每次调整报警阈值、修改状态跳转条件,都能立刻判断逻辑是否正确。第二件是验证 OLED 的驱动和显示布局,因为 SSD1306 在 Wokwi 里可以直接挂到 I2C 虚拟总线上显示,这样写代码的时候不用反复往开发板上烧录调试屏幕,效率高很多。

要模拟烟雾浓度从正常升高到报警的过程,可以在 Wokwi 的 diagram.json 里加一个滑动变阻器(Potentiometer),将其中间抽头接到 ADC 引脚上,运行时拖动滑块就能让 ADC 采样值连续变化,相当于手动模拟烟雾浓度上升。这样观察状态机的跳变、蜂鸣器节奏和 OLED 状态文字的切换,非常直观。

4.2 Proteus 仿真:更真实的电路级验证

如果要做更接近物理电路的验证,用 Proteus 会更合适。Proteus 里可以直接放置 STM32F103C8T6、DHT11、MQ-2、继电器、风扇等模型,加载编译好的 .hex 文件运行,还能用示波器观察引脚的波形。这样能验证电路连接的完整性,特别是 RST、BOOT、晶振这些引脚的处理,以及在模拟环境下有没有静态短路问题。

在 Proteus 中有一个小坑要提醒:DHT11 的模型对时序要求比实物更严格,经常出现读不到数据的情况。这不是代码问题,而是 Proteus 的 DHT11 模型对信号延时模拟得过于理想化,真实传感器的应答容忍度反而更高。遇到这种情况时,我在仿真里直接用一个可调电阻加 ADC 来模拟温度变化,只验证主控对温度数据的处理逻辑,传感器时序问题留到实机上验证。

仿真还有一个独特优势:可以制造"传感器故障"场景。比如把烟雾传感器的输出直接短路到 3.3V,或者把 DHT11 的 DATA 线断开,观察系统是否会在缺数据时进入安全状态而不会卡死。我在这套代码里对 DHT11 读取失败做了超时重试和保护,最多连续失败 3 次就显示"Temp Sensor Error",同时把温度条件当作无效,只用烟雾逻辑来判断报警状态。这种容错处理在实物调试中非常关键。

4.3 仿真和实物的差异在哪里

仿真跑通了不等于实机没问题。我在这个项目上体会最深的有三点:第一,仿真里传感器的输出是理想模型,输出电压的变化是平滑的、符合预期的,而实物的 MQ-2 信号带有大量噪声,断电重启后基线还可能漂移,所以仿真阶段观察到的那种阈值设置方案,到实物上很可能误报或不报。第二,I2C 和单总线时序在仿真中往往被简化,同样的代码在 Proteus 里跑得飞快,上真机却可能卡在 OLED 初始化的 while 等待循环里,这种问题仿真阶段完全察觉不到。第三,Proteus 和 Wokwi 里都不存在电源跌落的问题,而实际设备里继电器吸合瞬间的电流冲击、蜂鸣器鸣叫时对 ADC 参考电压的干扰,都是真实存在的噪声源,必须通过去耦、滤波、延长采样超时等方式在电路和代码两个层面同时消化掉。

所以我的建议是:仿真是用来验证逻辑和算法结构的,一定要做,但别迷信仿真,最终的可靠性必须靠实物跑出来的数据说话。

5. 现场调试与阈值标定:从会响到好用

5.1 MQ-2 预热与基线漂移的实战处理

MQ-2 刚上电那段时间是最让人头疼的。传感器内部加热丝从冷态到热态需要时间,在这期间气敏层的电阻变化非常剧烈,输出的电压动不动就从 0.2V 跳到 1.5V 再回落到 0.5V,读数完全没法用。我在程序里加了 10 秒预热延时之后,数据虽然不至于乱跳,但基线值还是会随时间缓慢漂移,特别是在环境湿度变化大的时候。

针对漂移问题,我的处理办法是在系统长时间运行后手动进入"基线校准"模式。按住确认键 5 秒进入校准,系统会在 10 秒内连续采样,取平均作为新的基线值,同时自动把所有阈值相对基线重新计算。这个功能听上去很简单,但实际使用中确实解决了很大问题——每次实验室通风系统启停、季节交替导致空气本底变化之后,不需要改任何代码,按几下按键就能重新校准。

5.2 阈值怎么标定才算合理

阈值标定是整个系统从"能跑"到"好用"的关键一步。我在实际标定时按以下步骤操作:

  • 先让系统在目标实验室正常运行 30 分钟以上,记录稳定后的正常基线值。比如正常环境下 MQ-2 分压后 ADC 读数在 300-350 之间。
  • 用打火机不点火、只释放一点可燃气体靠近传感器(注意安全,远离明火和易燃物),或者点一小片纸产生烟雾,观察 ADC 读数能冲到多少。通常在烟雾浓度很低的情况下,读数能冲到 1000-1500 左右。
  • 把预警阈值设在正常值和报警实测值之间偏下的位置,比如基线为 350 时,预警阈值取 600,报警阈值取 1200。留出这两级之间的空间是为了给误报和响应留出余量。
  • 如果实验室本身通风良好、空气质量高,可以适当把阈值调低一点提高灵敏度;如果实验室经常有酒精、丙酮等挥发性溶剂的气味,阈值必须调高,否则动不动就响。

这里有个很实用的经验:阈值不要一次性设到位,先设一个偏严的阈值跑一天,把一天内所有误报的时间点记录下来,再回头分析是气体干扰还是气流扰动,然后针对性调整。我在一次实测中发现系统凌晨 3 点误报,查串口日志发现是隔壁实验室有人在通风橱里倒试剂,气味顺着管道飘过来的。这种问题没法靠调阈值根治,只能在特定时段把灵敏度调低一点,或者优化安装位置。

5.3 误报与漏报的平衡怎么把握

传感器物理层的噪声是不可避免的,所以我从软件层面加了三个过滤机制。

第一个是连续确认机制。不管是烟雾浓度超阈值还是火焰传感器触发,都需要连续 3 次采样(每次间隔 1 秒)都满足条件,才真正进入报警状态。这样做的代价是报警响应时间多了 2 秒左右,但减少了大量由瞬时尖峰引起的误报。消防报警场景里,2 秒的延迟完全在可接受范围内。

第二个是"温度辅助判据"。单独烟雾浓度冲到预警阈值时先只进入预警状态,不启动声光报警联动;当温度同时超过 45℃ 时,才升级到报警状态。反过来,如果火焰传感器触发了,哪怕温度和烟雾都没到阈值,也会直接进报警状态。这个混合判断逻辑能有效区分"气雾干扰"和"真实火情"。

第三个是报警后的复位策略。普通独立式报警器只有断电才能复位,而实验室场景里可能是虚惊一场,需要快速静音恢复工作状态。我的做法是报警状态下短按确认键直接复位到正常状态,重新开始一个 10 秒的稳定检测窗口。这样既保留了人为干预能力,又避免了"复位后立即误报"的死循环。

5.4 开源工程包怎么用:目录结构与二次开发建议

开源工程文件按下面的目录组织,下载解压后路径清晰,不用猜:

LabFireAlarm/ ├── Doc/ # 项目文档和说明 │ ├── README.md # 使用说明 │ ├── LabFireAlarm_SCH.pdf │ └── datasheet/ # 各传感器数据手册 ├── Hardware/ # 立创EDA源工程 │ ├── LabFireAlarm_v1.0.json │ └── BOM.csv ├── Firmware/ # Keil MDK 固件工程 │ ├── LabFireAlarm.uvprojx │ ├── Core/ # 主程序、状态机 │ ├── Drivers/ # 外设驱动(OLED、DHT11、ADC等) │ └── Output/ # 编译生成的hex文件 └── Simulation/ ├── Proteus/ # Proteus 8.11 仿真工程 └── Wokwi/ # Wokwi 在线仿真配置 ├── diagram.json └── firmware.bin

固件工程是基于 STM32 标准外设库写的,没有依赖 HAL 库,原因是标准外设库的代码结构更简单直白,每一行都是寄存器操作的封装,适合教学和移植。如果你更熟悉 HAL 库,也可以很轻松地把底层 GPIO、ADC、I2C 初始化部分替换成 HAL 的对应函数,状态机逻辑和滤波算法完全不用动。

想做扩展的话,我列几个低成本高价值的方向:换成带 Wi-Fi 的 ESP32 模块,把报警信息推到手机通知;加一个 GSM 模块,实现无人时短信报警;把单机模式改成多节点组网,一个主控收集多个传感器节点的数据;或者把显示界面升级成中文字库的 OLED,信息展示更友好。

做这个项目前后花了接近三个月时间,中间推翻重来了两个版本。最大的体会就是:传感器本身的不确定性,远比代码逻辑的不确定性更难对付。一开始我以为只要把 MQ-2 的 ADC 值读出来、设个阈值、接到蜂鸣器上,这套系统就完成了,结果第一次实测就发现数据在 "0.3V 到 1.8V" 之间反复横跳,蜂鸣器响得像在弹电子琴。后来老老实实研究传感器特性、加滤波、加状态机、做阈值分级,才逐渐稳定下来。所以如果你准备照着这个开源项目做类似的消防或环境监测系统,我的建议是先从 MQ-2 加蜂鸣器把最简单的闭环跑通,每一个模块验证稳定了再接下一个,别急着堆功能。整个工程包放在代码仓库里,原理图、固件、仿真配置都在,直接对照着跑一遍,比我在这里写再多文字都来得实在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 1:45:48

STM32CubeMX 实战指南:从安装到 LwIP 以太网配置完整链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 1:45:15

Smith Chart与ADS阻抗匹配实战:从单频匹配到宽带优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 1:45:07

PlatformIO创建工程失败?彻底清理残留重装环境全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 1:45:02

STM32 SBUS协议解析:DMA+IDLE中断+环形缓冲区实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 1:44:52

PPT多视频同步播放全攻略:动画窗格与VBA方案详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 1:44:44

CRC校验彻底讲透:原理、参数与Modbus/PLC实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华