做嵌入式这几年,见过太多只跑流水灯和串口打印的STM32项目,真正能落地到具体场景、把传感器、控制逻辑和报警联动串起来的反而少见。这次开源的实验室消防预警控制系统,算是我做过的比较完整的一套小系统:STM32做主控,配合火焰检测、烟雾浓度检测和温度采集,实现声光报警、风扇排烟和继电器联动切断,还带手动复位和状态指示。代码、原理图和Proteus仿真全部打包开源,不管是拿来应付课程设计、毕业设计,还是想认真学一遍STM32裸机开发的完整流程,这套东西都值得你花一晚上好好拆一遍。
先说说这套系统能做什么。实验室、机房、仓库这类场景最大的痛点就是火情发现太晚,等肉眼看到明火或者闻到焦味,往往已经过了最佳处置窗口。这套预警系统的思路就是三路传感器各自盯一个维度,火焰传感器盯明火光谱,MQ-2盯烟雾和可燃气体浓度,DS18B20盯环境温度,三路信号汇入STM32F103C8T6,由主控统一判决,一旦满足报警条件,立刻驱动有源蜂鸣器和红色LED给出声光报警,同时自动开启排风扇降低有害气体积聚,并通过继电器切断非必要设备电源。整套逻辑不依赖上位机,通电即独立运行,非常适合作为嵌入式综合实训项目。
1. 整体设计思路与系统架构
1.1 为什么选STM32F103C8T6做主控
先说主控选型。STM32F103C8T6这块芯片可以说是嵌入式开源项目的“万金油”,72MHz主频、64KB Flash、20KB SRAM,管脚48个,价格十几块钱,资料铺天盖地。对于消防预警这种I/O密集型任务,它根本不存在性能瓶颈,反而是在开发体验和调试便利性上有巨大优势:支持SWD下载调试,一根几块钱的ST-Link就能搞定烧录和在线断点调试,库函数和HAL库两套开发方式随你挑,遇到问题随便一搜都是别人踩坑留下的解决方案。
如果换用51单片机,虽然也能做,但ADC精度、外部中断数量、定时器资源都会捉襟见肘。比如MQ-2烟雾传感器的模拟输出需要ADC采集,51自带的ADC要么没有要么精度堪忧,外扩ADC芯片又增加复杂度。换用ESP32或者STM32F4系列则属于杀鸡用牛刀,成本和复杂度都上去了。F103C8T6的10位ADC、7个定时器、37个GPIO,对于三路传感器加四路输出的系统规模来说留足了余量,甚至还能额外扩展WiFi模块做远程报警,这个后面细说。
1.2 系统功能拆解与状态划分
这套系统的工作逻辑可以用一个有限状态机来概括,而不是简单的if-else堆砌。整个系统划分成三个状态:正常监视态、预警态、火警态。
正常监视态下,主控周期性轮询三路传感器数据,所有输出处于关闭状态,绿色LED闪烁表示系统正常运行。当任何一路传感器数据超过预设阈值时,进入预警态——这时候蜂鸣器以较慢的频率间歇鸣叫,黄色LED点亮,但风扇和继电器不动作;如果连续多次采样仍然超阈值,或者多路传感器同时超阈值,则确认火警,进入火警态——蜂鸣器连续急促鸣叫,红色LED快闪,风扇启动排烟,继电器切断电源。火警态必须手动按复位按钮才能解除,防止误触发自动复位导致危险。
这套状态机的设计参考了工业消防主机的逻辑,核心思想就是防止单次采样抖动引起误报。实际环境中传感器信号不可能一直平稳,尤其火焰传感器受环境光影响波动很大,如果采样一次超阈值就报警,一天能报十几次假警,用不了多久就会被当成故障忽略掉。加上连续确认机制之后,系统的可靠性和实用性会提升一个档次。
1.3 各模块职责划分
系统按功能划分成五个模块:传感器采集模块、主控判决模块、声光报警模块、排烟断电执行模块、人机交互模块。传感器模块负责把物理量转换成电信号;主控模块负责ADC采集、数字信号处理、状态判决;声光报警模块由有源蜂鸣器和三色LED组成;执行模块包含风扇驱动和继电器控制;人机交互模块提供复位按键和系统状态指示。
模块化设计的最大好处是调试的时候不用从头查起。比如报警不响,先量蜂鸣器引脚有没有电平变化,有就是硬件问题,没有就是软件问题,一分为二,排查效率翻倍。对于学习来说,模块化解耦也让代码更容易读懂,每个源文件对应一个功能,不至于在一千行代码里找某个引脚配置找半天。
2. 硬件选型与原理图设计细节
2.1 传感器选型对比与接口说明
传感器是整个系统的感知层,选型直接决定系统的灵敏度和误报率。这套项目里我选了红外火焰传感器模块、MQ-2烟雾传感器模块和DS18B20防水温度传感器三个典型代表,正好覆盖了光、气、热三个维度,也对应了三种最常见的传感器接口类型:数字量输出、模拟量输出和单总线协议。
火焰传感器模块的核心是一个红外接收管,对波长760nm到1100nm的红外光敏感,火焰燃烧时会产生这个波段特征辐射。模块上带一个LM393比较器,把光强信号转换成高低电平输出,板载电位器可以调节触发灵敏度。要注意的是太阳光里也含有大量红外成分,所以这种简易火焰模块在阳光直射环境下容易误报。实验室场景还好,如果你要部署在窗边或者采光好的房间,建议加一个遮光罩,或者干脆选带紫外探测的火焰传感器,当然价格会贵不少。
MQ-2烟雾传感器是经典的电化学式气敏元件,内部加热丝加热二氧化锡半导体材料,遇到可燃气体和烟雾时电导率发生变化。它有两路输出:数字量DOUT经过板载比较器输出TTL电平,模拟量AOUT输出0到5V的模拟电压,接STM32的ADC引脚做浓度量化。MQ-2的坑在于上电需要预热,刚通电的前几分钟输出漂移很大,而且它会受温湿度影响,长期使用还会出现灵敏度衰退——所以软件里要做上电初始化延时,实际项目中还要定期标定。
DS18B20是Dallas半导体的单总线数字温度传感器,测量范围-55度到+125度,12位分辨率下精度正负0.5度。它最大的特点是单总线协议,一根数据线既能供电又能传数据,非常省引脚。坏处是时序要求严格,必须按芯片手册的时序图操作,初学的时候容易踩坑,后面会专门讲。
2.2 电源、复位与下载电路设计
原理图设计里最容易翻车的不是传感器接口,而是最基础的电源和复位电路。STM32F103C8T6的工作电压是2.0V到3.6V,典型3.3V,而大部分传感器模块工作在5V,所以这套系统采用5V单电源供电,板载AMS1117-3.3稳压芯片给主控供电,传感器模块统一用5V供电。
电源部分我画了三个关键元件:电源指示灯LED、100uF电解电容和100nF陶瓷电容。电解电容放在5V入口滤低频纹波,陶瓷电容放在3.3V输出端滤高频噪声,一大一小配合使用。很多新手画原理图只画一个电容,实际运行起来可能没问题,但在电磁环境复杂的实验室里,电源纹波会导致ADC采样值跳来跳去,模拟量监测根本没法看。
复位电路和BOOT电路也是基本功。STM32是低电平复位,复位脚通过10K电阻上拉到3.3V,按键按下时接地。这里有个细节:STM32的NRST引脚内部已经有上拉电阻,但为了增强抗干扰能力,外部再放一个10K上拉和100nF电容构成RC滤波,防止噪声引起意外复位。BOOT0和BOOT1各接一个10K下拉电阻,确保默认从Flash启动,这两个引脚悬空会导致偶尔下载程序不执行这种诡异问题。
下载调试电路我直接引出了SWD接口的四根线:SWDIO、SWCLK、GND、3.3V,5针排针搞定。相比JTAG的20针,SWD只用两根线就能完成下载和调试,省I/O又可靠,这是ST-Link调试器的标准接法。记得SWDIO要接10K上拉、SWCLK要接10K下拉,这是ST官方勘误表里明确的建议。
2.3 输出驱动与隔离设计
报警输出部分有两种负载:蜂鸣器和继电器。有源蜂鸣器内部自带振荡电路,通电就响,驱动电流大约20mA到30mA,STM32的GPIO在推挽输出模式下能提供大约20mA的驱动能力,理论上可以直驱,但我还是建议通过一个NPN三极管(比如S8050)来驱动。原因很简单:GPIO直驱蜂鸣器虽然能用,但引脚电流接近极限,长期高负荷工作会加速芯片老化,而且蜂鸣器开关瞬间的反向电动势有可能干扰芯片供电。用三极管驱动,GPIO只是提供一个毫安级的基极电流,蜂鸣器的电流从5V电源走,稳定可靠得多。
继电器的情况就更需要注意了。我选的是一路5V继电器模块,线圈吸合电流大概70mA,这已经远超GPIO的直接驱动能力,必须用三极管或ULN2003达林顿管驱动。另外继电器线圈是感性负载,断电瞬间会产生很高的反向电动势,如果不加续流二极管,轻则干扰系统死机,重则击穿三极管。所以我在继电器线圈两端并联了一个1N4007二极管,方向是反向并联,断电时二极管提供续流通路,保护驱动电路。电路里都加了LED作为动作指示,这样调试时一眼就能看出哪路输出在动作,不用万用表去量测。
传感器和主控之间的接口也考虑了隔离问题。火焰传感器和MQ-2模块都是TTL电平输出,可以直接接STM32的GPIO,但信号线上我串联了一个1K电阻,算是小阻抗匹配和过流保护。DS18B20的数据线必须接一个4.7K上拉电阻,这是单总线协议的标准要求,没有这个上拉电阻,时序波形会变形导致通信失败。
3. 仿真搭建与验证流程
3.1 Proteus仿真环境配置与元件模型处理
Proteus仿真很多人卡在第一步就放弃了——元件库里找不到STM32F103C8T6。这里说清楚,Proteus的元件搜索栏输入“STM32F103”或者“STM32F103C8”,在新版本(8.9以上)都能找到。找到芯片放到画布上之后,要双击芯片,在Program File里加载编译好的hex文件,这是仿真的关键一步。没有烧录hex文件的芯片在仿真里就是一坨不会动的硅,什么都不会发生。
仿真工程搭建的时候先把电源、地、晶振、复位、BOOT这些最小系统电路画好。晶振用8MHz,两个20pF负载电容并联到地。然后接传感器模块——Proteus的元件库里MQ-2和火焰传感器都能搜到,DS18B20也有现成的。如果找不到MQ-2的精确模型,一个替代方案是用电位器模拟它的模拟输出,手动调节电压就能模拟烟雾浓度变化,反而比真实模型更好控制测试条件。
执行机构部分,蜂鸣器用有源蜂鸣器模型,继电器用官方Relay模型,继电器后面接一个灯泡模组模拟被切断的设备。LED灯在仿真里可以用绿色、黄色、红色三种,分别代表正常运行、预警、火警三种状态。
3.2 仿真测试流程与波形验证方法
仿真流程我建议按这个顺序来:先最小系统验证,再单传感器验证,最后全系统联调。最小系统验证就是烧录一个最简单的GPIO翻转程序,看LED能不能按预期频率闪烁。这就排除了芯片配置、晶振起振、复位这些基础问题。
单传感器验证阶段,先只接火焰传感器,用一个信号源或者按钮模拟火焰触发,观察系统能否正确进入火警状态。这里要注意,仿真里的传感器模型是理想化的,输出只有0和1两个状态,和真实传感器那种模拟量连续变化的特性有差距。所以仿真只能验证逻辑正确性,不能验证ADC采样的实际精度。MQ-2模块的验证方式是用电位器分压代替传感器输出,把模拟电压从0V慢慢调高,观察系统状态切换的阈值点。DS18B20的验证反而最直观,仿真模型可以手动修改温度值,从25度改到60度,看系统能不能正确识别超温和联动。
最后全系统联调要验证几个关键场景:正常状态绿色LED亮、预警状态黄色LED亮且蜂鸣器间歇响、火警状态红色LED快闪且风扇继电器动作、手动复位后系统恢复。完整跑一遍这些场景,逻辑没有bug,仿真就算通过了。注意仿真通过不等于硬件通过,仿真验证的是程序逻辑,硬件验证还要看真实传感器特性和驱动电路可靠性,两者不能互相替代。
3.3 从仿真到实物的差异与注意事项
从仿真移植到实物,最常见的问题有三个:传感器电气特性不一致、驱动能力不足、信号干扰。
先说传感器特性不一致。仿真里MQ-2的输出电压可以精确控制,真实MQ-2的输出不仅和浓度有关,还和温度湿度老化程度有关。同一个浓度的烟雾,今天测输出2.5V,下周可能就只有2.0V。所以实物的阈值不能照搬仿真调试的参数,需要在实际环境里重新标定。火焰传感器也一样,仿真里不会受到日光灯频闪的影响,实际环境里就可能误报。
其次是驱动能力。仿真里没有“电流不够”这个概念,LED直接接GPIO也能亮,蜂鸣器不接三极管也能响。真实电路里GPIO的驱动能力是有限的,如果你在仿真里偷懒没加驱动电路,做实物的时候就要还债。
第三是干扰问题。仿真里不存在电源纹波和信号串扰,实物MCU和继电器之间如果没有做好隔离,继电器吸合瞬间的电流冲击就能把ADC采样值干得跳来跳去。处理办法是:主控板和功率驱动部分在PCB上分区域布线,传感器信号线尽可能短,电源入口加滤波电容,必要时加磁珠。
4. 代码实现与核心逻辑解析
4.1 Keil工程搭建与文件结构规划
代码部分我用的Keil MDK5开发环境,配合标准外设库(SPL)开发——虽然现在ST官方主推HAL库,但对这套逻辑量不大的系统来说,标准库的代码更直观,寄存器操作一目了然,初学者对照数据手册也更容易理解底层原理。如果你更喜欢HAL库,移植也不难,核心逻辑部分都是平台无关的。
工程结构我按模块划分文件,而不是所有代码堆在一个main.c里:
FireAlarm/ ├── USER/ │ ├── main.c │ ├── stm32f10x_it.c │ └── system_stm32f10x.c ├── HARDWARE/ │ ├── flame.c/h // 火焰传感器驱动 │ ├── mq2.c/h // MQ-2烟雾传感器驱动 │ ├── ds18b20.c/h // DS18B20温度传感器驱动 │ ├── buzzer.c/h // 蜂鸣器驱动 │ ├── relay.c/h // 继电器与风扇驱动 │ └── led.c/h // LED指示驱动 ├── SYSTEM/ │ ├── delay.c/h // 延时函数 │ ├── usart.c/h // 串口调试 │ └── adc.c/h // ADC采集 └── CORE/ ├── startup_stm32f10x_hd.s ├── core_cm3.c └── stm32f10x.h这样每个模块一个文件,头文件里声明对外接口函数,源文件里实现具体功能,谁出问题直接定位到对应文件,不用在一个几千行的main.c里翻来翻去。
4.2 传感器驱动代码的关键实现要点
MQ-2传感器接的是ADC采集通道,我用的是ADC1的通道0,也就是PA0引脚。初始化过程为:开启GPIOA和ADC1时钟,配置PA0为模拟输入,然后设置ADC的分频、采样周期、扫描模式等参数。采样周期我配置成55.5个周期,这对中等阻抗的信号源来说已经足够稳定。采集代码要注意一个细节:每次启动ADC转换之前,先调用ADC_SoftwareStartConvCmd(ADC1, ENABLE),转换完成后检查ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC),确认转换完成再读取数据,否则容易读到陈旧数据。
DS18B20的驱动是整个代码里最考验耐心的部分。它的单总线协议要求严格的时序配合,初始化时序、读时序、写时序的时间窗口都是微秒级的。核心是Delay微秒函数必须精确,我用定时器实现微秒级延时,而不是靠空循环,因为空循环的延时时间受编译器优化等级影响很大。重点说几个坑:初始化时序里的复位脉冲至少要保持480us低电平然后释放,等待从设备应答的时间窗口是60us到240us之间;写逻辑0要拉低总线60us,写逻辑1要拉低总线1us到15us然后释放;读时序必须在拉低总线1us到15us后立刻释放,并在60us到90us的时间窗口内采样数据线电平。如果你在实物上发现读出的温度是85度或者0度这种诡异的固定值,基本就是时序没把握好。
火焰传感器的驱动最简单,它就是普通GPIO输入模式读取电平。初始化时配置对应引脚为浮空输入或上拉输入,然后周期读取引脚电平。这里有个注意事项,火焰传感器的输出在没检测到火焰时是高电平(因为比较器反相输出),检测到火焰时是低电平,别搞反了。
4.3 主控状态机与报警联动的代码实现
主控判决逻辑我实现了上面说的三态状态机,核心代码用一个枚举变量保存当前状态,放在一个每100ms执行一次的周期函数里做状态迁移。这样设计的最大好处是逻辑清晰,不会出现状态混乱,而且扩展新状态非常容易。
状态机初始化进入STATE_NORMAL,此时三路传感器每100ms采样一次,每连续5次采样做算术平均,这个均值再参与阈值比较。为什么做平均而不是直接比较单次采样?因为MQ-2的输出有随机噪声,单次采样可能瞬间跳动超过阈值造成误判。取5次平均相当于一个小型低通滤波器,能让判定结果更平滑。想要更强的滤波效果可以做中值滤波,取5次采样排序后选中间的3次平均,不过对这个项目来说算术平均已经够用。
进入STATE_PREALARM的条件是单路传感器超过阈值但未连续确认,或者多路传感器中有两路同时超过警戒线。进入后蜂鸣器以500ms周期间歇鸣叫,黄色LED点亮,继续监测传感器状态。如果在后续10次采样(约1秒)内持续超阈值,则状态迁移到STATE_ALARM,立即启动所有执行机构:红色LED快闪、蜂鸣器连续响、继电器吸合切断电源、风扇开启排烟。在STATE_ALARM状态下,传感器数据不再参与状态判决,因为火警已经确认,不能因为烟雾浓度波动就自动解除报警,必须等人工确认安全后按复位按钮,系统回到STATE_NORMAL。
阈值的设定也要说一下。MQ-2的ADC读数我用12位精度,取值范围0到4095,对应0到3.3V电压。实测在正常室内空气环境下,MQ-2的ADC读数大概在200到500之间,我设报警阈值1200,考虑了一定余量。温度阈值设60摄氏度,实验室环境正常温度不会超过40度,超了基本可以断定有异常热源。火焰传感器是数字量,只要有低电平脉冲就认为检测到明火。这几个阈值都做成了宏定义,在头文件里集中管理,现场调试时改参数非常方便。
4.4 串口调试信息输出的价值
这套系统还加了一个调试利器——串口打印输出。通过USART1把系统状态、三路传感器原始值、状态机迁移信息实时输出到上位机串口助手。别小看这个功能,我调试状态机的时候,光看LED根本看不出来状态迁移的具体原因,有了串口日志,哪一路传感器因为什么原因触发了什么状态迁移,一行一行的拉出来看得清清楚楚。
串口输出我采用了阻塞式发送,频率不高,不影响主逻辑运行。波特率115200,每秒输出一次当前状态和数据。实际调试中这个输出每秒刷新一次足够用了。如果以后要接云平台做远程监控,这个串口接口可以直接改成Modbus协议或者接ESP8266透传,扩展起来很方便。
5. 常见问题与排查技巧实录
5.1 编译、下载与仿真高频报错速查表
这些年带过不少学生做类似项目,踩坑的地方高度集中。整理成一张速查表,对照排查能省大量时间。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| Keil编译报错Error: L6218E: Undefined symbol | 源文件未添加到工程、头文件路径未配置 | 检查工程里是否包含所有.c文件,Options for Target里的C/C++选项卡补全Include Paths |
| 烧录提示No target connected | ST-Link驱动异常、接线错误、板子供电不足 | 重装ST-Link驱动,检查SWD四线接线是否标准,单独给板子上电后再尝试连接 |
| 烧录时报Failed to load flash programming algorithm | 芯片型号选错 | 在Options for Target的Device选项卡里确认芯片型号是STM32F103C8 |
| 下载一次后第二次再也连不上芯片 | 代码里把SWD引脚复用成了GPIO | 按住复位键同时点击下载,启动瞬间抢走芯片控制权;或者用串口ISP模式全片擦除恢复 |
| 仿真运行但LED不闪烁 | hex文件未加载、晶振频率配置不对 | 双击芯片加载hex文件,检查Proteus里晶振频率和代码初始化SystemInit是否匹配 |
| DS18B20读到的温度恒为85度 | 上拉电阻缺失、复位时序有问题 | 确认4.7K上拉,逻辑分析仪抓时序看复位脉冲是否被正确应答 |
| MQ-2读数波动剧烈 | 电源纹波大、预热时间不足 | 检查电源滤波电容,软件上电延时2分钟后再开始采集 |
| 蜂鸣器不响 | 驱动三极管接反/损坏、GPIO配置错误、PWM频率不对 | 万用表量基极电压,确认三极管工作在放大/开关状态,有源蜂鸣器不要用PWM驱动 |
5.2 实测中传感器抗干扰与阈值标定的经验
实物调试中最头疼的就是传感器误报和漏报的平衡。火焰传感器对环境光敏感,白天靠窗的位置偶尔会被阳光中的红外光误触发。我实测的解决办法是:一是给传感器套一个黑色热缩管当遮光罩,只留正面检测窗口;二是软件上对火焰信号做100ms的去抖滤波,大于100ms的低电平才认为是有效火焰信号,瞬时噪声直接忽略。这两招组合下来误报率大幅下降。
MQ-2的标定步骤建议按这个流程走:系统上电后在洁净空气里运行10分钟,等传感器输出稳定,记录此时的ADC基线值。然后在正常使用时把报警阈值设为基线值的3到4倍。比如基线是400,报警阈值设1200到1600。这样既避免了环境本底浓度的个体差异,又有足够的灵敏度余量。如果现场有酒精、油漆等挥发性物质,这些气体也会让MQ-2读数升高,要考虑会不会引发误报。
一个容易被忽略的坑是MQ-2上电瞬间的输出尖峰。冷启动时加热丝还没达到工作温度,半导体材料阻值异常,模拟输出可能飙得很高,如果这时候程序立刻开始采集,第一个周期就会误触发报警。所以我在初始化代码里加了延时,系统上电后先等传感器预热稳定,再开始正常的周期采样。这个细节在仿真时看不出来,但在实物调试时几乎必踩。
5.3 从课程设计到工业部署的扩展方向
如果做完这套系统还想继续深入,三个方向值得考虑。一是加通信模块,用ESP8266通过串口和STM32对接,把报警信息推送到手机或者云平台,实现远程监控。这是目前物联网消防预警的主流架构,STM32做边缘采集,WiFi模块做数据上云,成本增加不到二十块钱。
二是加多节点组网。一个实验室装一套预警系统意义有限,一栋楼几十个实验室联网才有价值。可以用RS485总线把多个节点的STM32连成网络,每个节点一个地址,主机轮询各节点状态。ST的USART支持DMA传输,实现多机通信不会占用太多CPU。
三是上RTOS。当前这套前后台系统代码结构已经很清晰了,但因为传感器采集是周期轮询,CPU大部分时间在空等。如果换成FreeRTOS,可以拆成传感器采集任务、状态判决任务、报警执行任务、通信任务,每个任务独立运行,用消息队列传递数据,系统扩展性会好很多。把现在的代码移植到FreeRTOS也算是一个很好的学习进阶项目。
最后分享一点个人体会:这套系统做下来,最重要的不是代码写得多漂亮,而是理解了一个完整的嵌入式产品是怎么从需求变成方案的——确定功能、选型器件、画原理图、写驱动、调逻辑、做验证,每一步都有它的理由和取舍。你把这些环节都走了一遍,就不会再怕拿到任何一块不熟悉的芯片或者传感器了。