
做嵌入式这些年接触过不少单片机项目但真正让我觉得拿出来就能用、抄了就能跑的还得是这种把代码、原理图、仿真三件套一起打包的开源项目。今天要拆解的就是这么一套基于STM32的智能安防与燃气监测系统。它的定位很清晰——用STM32做主控配合燃气传感器、人体红外感应模块、蜂鸣器报警和数码管/LCD显示把家庭环境里最关键的燃气泄漏和非法入侵两个场景管起来。无论你是正在做课设的在校生、想入门STM32的嵌入式爱好者还是需要一套稳定安防逻辑做产品原型的开发者这套系统的硬件构成和软件框架都有值得参考的地方。我结合自己平时调板子的经验把里面的原理、代码逻辑、仿真验证以及实焊过程中容易踩的坑一起聊透。1. 项目源起这个燃气安防系统到底解决了什么问题1.1 家庭场景里真实的安防需求先别急着看代码任何一个项目都先想清楚它为什么存在。燃气泄漏和入室威胁是家庭环境里两个典型的安全痛点。燃气泄漏的可怕之处在于它的隐蔽性——甲烷、一氧化碳这类气体无色无味民用燃气会加臭但泄漏到一定浓度时人可能已经处于危险状态靠人鼻子去闻根本不现实。而人体入侵则更强调及时性如果等到盗贼已经进屋才反应报警的意义就打折了。这套系统把这两个需求合并到一块STM32最小系统上燃气传感器负责浓度检测红外热释电模块负责有人闯入感知两者任何一个触发条件满足系统立刻驱动蜂鸣器发出高分贝报警同时通过LED和数码管/LCD把当前的浓度档位和状态显示出来。整个逻辑链路不算复杂但它在教学和产品验证两个层面都有价值——对学习来说它把ADC采集、GPIO中断、定时器扫描、状态机切换这些STM32基础外设全部串起来了对产品验证来说它验证了多传感器融合集中控制报警输出这一套经典架构在小成本方案里的可行性。1.2 系统整体能力规划在动手画原理图之前我先把这个系统的功能边界列了个清单这也是我拿到任何项目都会做的第一件事燃气浓度检测通过ADC读取MQ系列传感器的模拟输出电压换算成浓度等级。人体感应热释电红外传感器输出数字电平信号直接送入GPIO。声光报警触发后蜂鸣器响LED以特定频率闪烁这里会涉及到如何去驱动一个有源的或者无源的蜂鸣器。状态显示当前燃气浓度档位、系统布防/撤防状态、报警状态通过数码管或者LCD1602显示。按键交互支持一键布防/撤防、报警阈值调节、报警消音等基础操作。仿真支持能在Proteus里完整跑通以上逻辑不依赖真实硬件也能做逻辑验证。规划完功能接下来面临的就是选型问题了。这也是很多初学者最容易纠结的地方——STM32型号那么多燃气传感器选MQ-2还是MQ-5热释电模块用HC-SR501还是直接贴片BISS0001芯片每个选择背后其实都有非常现实的理由。2. 硬件选型与电路设计每个元器件背后的取舍2.1 主控选择为什么是STM32F103系列这个项目用到的外设其实不多理论上8位单片机比如STC89C52或者AVR也能做。但我个人强烈推荐用STM32F103原因有三点第一ADC精度和稳定性。燃气传感器的输出是一个模拟电压信号需要用ADC来做数字化。STM32F103内置12位ADC采样精度远高于8位单片机的10位或者更低的精度即使燃气浓度变化很微弱也能够被捕捉到。第二处理余量。安防系统往往需要同时处理传感器采集、按键扫描、显示刷新和报警逻辑F103主频72MHz跑这套逻辑绰绰有余后续如果还要加WiFi模块上报云端或者加一个OLED显示曲线完全不需要换主控。第三生态。F103可以说是STM32圈子里资料最全、踩坑记录最多的型号对开源项目来说这意味着用户拿到手遇到问题能自己搜到答案这点非常关键。具体型号方面我建议直接用STM32F103C8T6。它属于F103系列里价格低、Flash和RAM够用64KB Flash20KB RAM、引脚数量适中的型号48脚LQFP封装手工焊接也不难。在Proteus仿真里这个型号有现成的元件模型这也给后面的仿真验证提供了方便。2.2 燃气传感器选型MQ-2还是MQ-5燃气传感器是这套系统里最核心的感知元件。市面上最常见的两款是MQ-2和MQ-5。这两个都属于半导体气敏传感器工作电压5V输出形式有两种一种是数字量输出通过板载电位器调节阈值另一种是模拟量输出接一个负载电阻后从传感器直接引出电压信号。MQ-2的检测范围比较广对液化气、丙烷、氢气、甲烷都有响应属于多面手但选择性一般。MQ-5对液化气和天然气主要成分就是甲烷的响应灵敏度更高更适合家用的燃气泄漏检测场景。如果从项目演示和通用性考虑用MQ-2会更容易在仿真和实物中体现浓度变化如果从更贴近真实应用场景考虑MQ-5是更合适的选择。在这套开源项目里建议按MQ-2来做原因很实在Proteus元件库里有MQ-2模型仿真可以直接拿到模拟量变化演示效果更直观。实物配套模块在淘宝上遍地都是价格也就几块钱接入电路极其简单。需要注意MQ系列传感器内部有一个加热电阻所以上电后有大概30到60秒的预热时间这段时间里输出是不稳定的软件上要做延迟处理不能一上电就立刻采数。2.3 关键外围电路细节确定了主控和传感器之后外围电路的设计就直接影响系统能不能稳定跑起来。电源电路方面系统需要一个稳定的5V供电给传感器加热和蜂鸣器同时还要给STM32提供3.3V。所以整个电源链路就是外部5V输入 → 滤波电容 → AMS1117-3.3稳压 → 3.3V给MCU。AMS1117这个LDO便宜、稳定、外围只需要两个电容非常适合这种项目。在5V输入端口和3.3V输出端各加一个100uF电解电容和一个104瓷片电容做去耦这是常规操作。传感器与MCU之间的连接MQ-2的模拟输出直接接STM32的PA0ADC1通道0数字量输出TTL引脚可以接另一个GPIO备用。HC-SR501人体红外感应模块有3个引脚VCC、GND、OUTOUT接到STM32的PB5之类的GPIO上配置为输入模式即可。报警执行机构是最容易翻车的地方。蜂鸣器分有源和无源两种有源蜂鸣器内部自带震荡源只要给高电平或者低电平取决于模块就能响无源蜂鸣器需要外部提供一定频率的方波才能发声。这套系统里建议用有源蜂鸣器因为逻辑简单——GPIO输出高电平就响输出低电平就停不用配置定时器PWM也能避免PWM频率不对导致蜂鸣器声音发闷的问题。如果希望报警声有节奏感软件上做200ms间隔的开关就能实现嘀—嘀—嘀的报警效果。按键部分考虑到STM32的GPIO资源充足直接做独立按键接法每个按键一端接GND另一端接GPIO内部启用上拉按下为低电平松手恢复高电平。注意在软件里做10到20毫秒的消抖否则一次按下会被误判成多次。3. 原理图设计要点照着画板子之前这几块电路必须看懂3.1 主控最小系统电路STM32F103C8T6的最小系统电路不算复杂但每一部分都有它的必要性。晶振电路推荐用8MHz无源晶振加两个20pF负载电容接到OSC_IN和OSC_OUT引脚。系统内部PLL可以把8MHz倍频到72MHz作为主频。复位电路用10K上拉电阻加0.1uF电容到NRST引脚按键可以接一个手动复位调试的时候方便。BOOT0引脚通过10K电阻下拉到GND让MCU从Flash启动这点如果漏接或者接反了STM32会一直进不了用户程序。3.3V供电引脚上我习惯在每个VDD引脚旁边放一个104去耦电容电容尽量靠近对应引脚。如果项目里用了ADC还要特别注意VREF如果有的话或VDDA引脚要单独接一个磁珠或小电阻再并电容滤波这样可以明显降低ADC采样的噪声。3.2 燃气检测与信号采集电路MQ-2模块的电路核心是一个分压结构传感器的体电阻会随着可燃气体浓度降低通过与一个固定阻值的负载电阻串联分压在负载电阻上得到一个和气体浓度正相关的电压信号。这个电压信号可以直接被STM32的ADC采集。如果直接用MQ-2的模块而非裸传感器模块上已经集成了比较器和电位器模拟输出引脚直接连MCU的ADC引脚就行电路上不需要再额外处理。这里我特别想强调一个容易被忽略的点模拟信号的完整性。燃气传感器的输出信号属于慢变信号频率很低但如果STM32主频跑在72MHzADC采样瞬间可能会有内部数字噪声耦合到模拟输入端。最简单的处理方式是在ADC引脚和GND之间加一个100nF的滤波电容组成一个低通滤波器。这个电容能滤掉高频噪声但对缓慢变化的浓度信号几乎无影响成本却只有几分钱效果非常明显。3.3 报警与显示电路蜂鸣器驱动电路要看蜂鸣器工作电流决定是否需要三极管驱动。ST常用的有源蜂鸣器模块一般自带三极管驱动电路可以直接用GPIO驱动此时把GPIO配置为推挽输出就行。如果用的是裸蜂鸣器驱动电流超过GPIO的驱动能力F103单个IO的最大灌电流约25mA拉电流更小就要用一个NPN三极管如S8050做开关管GPIO通过1K电阻接三极管基极发射极接地集电极接蜂鸣器负极蜂鸣器正极接5V。这样GPIO输出高电平三极管导通蜂鸣器响。数码管显示方面常见的4位一体共阴数码管需要段选和位选两组引脚。段选引脚通过限流电阻接到GPIO位选通过三极管驱动采用动态扫描方式轮流点亮各个位。刷新频率要大于50Hz才不会让人眼看到闪烁实际用定时器中断做2ms刷新一位4ms完整扫一遍肉眼完全感觉不到抖动。如果嫌数码管扫描代码费事也可以用LCD1602只要接8根数据线或者4线模式接4根加2根控制线初始化之后直接用现成的显示函数写起来更舒服一点。4. 软件架构与核心代码逻辑从初始化到报警联动的完整流程4.1 工程结构与模块划分软件是整个系统的灵魂。这套系统如果按照功能模块来划分代码可以分成四个层次驱动层GPIO、ADC、TIM、显示、按键这些硬件驱动的初始化与基本读写函数。传感器层封装燃气浓度读取、热释电状态读取的逻辑内部处理滤波和阈值判断。应用层状态机管理负责布防、撤防、报警、消音等状态的切换。主循环轮询按键、刷新显示、检查报警条件。工程结构上我用的是标准库加HAL库混用的方式。基础外设初始化用HAL库可以快速搞定核心逻辑尤其是ADC采样和按键消抖用标准库或者直接寄存器操作代码效率更高逻辑也更透明。开源包里如果用的是标准库版本动手移植到HAL库也不会太难核心逻辑不变。4.2 主循环与状态机设计安防系统的核心问题是系统必须随时响应外部事件但不同场景下它要做的事情、报警的触发条件是不一样的。直接把这个逻辑写成一坨if语句嵌套后期改一个阈值可能要翻大半天代码。所以这里我强烈建议用状态机来管理。我设计的状态分四种STATE_DISARMED撤防系统不上报入侵事件仅显示燃气浓度。STATE_ARMED布防监测燃气浓度和人体感应一旦燃气超阈值或者检测到人体进入报警状态。STATE_ALARM报警蜂鸣器鸣叫LED闪烁等待消音/撤防指令。STATE_SILENT消音报警被手动消音但浓度显示继续若浓度继续上升或再次触发入侵则重新报警。主循环的伪逻辑大致是while (1) { key_scan(); // 扫描按键处理按键消抖 update_gas_level(); // 读取ADC更新燃气浓度等级 update_display(); // 刷新数码管或LCD显示 switch (sys_state) { case STATE_DISARMED: if (key_disarm_pressed()) arm_system(); break; case STATE_ARMED: if (gas_level threshold || pir_detect()) { sys_state STATE_ALARM; alarm_start(); } if (key_disarm_pressed()) disarm_system(); break; case STATE_ALARM: if (key_silence_pressed()) // 消音按键 { sys_state STATE_SILENT; alarm_stop(); } else if (key_disarm_pressed()) { disarm_system(); } break; case STATE_SILENT: if (gas_level threshold || pir_detect()) { sys_state STATE_ALARM; // 再次触发 alarm_start(); } break; } }这个循环看起来简单但它是所有逻辑的骨架。每个状态内部的细节都值得展开接下来我会挑几个关键环节单独讲。4.3 核心模块代码讲解首先是ADC采集模块。ADC的配置有几个关键参数采样时间为最大采样周期55.5个周期转换模式用单次转换这样能获得最高的准确性。为了进一步降噪我在软件里做了一次均值滤波连续采样8次去掉最大值和最小值剩余6次取平均这样得到的浓度值非常稳定不会因为传感器噪声产生脉冲式的误报警。uint16_t read_gas_sensor(uint8_t times) { uint32_t sum 0; uint16_t min_val 4095, max_val 0; uint16_t val; for (uint8_t i 0; i times; i) { val adc_read_single(ADC_CHANNEL_0); sum val; if (val min_val) min_val val; if (val max_val) max_val val; } sum sum - min_val - max_val; return sum / (times - 2); }浓度等级划分方面12位ADC满量程是4095。实测下来在正常空气环境中MQ-2传感器的输出电压对应ADC读数大概在600到900之间这个数值会因为供电电压和传感器个体差异浮动。我习惯把浓度档位分成三档正常1500、预警1500~2500、危险2500。阈值可以做成变量通过按键在运行时调整这样系统就不需要每次改阈值都重新编译。再看按键消抖。按键处理是嵌入式项目里最容易被轻视但最影响体验的部分。我的做法是在定时器中断10ms周期里扫描按键状态记录连续N次采样结果如果都相同才认为按键有效。void TIM_IRQHandler(void) { static uint8_t key_filter_cnt[4] {0}; uint8_t raw_level; for (uint8_t i 0; i 4; i) { raw_level HAL_GPIO_ReadPin(KEY_PORT, key_pin[i]); if (raw_level 0) // 按下低电平有效 { if (key_filter_cnt[i] 5) key_filter_cnt[i]; if (key_filter_cnt[i] 5) { key_pressed[i] 1; // 置标志主循环处理 } } else { key_filter_cnt[i] 0; } } }这套消抖逻辑等效于50ms的确认时间比单纯的延时消抖可靠得多同时因为消抖发生在中断里主循环不会被阻塞。我在实际项目中一直用这种方式处理按键非常稳。再讲显示刷新。如果采用位选扫描每一位的显示数据要读取事先定义好的数码管段码表。0到9的字形段码可以用一个数组存起来显示函数根据需要显示的数字从数组里查表。扫描过程中先关掉所有位选消影再送出段码然后打开当前位的位选这样不会出现拖影。5. Proteus仿真验证不焊板也能把逻辑彻底跑通5.1 仿真环境搭建要点Proteus一直是我做单片机项目前期验证的首选工具特别是这种带传感器和报警外设的系统完全可以在不碰电烙铁的情况下把核心逻辑跑通。搭建步骤很简单在Proteus里放置STM32F103C8T6元件连接好电源和地把MQ-2气体传感器模型、HC-SR501人体感应模型也可以用一个按钮模拟、蜂鸣器、LED、数码管等外设都放上去然后导入编译好的HEX文件点击运行。仿真里最容易出错的地方是时钟配置。Proteus的STM32模型默认使用内部RC时钟如果程序里配置的是外部8MHz晶振倍频到72MHz仿真时可能出现程序跑飞或者定时不准确的问题。我的建议是仿真阶段直接把程序里的时钟源改成内部时钟HSI并适当降低主频比如用HSI/232MHz确保仿真和实物的逻辑行为一致。很多人仿真跑不通大部分是卡在这个时钟源的问题上。5.2 仿真调试中暴露的问题我在用这套系统做仿真验证时遇到了两个典型问题。第一个问题是MQ-2模型在仿真里的初始输出电压非常高接近5V导致系统一上电就直接进入报警状态。这其实是正常现象也模拟了真实MQ-2传感器的预热特性。解决方案就是在软件里加入系统上电后60秒内不进行浓度判断的处理这段时间只做显示不触发报警。这个逻辑在真实环境中同样必要因为MQ系列传感器加热电阻需要几十秒才能让敏感层到达工作温度。第二个问题是数码管动态扫描在仿真里的刷新率问题。如果软件里的延时函数是基于重装载值计算错误仿真运行速度会变得异常肉眼能看到明显的闪烁。这个问题的根因通常是系统时钟配置和实际仿真时钟不匹配。调时钟配置或者改延时参数之后闪烁就消失了。这类问题在仿真阶段暴露出来其实是好事如果直接上实物问题排查就得动用示波器了。5.3 仿真和实物的差异意识这里我多说一句很多新手容易犯的错误是仿真跑得通就觉得万事大吉。仿真模型毕竟是对真实器件行为的近似模拟它不会模拟传感器老化、电源纹波、按键抖动时序差异、蜂鸣器驱动不足这些现实问题。仿真最大的价值在于验证逻辑正确性而不是替代真实环境测试。我在做这套系统时仿真只用来确认状态机切换逻辑、阈值判断和显示结果符合预期真正的可靠性验证必须在实板上做。6. 实物调试踩坑记录从原理图到真实硬件之间隔着什么6.1 传感器上电后读数一直在跳怎么定位实物调试的第一步往往是先看传感器读数是否稳定。我自己第一次焊好这块板子时烧录程序后通过串口打印ADC读数发现数据一直在700到1000之间大幅跳动。这个时候不要急着怀疑传感器坏了先分清是电源噪声还是信号本身的问题。我的排查路径是这样的先拿万用表量传感器模块的VCC对GND电压发现电压在4.92V到5.08V之间波动这个幅度对于MQ模块来说其实不算小。换用USB供电后用示波器看5V引脚纹波大概有80mV属于比较明显的情况。解决方法是在传感器电源引脚附近加一个100uF电容和一个104电容做二级滤波再串接一个10R电阻把电源噪声压到20mV以内。做完这一步ADC读数就稳定多了。6.2 报警误触发频繁关键其实在阈值和去抖另一个高频问题就是误报。系统布防状态下燃气浓度偶尔的尖峰或者PIR传感器受到热源干扰比如空调出风、日光直射都会导致误报警。燃气浓度误报的根因在于ADC采样偶尔出现一个异常跳变的尖峰这时候单纯的平均滤波已经不够了我改成连续N次超阈值才确认报警的判断逻辑。#define CONFIRM_COUNT 5 uint8_t alarm_confirm_count 0; if (gas_level threshold) { if (alarm_confirm_count CONFIRM_COUNT) alarm_confirm_count; else alarm_trigger(); // 连续5次确认触发报警 } else { alarm_confirm_count 0; }PIR传感器的误报则更多是安装位置和灵敏度的物理问题。HC-SR501背后通常有两个电位器一个调节灵敏度感应距离一个调节延时时间。我建议把灵敏度调到中等偏低避免它把窗外路过的汽车热量或者空调热风识别成人。同时软件里加一条人体感应触发后系统在10秒内不重复触发报警这个逻辑能有效避免上次报警还没撤防PIR又连续触发的情况。6.3 烧录和下载问题最后聊一下烧录。这个项目如果用ST-Link烧录的话接线非常标准SWDIO接PA13、SWCLK接PA14、GND接GND如果目标板是3.3V供电还可以接3.3V给ST-Link做参考电平。我用ST-Link遇到过几次No target connected的问题排查下来发现是板子上的SWD引脚被复用成普通GPIO了。如果程序里初始化了PA13、PA14做其它功能SWD就会被禁用这时候只能用串口ISPBOOT0拉高方式先把Flash擦掉或者跳线到System Memory重新烧入一段没有禁用SWD的代码。如果手头用的是DAPLink或者其他CMSIS-DAP调试器接线方式和ST-Link基本一致工具链支持上也差不多只要在IDE的调试配置里选对CMSIS-DAP即可。烧录这个问题在开发初期耽误了我不少时间现在每次画板都会预留SWD接口的4根排针方便随时刷机。7. 开源资源导览与二次开发思路7.1 开源包里的文件都是干什么的这套开源项目的压缩包打开之后应该能看到几个典型的目录核心板原理图源文件通常是原理图工程或PDF、PCB文件如果是完整开源、KEIL/MDK工程目录核心代码、Proteus仿真工程以及一个README说明文档。拿到包之后不要急着编译烧录先按下面顺序过一遍先看README这里会写清楚开发环境版本、接线说明、烧录方式和常见问题。打开原理图PDF对照PCB或者实物照片把每个元器件的位号和连接关系搞明白。打开仿真工程先让仿真跑起来确认整体功能逻辑符合预期。最后再看代码工程重点理解main.c里的初始化和主循环逻辑。这样做的好处是先建立整体认知再深入细节不至于一头扎进几千行代码里懵圈。7.2 如何把它扩展成一套完整的家居安防系统这套系统做出来之后其实只是一个起点。如果把思路放宽扩展点非常多无线通信升级增加ESP8266或ESP32模块通过串口与STM32通信把燃气浓度和报警状态推送到手机App或者微信小程序。关键是厘清协议——串口通信建议把数据帧格式定义成帧头、数据类型、数据长度、内容、校验和的格式方便对端解析。多传感器接入在I2C总线上挂接SHT30温湿度传感器在另一个ADC通道接烟雾传感器MQ-7用于一氧化碳检测报警逻辑可以增加多条件组合判断比如高温燃气双重条件才触发。联动控制检测到燃气泄漏时除了报警还可以驱动继电器控制电磁阀切断燃气或者控制排风扇打开通风。这一步需要增加一个5V继电器模块MCU的GPIO通过三极管驱动继电器线圈继电器触点再接外部220V设备。注意强弱电隔离这部分如果做产品级设计必须格外谨慎。低功耗改造如果要用电池供电F103可以进入STOP模式通过PIR中断唤醒唤醒后再完成浓度检测和逻辑判断。不过F103的低功耗表现只能说够用真正要追求长续航建议换成STM32L4系列。我自己在扩展这块时的一个体会是先加通信再加联动。通信让系统从现场报警变成远程提醒这是最直观的价值提升联动则是从报警跨到处置需要更周全的安全逻辑设计。这两个方向都能让这套开源项目的价值成倍释放。最后想分享一个小技巧不论你怎么扩展代码里永远给关键模块留一个测试模式。比如按住按键3秒进入自检模式系统自动依次触发蜂鸣器、翻转LED、读传感器并显示数据这样每次装完现场都能快速判断硬件基本功能是否正常。这个习惯帮我在项目调试里节省了大量时间也推荐你试试。