做嵌入式这些年,接触过不少单片机项目,但真正让我觉得"拿出来就能用、抄了就能跑"的,还得是这种把代码、原理图、仿真三件套一起打包的开源项目。今天要拆解的就是这么一套:基于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 Flash,20KB 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的PA0(ADC1通道0),数字量输出(TTL引脚)可以接另一个GPIO备用。HC-SR501人体红外感应模块有3个引脚(VCC、GND、OUT),OUT接到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主频跑在72MHz,ADC采样瞬间可能会有内部数字噪声耦合到模拟输入端。最简单的处理方式,是在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/2=32MHz),确保仿真和实物的逻辑行为一致。很多人仿真跑不通,大部分是卡在这个时钟源的问题上。
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就会被禁用,这时候只能用串口ISP(BOOT0拉高)方式先把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、读传感器并显示数据,这样每次装完现场都能快速判断硬件基本功能是否正常。这个习惯帮我在项目调试里节省了大量时间,也推荐你试试。