先说个背景。我之前在学校实验室里负责过一段时间仪器设备维护跟不少同学聊过实验室安全管理的事。实验室跟普通办公室不一样里面有过夜充电的锂电池、长期通电的烘箱、各种加热设备真要出问题往往是下班后没人看见的时候。商用消防报警器不是不好但一套下来价格不低而且大多是黑盒方案探测阈值、联动逻辑都没法按自己的场景调。所以我自己用 STM32 做了套实验室消防预警控制系统把代码、原理图和仿真工程一起开源了出去。这套东西能实时检测烟雾浓度、火焰信号、温湿度触发声光报警还能通过继电器控制排风扇或者电磁阀。对做嵌入式入门、电子设计课程设计、毕业设计的同学来说它是很好的参考项目对实验室安全管理人员来说它也是个低成本的预警补充方案。这篇文章就把我做这个项目的完整思路拆开讲从需求分析、原理图设计、代码逻辑到仿真调试最后说说开源发布时怎么整理文件才能让别人快速跑起来。1. 消防预警系统到底解决什么问题需求拆解与整体方案选型很多人一上来就直接打开画图工具画原理图写代码其实这是最容易翻车的做法。我第一版就是这样结果做到一半发现传感器选型不合理ADC 通道不够用蜂鸣器驱动电流不够整个推翻重来。所以先花点时间把需求捋清楚比多写一百行代码都值。1.1 实验室火灾“早发现”为什么比家庭场景更难实验室里最危险的不是瞬间爆燃而是缓慢的热积累。比如烘箱温控失灵、电热套干烧、锂电池过充发热这类情况在初期几乎没有明火只有温度升高和少量烟雾。家庭里大多数人能闻到焦糊味但在实验室化学试剂本身就有各种气味通风橱一开人的嗅觉基本报废等肉眼看到烟的时候已经过了最佳处置窗口。所以消防预警系统要盯的核心不是“有没有火”而是“有没有异常趋势”。烟雾浓度从正常到升高的过程、环境温度是否异常爬升、是否存在红外火焰特征信号这三个维度放在一起判断比单一传感器靠谱得多。1.2 为什么选 STM32 而不是 51 或者 ESP32这个项目最早我用 STC89C52 试过做是能做但很憋屈。烟雾传感器输出的模拟量51 单片机没有 ADC需要外挂 ADC0809 之类火焰传感器和 DHT11 都还好报警输出也没问题。问题是代码一旦复杂起来比如要同时处理 ADC 采样、DHT11 时序、按键扫描、状态机切换8 位 MCU 的资源和代码组织能力会很吃力调试效率也低。后来换成 STM32F103C8T6这块板子现在可以说是嵌入式学习界的“标配货”了。四个理由让我确定用它外设完整内置 ADC、TIM、USART、I2C、SPI一颗芯片就能接烟雾传感器、火焰传感器、DHT11、OLED 显示屏、继电器不用东拼西凑。生态成熟标准外设库和 HAL 库资料铺天盖地遇到问题几乎都能搜到答案。价格便宜小批量几块钱一片打样做坏了几块也不心疼。性能余量够后面想扩展 4G 短信报警、ESP8266 联网上报STM32F103 依然能带得动。ESP32 我也考虑过它确实性能更强还自带 Wi-Fi但问题是用在这个场景里有点“杀鸡用牛刀”而且它的 ADC 线性度一般用来做烟雾浓度采样不如 STM32 的 12 位 ADC 顺手。再说实验室消防设备稳定性比联网功能优先级更高。1.3 系统总架构三个检测维度加两级响应这套系统的整体架构不复杂一句话描述就是传感器采集环境参数单片机做滤波和阈值判断根据危险等级执行不同动作。我设计的检测维度是这三路检测项目传感器信号类型说明烟雾浓度MQ-2模拟电压加热型半导体传感器对烟雾和可燃气体均敏感火焰信号火焰传感器模块数字电平红外波段检测带灵敏度电位器调节温湿度DHT11单总线数字辅助判断环境异常比如温度连续上升响应端包括声光报警和继电器联动。声光报警用的是有源蜂鸣器加红绿双色 LED继电器用来控制排风扇、电磁阀或者切断非关键设备电源。这一路是真正干活的不是只叫唤两声就完事。代码层面分成三层底层驱动DHT11 时序、ADC 采集、GPIO 控制、业务中间层滤波、阈值判断、消抖、应用层状态机、串口命令、OLED 显示。这样分层的好处是你想改传感器型号只需要换底层驱动业务逻辑不受影响想调报警灵敏度也只需要改阈值参数。2. 硬件原理图设计传感器选型与接口电路的关键细节原理图是整个项目的“骨架”很多初学者用开发板能跑通程序一到自己画板就懵原因就是没搞懂传感器模块的核心电路是怎样的。我详细说说每个功能模块的接口电路。2.1 MQ-2 烟雾传感器加热电阻和负载电阻的老问题MQ-2 属于半导体气敏传感器内部有两个部分一个是加热丝需要 5V 供电持续加热让敏感层达到工作温度另一个是二氧化锡敏感层遇到还原性气体时电导率会变化。等效来看敏感层就是一个阻值随气体浓度变化的电阻从洁净空气中的几十千欧到高浓度烟雾下的几千米欧。如果直接把这个变化的电阻接 ADC你没法获得稳定的电压。标准做法是把敏感层和一个固定负载电阻串联形成一个分压电路。我原理图里用的是 10k 负载电阻电源 5V分压节点接 STM32 的 ADC 输入。公式很简单[ V_{out} 5V \times \frac{R_{load}}{R_{sensor} R_{load}} ]洁净空气时传感器阻值高分压点电压低大概在 0.3V 到 0.6V 之间烟雾浓度升高传感器阻值下降分压点电压上升。实测在打火机燃气烟雾下这个电压能冲到 3.5V 以上。这里有两个容易被忽略的坑第一MQ-2 加热丝冷态电阻小上电瞬间电流接近 200mA如果电源设计余量不足会把 3.3V 电压拉垮导致单片机复位。我在原理图上给传感器单独加了 100uF 电解电容和 0.1uF 陶瓷电容做电源去耦同时让 MQ-2 的 5V 和单片机的 3.3V 在电源入口处做好隔离。第二STM32 的 ADC 输入引脚耐压在 VDD 以内不能直接把 MQ-2 的分压点接进去要先用两个电阻把 5V 分压成 3.3V 可用的范围。我习惯在 ADC 前端加一个跟随器或者至少加 RC 低通滤波R 取 10kC 取 0.1uF截止频率在 160Hz 左右既能滤掉工频干扰又不会拖慢响应。2.2 火焰传感器模块红外探头加比较器火焰传感器模块通常是一块小板上面有红外接收管、LM393 比较器和一个电位器。它能检测火焰发出的红外光谱检测到火焰时数字输出引脚从高电平跳变到低电平或者反过来取决于模块的设计。接入 STM32 很简单模块的 DO 引脚直接接一个 GPIO 输入即可。但我要提醒三点模块的灵敏度电位器非常敏感拧一点点就有很大变化最好用万用表量着输出电平来调而不是凭感觉。红外火焰传感器对阳光、白炽灯、甚至遥控器发出的红外信号也可能误触发。在实验室里如果窗户附近有阳光直射误报率会很高。我的处理方式是在软件里加了连续多次确认机制5 次采样里至少 4 次检测到火焰才确认报警。火焰传感器的响应速度极快探测距离却有限一般只有几十厘米到一两米。所以它更适合做近距离确认而不是大范围扫描。原理图上的接口电路很简单VCC 接 5VGND 接地DO 接单片机 PA0 这类带外部中断能力的引脚。考虑到 LM393 输出是开集结构模块上一般已经自带上拉电阻不需要额外加。2.3 DHT11 温湿度传感器上拉电阻真的不能省DHT11 是单总线数字传感器数据线在空闲时是高电平设备通过拉低特定时长来产生起始信号和数据位。因为数据线是开漏输出所以必须在外部接一个 4.7k 到 10k 的上拉电阻。我见过不少同学在面包板上直接插 DHT11 不加上拉结果读出来全是 0 或 255就是这个原因。DHT11 的精度不高温度正负 2 度湿度正负 5% RH但做消防预警完全够用。我要测的不是精确温湿度而是温度的变化趋势。如果两分钟内温度连续上升超过 3 度就算烟雾传感器和火焰传感器都没触发我也会让系统进入“注意”状态。DHT11 的供电范围是 3.3V 到 5V原理图上我直接接 3.3V避免电平不匹配的问题。数据引脚接 PC6中间串了一个 100 欧姆限流电阻万一接线短路也不至于烧引脚。2.4 电源树、继电器驱动与原理图检查重点整块板子的电源树是这样外部 12V 适配器接入经过 DC-DC 降压模块输出 5V5V 再经 AMS1117-3.3 输出 3.3V。之所以不用 USB 的 5V 直接供电是因为继电器吸合瞬间电流很大USB 口容易掉压。继电器驱动部分我只强调一点继电器线圈必须用三极管或 ULN2003 驱动不能直接接单片机引脚。STM32 GPIO 输出能力有限带不动继电器线圈。我用的是一片 ULN2003 里面的其中一路单片机 GPIO 接 ULN2003 输入输出端接继电器线圈线圈两端反并联一个 1N4007 二极管用于吸收断电时的反向电动势。这个二极管不定不能漏否则 ULN2003 很容易被击穿。画完原理图别急着画 PCB先自查几项用 ERC 检查看有没有悬空引脚、电源短路。每个 IC 的电源引脚旁边有没有去耦电容我习惯每个 IC 配一个 0.1uF。按键和复位电路是否接上拉或下拉确保默认电平稳定。继电器触点端和信号端是否拉开安全距离防止强电干扰弱电。我自己用嘉立创EDA画这个项目的原理图导出为 PDF 和图片放到了开源仓库的 Hardware 目录里。嘉立创EDA 的好处是国产、免费、库元件齐全MQ-2 和 ULN2003 这些常用模块直接搜就有封装不用自己画。3. 核心代码实现从外设初始化到状态机管理原理图搞定后最花时间的其实是固件代码。我按“先通串口再通传感器最后做业务逻辑”的顺序开发每一步都能验证结果不会到最后堆了几百行代码一出错不知道从哪找。3.1 工程搭建Keil5 环境与关键初始化配置我用的 IDE 是 Keil5芯片选择 STM32F103C8。如果你是用标准外设库开发需要先找到对应的芯片支持包如果你用 STM32CubeMX 生成工程可以省去不少时钟配置的麻烦。整个工程的初始化流程在 main 函数里按这个顺序设置系统时钟为 72MHz初始化 GPIOLED、蜂鸣器、继电器、火焰传感器输入初始化 ADC烟雾传感器采样通道初始化 USART1串口调试输出初始化 DHT11 数据引脚初始化定时器用做状态机的时基10ms 中断GPIO 初始化这部分需要注意“哪些引脚是推挽输出哪些是上拉输入哪些是模拟输入”。很多人把输入输出模式搞反或者该用上拉输入的场景用了浮空输入导致按键和传感器读数不稳定。下面这段是标准外设库的写法void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE); // PA1: 蜂鸣器输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA2: 继电器控制输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_Init(GPIOA, GPIO_InitStructure); // PC4: LED1 红色报警灯 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOC, GPIO_InitStructure); // PA0: 火焰传感器输入上拉输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); }ADC 初始化时我用了通道 1也就是 PA1 等下PA1 已被蜂鸣器占用了。实际项目里我用的是 PA4 作为 ADC 输入。这里需要修正代码中 PA1 是蜂鸣器PA4 是烟雾传感器 ADC 输入。ADC1 的通道 4 对应 PA4。void ADC_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟 72MHz / 6 12MHz // PA4 模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); }3.2 ADC 采样与烟雾浓度的换算逻辑STM32 的 ADC 是 12 位的读出的原始值范围 0 到 4095。要得到实际电压用这个公式float voltage (float)adc_value * 3.3f / 4095.0f;这里用的参考电压是 3.3V就是板载 VDDA。因为 MQ-2 输出经过分压后最高也只能到 3.3V 附近不会超出 ADC 量程。但我不建议直接用单次采样值做判断因为 MQ-2 本身受温度、湿度影响会有缓慢的基线漂移。我写的函数会对烟雾电压做 10 次滑动平均然后保存最近 30 秒的均值作为“当前基线”。判断逻辑是当前值与基线之差超过设定阈值才报警这样能抑制传感器自身漂移带来的影响。下面是工程里 ADC 采样的核心函数uint16_t ADC_GetAverage(uint8_t times) { uint32_t sum 0; for (uint8_t i 0; i times; i) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); sum ADC_GetConversionValue(ADC1); } return (uint16_t)(sum / times); } float Smoke_GetVoltage(void) { uint16_t avg ADC_GetAverage(10); float voltage (float)avg * 3.3f / 4095.0f; return voltage; }阈值参数的设置是一个关键点。实际使用时我先让系统“自学习”30 秒取这段时间的平均电压作为正常基线然后在这个基线上加一个偏移量作为报警阈值。偏移量我做成两个档位预警档 0.2V报警档 0.6V。这个数值不是拍脑袋定的而是实测打火机在不点火状态下靠近传感器半米处电压跳变约 0.15V 到 0.3V在点火状态下模拟烟雾电压跳变可以到 1V 以上。如果你的使用环境有酒精挥发、灰尘大等情况需要现场重新标定。3.3 DHT11 温湿度读取时序延时精度决定成败DHT11 单总线的时序要求比较严格但也没有难到要用定时器捕捉去做的程度。关键是把微秒级延时函数写好并且确认主频是 72MHz否则延时和时间不对应。DHT11 的读时序分三步主机拉低数据线至少 18ms然后释放并延时 20us 到 40us。DHT11 响应拉低 80us再拉高 80us。DHT11 连续发送 40 位数据每位数据由 50us 低电平和 26us 到 70us 的高电平组成高电平长代表“1”短代表“0”。40 位数据依次是湿度整数、湿度小数、温度整数、温度小数、校验和。校验和的算法是前四个字节相加低 8 位等于校验字节。代码中我用的微秒延时是基于 SysTick 的若是标准库就简单点static void Delay_us(uint32_t us) { uint32_t ticks (uint32_t)(us * (SystemCoreClock / 1000000)); SysTick-LOAD ticks - 1; SysTick-VAL 0; SysTick-CTRL | SysTick_CTRL_ENABLE_Msk; while (!(SysTick-CTRL SysTick_CTRL_COUNTFLAG_Msk)); SysTick-CTRL ~SysTick_CTRL_ENABLE_Msk; }需要注意的是如果直接用 delay_ms 函数去模拟 DHT11 的微秒延时非常容易翻车。因为很多 HAL 库的延时函数内部用了循环变量编译器优化等级一变实际延时时长就变了。我建议 DHT11 的底层时序尽量用寄存器操作或者 SysTick 硬延时。3.4 消防逻辑状态机消抖、连续确认与分级响应代码最能体现一个嵌入式工程师水平的地方不是某个传感器驱动写得多巧妙而是整个业务逻辑组织得是否清晰。我用一个枚举变量表示系统状态typedef enum { STATE_NORMAL, STATE_WARNING, STATE_ALARM } SystemState;状态迁移规则是这样的正常状态烟雾电压低于预警阈值火焰无触发温度正常。LED 绿色常亮蜂鸣器关闭。预警状态烟雾电压超过预警阈值或者温度单次上升超过 3 度。LED 橙色快闪蜂鸣器滴滴响继电器不动作。报警状态烟雾电压超过报警阈值或火焰触发且连续确认或温度连续 5 次采样都超过 50 度。LED 红色快闪蜂鸣器长响继电器吸合切断/联动外部设备。关键在“消抖”上面。我用了一个计数器只有连续 5 次扫描每次扫描间隔 100ms都达到报警条件才真正进入报警状态。这 500ms 的确认窗口能挡住绝大多数误触发包括偶尔的电磁干扰、人手在传感器附近挥动导致的瞬时烟雾读数跳变。状态机的主循环结构如下while (1) { smokeVoltage Smoke_GetVoltage(); fireLevel Flame_GetLevel(); tempHumidity DHT11_Read(); System_Scan(smokeVoltage, fireLevel, tempHumidity); int view State_View(); OLED_Update(view); UART_SendState(view); Delay_ms(100); }我挺建议状态机里加一个“手动测试模式”。按下按键后进入测试模拟烟雾和火焰信号强制让系统进入报警状态验证蜂鸣器、继电器能不能正常动作。消防设备的自检非常重要不然真到关键时刻才发现蜂鸣器不响那就成了摆设。4. 仿真与调试无硬件也能跑的调试路径以及仿真和实物的典型偏差很多人看到“仿真”就以为是 Proteus 跑个流水灯其实一套完整的消防预警系统仿真能帮你把硬件和软件逻辑分别验证出问题的时候快速定位到是哪一层的锅。4.1 用仿真搭出最小验证环境这个项目我提供了 Proteus 仿真文件包含 STM32F103C8、MQ-2用可变电阻替代、火焰传感器用按键模拟、DHT11 模型、蜂鸣器和 LED。把这些元件连好载入 Keil 编译出来的 hex 文件点击运行就能看到完整逻辑状态变化。用可变电阻模拟 MQ-2 的思路很巧妙。将可变电阻的分压点接入 ADC 输入转动旋钮调整电压值就能模拟烟雾浓度升高。实测下来把电压调整到 1V 以上时系统会从正常状态跳转到预警超过 1.5V 后进入报警。这样不用真的在仿真里找烟雾模型。火焰传感器的仿真模拟更简单我用一个按键接一个上拉电阻按键按下时引脚拉低模拟火焰触发。DHT11 在 Proteus 里有现成模型但它的时序响应和真实器件有区别后面我会讲到。仿真还有一个用处是看串口输出。我在代码里用 USART1 每 100ms 输出一行状态数据内容包括voltage,fire,temp,humidity,state。仿真里用虚拟终端直接观察这些数据能够很方便地验证状态机迁移是否正确。4.2 仿真和实物的差异哪些结论只能信一半仿真通过不等于硬件没问题我列几条真实的踩坑记录DHT11 模型在仿真里的时序太理想真实器件在某些批次上响应时间会有偏差。代码在仿真里一次就读通到实物上却经常第一次读失败需要重试三次以上。最终我在代码里加了一个“读失败自动重试”的逻辑每次读取最多重试 5 次如果全部失败就返回上一次的有效值。ADC 在仿真中没有噪声你会觉得 10 次平均和单次采样没区别。但实物上如果电源纹波大单次采样的数值会上下跳动几十个数。这就让平均滤波算法的重要性体现出来了。仿真的蜂鸣器只是“响与否”的逻辑但实物要区分有源蜂鸣器和无源蜂鸣器。有源蜂鸣器给高电平就响无源蜂鸣器需要给脉冲才能响。如果你照仿真里直接给高电平实物可能发出奇怪的声音。我在代码里用宏定义区分了两种蜂鸣器工程里默认是有源蜂鸣器。火焰传感器在仿真里就是高低电平但实物对红外干扰非常敏感夏天靠近白炽灯都可能误触发。所以代码里必须加确认次数这是仿真中完全暴露不出来的问题。这些差异提醒我一件事仿真最大的价值是验证“逻辑设计得对不对”无法验证“硬件电路稳不稳定”。凡是涉及模拟信号采集、电源完整性、传感器真实响应特性的地方最终必须上实物测试。4.3 ST-Link 调试和串口打印的实际经验开发阶段我坚持用 ST-Link 配合 Keil 进行在线调试遇到程序跑飞、硬件故障能在调试点打断点看变量数值效率远高于烧录后瞎猜。在线调试时最常用的是 Watch 窗口把systemState、smokeVoltage、fireCount加进去每次执行到关键断点时查看数值变化。很多时候问题一看变量就明白了比如发现smokeVoltage一直显示 0.01V那不是代码问题而是 ADC 配置错误或者引脚接错。串口打印是我排查问题最依赖的手段。我在代码里每个传感器读取函数执行完都会打一行日志但这个日志只在调试模式下开启正式发布时用#ifndef DEBUG关掉避免频繁打印拖慢主循环。还有个细节是在线仿真时如果开了串口中断DHT11 时序读取很容易被中断打乱导致读时序失败。因为 DHT11 对微秒级时序有要求中断服务函数一旦占用了太多时间就会错过数据位。我最后的处理方案是DHT11 读取期间暂时关闭串口接收中断__disable_irq(); DHT11_ReadRaw(); __enable_irq();这种方法简单粗暴但确实能解决 DHT11 在开关中断环境下读取失败的老大难问题。代价是串口可能在那一两个毫秒内丢字符对调试日志来说完全可接受。5. 开源项目怎么组织别人才会真的跑得起来开源不是把代码往 GitHub 一丢就完事的。我做嵌入式开源也有几年了感受特别深的一点是一个项目的“可复现性”直接决定它有没有价值。你觉得自己代码写得很清楚但别人拿到后可能没有对应型号的芯片包没有原理图不知道怎么接线第一印象就被劝退。5.1 仓库目录结构与核心文件说明这个消防预警项目的仓库结构如下lab-fire-prevention/ ├── README.md ├── Doc/ │ ├── 系统框架图.png │ ├── 接线图.png │ └── 使用说明.md ├── Hardware/ │ ├── FirePrevention_Sch.pdf │ ├── FirePrevention_Sch.png │ └── FirePrevention_PCB/ ├── Firmware/ │ ├── Core/ │ │ ├── Inc/ │ │ └── Src/ │ ├── Drivers/ │ │ ├── BSP/ │ │ └── SYS/ │ ├── Project/ │ │ └── MDK-ARM/ │ └── README.md ├── Simulation/ │ ├── FirePrevention.pdsprj │ └── 仿真说明.md └── Tools/ ├── STM32_ST-LINK_Utility.md └── 烧录脚本.batDoc目录里必须有接线图。不要只放原理图因为原理图是给电气工程师看的很多做软件的同学看着原理图还是不知道模块怎么连。我特意画了一个简化的面包板接线图用不同颜色标注电源线和信号线方便新手照着插线。Firmware里的项目路径是Project/MDK-ARM这样别人用 Keil 打开时能直接找到工程文件。很多开源项目把.uvprojx文件放在一堆源码的地底下找起来费劲。Simulation目录放 Proteus 工程文件并在说明文档里写清楚用什么版本打开、是否需要安装传感器模型库、如何加载 hex 文件。这些细节能省掉一批人在环境搭建上花的时间。5.2 README 怎么写才有用一份“五段式”模板我整理了一个适合自己的五段式 README 结构第一段放项目效果动图和功能列表让人 30 秒内看懂这是什么第二段是硬件清单并标注参考价格第三段是接线说明配一张图第四段是软件环境准备和编译烧录步骤第五段是校准与常见问题。硬件清单部分我建议把“必选”和“可选”分开。比如模块型号/规格数量参考价格必选/可选主控STM32F103C8T6 最小系统板115 元必选烟雾传感器MQ-2 模块110 元必选温湿度传感器DHT1115 元必选火焰传感器红外火焰传感器模块18 元必选蜂鸣器有源蜂鸣器模块13 元必选继电器5V 单路继电器模块18 元可选显示屏0.96 寸 OLED115 元可选仿真烧录器ST-Link V2120 元必选编译烧录部分写得像“傻瓜教程”也没关系宁可啰嗦也不让读者卡住。我写的步骤大致是安装 Keil5 和 STM32F1 芯片支持包。打开Firmware/Project/MDK-ARM下的工程。选择 ST-Link 调试器在 Settings 里选 SW 模式。编译工程确认 0 Error0 Warning。用 USB-TTL 或 ST-Link 连接板子点击 Download。打开串口助手波特率 115200观察状态日志。5.3 常见问题排查表把踩过的坑写进去开源项目的 README 除了教人怎么跑通还要写“跑不通怎么办”。我把开发过程中遇到的高频问题整理成表格实际效果也不错不少人反馈看表格就解决了问题。问题现象可能原因解决方式上电后 MQ-2 电压持续上升传感器预热未完成等待 2-5 分钟观察是否稳定DHT11 读取失败上拉电阻缺失或时序中断检查上拉电阻确保读取期间关闭串口中断火焰传感器频繁误报环境红外干扰调节灵敏度电位器修改确认次数继电器吸合瞬间单片机重启电源余量不足换更大功率适配器继电器模块单独供电仿真正常但实物不工作引脚定义不一致对照原理图逐一检查 GPIO 映射5.4 后续可以扩展的方向开源之后不少人问过我能不能加更多功能我这里给出几个我认为比较实用的扩展方向加一个 ESP8266 模块通过 MQTT 协议把报警信息推送到手机做远程预警。加一个锂电池加充电管理让设备在断电后还能继续工作一段时间用于临时实验室或户外场景。加一个 OLED 菜单系统用按键设置报警阈值、查看历史记录而不是每次改阈值都要重新编译烧录。把 DHT11 换成品 AHT20 或 SHT30温湿度精度提升一个量级响应速度也更快但成本和代码复杂度也会增加。增加多个传感器节点用 RS485 或 LoRa 组网实现整栋楼级别的消防预警覆盖。我个人在实际操作中的体会是这套系统真正的价值不是把某个具体传感器用好而是提供一个“感知—判断—响应”的完整闭环框架。你换掉任何一环比如把 MQ-2 换成激光粉尘传感器把 STM32 换成国产 GD32把继电器换成可控硅整体架构依然成立。这也是我坚持把它开源出来的原因希望后来者不要重走我在选型和调试上走过的弯路。最后提醒一句这类设备可以做安全辅助参考但实际消防验收和应用还是要以合规消防设备和制度为准。
