资讯动态

STM32智能安防与燃气监测系统:从原理图到代码完整开源实战

发布时间:2026/9/16 7:27:38 来源:尧图企业网站定制
1. 项目概述与方案选型做嵌入式这些年我见过太多人学完STM32却不知道拿它做什么最后只能点个灯、跑个流水线就草草收场。这个智能安防与燃气监测系统是我整理出来的一个非常适合练手和实际部署的开源项目方案整套东西包含完整的代码、原理图和Proteus仿真工程覆盖了传感器采集、ADC转换、逻辑控制、执行器驱动、状态显示这条完整的嵌入式开发链路。先说这个系统能干什么。它主要解决两件事一是燃气泄漏的实时监测与自动处置二是非法入侵的检测与声光报警。对租房一族、独居人群或者家里有老人小孩的家庭来说这两件事都是实打实的刚需。燃气泄漏的隐患不用我多说新闻里隔三差五就有相关事故而安防这块一套简单可靠的人体感应报警器能起到的震慑作用远比想象中大。这套系统的目标用户很明确已经学完C语言和STM32基础外设GPIO、ADC、定时器、串口的同学想做课设和毕设但找不到完整参考的学生以及想给家里做一个低成本安防小设备的电子爱好者。它不是一个“高大上”的项目但胜在完整、可复现、能扩展。你把它跑通了就等于把STM32最常用的外设全部过了一遍。硬件核心用的是STM32F103C8T6也就是俗称的“蓝药丸”这颗芯片在淘宝上几块钱一片资料多到看不完。传感器部分包括MQ-2气体传感器模块燃气/烟雾检测、HC-SR501人体红外感应模块人体检测、DHT11温湿度传感器用于环境数据采集和辅助判断执行端是蜂鸣器报警、LED指示灯、OLED显示屏再加一个继电器模块用来模拟切断燃气电磁阀。为什么选这套组合而不是直接买一个现成的燃气报警器原因很简单现成产品是一个黑盒你只知道它响了不知道它为什么响、怎么响、响多久。自己用STM32做一遍你才真正理解ADC采样怎么变成浓度百分比阈值判断怎么写才不容易误报继电器怎么驱动才不会被反电动势打坏。这些知识点才是面试和实际工作中真正会考你的东西。2. 需求分析与功能拆解2.1 核心需求梳理做项目最忌讳一上来就写代码先把需求想清楚后面能少走一大半弯路。我这套系统最开始梳理出来的需求有六个方面第一燃气浓度实时采集。用MQ-2传感器输出模拟量STM32通过ADC转换成数字量再换算成浓度相关的量化值在OLED上实时显示。第二燃气超标自动报警与联动。当浓度超过设定的第一阈值蜂鸣器间断鸣叫、LED红灯闪烁超过第二阈值更严重继电器吸合模拟切断燃气电磁阀蜂鸣器改为连续鸣叫。第三人体入侵检测。HC-SR501红外传感器检测到有人在探测范围内活动时触发声光报警并区分于燃气报警的提示方式OLED上同步显示“有人闯入”状态。第四环境数据采集显示。DHT11温湿度数据实时刷新在OLED第二屏上温度过高或湿度过低时给出不同提示。为什么要加这个功能一方面是因为DHT11的单总线协议是STM32开发中很经典的时序操作案例值得练手另一方面厨房温度异常升高往往比燃气泄漏更早出现多一个判断维度可以减少误报。第五阈值可调。按键可以调整燃气报警阈值避免不同环境下传感器基准差异导致的误报。这个功能看似不起眼却是工程化思维的重要一环——你的设备得能适配不同用户的实际场景。第六状态可视化。OLED屏幕上分区显示时间如果需要、燃气浓度、温湿度、报警状态不用接电脑也能直接观察整机运行情况。2.2 功能模块划分需求理清楚后我把系统拆成了几个独立模块每个模块对应一块硬件和一段相对独立的代码模块硬件功能STM32外设浓度采集MQ-2模块燃气/烟雾浓度ADC采集ADC1通道环境感知DHT11模块温湿度采集GPIO单总线人体感知HC-SR501模块红外人体感应GPIO外部中断声光报警蜂鸣器、LED分级报警提示GPIO输出TIM定时联动切断继电器模块控制电磁阀或排风扇GPIO输出人机交互OLED屏、按键显示状态、设置阈值I2C/GPIO这种模块化拆分的好处是每个模块都可以单独调试单独测试通过后再整合到主循环里出问题了也好定位——是传感器坏了、判断逻辑错了还是执行端没反应掰开揉碎了一查就清楚不用对着一个大main.c两眼一抹黑。2.3 为什么用STM32而不是51或者Arduino我经常被人问这个问题这里统一说下。51单片机也能做燃气报警但性能上限太低——没有内置ADC的型号还得外接ADC芯片8位机做浮点运算虽然这个项目用不上浮点和复杂状态切换也吃力。Arduino做这个当然更快但正因为太快太方便了你反而接触不到底层细节一个analogRead()就把ADC采样率、参考电压、通道配置全隐藏掉了学完和没学一样。STM32的定位刚好在两者之间。它给了你足够的自由度——ADC采样时间你可以自己配外部中断优先级你可以自己调PWM输出你可以自己定频率同时它的HAL库和外设库封装得又比较完备不用像51那样抠寄存器抠到头秃。这个项目做完你既有了实战经验又对STM32的常用外设建立了肌肉记忆再去做更复杂的FreeRTOS项目或者物联网网关设备起点完全不一样。3. 硬件电路与原理图设计3.1 整体电路架构原理图是这个项目的灵魂也是最值得开源的部分。整个电路的架构并不复杂核心思路是一颗STM32F103C8T6作为主控多个传感器模块通过不同的接口接到主控上执行器通过驱动电路由IO口控制电源部分统一供电。我按功能把原理图分成几个小的部分电源电路、MCU最小系统电路、传感器接口电路、执行器驱动电路、显示与按键电路。Proteus仿真工程里也是按这个分区画的看起来清清楚楚方便你对照着实物去查线。电源部分整个系统供电取5VUSB供电或5V适配器STM32的工作电压是3.3V所以需要一颗AMS1117-3.3稳压芯片把5V降到3.3V。这里有个容易踩的坑MQ-2模块的加热丝需要5V供电而它的信号输出引脚可以直接接到STM32的ADC引脚模块上已经有分压电路但HC-SR501有些人买到的是5V输出型号它的输出高电平接近5V直接怼到STM32的3.3V引脚上有超压风险需要在中间加分压电阻或者用支持5V容忍的引脚。STM32F103的很多IO是5V容忍的标注“FT”的引脚原理图上我特意把HC-SR501的输出接到了PA0PA0不是FT引脚所以中间加了一个10K6.8K的分压电阻把5V分到3.3V以下再进MCU。3.2 MCU最小系统设计STM32F103C8T6的最小系统包含几个部分3.3V供电、8MHz外部晶振、复位电路、BOOT引脚配置、去耦电容。供电部分除了AMS1117稳压出来的3.3V主供电每个电源引脚旁边都要放100nF的陶瓷去耦电容这点我见过太多人省了。芯片在工作时电流会瞬时跳变没有去耦电容就跟人跑步没穿鞋一样电源噪声直接耦合到ADC参考电压上燃气浓度读数会跳得你想砸东西。原理图上明确标注了VDD3.3和GND之间放置4个100nF电容分布在两个电源引脚旁边。晶振电路用8MHz无源晶振加两个20pF负载电容这是STM32的常规配置通过PLL倍频到72MHz主频。有些教程说用内部RC振荡器也行确实能跑但USART通信的波特率误差会大不少OLED刷新也容易出现闪烁或不稳定的现象既然做实物老老实实把外部晶振画上去。复位电路是高电平复位一个10K上拉电阻加一个100nF电容到地按键并联在电容两端。这个电容很关键它能对复位信号做滤波防止误触发复位。BOOT0和BOOT1都通过10K电阻下拉到地确保从Flash正常启动。这些细节看似不起眼但原理图不画对后面调试时就会出现“明明程序下载进去了怎么跑不起来”这种让人崩溃的问题。3.3 传感器接口电路设计MQ-2燃气传感器的接口电路是模拟信号链路的典型代表。从模块出来的AO引脚接一个0.1uF滤波电容然后再进STM32的PA1ADC1的通道1。为什么要加这个电容MQ-2的输出虽然是直流电平但实际会有高频噪声叠加这个电容和ADC输入引脚的等效阻抗构成低通滤波器把高频毛刺滤掉一部分硬件先做一次低通滤波软件再配合多次采样取平均双管齐下读数才能稳得住。HC-SR501人体红外模块的接口稍微复杂一点。我在模块输出和STM32引脚之间加了一个1K串联电阻和一个3.3V稳压管做限流和钳位保护。虽然加了三极管或分压电阻做电平转换更规范但稳压管方案在低成本场景下非常实用。模块的OUT引脚默认输出为低检测到人体时输出高电平3.3V或5V版本这个电平变化直接触发STM32的外部中断。DHT11的数据引脚接PA6数据线上拉一个4.7K电阻到3.3V这是单总线协议的硬性要求漏极开路输出需要外部上拉才能保证高电平的驱动能力。DHT11的VCC接3.3V注意这里不能接5V否则长时间运行芯片会严重发热数据也会出错。3.4 执行器驱动电路执行器部分是新手最容易画错的地方尤其是继电器的驱动。我用的是5V继电器模块模块上自带了ULN2003或三极管驱动但如果是自己画板子接裸继电器一定要按这个电路来STM32的PF1引脚输出3.3V经过一个NPN三极管比如S8050放大驱动继电器线圈继电器线圈两端必须反并联一个1N4007二极管方向是负极接电源正正极接三极管集电极。这个二极管是续流保护用的。继电器线圈本质是一个电感断电瞬间会产生反向电动势幅度可能是电源电压的数倍直接怼到三极管上分分钟击穿有了这个二极管电流就有了泄放回路保护驱动管不被击穿。我做项目时第一版原理图就漏了这个二极管实测时连续烧了两颗S8050后来才发现是反电动势在作祟这个教训记忆极其深刻。蜂鸣器电路相对简单。用的是有源蜂鸣器内置振荡源只要给高电平就会发声驱动电路和三极管驱动继电器一样只是省掉了续流二极管蜂鸣器内部线圈也有一定感性但阻抗较低实际影响不大。如果想控制不同报警节奏把IO口输出不同占空比或频率的PWM就行。OLED显示屏我用的是I2C接口的0.96寸模块SDA接PB7SCL接PB6上拉电阻模块上已经有了直接接线即可。I2C的好处是只占用两根线而且可以挂多个I2C设备后续想加个BMP280气压传感器也方便。按键电路是普通的GPIO输入带软件消抖三个按键分别对应“设置”、“加”、“减”不用外部上拉直接用STM32内部的上拉电阻。4. 软件代码实现与核心逻辑4.1 工程结构与初始化流程软件部分是我花了最多心思的地方。整套代码用标准外设库SPL写的而不是HAL库原因后面细说。工程分成几个核心文件main.c主逻辑、adc.c/hADC采集、dht11.c/h温湿度、oled.c/h显示、key.c/h按键、alarm.c/h报警状态机、gpio.c/h初始化。先回答为什么用标准库不用HAL库。你去看招聘要求嵌入式岗位大多要求“熟悉STM32标准外设库或HAL库”但标准库在代码可读性和学习底层原理上明显更有优势。HAL库封装太厚一个简单的GPIO初始化要经过好几层函数调用底层寄存器怎么配置的被包得严严实实。标准库则是半封装状态——你直接调库函数但翻翻源码就能看到寄存器的设置过程。对于学习阶段的读者强烈建议先啃标准库跑通后再接触HAL和STM32CubeMX那时候你看到的就不是API而是背后的原理了。系统初始化流程非常关键void System_Init(void) { HAL_Init(); // 如果使用HAL版则调用标准库版不需要 SystemClock_Config(); // 配置72MHz主频 GPIO_Init(); // 初始化所有GPIO引脚 ADC1_Init(); // 初始化ADC1通道1 TIM2_Init(); // 定时器2用于系统时基和按键消抖 I2C1_Init(); // 初始化I2C1用于OLED OLED_Init(); // 初始化OLED清屏显示logo DHT11_Init(); // 初始化DHT11引脚 Key_Init(); // 按键引脚初始化 Alarm_Init(); // 报警模块初始化默认关闭 printf(System Boot Success\r\n); // 串口调试打印 }这段代码的顺序是有考究的。先把时钟配好这是所有外设工作的基础然后是GPIO外设的信号通道接着是ADC、定时器、I2C——它们分别依赖GPIO的复用功能配置。OLED初始化和显示logo放在传感器初始化之后这样一开机就能看到系统状态方便判断程序是否跑挂。串口打印放在最后因为它是调试手段不阻塞主流程。4.2 燃气浓度采集与阈值判断燃气浓度采集是整个系统的核心我实现了两套判断逻辑。第一套是ADC采样与软件滤波。ADC1配置为连续采样模式采样时间拉长到55.5个周期可以有效降低信号源内阻带来的采样误差。代码里做了均值滤波和滑动平均#define ADC_SAMPLE_COUNT 20 uint16_t Get_Gas_Value(void) { uint32_t sum 0; for (uint8_t i 0; i ADC_SAMPLE_COUNT; i) { sum ADC1-DR; // 读取ADC转换结果 } return (uint16_t)(sum / ADC_SAMPLE_COUNT); // 取平均 }为什么不用单次采样结果因为单次ADC转换受到噪声影响很大MQ-2在预热不稳定阶段输出波动尤其明显。20次采样取平均之后读数基本稳定在正负5个LSB以内这个波动范围完全不会影响阈值判断。实测下来用这种简单均值滤波的效果甚至比一些花哨的卡尔曼滤波在CPU占用率和效果上更加均衡——毕竟这个场景是一个缓变量高阶算法反而可能把真实的浓度突变给平滑掉。第二套是阈值比较与状态机。我把报警分成了三个状态正常浓度低于阈值一、预警浓度在阈值一和阈值二之间、警报浓度高于阈值二。用状态机的思路而不是简单if-else嵌套好处是逻辑清晰且加入“滞回”很容易typedef enum { ALARM_STATE_NORMAL, ALARM_STATE_WARNING, ALARM_STATE_ALERT } AlarmState; AlarmState alarm_state ALARM_STATE_NORMAL; void Alarm_Task(void) { uint16_t gas Get_Gas_Value(); switch (alarm_state) { case ALARM_STATE_NORMAL: if (gas threshold_low) alarm_state ALARM_STATE_WARNING; break; case ALARM_STATE_WARNING: if (gas threshold_low - 20) // 滞回判断防止临界抖动 alarm_state ALARM_STATE_NORMAL; else if (gas threshold_high) alarm_state ALARM_STATE_ALERT; break; case ALARM_STATE_ALERT: if (gas threshold_high - 30) alarm_state ALARM_STATE_WARNING; break; } }这里用到了滞回比较的思想阈值一判断是50但低于50-20才恢复普通报警。实际上传统的滞回比较器硬件电路用在传感器阈值判断上很常见这里我在软件里也实现了同样的效果。这么设计是因为MQ-2传感器在临界浓度附近输出会发生轻微抖动如果没有滞回蜂鸣器会不停地响一声停一声声音极其扰人而且继电器的频繁吸合很伤触点缩短寿命。加了滞回区间后系统表现稳定多了。4.3 外部中断与人体检测逻辑人体检测我用的方式是HC-SR501的输出接入STM32的PA0引脚PA0配置为外部中断触发模式设为上升沿。这样设计的好处有两个一是检测响应速度最快不需要主循环轮询传感器一触发外部中断就发生了响应二是CPU占用率最低平时主要任务就去刷OLED和处理燃气采集不会被轮询扰乱了实时性。外部中断配置代码void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { // 确认是人体触发的上升沿 if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) SET) { human_detected 1; // 标志位置1主循环处理后续动作 interrupt_timeout 30; // 持续30个主循环周期约3秒 } EXTI_ClearITPendingBit(EXTI_Line0); } }你在中断里做太多事情是大忌中断服务函数应该短小精悍一般只做置标志位或者计数之类的快速处理实际的报警动作放到主循环里执行。否则中断嵌套一深程序的实时性和稳定性都会出问题。这是整个中断编程的核心思想这个项目的写法就是教科书级别的示例。在主循环里人体检测标志位一旦被置位我设置了一个30次的循环计时在这个时间窗内保持报警状态。为什么这么做HC-SR501模块本身有延迟时间和封锁时间的配置我把它调到了最小这样如果有人快速经过信号拉高后很快恢复低电平如果没有这个软件保持窗口蜂鸣器可能只来得及响一下就没声了这在实际安防场景里是不够的。加了窗口之后即使人已经离开探测区报警也会持续3秒足够产生警示效果。4.4 OLED显示与按键交互OLED用了I2C接口的0.96寸屏SSD1306控制器代码是标准库驱动。界面设计为两页轮显第一页显示燃气浓度和系统状态用大号数字和中文取模字库标注“燃气浓度 123 ppm”和状态栏“正常/预警/警报”第二页显示温湿度和时间。显示刷新的节奏我特意做了控制所有数据刷新周期为500ms避免过于频繁的I2C通信干扰主循环。OLED的I2C时钟频率设置为400kHz快速模式因为SSD1306完全支持这个速率。这里有一个需要注意的细节OLED的初始化序列一定要和屏幕驱动IC匹配市面上几块钱的OLED屏大部分是SSD1306但小部分用的是SH1106两个IC的初始化指令集略有不同如果OLED花屏或者不显示先查手里屏幕的驱动型号。按键交互的逻辑是长按和短按的组合短按“设置”键进入阈值设置界面此时屏幕显示“阈值080 ppm”按“”“-”键调节范围从20到200步进5。再次短按“设置”键保存并退出设置界面。长按“设置”键3秒恢复出厂阈值默认80 ppm。按键消抖用的是定时器轮询状态机的方式每10ms扫描一次按键状态连续检测到3次相同电平才判定为有效按键这样就杜绝了机械抖动的干扰。需要特别提醒的是不要在按键扫描里做延时消抖那种delay(20)的写法会让整个系统的实时性彻底崩塌你能明显感觉到OLED刷新变卡了。4.5 报警状态机与联动控制报警状态机是整个软件架构里最核心的设计我花了最多篇幅来讲它。系统运行在四个状态下关机/待机、正常监视、燃气报警、入侵报警。其中燃气报警根据严重程度又细分了预警和警报。状态之间的转移条件我画了一个逻辑表当前状态触发条件动作迁移结果正常监视燃气浓度阈值一蜂鸣器500ms间歇响LED慢闪转到预警正常监视人体传感器触发蜂鸣器急促响LED快闪转到入侵报警预警燃气浓度阈值二蜂鸣器连续响继电器吸合转到气体警报预警浓度回落至阈值一以下恢复到检测状态回正常监视气体警报浓度回落且持续30秒蜂鸣器停止继电器断开回正常监视入侵报警3秒窗口结束蜂鸣器停止LED灭回正常监视这个状态表写清楚后代码实现就非常直接了。每个状态对应一个处理函数状态转移不直接在其他函数里跳转而是通过修改state变量在主循环的switch-case里统一处理。这种结构的好处在于你可以在任何时间点插入调试信息看看代码执行到哪个状态或者在状态机外层再挂一个统计计数看看系统一天之内在各种状态里停留了多久——这些对排查间歇性问题太重要了。联动控制方面当进入“气体警报”状态时继电器吸合模拟切断燃气电磁阀同时串口输出“GAS LEAKAGE! VALVE CLOSED!”调试信息。当浓度下降且稳定30秒后继电器自动断开恢复供气。这里有一个人为设计的安全考虑浓度降到安全线以下后系统不立即恢复供气而是延迟30秒确保残余燃气散尽。这个延迟时间在代码里是通过定时器计数的不是delay浪费CPU实测体验很平滑用户不会觉得系统反应迟钝。5. 仿真验证与实际部署5.1 为什么必须做仿真仿真环节是这个开源项目里被人低估最多的部分——很多人觉得Proteus就是画个图跑个灯没啥实际意义。这种理解是错的。仿真的价值在于你可以用纯软件的临时手段测试程序的逻辑边界而这个过程如果放在实物上代价会大得多。举几个具体例子。你想测燃气浓度超过阈值二时系统强行关闭电磁阀的逻辑实物情况下你得拿打火机出气口怼着MQ-2猛喷这既危险又不干净而在Proteus里只需要把滑动变阻器的值调到某个位置模拟传感器的电压输出就行安全、干净、可重复。你想验证不同浓度下报警时间间隔是否符合设计要求实物上你调一个温度探头再调一次浓度极慢且不精确仿真里直接改电阻值一下就能跑出各种极端条件组合。这块仿真工程我做了完整适配和原理图是一一对应的。OLED显示、蜂鸣器、LED、继电器都有Proteus模型按键也一样。需要特别提醒的是Proteus里的MQ-2模型原理是“气体浓度设置→等效电压输出”的映射关系跟实物的敏感度和时延特性还是有不小的差异所以仿真通过不代表实物一定完美但仿真能保证你程序的逻辑框架是通的。5.2 Proteus工程搭建步骤搭建仿真的步骤我给大家拆解一下照着做基本不会卡壳。第一步在Proteus 8.x中新建工程选择STM32F103C8T6作为主控芯片。这个芯片在Proteus的库里的名称是“STM32F103C8T6”如果你搜不到检查一下是否安装了对应的芯片库Proteus 8.6以上版本一般都自带。第二步从库中拉出元件STM32F103C8T6、LM016L液晶显示这里我用的是LM016L模拟OLED显示因为Proteus里没有SSD1306的模型或者你可以用虚拟终端来替代显示、蜂鸣器SOUNDER、LED-RED、LED-GREEN、RESPACK-8排阻、BUTTON、滑动变阻器POT-HG模拟MQ-2的输出电压、直流电机MOTOR模拟电磁阀/排风扇。第三步按原理图连线特别注意给STM32的每个VDD引脚都接上3.3V电源和去耦电容每个VSS引脚接地VDDA同样接3.3VVSSA接地。Proteus仿真时电源引脚漏接是程序不跑的头号原因。晶振部分接8MHz晶振和两个20pF电容复位电路接一个10K上拉电阻和复位按键加电容。第四步双击STM32芯片加载编译生成的hex文件文件在Keil工程目录的Objects文件夹下设置晶振频率为8MHz。第五步运行仿真。默认状态下OLED上显示燃气浓度和状态滑动变阻器调节电压可以模拟燃气浓度上升按键模拟人体入侵触发。你甚至可以添加虚拟示波器Virtual Oscilloscope到MQ-2电压节点上观察ADC采样前的电压纹波。这套仿真工程跑通了再上手实物会轻松很多。5.3 从仿真到实物的移植要点仿真和实物之间的差距主要来自三个地方传感器真实输出特性、电源噪声、和时序差异。传感器特性差异是最大的变量。MQ-2在真实环境中有一个预热过程刚上电的头几十秒输出值从满量程慢慢下降到正常基线这个过程在Proteus里是不存在的。所以代码里我加了一个开机初始化逻辑系统启动后先延时60秒MQ-2预热期间OLED显示“预热中”60秒后自动校准当前值为基准零点。这样在实物上传感器的工作点被正确设置阈值判断也就准确了。这个细节在仿真中看不出区别但实物上不做这个处理你的燃气报警器大概率一开机就乱叫。电源噪声问题在仿真里基本不存在实物上如果使用劣质USB电源ADC读数会周期性跳动。解决办法是在电源入口加一个大容量电解电容100uF以上和一个100nF陶瓷电容低感实测能滤掉大部分电源纹波。时序差异也要注意。仿真里DHT11的数据读取速度远快于实物因为Proteus的器件模型нуж简化了GPIO的时序响应。实物上如果你没有严格按照DHT11 datasheet里的时序要求写驱动代码特别是起始信号拉低至少18ms、读数据时每bit的电平宽度判断经常会出现读取失败或者温度显示85度湿度的典型错误值。这些坑我在后面的调试部分详细说。5.4 部署建议与实际场景设置如果要在家里真正部署一套有几个实际部署的细节值得分享。传感器位置很讲究。MQ-2燃气传感器需要安装在距气源燃气灶、热水器较近但不在正上方的位置因为燃气主要是甲烷或液化石油气比空气轻或重的特性不同天然气比空气轻应该安装在高处液化石油气比空气重应该安装在低处。如果实在分不清家里用的哪种气就装在燃气具侧面距地面30cm-50cm的位置这个高度对两种燃气都能兼顾。HC-SR501人体传感器的安装高度建议1.5米到2米之间探测角度向下倾斜15到20度这样能覆盖门口到走廊的区域不会出现探测死角也不会因为装太低而频繁被宠物触发误报。继电器控制的电磁阀选型要注意市面上燃气电磁阀有“常闭型”和“常开型”燃气管道切断必须选常开型通电保持开启、断电自动关闭。这个安全细节不能搞错否则就会出现“报警时断电反而打开了阀门”的致命逻辑错误。OLED屏幕放在门口或客厅显眼位置方便日常扫一眼就知道家里的燃气浓度和环境状态。按键可以引线出来装在86盒里方便调节阈值而不必拆开整个设备外壳。6. 常见问题与排查技巧实录6.1 仿真和实物调试中的典型问题速查我在开发过程中整理了一份问题排查表这些都是我在实际调试中踩过的坑拿出来分享给大家。问题现象可能原因排查方法解决方案程序无法下载BOOT0引脚未拉低检查BOOT0电压是否为低确保BOOT0接10K下拉到地下载成功但程序不运行复位电路异常示波器测NRST引脚检查复位电容和按键按键两端并联100nFOLED花屏/白屏I2C地址不对或初始化序列不匹配用I2C扫描程序读取设备地址常见地址0x3C或0x3D改成匹配地址DHT11显示85度/0%湿度时序不对或上拉缺失示波器查看数据线波形重新检查时序代码确认4.7K上拉ADC读数跳动超过20LSB传感器未预热或去耦不足观察MCU电源纹波预热60秒加100uF电解电容继电器频繁吸合断开阈值临界抖动串口打印ADC值加入滞回区间扩大阈值差蜂鸣器误触发人体传感器被窗帘/宠物触发调整传感器位置和灵敏度旋转灵敏度电位器调整方向串口输出乱码波特率不匹配检查USART配置确认两边都是115200-8-N-1按键无响应未使能内部上拉或消抖失败测引脚电平变化配置GPIO为上拉输入优化消抖逻辑继电器吸合时OLED闪屏大电流导致电源跌落用万用表测电源电压继电器单独供电或用光耦隔离驱动6.2 MQ-2传感器基准漂移的处理用MQ-2做燃气报警器最头疼的问题就是基准漂移。MQ-2是半导体式气体传感器它的敏感层在空气中会缓慢氧化导致输出基线电压随时间发生变化。今天你校准好阈值50明天可能实测成了55下个月可能就是70了。这个问题我在项目里通过“自动校准”和“手动校准”双层机制解决。自动校准是在上电后先执行一个基线校准系统读取60秒内的ADC平均值作为当前环境基线后续浓度值都以这个基线为参考做差值。比如基线是200ADC值当前读到260那么显示为“燃气浓度 超出基线30%”或者用差值做阈值比较。手动校准是通过按键长按触发长按“减”键3秒以上系统记录当前ADC值为新基线。这个操作在搬家、换环境或者是季节交替湿度变化很大的时候用户自己就能随时校准。这个机制在仿真中体现不出来但实物的可维护性全都靠这种细节设计撑着。6.3 继电器的开关干扰这是我最想分享的坑之一。第一版实物电路的继电器模块直接与STM32共用一个5V电源继电器吸合的瞬间整块板子的电压都被拉低了几百毫伏造成OLED闪屏、DHT11读取失败、ADC读数跳变的现象一起出现当时排查了好久。后来我做了三件事彻底解决。第一继电器模块单独供电用一个独立的5V适配器给继电器供电与主控板只通过光耦或三极管进行信号隔离简单的方案是让继电器模块直接由外部电源供电信号端经过光耦控制第二在继电器电源输入端并联一个大容量电解电容470uF吸收吸合瞬间的浪涌电流第三把继电器线圈的反向保护二极管焊接牢靠确实能吸收反向电动势。这三招用上之后整个系统的稳定性提升了不止一个档次。6.4 程序跑飞和HardFault的快速定位STM32开发中遇到HardFault硬件错误中断是正常的不要慌。程序死机了可以看串口打印的信息。我的代码中实现了HardFault的捕获打印void HardFault_Handler(void) { volatile uint32_t* pLR (volatile uint32_t*)0xFFFFFFF9; volatile uint32_t* pPC (volatile uint32_t*)0xFFFFFFF8; printf(HardFault!\r\n); printf(LR 0x%08X\r\n, *pLR); printf(PC 0x%08X\r\n, *pPC); // 这里可以打开LED快闪作为死机信号 GPIO_SetBits(GPIOC, GPIO_Pin_13); while(1); }通过打印出来的PC指针值可以在Keil的View-Symbols Window里反查这个地址对应的函数是哪一个基本就能定位到是数组越界、空指针还是外设访问异常。我用这个方法排掉了一个DMA缓冲区越界的bug——当时程序运行十几分钟后就死机串口打印PC指到的是一个看起来毫无关联的函数但通过LR回溯发现是DMA中断服务函数里写越界了。这种排查思路是通用技能学会了可以治各种疑难杂症。6.5 耗电优化的一些思路虽然是固定供电设备但我还是做了点功耗优化这套优化思路后续做电池供电的设备时也用得上。默认情况下STM32F103的几乎所有外设时钟都开着芯片空转电流大概在30mA左右。我在系统进入“正常监视”状态后关闭了不用的外设时钟比如关闭SPI、USART2等外设时钟只保留ADC、定时器、I2C和DMA。实测整机电流从35mA降到了约20mA。这个幅度看着不大但如果改成电池供电版本同等电池容量下续航马上提升近一半。还想进一步省电的话可以把主循环改成“睡眠唤醒”模式STM32进入睡眠模式SLEEPONEXIT定时器每500ms唤醒一次执行任务任务执行完再回到睡眠。代码改造量不大我在工程里留了一个SAVE_POWER_ENABLE的宏开关打开后就是省电模式版本。这类低功耗思维是很多产品经理面试时特别爱问的点值得大家深入研究。7. 这套方案的扩展空间以上是基线版本。如果大家跑通了这套系统我梳理了几个后续可以延伸的方向难度从低到高。第一个方向是加联网能力。通过ESP8266或ESP32模块把燃气浓度和报警状态上传到云端或推送到手机微信/App。STM32通过串口和ESP8266通信用MQTT协议接入公共的物联网平台这样人不在家也能收到报警推送这才是真正意义上的“智能安防”。这个方向需要学一下AT指令集和MQTT协议难度不算大。第二个方向是加本地存储。用SD卡模块记录历史数据浓度超过阈值的时间点、入侵报警的时间和持续时间都记录下来方便事后回看。这个方向涉及FATFS文件系统移植是嵌入式开发中很常用的技能。第三个方向是更换更高性能的传感器。MQ-2是半导体传感器选择性差、寿命短可以把燃气检测部分换成电化学传感器的模块比如MQ-4、MQ-7或者更高端的ZE03系列接口电路基本不变只是ADC数据校准的公式需要调整。ZE03之类的电化学传感器输出的是RS485或UART数字信号数据准确度和稳定性比模拟传感器高出一个数量级。第四个方向是FreeRTOS系统改造。现在的主循环是超级循环架构功能多了以后实时性会变差。可以移植FreeRTOS把传感器采集、显示、按键、报警分别做成独立任务用消息队列和信号量做任务间通信。这在工程上是质的飞跃做完了简历上能多写一个“熟悉FreeRTOS多任务开发”。我自己的计划是往物联网和低功耗这两个方向走已经买好了ESP8266模块和小型锂电池接下来准备把整套系统做成一个可以贴在厨房墙上的独立小产品。等这个版本做完我会继续把RTOS版本和硬件3D模型文件一起开源出来需要的朋友可以持续关注。如果你把这套代码和电路吃透了说明你对STM32的GPIO、外部中断、ADC、定时器、I2C、USART这些外设已经建立了全局认知这是嵌入式开发里最核心的基础能力后续无论做汽车电子、工业控制还是智能家居底子都已经打好了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价