1. 项目背景与设计思路
做单片机的朋友,不管在学校还是公司,大概率都接触过“环境监测”类题目,而“火灾监测”又是其中相当有代表性的一类。这个项目用STM32做核心控制器,配合烟雾传感器、温度传感器、火焰传感器,搭建一套能从“察觉异常”到“分级报警”的完整链路。标题里“完整项目”这几个字是关键,说明它不是一个只跑通某个传感器的Demo,而是从硬件选型、电路设计、嵌入式软件、阈值策略到现场调试都覆盖的全流程东西。
我最早接到这类需求时,第一反应并不是急着买传感器,而是先想清楚:这套系统到底要防什么、防到什么程度、给谁用。家庭场景和工厂仓库的需求差别很大——家用要求成本低、误报少、易维护,仓库要求响应快、抗干扰强、能联动外部设备。这个项目最终定位是偏通用型的设计,核心目标是做到“多参数融合判定”,而不是单看某一个传感器的阈值触发,因为只靠单一指标来做火灾报警,误报率会让人崩溃。
这套系统适合的读者分两类:一类是正在做课程设计或竞赛项目的学生,可以把它当作一个能复现、能改装的底板;另一类是刚入门嵌入式、想搞明白“多传感器系统怎么协同工作”的开发者。你可以不买一模一样的模块,只要理解了数据采集、状态判断、告警联动这三层逻辑,换任何传感器都能用同样的思路做出来。
核心设计思路可以归纳成一句话:让不同物理量的传感器各自完成检测,再由MCU统一做时间和空间的关联判断。温度上升可能只是天气热,烟雾浓度升高可能是做饭油烟,但温度持续上升同时烟雾浓度同步抬高,再加上火焰传感器捕捉到红外波动,这就是一个高置信度的火灾信号。硬件上各传感器各司其职,软件上做综合决策,这也是这套系统区别于普通“单传感器阈值报警”项目的核心。
2. 硬件选型与电路设计
2.1 传感器选型的三条硬标准
先说MCU。这里选用STM32F103C8T6,也就是俗称的“蓝丸”核心板。选它的理由很直接:主频72MHz,Flash 64KB,RAM 20KB,12位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烟雾模拟输出 | PA0 | ADC1通道0,采集烟雾电压 |
| DS18B20数据 | PB1 | GPIO开漏输出,单总线协议 |
| 火焰传感器模拟输出 | PA1 | ADC1通道1,采集红外强度 |
| OLED(I2C) | PB6、PB7 | 标准I2C接口,SCL、SDA |
| 蜂鸣器 | PB8 | 推挽输出,低电平驱动 |
| 双色LED或继电器 | PB9 | 报警联动输出 |
| 串口TX/RX | PA9、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.4V,30cm以外则回落到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函数到USART1:
int 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端的代码框架是不用大改的,只需要新增一个通信任务,定时把传感器数据和告警状态透传出去就行。整个项目的核心设计思路已经立住了,后面往哪个方向扩展都能顺手接着用。