ARTICLE DETAIL

资讯详情

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

基于STM32的实验室消防预警系统:从传感器选型到MQTT上报的完整实战

基于STM32的实验室消防预警系统:从传感器选型到MQTT上报的完整实战 1. 项目缘起与整体设计思路1.1 为什么选择STM32做消防预警控制器实验室场景的消防预警和商用楼宇、工业厂房有本质区别。实验室里常见的可燃物是酒精、乙醚、氢气、一氧化碳这类试剂挥发物火源往往是电烙铁忘关、电池过充、加热台失控。这类场景的特点是空间小、人员流动频繁、无人值守时段长一旦起火留给人的反应窗口可能只有几十秒。市面上成套的消防主机价格动辄上万而且接口封闭想接自己实验室的排风、断电、门禁联动基本没戏。所以自己用STM32搭一套成本能压到两三百块逻辑完全可控还能顺手把数据接到自己的服务器上。我选STM32F103C8T6作为主控理由很直接这颗芯片在实验室里几乎人手几片资料多到溢出价格常年稳定在十块钱上下外设资源对消防预警这个量级的任务绰绰有余。具体算一下资源账需要驱动MQ-2烟雾传感器ADC一路、DHT11温湿度单总线GPIO一个、火焰传感器GPIO或ADC、蜂鸣器GPIO、继电器输出GPIO、OLED显示I2C两线、ESP8266上报UART两线。加起来ADC用2路、GPIO用6到8个、I2C一组、UART一组F103C8T6的48引脚封装完全吃得下连引脚复用都不用太纠结。注意消防类项目对可靠性要求高于普通DIY项目。STM32F103的工业级温度范围是-40到85摄氏度实验室环境完全覆盖但如果你的实验室有高温烘箱区域传感器和主控要分开布置别把板子贴在烘箱旁边。1.2 系统架构的分层设计整个系统我按四层来切感知层、决策层、执行层、交互层。这么切的好处是每一层可以独立调试传感器坏了不影响继电器逻辑屏幕花了也不影响上报。感知层负责把物理量转成电信号包括MQ-2烟雾浓度、DHT11温湿度、火焰传感器的红外强度。这里有个设计取舍MQ-2输出的是模拟电压理论上用ADC读最细但MQ-2本身漂移大读出来的绝对值意义有限所以我最终用的是“ADC读值滑动窗口比较阈值”的方案而不是追求绝对浓度标定。火焰传感器我用了数字输出和模拟输出双路数字口做快速触发模拟口做趋势判断。决策层就是STM32里的状态机。我没有用RTOS因为任务数量少、实时性要求明确裸机加定时器调度反而更可控。主循环100毫秒跑一轮每轮读传感器、更新状态、刷屏幕、判断是否触发报警。报警分三级一级是预警温湿度超标或烟雾轻微上升二级是告警烟雾持续上升或检测到火焰三级是紧急多传感器同时触发。分级的好处是避免误报直接把总闸拉了实验室里做个加热实验就跳闸谁也受不了。执行层包括蜂鸣器、继电器组、LED指示灯。继电器我留了三路一路控排风扇、一路控总电源、一路备用可以接门禁或声光报警器。这里必须强调继电器控总电源这件事一定要慎重我在代码里做了双重确认三级报警持续5秒以上才动作而且动作后需要手动复位防止自动恢复供电造成二次事故。交互层是OLED本地显示加ESP8266无线上报。本地显示保证断网时也能看到实时数据无线上报让不在实验室的人也能收到告警。上报协议我用的是MQTT主题分三个实时数据、告警事件、设备状态。这样服务端可以分别处理不会因为数据量大把告警淹没了。1.3 开源内容的组织方式这个项目开源的时候我分了四个目录firmware放Keil工程和源码hardware放原理图和PCB文件simulation放Proteus仿真工程docs放说明文档和接线图。这么分是因为不同的人关注点不一样有人只想看代码逻辑有人想直接打板有人想先仿真验证再动手。目录清晰能省掉大量“这个文件在哪”的沟通成本。原理图我用的是嘉立创EDA画的原因很简单免费、元件库全、可以直接一键下单打板。仿真用的是Proteus 8.9虽然Proteus对STM32的仿真支持不算完美但跑通传感器逻辑和继电器动作时序足够了。代码部分我保留了完整的注释关键函数都写了调用示例方便别人改成自己的传感器组合。2. 核心硬件选型与原理图关键细节2.1 传感器选型的实战考量MQ-2烟雾传感器是这个项目里最需要说清楚的器件。它的核心是二氧化锡气敏材料加热后表面吸附氧气遇到还原性气体时电阻下降通过负载电阻转成电压输出。这里有个常见误区很多人以为MQ-2只能测烟雾其实它对液化气、丙烷、氢气、酒精都有响应只是灵敏度不同。实验室里酒精挥发是最常见的干扰源所以我在代码里加了“酒精模式”和“烟雾模式”的阈值切换通过按键选择当前场景。DHT11温湿度传感器精度一般温度±2摄氏度、湿度±5%但胜在便宜和单总线协议简单。消防预警里温度趋势比绝对值更重要所以我用的是一个10点滑动平均加斜率计算温度在30秒内上升超过5摄氏度就触发预警而不是等温度到某个固定值。这个逻辑在实验室场景特别有用因为加热台失控时温度上升很快但绝对值可能还没到60度。火焰传感器我选的是五路红外接收管模块探测波长范围760到1100纳米正好覆盖火焰的 infrared 辐射峰值。这里要注意火焰传感器的数字输出容易受日光灯干扰我在原理图上加了RC滤波代码里也做了连续三次采样确认才认定有效。传感器型号接口方式关键参数实验室适配说明烟雾MQ-2ADC加热电压5V负载电阻可调需预热24小时以上才稳定温湿度DHT11单总线温度0-50度湿度20-90%采样间隔不低于1秒火焰五路红外GPIO/ADC探测角60度距离0.8米避免正对日光灯显示SSD1306I2C128x64像素地址0x78或0x7A通信ESP8266-01SUART波特率115200需独立3.3V供电2.2 电源部分的坑与解决方案消防预警系统对电源的要求比普通项目高因为断电意味着系统失效。我的方案是12V输入经过LM2596降到5V给传感器和继电器再经过AMS1117-3.3降到3.3V给STM32和ESP8266。这里有个细节ESP8266在发射瞬间电流能冲到300毫安以上如果和STM32共用一路3.3V电压会被拉低导致STM32复位。我在原理图上给ESP8266单独加了一路AMS1117输入侧并了470微法电解电容和0.1微法陶瓷电容。继电器部分我用的是光耦隔离加三极管驱动继电器线圈两端反并了续流二极管。这个电路很经典但新手容易犯的错是忘记续流二极管结果继电器断开瞬间的反向电动势把三极管击穿。我在原理图上把续流二极管画得很显眼就是怕别人抄的时候漏掉。提示如果你要用锂电池做备用电源建议用TP4056做充电管理再加一个升压模块到5V。但要注意锂电池在高温环境下的安全风险实验室消防系统本身不应该成为新的火源。2.3 原理图绘制中的可制造性设计画原理图的时候我特意做了几件事方便别人复现。第一所有元件都标注了封装电阻电容用的是0805方便手焊也方便贴片。第二接口全部用排针引出包括SWD调试口、UART口、I2C口这样别人可以用杜邦线快速接线测试不用一上来就焊板子。第三电源部分加了测试点和指示灯上电后能直观看到5V和3.3V是否正常。PCB布局上我把模拟部分MQ-2的负载电阻和ADC走线和数字部分继电器、蜂鸣器分开了地平面做了单点连接。这个做法在低速电路里不是必须的但能减少传感器读数的跳动。实测下来分开铺地之后MQ-2的ADC读数波动从±30降到了±8效果很明显。3. 软件架构与核心代码实现3.1 裸机状态机的设计方法整个软件的核心是一个三级状态机我用枚举定义状态用switch-case处理转移。状态定义如下typedef enum { STATE_IDLE 0, // 正常监测 STATE_WARNING, // 一级预警 STATE_ALARM, // 二级告警 STATE_EMERGENCY, // 三级紧急 STATE_LOCKED // 锁定待复位 } SystemState_t;状态转移的条件不是单一传感器触发而是多条件组合。比如从IDLE到WARNING需要满足温度30秒上升超过5度或者烟雾ADC值超过基线200且持续3秒或者火焰传感器数字口连续3次有效。这种“与或组合”的逻辑能大幅降低误报率。主循环的节奏是100毫秒一轮但传感器采样频率不一样。DHT11最快1秒一次MQ-2可以100毫秒读一次但需要软件滤波火焰传感器可以10毫秒读一次做快速响应。我在定时器中断里做了分频计数每10毫秒置一个标志位主循环根据标志位决定这一轮读哪些传感器。3.2 传感器数据滤波与阈值自适应MQ-2的原始ADC读数噪声很大直接比较阈值会疯狂误报。我用了两层滤波第一层是去极值平均连续采15次去掉最大最小各3个剩下9个求平均第二层是滑动窗口保存最近20个平均值用中位数作为当前有效值。这两层下来读数已经非常平稳。阈值不是固定值而是自适应基线。上电后前60秒系统处于“学习模式”采集环境本底值取平均作为基线。之后阈值设为基线加偏移量偏移量根据当前状态动态调整。比如在WARNING状态下阈值会收紧因为已经有一次异常了系统进入更敏感的模式。#define BASELINE_SAMPLES 600 // 60秒 x 10次/秒 #define WARNING_OFFSET 150 #define ALARM_OFFSET 300 uint16_t baseline 0; uint16_t threshold_warning 0; uint16_t threshold_alarm 0; void update_baseline(uint16_t adc_value) { static uint32_t sum 0; static uint16_t count 0; if (count BASELINE_SAMPLES) { sum adc_value; count; if (count BASELINE_SAMPLES) { baseline sum / BASELINE_SAMPLES; threshold_warning baseline WARNING_OFFSET; threshold_alarm baseline ALARM_OFFSET; } } }这段代码有个细节学习模式期间如果检测到异常会立即退出学习并进入WARNING防止在学习阶段发生真实火情却被忽略。3.3 继电器联动逻辑与安全互锁继电器控制是消防系统里最需要谨慎的部分。我的逻辑是一级预警只开蜂鸣器和LED不动继电器二级告警开排风扇仍然不动总电源三级紧急才断总电源而且必须满足“烟雾和火焰同时触发”或者“温度超过60度且持续10秒”这样的硬条件。断总电源后系统进入LOCKED状态所有继电器保持断开直到人工按下复位键。这个设计是为了防止自动恢复供电。我见过太多项目为了“智能化”让系统自动恢复结果复电瞬间引燃残留可燃气体造成二次事故。void relay_control(SystemState_t state) { switch (state) { case STATE_IDLE: RELAY_FAN_OFF(); RELAY_POWER_ON(); RELAY_AUX_OFF(); break; case STATE_WARNING: RELAY_FAN_OFF(); RELAY_POWER_ON(); RELAY_AUX_OFF(); break; case STATE_ALARM: RELAY_FAN_ON(); RELAY_POWER_ON(); RELAY_AUX_OFF(); break; case STATE_EMERGENCY: RELAY_FAN_ON(); RELAY_POWER_OFF(); RELAY_AUX_ON(); break; case STATE_LOCKED: RELAY_FAN_OFF(); RELAY_POWER_OFF(); RELAY_AUX_OFF(); break; } }注意继电器控总电源的接线一定要在火线上做切断零线保持连通。如果切断零线设备外壳可能仍然带电有触电风险。这个细节在原理图上我用红色标注了。3.4 OLED显示与本地交互设计OLED屏幕我用的是SSD1306驱动I2C接口显示内容分三行第一行是当前状态和持续时间第二行是烟雾ADC值和温度湿度第三行是阈值和系统运行时间。刷新率设为5赫兹太快了没必要太慢了状态变化看不出来。按键我设计了三个模式键、复位键、静音键。模式键切换酒精模式和烟雾模式复位键清除LOCKED状态静音键临时关闭蜂鸣器但保持LED指示。这里有个交互细节静音后如果状态升级蜂鸣器会重新开启防止有人静音后忘记恢复。void oled_refresh(void) { OLED_Clear(); OLED_ShowString(0, 0, state_str[current_state]); OLED_ShowString(0, 2, ADC:); OLED_ShowNumber(32, 2, mq2_value, 4); OLED_ShowString(64, 2, T:); OLED_ShowNumber(80, 2, temperature, 2); OLED_ShowString(0, 4, TH:); OLED_ShowNumber(24, 4, threshold_warning, 4); OLED_ShowString(64, 4, Run:); OLED_ShowNumber(96, 4, run_minutes, 4); OLED_Refresh(); }3.5 ESP8266无线上报与断网处理ESP8266我刷的是AT固件STM32通过UART发AT指令控制。上电后先发ATCWMODE1设为Station模式然后ATCWJAP连WiFi再ATCIPSTART连MQTT服务器。这里有个坑ESP8266的AT固件版本很多不同版本指令返回值不一样我在代码里做了超时重试和返回值模糊匹配不要求完全一致只要包含“OK”或“CONNECT”就认为成功。断网处理是必须的。我的策略是网络断开后本地系统继续正常工作数据存在STM32的Flash里最多存1000条记录。网络恢复后先补发历史告警再发实时数据。这个逻辑用了一个环形缓冲区实现写指针和读指针分开满了就覆盖最旧的数据。typedef struct { uint32_t timestamp; uint16_t mq2_value; uint8_t temperature; uint8_t humidity; uint8_t state; } LogEntry_t; LogEntry_t log_buffer[1000]; uint16_t log_write_idx 0; uint16_t log_read_idx 0; void log_data(uint32_t ts, uint16_t mq2, uint8_t temp, uint8_t hum, uint8_t st) { log_buffer[log_write_idx].timestamp ts; log_buffer[log_write_idx].mq2_value mq2; log_buffer[log_write_idx].temperature temp; log_buffer[log_write_idx].humidity hum; log_buffer[log_write_idx].state st; log_write_idx (log_write_idx 1) % 1000; if (log_write_idx log_read_idx) { log_read_idx (log_read_idx 1) % 1000; } }4. 仿真验证与实物调试对比4.1 Proteus仿真工程的搭建要点Proteus仿真STM32需要加载HEX文件元件库里搜STM32F103C8可以找到。传感器部分MQ-2在Proteus里没有现成模型我用滑动变阻器模拟模拟输出用按钮模拟数字输出。DHT11有现成模型但时序比较严格仿真里偶尔会读失败多试几次就好。仿真最大的价值是验证逻辑不是验证电路。我在仿真里重点跑了几个场景正常监测、烟雾缓慢上升、火焰突然触发、传感器断线。传感器断线这个场景特别重要实物调试时如果传感器线松了ADC读数会飘到0或者满量程代码里必须能识别这种异常并报警而不是当成正常数据。仿真工程里我加了一个虚拟终端把UART输出的调试信息显示出来这样不用接串口线就能看到系统状态变化。这个技巧在调试状态机的时候特别省事。4.2 实物调试中的典型问题记录实物调试遇到的问题比仿真多得多我挑几个有代表性的说。第一个问题是DHT11读失败率高。排查后发现是上拉电阻用了10K换成4.7K之后成功率从70%提升到99%。原因是DHT11的单总线在长导线上的上升沿变缓10K上拉太弱。这个细节在数据手册里没写是实测出来的。第二个问题是继电器动作时OLED花屏。原因是继电器线圈和OLED共用5V动作瞬间电压跌落。解决方案是在OLED的VCC和GND之间并一个100微法电容同时把OLED的供电线单独走一路。第三个问题是ESP8266连不上MQTT服务器。排查发现是AT固件的MQTT指令格式和文档不一致最后改用透传模式STM32直接发MQTT报文ESP8266只做TCP透传。这个方案更稳定因为不用依赖AT固件的MQTT实现。问题现象排查过程根本原因解决方案DHT11读失败示波器看时序上拉电阻过大换成4.7KOLED花屏电压监测继电器动作拉低5V加100uF电容MQTT连不上串口抓AT指令AT固件版本差异改用TCP透传烟雾误报对比环境数据酒精挥发干扰加模式切换火焰传感器误触发遮光测试日光灯红外干扰加RC滤波和连续确认4.3 仿真与实物的差异分析仿真里跑通的逻辑实物不一定跑得通反过来也一样。最大的差异在传感器噪声和电源质量。仿真里传感器输出是理想的实物里MQ-2的读数在加热丝通断时会跳变DHT11在潮湿环境下会读数异常。所以我的建议是仿真用来验证状态机和通信协议实物调试重点解决电源和传感器接口。还有一个差异是时序。Proteus仿真STM32的指令周期和实物有偏差特别是涉及精确延时的单总线协议。我在仿真里DHT11能读成功实物里必须把延时参数微调才能稳定。所以仿真通过不代表实物通过实物通过才是真的通过。5. 开源项目落地与二次开发建议5.1 代码结构的可扩展性设计开源的时候我特意把传感器驱动和业务逻辑分开了。drivers目录下每个传感器一个文件对外只暴露init、read、get_value三个函数。业务逻辑在app目录下通过统一的传感器数据结构访问数据。这样别人想换传感器只需要改驱动文件业务逻辑不用动。typedef struct { uint16_t mq2_value; uint8_t temperature; uint8_t humidity; uint8_t flame_digital; uint16_t flame_analog; uint8_t valid; } SensorData_t; SensorData_t sensors; void sensors_update(void) { sensors.mq2_value mq2_read_filtered(); sensors.temperature dht11_get_temp(); sensors.humidity dht11_get_humidity(); sensors.flame_digital flame_read_digital(); sensors.flame_analog flame_read_analog(); sensors.valid 1; }这种结构的好处是如果你想加一个CO传感器只需要在drivers里加一个文件在SensorData_t里加一个字段在sensors_update里加一行调用。业务逻辑里判断报警条件时多一个或条件就行。5.2 从实验室到小空间的适配改造这套系统虽然是为实验室设计的但改一改可以用在机房、仓库、小型车间。改造的关键是阈值和联动逻辑。机房场景温度控制更严格可以把温度预警阈值从5度/30秒改成3度/30秒。仓库场景可能没有排风扇继电器就只控声光报警器。如果要接入现有的消防系统可以把继电器输出改成干接点信号接到消防主机的输入模块上。这样自建系统作为补充探测不替代原有系统合规性和安全性都更好。5.3 常见二次开发问题速查很多人拿到开源代码后第一件事是改传感器这里整理几个高频问题。换用MQ-135空气质量传感器接口和MQ-2一样但预热时间更长建议24小时以上。阈值偏移量要重新标定因为MQ-135对CO2更敏感。换用SHT30温湿度I2C接口精度更高但需要改驱动。SHT30的I2C地址是0x44和OLED的0x78不冲突。增加4G上报可以用Air724模块AT指令集和ESP8266类似但功耗更高电源要重新设计。增加本地存储STM32F103的Flash只有64KB存不了太多数据。建议外挂W25Q64 SPI Flash可以存几万条记录。提示二次开发时一定要保留看门狗功能。我在代码里开了独立看门狗超时时间2秒主循环里喂狗。如果程序跑飞看门狗复位后系统会重新初始化传感器重新预热虽然有几秒空窗期但比死机强。6. 实操心得与避坑清单6.1 传感器预热与标定的经验MQ-2的预热时间官方说24小时我实测下来通电48小时后的读数最稳定。如果你急着用至少预热2小时但前几天的读数会慢慢漂移。我的做法是在代码里加一个“老化补偿”系统运行时间越长基线自动微调每天调整幅度不超过5个ADC值。标定的时候不要用打火机的气体直接喷浓度太高会把传感器“毒化”灵敏度永久下降。正确做法是用酒精棉球放在传感器附近让它自然挥发观察ADC上升曲线。正常情况应该看到ADC在30秒内上升200到500然后慢慢回落。6.2 继电器选型与寿命管理继电器我推荐用松下的或者宏发的别用太便宜的。便宜继电器的触点容易氧化用几个月后接触电阻变大控总电源时可能发热。我的做法是选10A触点容量的继电器实际只过2到3A的电流降额使用寿命长很多。还有一点继电器动作时会有“咔嗒”声实验室里晚上特别明显。如果放在安静环境可以考虑用固态继电器但没有机械触点断开时仍有微小漏电流控总电源要慎重。6.3 代码调试的实用技巧调试STM32代码SWD比串口打印好用得多。我习惯在关键变量上设watchpoint状态机切换时自动断下来看上下文。Keil的Logic Analyzer功能也能用把GPIO翻转和传感器数据一起看时序问题一目了然。如果手头有Saleae逻辑分析仪抓DHT11的单总线时序特别方便。DHT11的时序是主机拉低18毫秒然后释放传感器响应80微秒低电平加80微秒高电平之后是40个数据位。用逻辑分析仪一看就知道哪里的延时不对。6.4 长期运行的稳定性建议消防预警系统是要7x24小时运行的稳定性比功能多更重要。我做了几件事第一看门狗必须开而且要在主循环里喂不能在中断里喂。第二每个传感器读取失败后重试3次3次都失败就标记该传感器故障但不影响其他传感器工作。第三OLED屏幕每隔24小时清屏一次防止像素老化。第四ESP8266每隔6小时重连一次WiFi防止长时间连接后假死。最后分享一个我踩过的坑一开始我把系统放在实验室角落结果那个位置WiFi信号弱ESP8266频繁掉线。后来把天线引出来贴在铁架子上信号强度从-85dBm提升到-65dBm掉线问题就解决了。所以无线模块的位置比参数调优更重要先解决物理层问题再调软件。
返回列表