
1. 为什么做这套安防燃气二合一系统项目初衷与功能边界去年家里装修厨房和入户门之间那堵墙被敲掉做了开放式格局。装完看着是通透但心里始终有个疙瘩燃气灶那头万一有个闪失客厅离厨房太近睡觉时根本听不到厨房里的动静。市面上卖独立燃气报警器的不少几十块到几百块都有但我翻了翻拆解评测多数用的还是低端运放比较器方案报警阈值傻傻的固定既不能显示浓度数值也没法和家里其它传感器联动。与其买一堆各管各的盒子不如自己用STM32做一套同时管安防和燃气监测的系统。这个项目的核心思路很简单一块STM32作为大脑把人体感应、燃气浓度、环境温度这几路信号统一采集进来输出一路声光报警和一路继电器控制。继电器可以用来切断燃气电磁阀也可以接排风扇强制排气。所有状态在本地屏幕显示并预留了串口扩展方便以后接WiFi模块做远程推送。我个人设定了一个原则既然叫智能安防就不能只是响个蜂鸣器得让用户一眼看出现在环境状态到底是什么。功能边界也提前划清楚否则做着做着容易跑偏。第一不做云平台依赖本地逻辑必须完整跑通第二燃气监测优先安防辅助毕竟燃气泄漏是能出大事的第三控制输出要能独立手动操作万一自动控制逻辑出问题还能手动兜底。这套定位决定了我后面所有硬件选型和代码结构的设计方向。适合谁参考这个项目如果你玩过一点STM32基础操作比如点亮LED、读个按键那这套系统的代码量和逻辑难度刚刚好能帮你把GPIO、ADC、定时器、外部中断这些外设串起来做一个真正有用的东西。如果你是纯新手照着原理图和代码焊一套边焊边查手册也能把嵌入式开发的完整流程走通一遍。2. 硬件选型与原理图设计传感器型号对比和电路连接细节确定功能后我开始挑硬件。主控选了STM32F103C8T6也就是大家常说的最小系统板那个芯片。这颗料现在非常便宜淘宝几块钱一片资料铺天盖地Keil下工程配置也不折腾。它自带64KB Flash和20KB RAM跑我这套逻辑绰绰有余加上主频72MHz即便以后想加个简单的RTOS都够用。燃气传感器这块国产玩家基本绕不开MQ-2。它属于半导体气敏传感器内部加热丝把敏感层加热到一定温度遇到可燃气体时电导率变化通过一个负载电阻把变化转成电压。MQ-2对丙烷、丁烷、甲烷都有响应正好覆盖天然气的主要成分。价格五块钱上下一个模拟量输出引脚直接接ADC就行。安防人体感应我选了HC-SR501热释电红外模块。它输出的是数字电平有人活动时输出高电平没人时低电平还自带两个电位器可以调灵敏度和延时。这玩意儿功耗极低很适合做存在检测。但注意它只能感应移动的人体坐着不动超过几十秒它会忘记你这一点我在代码里做了特殊处理后面细说。电源方案上我犯过好几次迷糊这里重点提醒一下。MQ-2的加热丝工作电流接近150mA到200mAHC-SR501标称工作电流50mA左右加上STM32核心板和蜂鸣器继电器瞬间峰值电流可能超过400mA。如果随便用一个USB口供电有些质量差的USB口带不动会造成电压跌落单片机反复复位。所以原理图里我加了一个AMS1117-3.3稳压芯片输入接5V适配器输出3.3V给主控和传感器逻辑供电而MQ-2的加热回路直接走5V不经过稳压避免发热影响主控稳定。五伏输入前还加了一个470uF电解电容和100nF陶瓷电容并联用来吸收继电器吸合瞬间的电流尖峰。电路连接我整理成一张表方便后面焊接时核对模块引脚STM32引脚说明MQ-2模拟输出A0PA1ADC1通道1读取燃气浓度电压HC-SR501OUTPA0外部中断输入检测人体活动蜂鸣器有源I/OPB5高电平触发继电器模块INPB6低电平触发注意模块逻辑OLED显示屏SDA/SCLPB7/PB8I2C通信显示状态和浓度按键1按键输出PB0手动布防/撤防按键2按键输出PB1手动控制继电器开/关继电器模块的选择有个容易翻车的点。市面上常见的低电平触发继电器模块IN引脚输入低电平时继电器吸合高电平时断开。刚上手时我从库房里翻出一个高电平触发的接上去发现代码写反了报警时继电器反而断开。所以画原理图时一定要确认模块的触发逻辑或者干脆用光耦隔离型继电器让主控引脚只驱动光耦LED避免继电器线圈反向电动势烧引脚。原理图里我在继电器IN和GND之间反并联了一个1N4007二极管叫续流二极管作用是在继电器断电瞬间把线圈里的能量泄放掉保护主控引脚。OLED我选了0.96寸I2C接口的四根线搞定显示浓度百分比、温度、布防状态和一排运行状态图标。I2C上拉电阻在模块上已经有了不需要额外画但STM32的I2C引脚需要配置为开漏输出并确保总线上有4.7k上拉这点模块出厂都处理好了只是提醒一下如果自己画板子别漏。3. 软件架构与代码实现状态机逻辑让系统不混乱硬件搞定后最大的工作量在代码。我一开始写的是裸机轮询主循环里读一遍ADC、读一遍红外、扫一遍按键跑起来倒是能工作但问题在于蜂鸣器报警的时候延时函数会卡住整个循环传感器数据更新不及时按键响应也变得迟钝。后来我改成基于定时器中断的前后台架构定时器每50毫秒产生一次中断在中断里只置标志位主循环根据标志位依次处理传感器读取、逻辑判断、显示刷新和报警输出。这样逻辑清晰扩展起来也方便。整个程序的核心是一个状态机我定义了四个状态状态含义进入条件IDLE待机无异常浓度正常且未检测到入侵ALERT_GAS燃气报警燃气浓度超过阈值ALERT_INTRUDE安防报警布防状态下检测到人体活动MANUAL_CTRL手动控制模式用户按按键切换继电器状态机的好处是每个状态对应的行为非常明确不会出现燃气报警响了但同时又在正常显示温度这种逻辑冲突。比如在ALERT_GAS状态下系统每100毫秒刷新一次浓度蜂鸣器响1秒停1秒OLED屏幕上显示红色的GAS!大字同时继电器自动吸合如果接了电磁阀就切断燃气。一旦浓度降到安全阈值以下并持续30秒状态机才自动回到IDLE避免浓度抖动造成继电器反复动作。ADC采集这部分需要处理读数漂移问题。MQ-2在刚上电时输出并不稳定有个预热过程大约前30秒电压会慢慢爬升。我在初始化时连续读取20次取平均值作为基准值后续每隔1秒读一次算出与基准值的差值再利用差值映射到浓度百分比。因为MQ-2数据手册给的是典型的浓度-电压曲线而非线性关系所以我用了一个分段映射表const uint16_t adc_thresholds[] { 500, 700, 900, 1150, 1400 }; const uint16_t gas_percent[] { 2, 5, 10, 20, 40 };这个表的意思是如果ADC采样值12位落在对应区间就线性插值算出当前质量浓度。这个表是我拿标准气样实测后标定的不同批次的MQ-2离散性比较大各位照着抄之后建议用打火机放气但注意安全实际校准一遍。人体红外检测这块我遇到一个常见的坑。HC-SR501默认工作在可重复触发模式也就是说只要人在感应范围内持续活动输出就一直保持高电平但人坐着不动一段时间后输出会变低即使人还在屋里。我的代码里没有直接读电平而是在上升沿触发一次外部中断记录有人活动的时间戳然后系统只要在30分钟内持续收到过中断就认为屋里有人。同时用软件逻辑做消抖连续两次中断间隔小于2秒的只算一次活动防止继电器频繁联动。串口部分我留了USART1每秒输出一帧调试信息包括当前ADC原始值、映射后的浓度百分比、状态机状态和按键动作。这一步强烈建议保留因为焊接完第一次上电时光看OLED可能不知道系统卡在哪里串口信息能帮你快速定位。下面把主循环的骨架贴出来核心逻辑都在里面int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); OLED_Init(); uint16_t baseline Read_Gas_ADC_Baseline(); uint8_t state IDLE; while (1) { if (flag_50ms) { flag_50ms 0; Handle_50ms(); } if (flag_500ms) { flag_500ms 0; gas_voltage Read_Gas_ADC(); gas_level Map_To_Percent(gas_voltage, baseline); Update_Display(state); } if (flag_key_scan) { flag_key_scan 0; Handle_Key(); } switch (state) { case IDLE: if (gas_level GAS_THRESHOLD) state ALERT_GAS; if (armed_flag human_detected) state ALERT_INTRUDE; break; case ALERT_GAS: GPIO_Set_Buzzer(1); GPIO_Set_Relay(1); if (HAL_GetTick() - gas_alert_start 30) { if (gas_level GAS_THRESHOLD) { state IDLE; GPIO_Set_Buzzer(0); GPIO_Set_Relay(0); gas_alert_start 0; } } break; case ALERT_INTRUDE: GPIO_Set_Buzzer(1); if (HAL_GetTick() - intrude_alert_start 30) { state IDLE; GPIO_Set_Buzzer(0); } break; default: state IDLE; break; } } }注意代码里我用了HAL_GetTick()来做超时控制替代了原来的HAL_Delay()报警状态退出条件里加了一个30秒持续判断这算是个细节但很关键否则浓度在临界值上下波动时蜂鸣器会一直响邻居要被烦死。4. 仿真环境搭建与验证用Proteus跑通逻辑再动手焊板画完原理图、写完代码我强烈建议先做一轮仿真再动电烙铁尤其如果你是第一次玩STM32仿真能帮你把接线和逻辑错误在烧坏硬件之前暴露出来。我用的是Proteus 8.9虽然它自带的STM32模型没有真实硬件那么完整但足够验证状态机逻辑和ADC采集流程。仿真文件我搭了三个部分大家如果有兴趣复现可以直接对照搭第一部分是STM32F103C8T6的最小系统电路包含8MHz晶振、复位电路和BOOT0下拉。Proteus里STM32的晶振配置要注意它默认使用HSI内部时钟如果你代码里用了HSE外部晶振仿真会报错或者不跑我这里直接把SystemClock_Config()里改成HSI或者把工程里的HSE_VALUE改成8MHz两条路都可以。第二部分是模拟输入源。因为Proteus里没有现成的MQ-2模型我用了两个电位器来模拟传感器输出一个接ADC引脚用来手动调节浓度另一个接地用来模拟基准值。仿真时我拖动电位器改变电压观察OLED显示和状态跳转。这里有个小技巧把ADC读取的原始值放到一个全局变量里然后在仿真里通过串口波形窗口观察它随电位器变化的曲线能更直观地看到数据链路通没通。第三部分是逻辑分析仪。Proteus里有逻辑分析仪工具我把蜂鸣器和继电器的引脚探针接上去观察它们在报警状态下的波形。比如燃气报警时蜂鸣器引脚应该出现周期性的1s高1s低波形继电器应该是稳定的高电平直到退出报警。如果发现波形频率不对多半是延时逻辑里的HAL_GetTick()用法有问题。仿真跑通之后我对逻辑的信心足了很多。但仿真毕竟只是逻辑层面的验证真正焊完板子后问题还是不少。我给个建议仿真验证的是思路对不对真实硬件的价值是验证电路稳不稳。所以仿真阶段不用过度纠结模拟量的精度把更多时间花在验证状态转移和边界条件上比如浓度突然拔高、人体检测和燃气报警同时触发哪种模式优先级更高——我的设计是燃气报警优先级高于防入侵因为燃气泄漏是更危险的事件。5. 实物焊接与调试传感器校准和供电稳定性处理的实战记录实物焊完在调试台上躺了两天我才把问题完全清干净。最大的坑来自MQ-2的预热时间。第一次上电我每隔一秒读一次ADC发现数值一直在缓慢上升大概过了十几分钟才稳定在一个较高值。开始我以为是传感器坏了后来查资料才明白半导体气敏元件在通电后内部加热丝把表面温度提升到工作温度氧化物层的表面电阻需要一段时间才能稳定。所以程序里初始化的基准值不能在上电瞬间读而应该延时3分钟后读或者干脆在上电后翘起一块标定中的重置标志直到系统自动确认ADC基线稳定才开始正常报警。第二个坑是继电器模块的误动作。仿真时继电器模块逻辑很干净但实物在继电器吸合的瞬间STM32偶尔会复位一次。用万用表量电压发现5V电源在继电器吸合瞬间有个明显的跌落尖峰超过0.6V。我在电源输入端加了大电容后改善了一些但彻底解决是换成光耦隔离型继电器让继电器回路的电流不经过主控电源地。如果你手头已经有低电平触发继电器可以在继电器模块的电源引脚和主控电源之间串一个小功率的TVS管或者压敏电阻并确保继电器地线单独是一根粗线回到电源不要经过STM32核心板的GND。第三个坑是HC-SR501的误触发。这模块耐造但抗干扰能力一般。我放在实验室桌子上旁边一台老款手机充电器一插它时不时就自己触发一次。排查下来发现是充电器产生的电磁干扰耦合到模块输出线上。解决方法是把模块输出到PA0的杜邦线换成屏蔽线并且在代码里做了一级软件低通滤波外部中断里不立即置位human_detected而是先把一个记数变量加1累计3次中断且间隔不超过2秒才确认有人活动这样就能过滤掉单次尖峰干扰。实测误触发从每天几十次降到了几乎为零。第四个问题是OLED刷新卡顿。刚开始我在主循环里每次直接调OLED_ShowString刷新刷新一次I2C通信就要2毫秒屏幕上出现肉眼可见的闪烁而且在刷新期间中断进来了会造成数据错乱。后来源码里改成双缓冲用一块显存数组维护要显示的内容定时器每100毫秒触发一次把显存打到OLED上。我在中断优先级上把I2C DMA中断设为最高优先级防止其它外设干扰显示数据这样屏幕非常干净。校准这块我多说两句。MQ-2的输出和浓度不是线性的而且受温度和湿度影响很大。我的做法是在窗口通风良好的环境下把基准值标定为0%然后用打火机放一秒钟气体等ADC读数稳定后把打火机气体浓度按100% LEL爆炸下限的百分比做粗标再映射到2%、5%、10%这些档位。这个方法精度当然比不上专业标定仪但家用场景完全够用灵敏度设定在8% LEL左右报警远低于燃气爆炸下限。6. 开源文件怎么组织代码、原理图和仿真项目的目录结构建议既然定位成开源项目文件结构就得让拿到的人能快速上手。我整理成了下面这个布局smart-safety-gas-monitor/ ├── README.md ├── Hardware/ │ ├── schematic_v1.1.pdf │ ├── schematic_v1.1.sch (立创EDA源文件) │ └── BOM.csv ├── Firmware/ │ ├── Core/ │ ├── Drivers/ │ ├── Middlewares/ │ └── project.uvprojx (Keil工程) ├── Simulation/ │ └── safety_gas_sim.pdsprj (Proteus工程) ├── Documentation/ │ ├── calibration_notes.md │ └── wiring_guide.md └── 3DModels/ (可选外壳文件)README里我写了五样东西功能特性、硬件清单和接线表、快速上手步骤、状态机说明、常见问题FAQ。BOM清单里尽量标了购买代用的型号比如电容电阻给封装尺寸继电器模块给5V低电平触发带光耦这类关键词方便大家搜。仿真和代码虽然都能直接打开但真正复现时最容易出问题的还是Keil的芯片型号选择。我用的Keil 5在Device选型里要选STM32F103C8同时在C/C选项卡的Define里加上STM32F103xB宏定义不然HAL库的管脚定义会不匹配。Proteus仿真需要安装STM32芯片的模型文件如果你打开工程提示找不到芯片去Proteus的Library Manager里更新一下STM32库。整个项目文件我打包放在一个目录里代码注释写了大量中文命名也尽量语义化比如Read_Gas_ADC()和Set_Alert_State()拿到手不看我文章也能看懂大概。开源的意义不只在于分享最终结果更在于让别人能沿着你的思路再改进比如加上ESP8266模块做微信推送或者加个按键密码键盘做撤防布防这些扩展方向我已经在README里列了几个待办事项。7. 好好用这套系统的几个经验安装位置、报警阈值和日常自检建议硬件跑稳定、软件逻辑理顺之后剩下的就是怎么把一套DIY设备用好。安装位置比想象中重要得多燃气传感器不是随便找个地方挂上就好。按照燃气泄漏扩散规律天然气比空气轻泄漏后会向上飘传感器应该安装在距离天花板30厘米以内、且靠近燃气管道和灶台的位置。但要避免直接安装在灶台上方那种地方烹饪油烟会严重干扰传感器造成无数误报警。更合适的做法是装在厨房对角线方向的墙壁高处既避开烟机气流又能捕捉到泄漏气体扩散后的浓度。人体感应模块的安装位置也讲究。不要正对窗户或暖气太强烈的红外辐射变化会触发误报。我装在入户门内侧上方约2.2米斜向下45度覆盖玄关到客厅的通道。这个位置既能检测到推门进来的外人又不会让窗外经过的人引发误报。报警阈值设定分两档。我默认设的是浓度达到3% LEL时LCD上显示预警状态但不响铃只点亮一个黄色小图标达到8% LEL时进入报警状态蜂鸣器响且切断电磁阀。8%这个值不是拍脑袋拍的天然气爆炸下限大约是5%体积浓度8% LEL相当于空气里的甲烷含量刚刚达到0.4%距离危险还很远这时候报警既不会等太久也不会因为偶尔的油烟挥发送来误报。如果是老旧社区管道气气源杂质多我建议把预警值下调到2% LEL报警值保持8%不变。日常自检不能省。我每周五固定做一次全链路测试用打火机在传感器进气口放一秒钟气观察是否在10秒内进入报警状态然后按一下按键手动切换继电器确认电磁阀能正常断开最后用毛巾遮挡人体传感器看是否触发布防逻辑。这一套下来五分钟搞定但能确保设备在关键时刻不会掉链子。电源方面我给系统配了一个5V 2A的开关电源适配器长期满负荷带载毫无压力。但千万别图便宜用劣质充电头之前我试过一个标称1A的带继电器吸合瞬间电压掉到3.8V屏幕直接白屏。后来换成正规品牌适配器这套系统已经连续运行三个月没掉过一次线。再分享一个连资深玩家都可能忽略的细节STM32的ADC输入阻抗问题。MQ-2的模拟输出内阻比较大直接接PA1引脚时如果板上走线很长可能会引入额外压降。我在PA1和对地之间加了一个100nF电容用来滤除高频干扰再用一个电压跟随器电路就一个运放芯片比如MCP6001做缓冲实测ADC读数稳定。如果你只在面包板上玩加个小电容就够但做成PCB就必须考虑阻抗匹配了。这套项目做下来我最大的体会是嵌入式开发里真正耗时间的不只是代码和电路而是调试过程中的每一个为什么——为什么电压会掉、为什么传感器会漂、为什么误报。把这些为什么记录下来其实就是最好的文档。我的代码和原理图已经整理好仿真文件也调通了希望拿到这套资料的朋友不要只是焊完听个响可以试着改改阈值、换换传感器、加个远程模块让这套系统真正贴合你自己的居住环境。