首页 / 资讯中心 / 文章详情

基于STM32的多传感器火灾监测系统:从硬件选型到分级告警完整实现

基于STM32的多传感器火灾监测系统:从硬件选型到分级告警完整实现 ★ FEATURED ARTICLE
1. 项目背景与设计思路做单片机的朋友不管在学校还是公司大概率都接触过“环境监测”类题目而“火灾监测”又是其中相当有代表性的一类。这个项目用STM32做核心控制器配合烟雾传感器、温度传感器、火焰传感器搭建一套能从“察觉异常”到“分级报警”的完整链路。标题里“完整项目”这几个字是关键说明它不是一个只跑通某个传感器的Demo而是从硬件选型、电路设计、嵌入式软件、阈值策略到现场调试都覆盖的全流程东西。我最早接到这类需求时第一反应并不是急着买传感器而是先想清楚这套系统到底要防什么、防到什么程度、给谁用。家庭场景和工厂仓库的需求差别很大——家用要求成本低、误报少、易维护仓库要求响应快、抗干扰强、能联动外部设备。这个项目最终定位是偏通用型的设计核心目标是做到“多参数融合判定”而不是单看某一个传感器的阈值触发因为只靠单一指标来做火灾报警误报率会让人崩溃。这套系统适合的读者分两类一类是正在做课程设计或竞赛项目的学生可以把它当作一个能复现、能改装的底板另一类是刚入门嵌入式、想搞明白“多传感器系统怎么协同工作”的开发者。你可以不买一模一样的模块只要理解了数据采集、状态判断、告警联动这三层逻辑换任何传感器都能用同样的思路做出来。核心设计思路可以归纳成一句话让不同物理量的传感器各自完成检测再由MCU统一做时间和空间的关联判断。温度上升可能只是天气热烟雾浓度升高可能是做饭油烟但温度持续上升同时烟雾浓度同步抬高再加上火焰传感器捕捉到红外波动这就是一个高置信度的火灾信号。硬件上各传感器各司其职软件上做综合决策这也是这套系统区别于普通“单传感器阈值报警”项目的核心。2. 硬件选型与电路设计2.1 传感器选型的三条硬标准先说MCU。这里选用STM32F103C8T6也就是俗称的“蓝丸”核心板。选它的理由很直接主频72MHzFlash 64KBRAM 20KB12位ADC带3个串口、I2C、SPI全都有价格又便宜。这个配置跑一个多传感器融合的小系统绰绰有余而且网上资料非常丰富哪怕调试时遇到奇怪问题基本都能搜到类似的坑。如果是学生项目用STM32F103系列在答辩时也比较站得住脚至少能让评委明确看到你用了一个正经的ARM Cortex-M3内核MCU。传感器的选择上我给自己定了三条硬标准。第一是输出信号简单最好直接是模拟电压或单总线数字信号这样MC端不需要额外做复杂的协议解析第二是响应时间要匹配应用场景火灾监测要求传感器能在几秒内反映浓度变化第三是供电电压和MCU兼容避免一个系统里出现三套电平标准徒增烦恼。最后定下来这三个烟雾浓度检测用MQ-2输出0~5V模拟电压浓度越高电压越高成本在十块以内。MQ-2对液化气、丙烷、氢气、烟雾都有响应虽然不是专业的光学烟雾探测器但在这种教学级项目中已经非常能说明问题。温度检测用DS18B20单总线协议一根线就能搞定数据通信测温范围-55℃到125℃精度0.5℃。最贴心的是它支持外部供电和寄生供电两种模式我用的外部供电方式信号线上再挂一个4.7kΩ上拉电阻稳定得很。火焰检测用带模拟输出的火焰传感器模块核心是一个红外接收二极管它对760nm到1100nm波长的红外光敏感火焰在这个波段有强烈的辐射特征。模块上有个电位器可以调比较器阈值同时把模拟输出也引出来了我直接用ADC读模拟量不靠数字引脚判断这样能读出“火焰离得越近电压越高”这样的连续信息。选型时的经验是尽量给每个传感器留两种信号输出一种数字一种模拟。数字信号适合做事件触发的开关量模拟信号适合做趋势分析。比如火焰传感器模块虽然数字输出接上就能用但我还是把模拟量引回了ADC引脚实际调试时发现模拟输出能反映火焰距离变化这在后期做防误报逻辑时非常有用。2.2 供电拓扑和引脚分配供电设计是整个项目里最容易翻车的地方很多人拿一块开发板加面包板就把系统搭起来了结果传感器一多板子上的3.3V电压被拉偏ADC参考电压抖动读数自然飘得离谱。这个项目里我用了双电源方案一套5V电源给MQ-2模块和火焰传感器模块供电一套3.3V给MCU、OLED和DS18B20供电两套电源共地通过0Ω电阻或者磁珠在单点连接。为什么这么干因为MQ-2内部有一个加热电阻需要持续供电来维持传感器工作温度它的瞬时加热电流能达到150mA以上如果和MCU共用同一路线3.3V上的纹波会明显增大。引脚分配我花了点心思尽量让硬件布局在逻辑上清晰功能引脚说明MQ-2烟雾模拟输出PA0ADC1通道0采集烟雾电压DS18B20数据PB1GPIO开漏输出单总线协议火焰传感器模拟输出PA1ADC1通道1采集红外强度OLEDI2CPB6、PB7标准I2C接口SCL、SDA蜂鸣器PB8推挽输出低电平驱动双色LED或继电器PB9报警联动输出串口TX/RXPA9、PA10调试日志输出这里有一个细节值得提醒DS18B20的数据引脚一定要配置成开漏输出配合外部上拉电阻这样才能在单总线协议下实现“线与”功能。之前有人直接把引脚配成推挽输出发读时序倒是正常整天都收不到数据后来查了协议规范才发现问题是出在这。2.3 现场接线的几个易错点硬件接线看着简单踩坑的地方不少。第一是MQ-2模块供电电压问题市面上的模块分为3.3V版和5V版有些板载了电压转换芯片有些没有购买时要确认一下。我第一次买回来的模块标记是5V供电结果直接接了3.3V加热丝温度不够传感器输出值几乎不动白忙活了一下午。第二是DS18B20的手工封装区别。这个元件有两种常见形态一种是TO-92三脚直插一种是防水不锈钢探头。直插的引脚排列是面向平面从左到右依次为GND、Data、VCC这个顺序和一般三极管不一样接线前最好用万用表确认哪根是哪根省得上电瞬间冒烟。第三是火焰传感器的安装角度。红外接收二极管有一个方向性正面朝向火焰的时候检测效果最好。我一开始把传感器平放在桌面火焰从上方烧起来结果读数变化非常微弱后来把传感器倾斜45度朝向预期火源方向灵敏度立刻正常了。这个不算电路设计问题但调试时的直观感受影响很大。3. 软件框架与核心实现3.1 整体代码结构和任务调度我写这套代码时没有直接怼一个超级大的main函数而是按模块拆开各管各的传感器驱动、数据处理逻辑、显示、告警控制、调试输出。这样做的目的是方便后期裁剪和扩展比如未来想把MQ-2换成MQ-7只需要改驱动层的几个函数就行。代码基本结构如下// main.c int main(void) { HAL_Init(); SystemClock_Config(); // 72MHz主频 MX_GPIO_Init(); MX_ADC1_Init(); MX_I2C1_Init(); MX_USART1_Init(); ds18b20_init(); oled_init(); sensor_data_t sensor {0}; fire_status_t fire_state FIRE_STATE_NORMAL; while (1) { sensor.smoke_vol read_smoke_voltage(); sensor.temperature_c ds18b20_read_temperature(); sensor.flame_vol read_flame_voltage(); fire_state evaluate_fire_level(sensor); update_alarm_output(fire_state); render_display(sensor, fire_state); print_debug_log(sensor, fire_state); HAL_Delay(200); // 5Hz轮询周期 } }整体调度用的是简单的前后台结构也就是大循环轮询模式。对于这种数据变化不要求微秒级响应的系统来说这个调度策略是最合理的。5Hz的轮询速率已经非常充裕MQ-2的响应时间本身就有一秒左右的量级DS18B20完成一次温度转换也要约750ms轮询频率再高也是白费CPU资源反而让ADC引脚上的读数在快速切换时引入了电容充放电误差。3.2 ADC采样与滑动滤波ADC采样是整套系统的“感官输入端”如果这里的数据不准后面所有判断都是空中楼阁。STM32F103的12位ADC是8个通道里可以做扫描模式但我的设计里只用了PA0和PA1两个通道所以采用单次转换通道切换的方式就够了。关键点是单次采样值绝不能直接拿来做阈值判断必须加滤波。我用了最简单的滑动平均滤波算法每个通道保留10次采样值每次采样取平均值这个成本极低但能有效抑制传感器输出信号上的高频噪声。#define SAMPLE_BUF_SIZE 10 typedef struct { uint16_t buf[SAMPLE_BUF_SIZE]; uint8_t index; uint32_t sum; } adc_sampler_t; uint16_t adc_sampler_put(adc_sampler_t* sampler, uint16_t val) { sampler-sum - sampler-buf[sampler-index]; sampler-buf[sampler-index] val; sampler-sum val; sampler-index (sampler-index 1) % SAMPLE_BUF_SIZE; return sampler-sum / SAMPLE_BUF_SIZE; }这下有个问题需要注意滑动平均滤波虽然能滤掉毛刺但也会让“突变信号”的响应变慢。比如火焰传感器瞬间捕捉到红外信号电压可能直接跳变0.5V滑动平均之后会变成一个慢坡。所以我采取了折中方案对于MQ-2的烟雾电压用10次采样做50ms间隔的滑动平均因为烟雾扩散本身就是一个渐变过程对于火焰传感器的模拟输出只做连续三次采样取中间值这样既能剔除随机尖峰又不至于把快速变化的火焰信号磨平。ADC把采集到的数字量转换成电压值计算方法很简单float adc_to_voltage(uint16_t adc_val) { return (float)adc_val * 3.3f / 4095.0f; }注意STM32F103的ADC参考电压在大多数BluePill板子上是接到3.3V的所以基准就是3.3V。如果后期系统供电改成3.0V这个换算系数也必须跟着改否则测出的电压会比真实值高0.3V左右这个误差在阈值判定时会非常致命。3.3 温度读取与结构体封装DS18B20的驱动代码网上一搜一大片但能把“正确率”做到接近100%的并不多。核心原因在于这个传感器的时序精度要求是用微秒级来衡量的不能用太慢的IO操作。我在F103上用寄存器操作写得快一些也支持了CRC校验来保证数据完整性。单总线的完整时序分为初始化、ROM命令、功能命令三个阶段。这里只写重点uint8_t ds18b20_reset(void) { uint8_t presence 0; ds18b20_low(); delay_us(480); ds18b20_release(); delay_us(70); presence ds18b20_read_pin(); delay_us(420); return (presence 0) ? 0 : 1; // 返回0表示检测到应答脉冲 }初始化阶段主机拉低总线480微秒以上发送复位脉冲然后释放总线DS18B20会回应一个60~240微秒的低电平脉冲。注意读回数据的那个70微秒延时很关键延时太长会错过应答信号延时太短则读到一个假的高电平。功能代码里我用了读两条传感器的方案——实际上就一块DS18B20但我在结构体中预留了两个温度槽位方便后期扩展多路温度监测。这些温度数据从寄存器里读出来后是12位的低4位代表小数部分换算公式是float ds18b20_convert_raw(int16_t raw) { return (float)raw * 0.0625f; // 12位分辨率每一步代表0.0625℃ }如果读出的原始数据是0x0191也就是十进制401那么实际温度就是25.06℃用这个方法不用去担心符号位问题。但存储结果时我用了float类型在比较阈值时要注意直接在float之间比较没问题但如果你把温度值打出来用串口给上位机每次显示的位数基本都在几位有效数字的变动肉眼追踪起来不太方便所以最好在打印时统一用%f只保留一位小数。3.4 火焰信号的多级判断火焰传感器模块的模拟输出接在PA1上ADC读数经过三次取中值处理之后得到了一个0~3.3V的电压值。火焰越近、越旺盛这个电压越高。根据我实际测试把火焰放在20cm距离处电压大约在1.2V左右10cm处能达到2.4V30cm以外则回落到0.5V以下。这里我没有直接用单一电压值做阈值而是把它分成三个挡位对应近距离强火警、中距离预警、远距离监测。这个设计的出发点是很多时候现场并不会瞬间出现一个大火团而是一个小火苗慢慢变大。如果只在“电压超过某个值”时才报警等触发的时候火苗已经不小了。有了距离分区可以在小火阶段就触发预警一定程度上提升系统的早期发现能力。typedef enum { FLAME_STATE_NONE 0, FLAME_STATE_WARNING, FLAME_STATE_DANGER } flame_state_t; flame_state_t check_flame_state(float volt) { if (volt 2.0f) return FLAME_STATE_DANGER; if (volt 0.8f) return FLAME_STATE_WARNING; return FLAME_STATE_NONE; }这个分级判断还有一层好处就是能配合告警策略做防误报。比如火焰传感器上偶尔会有阳光里的红外成分干扰虽然它的灵敏度被滤光片压低了许多但大晴天的窗边直射光依然可能让它输出一个中高电压。这时候如果光凭火焰传感器就报警十有八九是误报但如果我们要求“火焰DANGER级”必须同时满足烟雾电压也偏高误报就能被压下来。3.5 OLED展示与串口日志人机交互部分我用了一块0.96寸OLED屏幕SSD1306驱动芯片I2C接口。为什么不选LCD1602因为1602需要占用至少6个IO口还得调节背光和对比度而OLED只需要两根信号线显示内容也更加自由可以做中文界面虽然在STM32裸机上做中文字库要费一点Flash空间但对这个项目来说可以接受。OLED显示内容我分了三个页面按按键切换。第一页显示传感器实时数据包括烟雾电压、温度、火焰电压第二页显示告警状态和触发条件第三页显示系统运行时间和最近一次告警记录。当然在这个版本里我简化成了单页面显示把关键状态都放在一屏里字符尺寸用8x6格式能塞下不少信息。调试时串口日志很重要。我习惯每隔一秒钟向串口打印一条格式化好的数据行方便在PC端用串口工具观察曲线变化。格式如下printf(SMOKE:%.2fV TEMP:%.2fC FLAME:%.2fV STATUS:%d\r\n, smoke, temperature, flame, (int)state);注意使用printf时必须在代码里重定向fputc函数到USART1int fputc(int ch, FILE* f) { HAL_UART_Transmit(huart1, (uint8_t*)ch, 1, HAL_MAX_DELAY); return ch; }串口波特率我固定用115200这在大多数串口助手里都是默认选项。如果你发现输出乱码先去看时钟树配置是否真的把USART时钟配到了APB2上并且系统主频确实是72MHz。很多时候乱码不是波特率不对而是芯片实际运行频率和代码里写的初始化值不匹配。4. 告警策略与防误报设计4.1 分级告警设计告警的逻辑不能是“非黑即白”。我把系统状态分成四个等级正常NORMAL所有参数都在安全区间指示灯绿色常亮。关注ATTENTION任一参数超过警戒线但在可控范围蜂鸣器每秒短鸣一次相当于“有可疑情况但不要慌”。预警WARNING两个以上参数同时越界蜂鸣器快速鸣叫OLED显示警示符号触发LED闪烁。火警FIRE三个参数同时严重越界或者烟雾和火焰同时触发DANGER级蜂鸣器持续长鸣同时继电器吸合模拟启动外部灭火设备或切断电源。为什么必须分级直接点说不分级的系统没法用。厨房里炒个菜油烟浓度可能在十几秒内飙升到阈值上方如果系统瞬间进入火警状态全家都得被吓到。分级给了时间缓冲也让现场人员能根据告警强度判断事情的严重程度。在实际部署中预警是最常见、最重要的阶段烟刚起来的时候能提醒人就省去了后面的大麻烦。4.2 多传感器投票机制多传感器融合是这个项目的精华所在。我实现了一个轻量级的“投票”机制每个传感器根据当前读数和持续时间投出正常、关注、越限三档票系统再根据票数组合输出最终状态。这个机制的好处是让每个传感器都能表达自己的“置信度”而不是简单做逻辑与或。具体策略如果温度正常、火焰正常、烟雾高系统认为可能是“油烟干扰”判定为关注如果温度高、火焰预警、烟雾正常系统认为可能是“热源靠近传感器”判定为关注只有“烟雾高且温度高”或“烟雾高且火焰预警以上”才进入预警三人同时越限直接火警。实现上我用了一个简单的评分表条件组合判定结果仅烟雾高关注仅温度高关注仅火焰预警关注烟雾高 温度高预警烟雾高 火焰预警预警温度高 火焰预警预警烟雾高 温度高 火焰预警以上火警火焰DANGER且持续3秒火警为什么保留“火焰DANGER且持续3秒”这条单通道直达火警的逻辑因为在真实火情中突然出现的火焰往往是瞬间形成的此时如果非要等多传感器一起触发系统可能已经失去了早期灭火窗口。持续3秒是为了排除偶尔的红外脉冲干扰不至于一只苍蝇飞过去就触发火警。4.3 消抖与定时器配合在告警输出环节一个隐形的坑是“抖动”。当某个参数恰好卡在阈值边缘时测量值的微小波动会导致状态的频繁切换蜂鸣器和LED就会发出那种让人烦躁的“嘟嘟嘟嘟”声。解决方式是在状态判定模块里加入消抖时间窗口也就是状态必须稳定持续一段时间才允许切换。我用了比较朴素的方法每个状态用一个计数器累计每次状态变化时先加一个“候选”标记只有当标记持续满足3个轮询周期即约600ms才真正切换状态否则就撤销候选恢复之前的状态。fire_status_t evaluate_fire_level(sensor_data_t* data) { fire_status_t raw_status get_raw_status(data); static fire_status_t stable_status FIRE_STATE_NORMAL; static uint8_t stable_cnt 0; if (raw_status stable_status) { stable_cnt 0; } else { stable_cnt; if (stable_cnt 3) { stable_status raw_status; stable_cnt 0; } } return stable_status; }这是一个很小但很实用的技巧实际运行后整个系统的告警体验会舒服很多。系统从“正常”跳变到“火警”时大约0.6秒的延时不会造成安全缺口但换来的是完全消除状态抖动带来的虚假切换。5. 调测实录与常见问题5.1 实测数据怎么判定阈值阈值设置是整个项目里最需要“跑现场”的环节。不会有一个万能数告诉你“烟雾电压大于1.5V就是火警”因为这个基准值会随环境而变化。我把自己的调试方法说一下。拿到板子后先上电预热半小时保证MQ-2的工作状态稳定。然后用万用表或者串口打印工具记录一分钟的烟雾电压基准值。我实测的正常环境下基准电压大约在0.5~0.6V之间波动这是因为MQ-2在洁净空气中的输出并非完全为零它有一个零点漂移。这个零漂受温度和湿度影响比较大。然后分别做三组测试距离传感器10cm处点燃一根蜡烛记录烟雾电压变化用电吹风吹热风测温度通道的响应用打火机的火焰靠近火焰传感器记录电压变化。把这些数据画成表格我就有了一个相对合理的“本底值”。阈值就设置为本底值上浮一定比例一般烟雾阈值取本底值加0.8V左右温度阈值取环境本底15℃火焰阈值则根据火焰距离标定选取。有一点经验之谈不要把阈值定得太紧。太紧意味着任何环境波动都会触发汇报系统会被噪音淹没最终没办法用。宁可让报警阈值稍微“钝感”一点把灵敏度调节的空间留给后续的参数配置也要先保证系统运行稳定。毕竟火灾监测系统的核心是“可靠”不是“敏感”。5.2 电源纹波引起的读数跳变调试中最让我头疼的问题出在电源上。MQ-2的加热电阻通电瞬间的冲击电流导致3.3V电源产生了一个明显的纹波ADC读数就在0.7~1.2V之间不停跳变。最初我还以为是传感器坏了换成新的也一样后来用示波器测3.3V电源才发现电源线上叠加了一个约500mV的50Hz甚至更高频的杂波。解决办法有两层。第一层是在硬件上给每个传感器模块加一个10~100微法的去耦电容尽量靠近模块电源引脚安装第二层是在软件上增加采样间隔让每次ADC触发都错开MQ-2加热周期的影响。后来我还试过给MQ-2单独用一个LM2596降压模块供电效果立竿见影ADC读数的波动从0.5V降到0.05V以内。调试这个问题的过程也提醒我遇到传感器读数异常首先要怀疑供电其次才考虑屏蔽和滤波。5.3 传感器热机与预热时间MQ-2这类半导体气体传感器有一个绕不开的物理特性需要预热。它的内部有一个加热丝传感器正常工作需要加热器把元件温度提高到固定值。刚从冷启动到稳定浓度检测往往需要5分钟以上。首次调试时我上电后立刻采样然后对着传感器吹半口烟发现数值几乎不动以为是驱动代码写错了实际上就是传感器还没热透。后来我在系统里加了“预热检测”逻辑上电后前5分钟显示“预热中”不计入告警判断5分钟后再开始正常工作。这个细节在实际产品里可能不是必须的但在教学演示和调试阶段能极大减少困扰。DS18B20没有预热问题但距离传感器太近时会受到MCU及其他芯片发热的影响。我在实测中发现把DS18B20放在STM32核心板旁边读到的温度比实际环境温度高了大约2℃。所以布置温度探头时尽量远离发热器件或者用延长线把探头拉到目标监测区域。5.4 串口乱码与时钟配置串口乱码这个坑基本是做STM32的人都遇到过的。表现是PC端串口助手收到的数据里夹杂大量“烫烫烫”或者0xFF、0xFE之类的异常字符。大多数情况下问题不在串口本身而在时钟配置。STM32F103的USART时钟是从APB2总线来的默认配置下APB2时钟是72MHz串口波特率就是在这个时钟基础上算出来的。如果SystemClock_Config里把系统时钟配置搞错比如PLL倍频系数配错导致系统实际运行在36MHz或更低那串口波特率自然就和PC端设置的115200对不上了。排查方法很简单在初始化代码里读一下实际的系统时钟值或者用一个简单的LED闪烁程序来验证主频是否异常。另外重定向printf的时候如果使用KEIL的微库或者GCC的retarget机制代码写法有区别。我遇到过一次“能打印但是第一个字符丢失”的情况原因是在初始化流程里串口复位时缓冲区还没准备好就输出了第一条日志解决办法是把第一条打印放到HAL_UART_Init完成并且延时100ms之后。这种小问题看着不大但在你怀疑整条数据链是否通的时候很影响判断。5.5 系统运行稳定性的三天实测整个系统调通之后我连续跑了三天每天8小时记录了几组比较有意思的数据第二天早上刚开机时MQ-2的基线电压比前一天下午高了约0.15V——这是因为夜间环境湿度上升半导体传感器的基线会漂。这个现象让我的阈值设置必须留出足够余量不能采用固定值而应该在软件里做“动态基准电压跟随”也就是每隔一段时间获取最小电压作为当前环境的底噪值再在此基础上叠加上升量作为报警阈值。动态基准的代码实现也很简单void update_reference_baseline(float current_smoke) { static float ref_baseline 0.5f; if (current_smoke ref_baseline) { ref_baseline ref_baseline * 0.9f current_smoke * 0.1f; } else { ref_baseline ref_baseline * 0.95f current_smoke * 0.05f; } }这个逻辑是短期内低值可信认为是环境底噪高值不直接拉高基准而是用很小的系数缓慢跟随防止一个飘高的噪声把基准带上去。实现了这个之后系统的误报率进一步下降三天实测下来没有一次误报模拟火源测试时响应时间在2秒左右。这个表现就已经比较让人放心了。实际项目中的一些补充体会如果把这套系统从“能跑通”推到“能交付”我还会关注几个小细节。一是加一个看门狗定时器防止MCU跑飞后系统无声无息地罢工火灾监测设备最怕的不是报假警而是该报警时不报警二是把阈值参数做成可配置项存在Flash或者EEPROM里这样不用重新编译代码就能调整灵敏度三是给系统加上自检机制上电时蜂鸣器响一声、LED闪几下确认输出设备正常这个细节在课程设计和实际项目里都很加分。如果后续想在这个基础上扩展可以考虑加一个ESP8266或者类似的WiFi模块把传感器数据上报到云端或者小程序这样就能实现远程监控拓展性很足。MCU端的代码框架是不用大改的只需要新增一个通信任务定时把传感器数据和告警状态透传出去就行。整个项目的核心设计思路已经立住了后面往哪个方向扩展都能顺手接着用。
阅读完成 · 觉得有帮助?
咨询建站