简介:本资源面向嵌入式初学者与电子设计竞赛参赛者,提供一套基于STM32单片机的室内燃气监测报警系统完整设计方案,用于解决燃气浓度实时检测与超标报警的工程实践问题。压缩包共202个文件,约3.89MB,以c与h源码文件为核心,配合d、o、crf等编译中间文件,以及hex固件、uvproj工程、pdsprj仿真工程和pdf说明文档,覆盖从源码到仿真验证的完整链路。系统以STM32为核心,通过ADC采集MQ系列气体传感器信号,经阈值比较算法判断浓度是否超标,并驱动LCD1602显示实时数值与报警状态,同时联动LED与蜂鸣器实现声光报警,软件基于Keil与STM32CubeMX开发,可在Proteus中完成虚拟联调。目前已有4071人学习下载,读者可借此掌握ADC采样、中断处理、外设接口驱动与嵌入式程序结构,获得可直接复用的工程模板与排错参考。
1. 从一份“基于STM32单片机的室内燃气监测报警系统设计”说起:为什么它值得你花一个周末跑通
厨房里那根橡胶软管老化、接口松动,燃气在密闭空间里慢慢积聚,人却毫无察觉——这类事故的新闻每年都能看到。基于STM32单片机的室内燃气监测报警系统设计,本质上就是用一个几十块钱的MCU,把“看不见的气体浓度”变成“看得见的数字和听得见的报警”。它解决的不是高精尖问题,而是把传感器采集、阈值判断、声光报警、执行联动(比如关阀、开排风)串成一条可靠的闭环。适合谁?适合刚学完STM32 GPIO和ADC、想找一个完整项目练手的学生;也适合做智能家居、安防类产品的工程师,拿它当最小可行原型。我见过太多人卡在“传感器读出来全是0”或者“报警器半夜乱叫”上,这篇就把这些血泪经验摊开讲。
2. 选型与原理:MQ系列传感器、STM32最小系统和报警逻辑怎么搭
2.1 为什么是MQ-2/MQ-5,而不是催化燃烧或红外
室内燃气监测,核心是检测甲烷(天然气主成分)或液化石油气。常见方案有三类:催化燃烧式、红外光学式、半导体式。催化燃烧式精度高但功耗大、寿命短,红外式贵且需要光路清洁,而MQ系列半导体传感器便宜、驱动简单、对甲烷和LPG都有响应,非常适合低成本报警器。MQ-2对可燃气体敏感,MQ-5对液化气更专一,MQ-4偏甲烷。选型时看你要测什么气:天然气选MQ-4或MQ-2,液化气选MQ-5。注意,MQ传感器输出的是模拟电压,浓度越高电压越高(负载电阻上的分压),但它的输出受温湿度影响大,所以必须做预热和温漂补偿,否则就是“玄学报警”。
2.2 STM32最小系统的关键引脚分配
我一般用STM32F103C8T6核心板,资源够、资料多。引脚分配如下表,照着接不会乱:
| 功能 | 引脚 | 说明 |
|---|---|---|
| MQ传感器模拟输出 | PA0 | ADC1_IN0,采集电压 |
| 蜂鸣器 | PB5 | 推挽输出,高电平响 |
| LED红灯(报警) | PB6 | 推挽输出 |
| LED绿灯(正常) | PB7 | 推挽输出 |
| 继电器(排风/关阀) | PB8 | 推挽输出,驱动继电器模块 |
| 按键(消音/自检) | PA1 | 上拉输入,按下低电平 |
| OLED SCL | PB10 | I2C2_SCL |
| OLED SDA | PB11 | I2C2_SDA |
供电用5V给MQ传感器加热丝,3.3V给STM32和OLED。继电器模块单独5V供电,避免拉低MCU电压。
2.3 报警逻辑:阈值、回差和延时,一个都不能少
最简单的逻辑是“ADC值 > 阈值就报警”,但实际会翻车:浓度在阈值附近波动时,报警器会疯狂启停。必须加回差(迟滞)和确认延时。我常用的参数:报警阈值对应ADC值2000(约1.6V,具体标定见下一章),回差100,即降到1900以下才解除报警;确认延时2秒,即连续2秒超过阈值才触发。这样既灵敏又不神经质。
3. 动手实现:从ADC采集到声光联动的完整代码路径
3.1 用CubeMX配置ADC与GPIO,生成最小工程
先用STM32CubeMX建工程,选STM32F103C8T6,配置RCC外部晶振,SYS调试口。ADC1选IN0,设置连续转换模式、采样时间55.5周期。GPIO按2.2节表格配置。I2C2配置为标准模式100kHz。生成Keil或Makefile工程。关键配置截图不贴了,直接看代码里的初始化。
3.2 ADC多采样取平均与浓度换算
MQ传感器输出有噪声,单次采样跳动大。我一般连续采16次,去掉最大最小各2个,剩下12个取平均。代码:
// adc.c #include "adc.h" #define SAMPLE_TIMES 16 #define DISCARD 2 uint16_t Get_ADC_Average(void) { uint16_t samples[SAMPLE_TIMES]; uint32_t sum = 0; uint8_t i, j; uint16_t temp; for (i = 0; i < SAMPLE_TIMES; i++) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); samples[i] = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); HAL_Delay(2); // 等待传感器输出稳定 } // 冒泡排序,去掉最大最小各DISCARD个 for (i = 0; i < SAMPLE_TIMES - 1; i++) { for (j = 0; j < SAMPLE_TIMES - 1 - i; j++) { if (samples[j] > samples[j+1]) { temp = samples[j]; samples[j] = samples[j+1]; samples[j+1] = temp; } } } for (i = DISCARD; i < SAMPLE_TIMES - DISCARD; i++) { sum += samples[i]; } return (uint16_t)(sum / (SAMPLE_TIMES - 2 * DISCARD)); }逻辑说明:先采16个点,排序后掐头去尾,中间12个求平均。参数SAMPLE_TIMES和DISCARD可按实际噪声调整,噪声大就加大采样数。注意每次启动ADC前要确保上一次转换结束,HAL_Delay(2)是给MQ传感器内部阻抗匹配留时间,太小会导致读数偏低。
3.3 报警状态机与消音按键处理
报警不能一超阈值就永久响,要支持消音。我用一个状态机:
// alarm.c #include "alarm.h" typedef enum { STATE_NORMAL, STATE_WARNING, STATE_ALARM, STATE_MUTED } AlarmState; AlarmState state = STATE_NORMAL; uint32_t alarm_start_tick = 0; uint8_t mute_flag = 0; void Alarm_Task(uint16_t adc_val) { uint16_t threshold_high = 2000; uint16_t threshold_low = 1900; switch (state) { case STATE_NORMAL: if (adc_val > threshold_high) { if (alarm_start_tick == 0) alarm_start_tick = HAL_GetTick(); if (HAL_GetTick() - alarm_start_tick > 2000) { // 确认延时2秒 state = STATE_ALARM; alarm_start_tick = 0; } } else { alarm_start_tick = 0; } break; case STATE_ALARM: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); // 蜂鸣器 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); // 红灯 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); // 绿灯灭 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); // 继电器动作 if (mute_flag) { state = STATE_MUTED; mute_flag = 0; } if (adc_val < threshold_low) { state = STATE_NORMAL; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); } break; case STATE_MUTED: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); // 关蜂鸣器 // 保持红灯和继电器 if (adc_val < threshold_low) { state = STATE_NORMAL; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); } break; } } // 按键中断回调里置位mute_flag void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_1) { mute_flag = 1; } }逻辑说明:STATE_NORMAL下检测超阈值并计时,2秒后进ALARM。ALARM下蜂鸣器、红灯、继电器全开,若消音按键按下则进MUTED,只关蜂鸣器,红灯和继电器保持。浓度降到低阈值以下才回NORMAL。参数threshold_high和threshold_low就是回差,根据标定调整。注意HAL_GetTick()溢出问题,这里没处理,实际产品要加溢出判断。
3.4 OLED显示与串口调试输出
OLED用SSD1306,I2C地址0x78。显示内容:当前ADC值、状态、阈值。串口每500ms打印一次,方便标定。代码略,重点是用sprintf格式化到缓冲区再刷屏,别在中断里刷OLED。
4. 标定与阈值设定:把ADC值变成可信的报警线
4.1 用标准气体或打火机气做两点标定
没有标准气体怎么办?我一般用打火机气(丁烷)对准传感器,注意安全,通风环境,少量释放。记录两个点:洁净空气下ADC值(约200-400),和打火机气靠近时ADC值(约2500-3500)。报警阈值取洁净空气值的3-5倍,比如洁净空气300,阈值取1500-2000。更严谨的做法是用甲烷标准气(比如1000ppm)标定,但成本高。标定时预热至少24小时,MQ传感器第一次用要老化。
4.2 温湿度补偿的简化做法
MQ传感器输出随温度升高而降低,湿度大也影响。没有温湿度传感器时,至少做开机预热和定期基线校准。我加一个DHT11,每10分钟读一次温湿度,用经验公式修正:修正后ADC = 原始ADC * (1 + 0.005*(25 - 温度))。湿度影响小,先忽略。这个系数是实测拟合的,不同传感器有差异,别照搬。
4.3 阈值写入Flash,避免每次上电重设
用STM32内部Flash模拟EEPROM,存阈值和标定参数。代码用HAL_FLASH_Program,注意擦除页。简单做法:定义结构体,存到0x0801F000地址。上电读取,若无效则用默认值。
5. 避坑与排查:传感器预热、电源干扰和误报警的 5 个真实翻车现场
5.1 现象:上电后ADC一直是0或满量程
原因:MQ传感器加热丝没供电,或者负载电阻没接。MQ-2有4个引脚,两个加热(H),两个输出(A/B),输出端要串负载电阻(通常10kΩ)到地,A或B接ADC。解决:用万用表测加热丝两端应有5V,输出端对地电压随气体变化。
5.2 现象:报警器半夜乱叫,白天正常
原因:温湿度变化导致基线漂移,或者电源纹波大。解决:加回差和延时,电源加100μF电解电容和0.1μF陶瓷电容,ADC参考电压用外部TL431更稳。
5.3 现象:继电器动作时MCU复位
原因:继电器线圈反电动势拉低电源。解决:继电器模块单独供电,线圈并联续流二极管(模块一般自带),MCU电源加磁珠隔离。
5.4 现象:OLED显示闪烁或花屏
原因:I2C速率太高或上拉电阻太大。解决:降到100kHz,上拉电阻用4.7kΩ,电源加滤波。
5.5 现象:消音后无法恢复报警
原因:状态机里MUTED状态没处理浓度继续升高的情况。解决:MUTED下若浓度超过更高阈值(比如2500),强制回ALARM并响蜂鸣器。
6. 进阶技巧:用滑动窗口滤波和双阈值联动提升可靠性
滑动窗口滤波比去极值平均更平滑。维护一个长度为10的环形缓冲区,每次新采样替换最旧值,求平均。代码:
#define WINDOW_SIZE 10 uint16_t window[WINDOW_SIZE]; uint8_t idx = 0; uint16_t Sliding_Window_Filter(uint16_t new_val) { uint32_t sum = 0; window[idx] = new_val; idx = (idx + 1) % WINDOW_SIZE; for (uint8_t i = 0; i < WINDOW_SIZE; i++) { sum += window[i]; } return sum / WINDOW_SIZE; }双阈值联动:低阈值(比如1500)触发预警,黄灯闪烁,蜂鸣器间歇响;高阈值(2000)触发紧急报警,红灯常亮,蜂鸣器长鸣,继电器动作。这样给用户反应时间。我一般还会加一个“传感器故障检测”:如果ADC值持续为0或满量程超过10秒,判定传感器断线或短路,红灯快闪,蜂鸣器特殊频率。这些技巧让系统从“能用”变成“可靠”。最后说个习惯:每次改完阈值,一定用打火机气实测一遍,别只信仿真。希望帮到你。
本文还有配套的精品资源,点击获取