STM32 的项目我做过不少但真正能把开源、代码、原理图、仿真三件套凑齐、还敢往自家厨房墙上装的只有这套智能安防与燃气监测系统。它不是那种点个灯、转个电机的练手板而是把燃气泄漏检测、人体入侵检测、门窗状态监测、声光报警、排风联动这几件事串成一条完整的闭环链路从模拟前端一直做到报警决策和远程上报。对刚摸到 STM32 门槛的朋友来说它是一个很好的横向拉通项目——你会被迫去搞明白 ADC 采样精度怎么保证、状态机怎么写才不打架、继电器和蜂鸣器为什么老干扰 MCU、仿真里跑得好好的东西为什么一上实物就翻车。我在把它从零做到能稳定跑在家里的一年多里前前后后改了四版原理图、重写了三轮固件踩的坑比写的代码多。这篇就按我自己的复现路径来拆先讲整体设计和选型逻辑再抠原理图里的关键电路接着是固件架构和核心代码然后是仿真验证与仿真发散的排查最后落到调试标定、常见问题速查以及开源资料的整理和二次开发。内容偏实操参数和取值我会尽量给出推算过程你能直接抄也能按自己的元器件改。适合的人群很明确会点 C 语言、能看懂基本电路图、手上有一块 STM32 开发板和一支烙铁的嵌入式爱好者纯小白也能看基础概念我会用生活化的比方讲清楚。1. 系统整体设计与方案选型思路1.1 需求拆解安防和燃气这两条主线到底要做到什么程度很多人做这类项目第一步就错了——直接打开 Keil 建工程边写边想。我的习惯是先拿张纸把这个盒子装在家里一年 365 天不断电它必须做到什么列清楚再反过来推硬件。安防这条线核心需求其实只有三个有人非法进入要能感知、感知到要能叫、叫了之后你不能误报到把邻居吵醒。燃气这条线更狠它关系到人身安全需求是能测、测得准、测得久、坏了要能自己知道。把需求翻译成技术指标大致是这样一张表功能主线用户诉求技术指标对应方案燃气监测泄漏能提前发现检测下限优于 300ppm响应小于 30sMQ 系列气敏传感器 12 位 ADC燃气监测不能天天假报警阈值带回差温湿度漂移要补偿迟滞比较 基线自校准燃气监测失效要能自检传感器断线/短路可识别采样值上下限判据人体入侵有人在布防时进房探测距离 5m 以上热释电红外模块门窗状态门被推开要知道开合状态实时上报干簧管门磁报警输出现场能听见、能看见声压 85dB 以上有源蜂鸣器 高亮 LED联动控制泄漏时自动排风断气1 路继电器输出三极管驱动 光耦隔离数据留存能看历史、能远程看本地显示 串口上报OLED 无线模块这张表看着简单但它决定了后面所有的硬件端口分配和软件任务划分。我见过太多人先画板子再想需求结果发现 ADC 通道不够、继电器和传感器抢引脚只能飞线。先定需求再定资源这是省时间的第一步。1.2 主控与外围器件选型的取舍逻辑主控我最终落在 STM32F103C8T6 上。理由很实在价格便宜、资料多到烂大街、72MHz 主频足够跑这套逻辑、12 位 ADC 有 10 个外部通道、两路 SPI、两路 I2C、三路 USART资源余量够我以后往上加东西。有人会问为什么不选 F4 或者 G0我的判断是这个项目里没有任何需要浮点运算或者高速信号处理的环节5000 行以内的 C 代码在 F103 上跑得轻轻松松多花钱买性能属于浪费。如果你手上只有 F407 开发板也能直接移植无非是改一下时钟配置和 HAL 库的头文件。传感器这块是重头戏。燃气检测常见的方案有半导体式、催化燃烧式、电化学式和红外式。我选半导体式MQ 系列核心原因有三个一是便宜且容易买几十块钱能买一整套模块二是它输出的是模拟电压能让我练习 ADC 采样、滤波、标定这一整条信号链教学价值高三是它对甲烷、液化气这类可燃气体有足够响应家用场景够用。代价是它需要预热、会受温湿度影响、长期会有基线漂移。这些缺点不是不能解决但要靠软件补偿和定期校准来兜后面章节会详细讲。人体感应我用了 HC-SR501 这类热释电模块原因就一个字省。它内部已经做了菲涅尔透镜、信号放大和比较判决输出一个干净的数字电平MCU 只要读引脚就行。代价是它输出电平跟供电电压绑定用 5V 供电时输出接近 5V直接怼到 STM32 的 3.3V 引脚上会有风险必须处理。这个细节后面单独讲。1.3 供电、隔离与安全冗余的设计取舍供电是这个项目里最容易被忽视、却最容易翻车的部分。我的思路是把电源分成三条独立路径12V 输入经过一级降压到 5V供给继电器、蜂鸣器和大功率外设5V 再经过 LDO 降到 3.3V单独供给 MCU、ADC 参考和 OLED气敏传感器的加热丝单独从 5V 取电并且加了大电容和磁珠避免它的脉冲电流污染 MCU 的电源。为什么要这么分因为你把万用表接在 MCU 的 VDD 上看波形就明白了——蜂鸣器一响电源线上会叠加一大坨毛刺ADC 采出来的值能跳几十个码值燃气读数跟着乱飘报警逻辑直接被带偏。分开供电之后这个现象基本消失。安全冗余方面我做了三件事。第一继电器回路和 MCU 之间用光耦隔离驱动三极管和续流二极管一个都不能省否则继电器断电瞬间的反向电动势会把三极管打穿。第二燃气报警一旦触发排风扇和燃气电磁阀的联动动作是写死在中断里的最高优先级不经过任何可以被打断的普通流程。第三固件里加了独立看门狗保证程序跑飞之后能在 1 秒内复位重来。注意这套系统属于个人自制的辅助提醒装置可以用于日常观察和技术学习但它不能替代任何通过正规渠道安装的专业报警设备也不能作为家庭安全的唯一依赖。厨房这类场所的气源管理始终要以专业产品和规范操作为准。2. 硬件原理图的关键细节与实操要点2.1 主控最小系统晶振、复位和 BOOT 引脚怎么配最小系统看着简单但它是整个项目能不能稳定跑起来的基石。外部高速晶振我用 8MHz 无源晶振配两个 22pF 负载电容。这个 22pF 不是随便填的它要跟晶振规格书里的负载电容匹配经验算法是 CL ≈ 2×(C1×C2)/(C1C2) Cstray其中 Cstray 一般取 3 到 5pF。如果晶振起振困难或者频率偏得厉害先查这两个电容再查板子走线是不是太长。晶振到 MCU 的走线要尽量短、尽量对称底下不要走其他信号线最好铺一层完整地。复位电路我用的是 10k 上拉加 100nF 电容的标准组合再并一个手动复位按键。有人喜欢省掉那个 100nF我建议不要省它对上电瞬间的电源抖动有抑制作用尤其是你板子上有大电容和大电感的时候。BOOT0 必须用一个 10k 电阻下拉到地BOOT1也就是 PB2也是。这两个脚悬空是新手最容易犯的错表现是程序烧进去不跑、或者偶尔能跑偶尔不跑。我在原理图上直接把 BOOT0 做成下拉电阻 跳线帽的形式需要进串口下载模式的时候手动短接到 3.3V平时保持下拉省心。2.2 燃气传感器的模拟前端与 ADC 采样链路这是整个项目里最需要讲清楚的一段。MQ 系列气敏传感器本质上是一个随气体浓度变化的可变电阻模块内部把它和一个负载电阻 RL 串起来从中间取分压输出。所以输出电压和浓度不是线性关系而是接近对数关系这也是为什么不能简单地电压超过多少就报警——你要么做分段查表要么在线性化之后再做判断。我的做法是模块 AO 输出先经过一个 RC 低通滤波1k 电阻 100nF 电容截止频率约 1.6kHz再进 STM32 的 ADC 通道。这个 RC 不是摆设传感器的输出本身噪声就不小加上线缆耦合的空间干扰不滤波的话 ADC 读数的抖动能达到 ±30 个码值滤波之后能压到 ±5 以内。关于 ADC 精度这里要给个具体推算。STM32F103 的 ADC 是 12 位参考电压 3.3V那么理论分辨率是 3.3V / 4096 ≈ 0.806mV。看起来挺细但实际有效位数ENOB受噪声、参考电压稳定性、采样时间影响实测下来通常在 10 到 11 位之间。也就是说你实际能分辨的最小电压变化大约是 1.6 到 3.2mV。这个数字决定了你的阈值区间不能设得太窄否则读数在阈值附近来回跳报警状态就会反复切换。还有个容易被忽略的点ADC 输入阻抗和采样时间的关系。STM32 的 ADC 采样保持电容大概 8pF如果外部源阻抗太高比如你直接用 100k 的分压电阻采样时间不够的话电容充不满读数会系统性偏低。我的经验是外部源阻抗尽量控制在 10k 以内采样时间设置成 55.5 个周期或者更长这样最保险。原理图里我在 ADC 输入脚前面加了一个 100Ω 的串联电阻配合前面的 RC 一起既限制了源阻抗又不影响带宽。2.3 人体红外、门磁、蜂鸣器和继电器驱动电路HC-SR501 的电平问题前面提了一句这里展开说。这个模块的工作电压是 4.5V 到 20V输出高电平理论上接近供电电压。如果你给它 5V输出就是 5V 左右而 STM32 的 IO 绝对最大额定值是 VDD0.3V也就是 3.6V长时间这么接会把引脚内部的保护二极管一直导通轻则漏电、重则损坏。我的处理方式有两种你可以按手头器件选一是用两个电阻搭个分压比如 10k 和 20k输出 5V 时得到约 3.3V二是直接用光耦或者专用电平转换芯片。分压方案便宜但要注意分压电阻会影响信号边沿如果传感器输出变化不快人体感应本来就是秒级完全够用。门磁用的是干簧管一端接 3.3V另一端接 MCU 引脚同时用外部 10k 电阻下拉到地并在引脚附近对地并一个 100nF 电容做硬件消抖。为什么要硬件消抖因为干簧管在磁铁靠近的临界位置会抖动几十毫秒如果软件消抖做得不够一次关门能被识别成三次。硬件消抖加上软件 20ms 的确认就非常稳了。蜂鸣器分有源和无源两种很多人搞混。有源蜂鸣器内部自带振荡电路给它通电就响频率固定用 GPIO 直接驱动经过三极管就行无源蜂鸣器需要外部给一定频率的方波才会响用定时器输出 PWM 驱动好处是能调频率、能发出不同的报警音。我选的是无源蜂鸣器配 PWM因为燃气报警和入侵报警用不同的音调用户不用看屏幕就能分辨出是哪一类事件。继电器驱动就是最经典的三极管电路MCU 引脚经过 1k 基极电阻驱动 S8050集电极接继电器线圈线圈两端反向并一个 1N4148 做续流。这里有两个细节。第一续流二极管的方向绝对不能接反接反了继电器根本不吸合还容易烧三极管。第二继电器线圈的电源要和 MCU 的电源分开走线回到电源入口处再汇合避免线圈电流在 MCU 的地平面上产生压降。我第一版板子没注意这个结果继电器一吸合OLED 就闪一下后来在继电器电源脚旁边加了一个 100μF 电解电容和 0.1μF 陶瓷电容的并联组合问题解决。2.4 电源树设计和 PCB 布局的几条硬规矩把前面说的画成电源树大概是12V 适配器输入 → 防反接二极管 / 自恢复保险丝 → 一级 DC-DC 降压到 5V → 分三路一路给继电器和蜂鸣器一路给传感器加热丝加磁珠隔离一路给 LDO 输入 → LDO 输出 3.3V → MCU、ADC 参考、OLED。如果你要做便携版本想用锂电池供电那就涉及升压和充电管理市面上有一类集成度很高的移动电源管理方案能把充电、升压、电量显示都整合在一颗芯片里配一套官方参考设计就能用。但我要提醒一句锂电池的充放电管理涉及安全一定要用成品模块或者经过验证的方案不要自己裸搭充电电路尤其是厨房这种高温高湿环境宁可多花几十块钱用带保护板的成品电源板。PCB 布局上我的几条硬规矩模拟部分和数字部分分区布置ADC 输入走线远离 PWM 和继电器驱动走线地平面尽量完整模拟地在靠近 ADC 入口处单点接到数字地去耦电容紧贴每个电源引脚0.1μF 和 10μF 成对出现晶振周围做包地处理。这些规矩听着老生常谈但真正做到之后ADC 的噪声表现会有肉眼可见的变化。我实测过同一块板子改布局前后ADC 的峰峰噪声从 40 个码值降到了 6 个码值这个差距直接决定了你的报警阈值能不能设得精细。提示原理图阶段就把测试点留出来。我习惯在 5V、3.3V、传感器 AO、ADC 输入这几个位置各放一个测试焊盘调试的时候万用表和示波器探针一夹就上比到处找飞线点省太多时间。3. 固件架构与核心代码实现3.1 工程结构和初始化顺序这套固件我没有用 RTOS理由是任务数量少、实时性要求不高裸机加状态机完全够用而且调试更直观。工程目录我分成了这么几层Core放主循环和中断入口Drivers放 HAL 库和底层外设封装BSP放板级支持OLED、传感器、继电器这些具体器件App放业务逻辑状态机、报警判定、数据上报Utils放滤波、CRC、环形缓冲这类通用工具。分层的好处是以后换传感器只需要改 BSPApp 层几乎不用动。初始化顺序也很讲究。我的顺序是HAL 库初始化 → 系统时钟配置 → GPIO 初始化先把继电器和蜂鸣器设成安全的关闭状态→ 看门狗启动 → ADC DMA 初始化 → 定时器初始化 → 串口和 OLED 初始化 → 最后才打开中断。把继电器初始化放在最前面是因为上电瞬间 IO 状态不确定如果继电器引脚恰好被拉高燃气阀可能在开机时就误动作。把看门狗放在外设初始化之前是为了防止某个外设初始化卡死导致系统一直挂在那里不重启。3.2 ADC 多通道采样与数字滤波的完整实现采样我用的是定时器触发 DMA 循环搬运的方式这样 CPU 完全不用管采样过程数据自动填进缓冲区。下面是核心代码结构可以直接套用#define ADC_CH_NUM 4 #define SAMPLE_DEPTH 32 #define ADC_BUF_SIZE (ADC_CH_NUM * SAMPLE_DEPTH) static uint16_t adc_dma_buf[ADC_BUF_SIZE]; void ADC_DMA_Init(void) { // 1. 校准ADC这一步不能省否则读数有系统偏差 HAL_ADCEx_Calibration_Start(hadc1); // 2. 启动DMA循环搬运缓冲区满一轮自动从头开始 HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_dma_buf, ADC_BUF_SIZE); } // 从DMA缓冲区里取出某个通道最近N次采样做中值滑动平均 uint16_t ADC_GetFiltered(uint8_t ch) { uint16_t tmp[SAMPLE_DEPTH]; uint32_t sum 0; uint8_t i, j; for (i 0; i SAMPLE_DEPTH; i) { tmp[i] adc_dma_buf[i * ADC_CH_NUM ch]; } // 冒泡排序取中间值先把脉冲干扰干掉 for (i 0; i SAMPLE_DEPTH - 1; i) { for (j 0; j SAMPLE_DEPTH - 1 - i; j) { if (tmp[j] tmp[j 1]) { uint16_t t tmp[j]; tmp[j] tmp[j 1]; tmp[j 1] t; } } } // 去掉两端各1/4的极值剩下的求平均 uint8_t start SAMPLE_DEPTH / 4; uint8_t end SAMPLE_DEPTH - start; for (i start; i end; i) { sum tmp[i]; } return (uint16_t)(sum / (end - start)); }为什么是中值 去极值平均这种组合而不是单纯的滑动平均或者卡尔曼两个原因。第一气敏传感器最怕的是突发脉冲干扰比如继电器动作的瞬间滑动平均会被这种孤立大值拖偏而排序取中值天生免疫单点异常。第二卡尔曼滤波需要建立噪声模型参数调起来很玄学我试过调参的时间够我把中值滤波写三遍了效果差别在这个应用里看不出来。实用主义优先。这里有个性能细节要注意32 个数的冒泡排序在 72MHz 的 F103 上大概要几百微秒如果你在 1ms 的定时中断里调用CPU 占用率会明显上升。我的做法是把这个滤波过程放在主循环里以 100ms 为周期执行一次中断里只负责采样和标志位这样 CPU 负载控制在 5% 以内。3.3 报警状态机的设计与防抖动处理状态机是这个项目的大脑。我把它设计成七个状态用一张 switch-case 表来实现每个状态的迁移条件都写得非常明确绝不允许多个条件同时成立导致行为不确定。状态含义进入条件退出条件INIT上电初始化复位初始化完成WARMUP传感器预热初始化完成预热计时结束DISARMED撤防预热完成或用户撤防用户布防ARMING布防延时用户布防延时结束或用户撤防ARMED警戒布防延时结束触发报警或用户撤防ALARM报警中检测到入侵或燃气超标用户消音且条件解除FAULT故障传感器断线或自检失败故障恢复燃气报警和安防报警共用一个 ALARM 状态但内部用标志位区分报警源因为它们的联动动作不一样燃气报警要开排风扇、关燃气阀安防报警只响蜂鸣器和闪灯。把联动动作分开处理能避免燃气泄漏时只是响一声而没有实际处置。防抖动的核心是迟滞比较。假设燃气报警阈值设在 1200mV解除阈值设在 800mV那么只有当采样值连续 3 次超过 1200mV 才进入报警只有连续 5 次低于 800mV 才解除报警。为什么要这么设计因为如果进出都用同一个阈值当浓度刚好卡在阈值附近时继电器会以每秒好几次的频率啪啪啪地开关既吵人又伤触点我第一版就是这么翻车的邻居以为我家在装修。// 迟滞比较 连续确认response 是要连续满足的次数 static uint8_t above_cnt 0, below_cnt 0; #define GAS_ALARM_MV 1200 #define GAS_CLEAR_MV 800 #define CONFIRM_ALARM 3 #define CONFIRM_CLEAR 5 uint8_t Gas_Judge(uint16_t mv) { static uint8_t gas_alarm 0; if (mv GAS_ALARM_MV) { above_cnt; below_cnt 0; if (above_cnt CONFIRM_ALARM) gas_alarm 1; } else if (mv GAS_CLEAR_MV) { below_cnt; above_cnt 0; if (below_cnt CONFIRM_CLEAR) gas_alarm 0; } else { // 处在迟滞带内计数清零但不改变状态这就是记忆效果 above_cnt 0; below_cnt 0; } return gas_alarm; }3.4 人机界面与数据上报的抽象层设计OLED 我用的是 0.96 寸 I2C 屏显存 128×64。I2C 引脚必须外接 4.7k 上拉电阻到 3.3V有些模块自带上拉有些没有买之前一定要看清。刷新策略上我没有用全屏刷新而是划分成三个固定区域状态栏当前布防状态、WiFi 连接情况、数值区燃气浓度、传感器原始电压、事件区最近一次报警时间和类型。只刷新变化的区域能把刷新时间从 30ms 降到 5ms主循环跑起来更流畅。串口上报我封装了一个Report_Send()接口内部把数据打包成固定格式的字符串具体是走无线模块还是走有线串口由编译宏切换。这样做的好处是业务层完全不关心物理链路是什么。我的上报格式是$SEC,ARM1,GAS0456,DOOR0,ALM0#这种自定义的简洁格式解析起来比 JSON 快也不占带宽。如果你要接云平台在中间加一层转换就行。关于无线模块有一点必须提醒这类模块在发射瞬间的峰值电流能到 200mA 以上而 STM32 的 3.3V LDO 如果只按 100mA 选型模块一发数据电源就被拉垮MCU 跟着复位。这个现象的典型表现是每隔几十秒重启一次很容易被误判成软件问题。解决方式是在无线模块的电源脚旁边并一个 470μF 以上的电容或者干脆给模块单独配一路 LDO。3.5 看门狗与异常恢复机制独立看门狗IWDG我配的是 1 秒超时喂狗动作放在主循环的最后一步。这样设计的关键在于如果任何一个任务卡住导致主循环走不完一圈狗就会咬。喂狗绝对不能放在定时中断里那等于把狗拴在了不会停的地方完全失去意义。除了看门狗我还做了几层恢复机制。第一层是通信超时重试无线模块连不上就在后台静默重连不影响本地报警功能。第二层是传感器自检每隔 10 分钟检查一次 ADC 原始值是否落在合理区间比如 50mV 到 3200mV 之间超出范围就进入 FAULT 状态并在屏幕上显示传感器异常同时蜂鸣器发出特定节奏的提示音。第三层是配置参数的双备份存在 Flash 的两个不同扇区读取时做 CRC 校验一个坏了就用另一个。这里要说一个反直觉的经验故障状态下系统应该降级运行而不是直接停机。比如传感器坏了安防功能仍然要正常工作只是燃气监测能力暂时失效并且必须明确告知用户。我在 FAULT 状态里就是这么做的入侵检测保持在线燃气报警逻辑挂起但持续提示异常。这个设计思路来自我对实际使用的理解——设备最怕的不是功能少而是坏了你却不知道。4. 仿真验证从功能验证到仿真发散的排查4.1 仿真平台的选择和它能做什么、不能做什么这类项目能用的仿真工具大致分三类各有各的用处不要指望一个工具全包。第一类是电路级仿真比如 LTspice、Multisim、TINA 这类 SPICE 系工具适合验证模拟前端RC 滤波的截止频率对不对、分压网络的静态工作点合不合理、运放跟随器的输入输出范围够不够。第二类是单片机联仿比如 Proteus 配上 Keil 生成的 hex 文件能让你在没打板子之前就跑通按键、显示、继电器逻辑验证软件的流程是不是自洽。第三类是控制算法仿真比如在 Simulink 里搭一个温度或浓度变化的模型验证你的滤波和判定逻辑在时域上的表现。它们做不到什么这点更要说清楚。仿真给不了你真实的传感器特性曲线给不了 PCB 上的噪声耦合也给不了继电器动作时的电源跌落。所以我的态度是仿真用来验证逻辑对不对和电路算得准不准实物用来验证能不能稳定跑。指望仿真过了一切就万事大吉那必然要翻车。我做这套项目的时候仿真只花了大概两天实物调试花了将近三周比例差不多就是这样。4.2 用信号注入法验证报警逻辑仿真里最有价值的操作是信号注入。在 Proteus 里我没有真实的燃气传感器就在 ADC 输入引脚上接一个可调电位器或者直接用信号源手动改变电压值观察状态机是否按预期迁移。具体要验证这几个场景电压缓慢上升越过 1200mV 时是否在连续 3 次确认后进入报警、继电器是否正确吸合电压在 1000mV 附近小幅波动时是否稳定保持原状态而不反复跳变电压突然升到 3000mV模拟传感器短路时是否进入 FAULT 而不是误报。在 LTspice 里我把模拟前端单独拎出来做了交流扫描验证 RC 滤波的截止频率。理论上 1k 100nF 的截止频率是 1/(2π×1000×100e-9) ≈ 1.59kHz仿真出来是 1.58kHz误差在可接受范围说明我的计算和模型是一致的。这一步看着简单但它能帮你确认元件取值避免焊上去才发现带宽不够或者过度滤波导致响应变慢。还有一点非常实用仿真的时域响应能帮你估参数。比如我把滤波后的信号做了一个阶跃激励观察到从 10% 到 90% 的上升时间大约是 40ms这意味着我的采样周期取 10ms 是合理的既能跟上变化又不至于过采样浪费 CPU。这种参数在实物上是很难单独测量的因为有太多噪声干扰。4.3 仿真发散到底是怎么产生的怎么收敛仿真发散这个词是我在查资料的时候经常看到的很多人遇到仿真跑着跑着数值越来越大、最后直接报错或者输出无穷大。这块我踩过的坑不少梳理一下原因和对策。发散的本质是求解器在某个时间点无法找到满足所有节点方程的解或者找到了一个数值上不稳定、误差被不断放大的解。常见原因有这么几类第一类是有节点没有直流通路。比如一个电容两端都悬空或者某个节点只通过电容连接到其他部分。SPICE 在算直流工作点的时候不知道这个节点的电压该是多少就会给出一个巨大的值或者报错。对策很简单在关键节点到地之间加一个 1MΩ 到 10MΩ 的大电阻给它一个确定的直流路径对电路功能几乎没有影响。第二类是步长设置不当。开关电源、继电器这类包含快速跳变的电路如果最大步长设得太大求解器会跳过跳变瞬间导致误差累积。对策是在.options里限制最大步长比如把TMAX设成开关周期的几十分之一或者用.tran 0 10m 0 1u这样的写法明确指定最大步长。第三类是模型本身有问题。理想开关、理想运放、没有寄生参数的理想电感在数学上都是病态的容易导致不收敛。对策是把理想器件换成带一点点寄生参数的现实模型比如给电感加 1mΩ 的串联电阻给开关加 0.1Ω 的导通电阻。这些看似微不足道的参数正是让求解器抓得住的关键。第四类是初始条件冲突。比如两个电容并联但初始电压设置矛盾或者电感的初始电流和电路拓扑不匹配。对策是用.ic语句显式指定初始条件或者先跑一次工作点分析.op再跑瞬态。Proteus 这边的发散表现不太一样它更多是仿真卡死或者逻辑不跑。常见原因有MCU 的隐藏电源引脚没有接到 VDD/VSS 网络这个坑我踩过现象是单片机死活不工作时钟频率设置的晶振值和 hex 文件里配置的不一致导致定时器全乱某个元件没有仿真模型加载的时候直接报错程序里有死循环阻塞了仿真调度。排查顺序建议是先看有没有报错信息再检查电源网络然后核对时钟频率最后用点灯程序验证基本环境是不是通的。注意仿真里跑通的报警延时参数搬到实物上一定要重新调。因为仿真里的浓度变化是理想阶跃实物上气体扩散需要时间房间大小、通风条件都会影响。我在仿真里设的 3 秒确认时间实物上改成了 8 秒才合适。5. 调试标定与常见问题速查实录5.1 燃气传感器的标定流程和参数推算MQ 系列传感器出厂时的一致性一般同一批次的模块基线电压能差出 20% 以上所以必须逐个标定不能把别人的阈值直接拿来用。我的标定流程是这样的第一步老化。新传感器要通电连续工作 24 到 48 小时让加热丝稳定、敏感材料结构稳定。这期间读数是会缓慢变化的不要着急调参数。很多人拿到模块就开始调结果第二天发现全变了白忙一场。第二步基线测定。把传感器放在洁净空气中最好是室外或者通风良好的房间预热至少 3 分钟之后记录稳定读数。以我手上这个模块为例输出经过分压后进 ADC 的稳定值大约对应 380mVADC 码值约 470。这个值就是基线记为 V0。第三步确定阈值。这里有两个思路。如果你有标准气体可以直接测出对应浓度的输出电压这叫绝对标定最准但成本高。如果没有标准气体大多数人都没有就用相对法把阈值设在基线的 2 到 3 倍。我的基线是 380mV报警阈值设成 1200mV大约是基线的 3.2 倍。这个取值不是拍脑袋的我参考的是气敏传感器典型的灵敏度曲线在这个浓度区间输出已经有明显变化能有效区分厨房正常炒菜油烟和真实泄漏。解除阈值设成 800mV是基线的 2.1 倍留出了足够的迟滞带。第四步温湿度补偿。这个可选但很有用。我在板子上留了一个数字温湿度传感器的位置采集到的温度和湿度用来对基线做修正。经验公式是基线随温度每升高 10℃ 大约漂移 3% 到 5%方向上通常是温度升高基线下降。我在固件里做了一个简单的线性修正实测下来能把夏季和冬季的误报率降低一半以上。如果你不想增加成本也可以只用软件方案每天凌晨厨房没人、通风良好时自动采一次基线并更新这就是所谓的自动基线校准简单有效。5.2 常见问题速查表下面这张表是我这一年多攒下来的基本覆盖了调试过程中最常遇到的症状。遇到问题先查这张表能省掉大量抓耳挠腮的时间。症状可能原因排查方法解决办法程序烧进去不运行BOOT 引脚悬空或接错万用表测 BOOT0 电平BOOT0 加 10k 下拉下载一次后就锁死关闭了调试引脚功能检查是否误配 SWD 引脚为普通 IO用串口 ISP 擦除后重配ADC 读数一直为 0通道配置错或 DMA 没启动示波器测输入脚电压核对通道映射和 DMA 使能ADC 读数乱跳电源噪声、滤波不足示波器看 VDD 纹波加去耦、改善 RC 滤波、分路供电燃气读数缓慢上升不回落传感器预热不足或中毒观察 24 小时曲线延长预热、必要时更换传感器人体感应误触发电平不匹配、探头朝向不当测输出电平是否为 5V加分压或电平转换调整安装角度继电器吸合导致复位电源被拉垮、地线干扰测复位瞬间 3.3V 波形分开供电、加大电容、优化地平面蜂鸣器声音小或沙哑有源无源接错、驱动电流不足确认蜂鸣器类型换驱动方式无源用 PWMOLED 不显示上拉电阻缺失、地址不对用 I2C 扫描程序确认地址补上 4.7k 上拉修正地址无线模块频繁断连峰值电流不足测发射瞬间电源电压加大电容或单独配 LDO看门狗频繁复位某个任务耗时过长在循环里打时间戳把耗时任务拆分或降到低频执行仿真卡死不动隐藏电源未接、时钟配置不符检查网络标号和时钟频率接上 VDD/VSS核对晶振值5.3 几条踩过坑才明白的经验第一报警阈值宁高勿低。新手总怕漏报把阈值调得很低结果炒个辣椒就报警家人两天就把设备拔了。一个被拔掉的报警器防护效果是零。宁可灵敏度稍微保守一点也要保证它长期在那个位置被信任。第二所有继电器动作前加延时。不要一检测到条件就立刻切换继电器给它 200ms 的确认窗口。这个延时在电气上能避开电源瞬间跌落和干扰尖峰在体验上也能避免继电器像打字机一样响个不停。第三屏幕和指示灯的信息要分层。正常状态下只显示关键信息布防状态、燃气读数异常状态下才弹出详细信息。我一开始把所有信息都堆在屏幕上结果家人根本看不懂现在简化为一个大字状态加一行小字接受度高了很多。第四把调试口留出来。我在板子上留了四针的调试排针3.3V、GND、SWDIO、SWCLK并且固件里保留了一个调试模式进入后会通过串口输出详细的内部状态包括每个通道的原始值、滤波值、当前状态机状态、计时器计数。这个模式在排查偶发误报这类难缠问题时价值极高因为偶发问题靠肉眼观察根本抓不住。第五写日志到 Flash。每当发生报警、故障、状态切换就把一条带时间戳的记录写进 Flash 的日志区。这里要注意 Flash 的擦写寿命我采用环形缓冲的方式写满之后从头部覆盖并且只在重要事件时写不要每秒都写。有了这个日志你才能回过头分析昨天晚上两点为什么响了一次。6. 开源资料的组织与二次开发建议6.1 代码仓库的目录规范与文档习惯开源一个项目代码只是其中一部分甚至不是最重要的部分。我做这套东西的时候仓库结构是这样的根目录放 README、LICENSE 和一个docs文件夹firmware放完整工程包含 Keil 工程文件和源码hardware放原理图、PCB 源文件和导出的 PDF、Gerbersimulation放仿真工程文件和说明docs里放元件清单、标定步骤、接线图。README 我建议写清楚四件事这个项目能干什么、需要买哪些元器件附一张表含型号、数量、大致价位、怎么把代码烧进去要具体到用哪个工具、哪个接口、以及已知的限制和注意事项。很多人写 README 只写这是一个基于 STM32 的智能安防系统然后就没了别人拿到手根本不知道怎么往下走。我在 README 里明确写了本项目为个人学习用途不构成安全设备的替代方案这既是对别人负责也避免了一些不必要的麻烦。原理图导出 PDF 的时候要注意如果用 AD 之类的工具直接导出可能会出现只导出当前选中区域的情况。我的做法是先在原理图里确认没有选中任何对象然后使用智能 PDF 导出功能勾选整个工程或者逐页设置导出之后再翻一遍每一页确认元件标号和网络标号都能看清。如果图纸太大导出时把分辨率调到 600dpi 以上不然放大后字是糊的。Gerber 文件导出后一定要用 Gerber 查看器打开看一遍确认每一层都有内容、钻孔文件和阻焊层没有错位。6.2 元器件清单整理和替代方案BOM 表我喜欢按功能分块排列而不是按位号排序。这样别人一眼就能看出哪几个件是核心、哪几个是外围。核心器件MCU、气敏传感器、热释电模块我标注了必须匹配的型号外围器件电阻、电容、三极管标注了参数范围允许替换。类别器件关键参数备注主控STM32F103C8T672MHz, 64KB Flash可用同系列替代燃气传感半导体气敏模块5V 供电, 模拟输出需预热和标定人体感应热释电模块3~5V, 数字输出注意电平匹配门磁干簧管常闭型配 10k 下拉显示OLED 0.96 寸I2C 接口需 4.7k 上拉提示无源蜂鸣器3.3V, PWM 驱动配三极管执行5V 继电器模块触点容量 10A光耦隔离电源DC-DC LDO12V→5V→3.3V分路供电替代方案我一般会给出两套。比如主控可以用 STM32F103 的其它封装也可以用国产的兼容型号把时钟配置和 HAL 库头文件改一下就行。传感器如果想升级精度可以换成电化学式或者红外式接口形式可能从模拟变成串口此时只需要改 BSP 层的读取函数App 层的判定逻辑基本不用动这就是分层的价值。6.3 二次开发的几个扩展方向这套项目的架构留了不少扩展空间我自己也在陆续加东西。第一个方向是多点位组网把几个节点分布在不同房间通过无线模块汇总到一个主节点主节点做集中显示和联动。这个改造的关键是设计一个简洁的通信协议我建议用固定长度的二进制帧而不是字符串节省带宽也便于校验。第二个方向是接入家居环境联动。比如燃气报警时不仅开排风扇还可以联动智能插座切断气源附近的电器、推送消息到手机。这里要注意的是联动动作必须有本地兜底网络断了你不能连报警都不响所以我的设计里所有联动都是本地优先远程为辅。第三个方向是把它改造成通用的环境监测节点。这套代码里的 ADC 采样、滤波、迟滞判定、日志记录这几个模块是通用的把气敏传感器换成温湿度、空气质量、光照传感器代码复用率能在 70% 以上。我甚至拿同一套框架做过一个小型的鱼缸监测装置测水温、控制加热棒和补光灯改动量比我想象的小得多。第四个方向是固件在线升级。这个需要留出专门的 Bootloader 分区实现起来有一定门槛但一旦做通后续修复 bug 就不用把设备拆下来重新烧录了。我的建议是如果你打算把这个东西长期装在家里并且会不断改代码那 IAP 功能值得投入时间去做因为反复拆装设备的成本远高于写 Bootloader 的成本。我在实际使用中体会到的一点是这类嵌入式项目真正的门槛从来不在写代码而在于把一整套东西从能跑打磨到敢让它 24 小时无人值守地跑。中间差的那段距离全是靠对细节的反复推敲和大量真实场景的验证补起来的。如果你的板子第一次上电就能稳定工作一整天那是运气如果它连续报警了三次都是误报那才是正常的开始剩下的工作就是把这些误报一个一个找出来、干掉。