
简介这份资料面向电子信息、自动化等专业学生是围绕 51 单片机火灾检测报警系统的毕业设计文档可用于课程设计选题、论文框架参考与实物方案比对。压缩包仅 1 个 doc 文件约 1.27MB包含摘要、目录、绪论、系统总体方案、硬件设计与软件程序等完整章节结构接近正式毕业论文。设计中以 STC89C52 为核心配合 1602 液晶、电阻式烟雾传感器、温度传感器和火焰传感器给出最小系统、液晶显示、烟雾检测等模块的电路与程序实现并重点阐述烟雾浓度、可燃气体浓度、温度、火焰信号的采集、阈值判断及声光报警逻辑。读者可借此掌握传感器选型、阈值设定、软硬件联调思路也可参考其系统框图与目录组织方式完成自己的设计。目前已有 104 人学习适合作为 51 单片机毕业设计的入门参考。1. 从一次油烟误报说起火灾检测真正难的是判据厨房里炸带鱼产生的油烟能把一只半导体烟感推过报警阈值而真正的阴燃前二十分钟可能只让烟雾通道的 ADC 码值爬升十几个数。这两个场景放在一起就构成了火灾检测的核心矛盾——灵敏度调高天天误报调低调低真着火时不响。基于单片机的火灾检测设计本质上是用一颗低成本 MCU 把烟雾、温度、火焰三类原始信号采上来再靠滤波、多判据表决和状态机把「有烟」和「着火」区分开。它适合两类人一类是在做单片机毕业设计或课程设计需要一套能跑通、能答辩、能现场演示的方案另一类是手上已经有 51 或 STM32 板子想把传感器读数变成可靠动作的工程师。整套系统器件不贵难的是阈值怎么定、干扰怎么滤、报警怎么分级。2. 火灾检测的信号链拆解与单片机、传感器选型2.1 三类火灾特征量与传感器的对应关系火灾从阴燃到明火释放的物理量是分阶段出现的。阴燃阶段先出颗粒物和气溶胶温度上升很慢转入明火后温度陡升同时伴随 760nm 到 1100nm 波段的辐射。这意味着单一传感器一定会有盲区只测烟明火酒精燃烧几乎不产烟只测温阴燃阶段等到温度过阈值时火已经烧起来了。特征量常用器件输出形式典型响应主要干扰源烟雾/可燃气体MQ-2、TGS813 半导体气敏模拟电压 比较器数字量10~30 s油烟、酒精、水汽、温湿度漂移温度DS18B20、NTC、K 型热电偶单总线数字 / 电阻 / 微弱 mV1~5 s环境温度基线、探头热惯性火焰辐射红外接收管 LM393 模块数字量低电平有效 1 s阳光、白炽灯、电焊弧光工程上的常规做法是「烟为主判据温为确认判据火焰为加速判据」。烟感负责早期发现温度负责排除油烟这类不升温的干扰火焰负责在明火出现时立刻拉高报警等级。三者的采样周期也不一样烟和火焰可以 100ms 一次DS18B20 一次转换要 750ms12 位分辨率必须异步处理不能放在同一个循环里死等。2.2 51 单片机与 STM32 的选型边界选型的判断点其实只有三个需要几路模拟量、要不要跑滤波算法、有没有现成的开发环境。STC89C52RC 这类经典 51 单片机5V 供电、DIP40 封装、12MHz 或 11.0592MHz 晶振有 3 个定时器和 1 个 UARTKeil C51 一装就能编译用 stc-isp 之类的单片机烧录软件走串口下载几分钟就能点亮第一颗 LED。它的短板很明确没有片上 ADCRAM 只有 128 字节52 是 256 字节跑浮点运算吃力。如果你的方案里只有「比较器输出 阈值判断」51 完全够用而且 stc 单片机复位电路简单一颗 10uF 电容加 10k 电阻就能搞定调试时不容易出玄学问题。STM32F103 这一档则是另一种取舍12 位 ADC 多通道、DMA 搬运、RAM 几十 KB能同时采 4 路模拟量还留有余量跑滑动滤波和串口协议栈。代价是开发环境更重用 DAPLink 或 JLink 烧录时偶尔会遇到识别不到单片机的情况多半是 SWD 线太长、复位脚被占用或者目标板没共地。提示单点检测、教学演示、预算敏感的场合选 STC89C52RC需要多路模拟量同采、要在片内做滤波和趋势判断的场合选 STM32。不要为了「看起来高级」硬上 STM32多出来的复杂度会吃掉调试时间。2.3 没有 ADC 的 51 单片机怎么读模拟量MQ-2 模块通常同时引出 AO模拟输出和 DO比较器数字输出。只接 DO 最省事但阈值由模块上的蓝色电位器决定你在固件里改不了而且丢掉了浓度趋势——无法做「浓度持续上升」这类判断。三条常见路线第一种是直接外挂 ADC0809并行接口占 8 根数据线加 3 根地址线51 的 IO 本来就不宽裕接完基本没剩几根。第二种是用 PCF8591I2C 两线4 路 8 位模拟输入加 1 路 DAC 输出地址由 A0/A1/A2 决定默认写地址 0x90、读地址 0x91。这是我一般会推荐的做法占用引脚少代码也好封装。/* PCF8591 单通道读取I2C 底层用普通 IO 模拟 */ unsigned char PCF8591_Read(unsigned char ch) { unsigned char val; I2C_Start(); I2C_SendByte(0x90); // 写地址 I2C_WaitAck(); I2C_SendByte(0x40 | (ch 0x03)); // 控制字允许DAC输出选择通道0~3 I2C_WaitAck(); I2C_Start(); I2C_SendByte(0x91); // 读地址 I2C_WaitAck(); I2C_RecvByte(); // 第一次读到的是上一次转换结果丢弃 I2C_NoAck(); I2C_Stop(); I2C_Start(); I2C_SendByte(0x91); I2C_WaitAck(); val I2C_RecvByte(); // 本次转换结果 I2C_NoAck(); I2C_Stop(); return val; }这里的关键点是 PCF8591 的转换结果有「一拍延迟」发出通道控制字之后的第一次读拿到的是上一轮的值。丢掉第一拍再读数据才对得上号。控制字里的 0x40 是模拟输出使能位如果你不用 DAC 输出去掉它也行但保留更省事避免后续想加模拟量输出时再改代码。I2C 的 SCL 频率别超过 100kHz51 上直接用延时翻转 IO 就好不需要额外上拉以外的器件。3. 烟感、温感、火焰探测器的采集电路与单片机最小系统3.1 STC89C52RC 最小系统与电源去耦最小系统就四块电源、晶振、复位、EA 引脚。晶振频率这里有个坑——如果你要用串口上报数据必须选 11.0592MHz因为只有这个频率能整除出标准的 9600 波特率12MHz 下算出来的波特率有误差短帧勉强能用长帧会丢字节。用不到串口的话12MHz 更整齐。复位电路按经典的高电平复位接法10uF 电解电容正极接 VCC、负极接 RESETRESET 再经 10k 电阻下拉到 GND并联一个按键做手动复位。上电瞬间电容充电让 RESET 维持一段高电平时间常数 10k × 10uF 100ms足够覆盖单片机的上电稳定时间。EA 引脚31 脚必须接 VCC否则会从外部总线取指程序根本跑不起来。电源部分MQ-2 的加热丝电流在 150mA 左右加上继电器线圈整板峰值可能到 250mA。用 7805 线性稳压时输入端要并 100uF 以上电解电容输出端并 0.1uF 陶瓷电容且离单片机 VCC 引脚越近越好。传感器加热丝的通断会在电源上打出纹波去耦不到位时表现为 ADC 读数每隔几秒跳一下很难查。3.2 MQ-2 与 DS18B20 的接线与上拉电阻MQ-2 的接法VCC 接 5VGND 接地AO 接 PCF8591 的 AIN0。模块内部已经把敏感体串在分压里了外部不需要再加负载电阻。需要注意的是预热——出厂后长期存放的传感器至少要通电老化 24 小时每次上电也要预热 3 分钟以上头几分钟读数会从高往低缓慢回落这段时间的采样值不能参与报警判断固件里要留一个预热屏蔽窗口。DS18B20 走单总线DQ 引脚接 4.7k 上拉到 5V接单片机 P3.3。一定要用外部供电VDD 接 5V而不是寄生供电寄生供电在长导线上驱动能力不够会出现读回 85.0℃ 这个经典错误值——那是上电默认值说明时序没对上。探头导线超过 2 米时建议再并联一个 0.1uF 到地做滤波。单片机引脚连接对象方向备注P3.2 / INT0火焰传感器 DO输入低电平有效可触发外部中断P3.3DS18B20 DQ双向4.7k 上拉到 5VP2.0蜂鸣器经三极管输出低电平有效P2.1继电器模块 IN输出高电平吸合P1.6 / P1.7PCF8591 SCL / SDA输出/双向各 4.7k 上拉P0 口LCD1602 D0~D7输出需 10k 排阻上拉3.3 火焰传感器的比较器输出与外部中断火焰传感器模块本质是一个红外接收管加 LM393 比较器模块上的电位器决定触发阈值DO 输出低电平表示检测到火焰辐射。它的视场角大约 60 度探测波段 760nm 到 1100nm正对阳光或靠近白炽灯会直接误触发。现场的规避办法是把它朝下倾斜 15 度左右安装避开窗户方向。硬件上接 P3.2INT0配置成下降沿触发。注意 INT0 是电平/边沿混合的脚和普通 IO 不同初始化时必须把 IT0 置 1 才能用边沿触发。下面这段是端口初始化加火焰中断的骨架#include reg52.h sbit BUZZER P2^0; sbit RELAY P2^1; volatile bit flame_flag 0; // 火焰中断标志主循环读取 void Port_Init(void) { P2 0xFF; // 蜂鸣器关、继电器断 IT0 1; // INT0 下降沿触发 EX0 1; // 打开外部中断0 EA 1; // 打开总中断 } void INT0_ISR(void) interrupt 0 { flame_flag 1; // 中断里只置标志不做打印和延时 }这里刻意把中断服务函数写得极短只置一个标志位真正的报警动作放到主循环里做。原因是火焰传感器的 DO 在火光抖动时会连续翻转如果在中断里直接鸣喇叭或者走串口发送中断嵌套和 UART 发送的等待会把时序拖乱表现为蜂鸣器声音断续甚至程序跑飞。这是新手最常见的写法错误之一。4. 固件实现多传感器采样、滑动滤波与三态报警状态机4.1 定时器中断里的 10ms 采样节拍主循环里用delay卡时间是不行的LCD 刷新和串口发送都会打乱采样节拍。正确做法是让 51 单片机的定时器 0 提供固定节拍主循环只负责判断和显示。11.0592MHz 晶振下机器周期是 12 / 11.0592 ≈ 1.0851 微秒10ms 对应 9216 个机器周期。定时器 0 用方式 116 位初值 65536 - 9216 56320也就是 0xDC00。#include reg52.h volatile unsigned int tick10ms 0; // 10ms 计数用于分频出不同采样周期 volatile bit flag_100ms 0; // 每 100ms 置位一次 volatile bit flag_1s 0; // 每 1s 置位一次 void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // T0 方式116位定时 TH0 0xDC; // 10ms 11.0592MHz TL0 0x00; ET0 1; TR0 1; } void Timer0_ISR(void) interrupt 1 { TH0 0xDC; // 方式1不是自动重装必须手动补初值 TL0 0x00; tick10ms; if (tick10ms % 10 0) flag_100ms 1; // 100ms 一档 if (tick10ms % 100 0) flag_1s 1; // 1s 一档 }方式 1 不会自动重载初值中断里必须手动补写 TH0/TL0漏了这一步定时周期会变成 65.5ms整套节拍全歪。分频出两档节拍之后烟雾和火焰走 100ms 档DS18B20 温度转换走 1s 档两者互不阻塞。4.2 滑动平均加去极值滤波MQ-2 的原始读数噪声不小直接拿单次采样去比阈值报警会跟着噪声抖。常见的滤波有三档一阶低通简单但响应滞后、算术平均平滑但怕脉冲干扰、去极值滑动平均窗口内排序后掐头去尾再求均值。第三种对抗偶发脉冲最有效代价是需要一个 8 字节的临时数组。#define WIN 8 // 窗口长度取 8 兼顾平滑度与 RAM 占用 static unsigned char buf[WIN]; static unsigned char idx 0; unsigned char Filter_Sliding(unsigned char sample) { unsigned char i, j, tmp, tmpbuf[WIN]; unsigned int sum 0; buf[idx] sample; idx (idx 1) % WIN; // 环形写入避免整体搬移 for (i 0; i WIN; i) tmpbuf[i] buf[i]; for (i 0; i WIN - 1; i) // 冒泡排序窗口只有 8开销可接受 for (j 0; j WIN - 1 - i; j) if (tmpbuf[j] tmpbuf[j 1]) { tmp tmpbuf[j]; tmpbuf[j] tmpbuf[j 1]; tmpbuf[j 1] tmp; } for (i 1; i WIN - 1; i) sum tmpbuf[i]; // 去掉最小和最大 return (unsigned char)(sum / (WIN - 2)); }窗口长度是可调参数改成 4 响应快但仍会抖改成 16 平稳但报警延迟明显增加。RAM 只有 128 字节的 51 上不建议超过 8。注意去极值只掐掉了各一个极端值如果现场有持续的脉冲干扰源比如继电器吸合瞬间还需要在硬件上加 RC 或者软件上加连续 N 次越限才确认的计数。4.3 三态状态机与多数表决判据状态机设三态正常、预警、火警。判据采用「单参数直接触发 双参数联合触发」的组合逻辑避免任一传感器单独抖动就报警。#define SMOKE_WARN 120 // 烟雾预警阈值滤波后 0~255 #define SMOKE_FIRE 180 // 烟雾火警阈值 #define TEMP_WARN 45 // 温度预警阈值单位 ℃ #define TEMP_FIRE 60 // 温度火警阈值 typedef enum { ST_NORMAL 0, ST_WARN, ST_FIRE } sys_state_t; sys_state_t Judge(unsigned char smoke, unsigned char temp, unsigned char flame) { unsigned char score 0; if (smoke SMOKE_FIRE) score 2; // 烟是主判据权重高 else if (smoke SMOKE_WARN) score 1; if (temp TEMP_FIRE) score 2; else if (temp TEMP_WARN) score 1; if (flame) score 2; // 检测到火焰辐射 if (score 4) return ST_FIRE; // 任意一项火警级 一项预警级 if (score 2) return ST_WARN; return ST_NORMAL; }打分制的意义在于把「与」和「或」揉在一起烟浓度单独冲到火警级时得 2 分还不足以进火警必须叠加温度预警或者火焰信号。这样油烟场景——烟高但温度不升——最多停在预警态不会拉响火警。反之明火场景烟温和火焰同时到位一次就能推进到火警。阈值本身要在现场标定不是抄一组数就完事。4.4 蜂鸣器、继电器与串口上报帧格式输出分三路蜂鸣器做本地声光报警继电器做联动切风机、断燃气阀串口做数据上报给上位机或网关。串口帧必须有帧头和校验否则接收端无法从字节流里切分报文。void Uart_SendFrame(unsigned char smoke, unsigned char temp, unsigned char flame, unsigned char state) { unsigned char i, sum 0; unsigned char f[9]; f[0] 0xAA; f[1] 0x55; // 帧头便于接收端同步 f[2] smoke; f[3] temp; f[4] flame; f[5] state; for (i 0; i 6; i) sum f[i]; f[6] sum; // 累加和校验 f[7] 0x0D; f[8] 0x0A; // 帧尾 for (i 0; i 9; i) { SBUF f[i]; while (!TI); TI 0; } }串口初始化在 11.0592MHz 下用定时器 1 方式 2 做波特率发生器9600bps 对应 TH1 TL1 0xFDSCON 配成 0x50方式 1、允许接收。注意定时器 1 的工作方式由 TMOD 高四位控制配置时用TMOD (TMOD 0x0F) | 0x20;只改高四位别把定时器 0 的方式位一起冲掉——这是把这套代码从示例工程移植到自己板子上时最容易翻车的一处。5. 现场标定、误报抑制与串口自检5.1 用打火机气体做阈值标定出厂代码里的阈值只能当占位符。标定要分两步走先测本底再测响应。把板子放在实际安装位置通电预热 10 分钟记录烟雾通道滤波后的稳定值这就是本底值通常在 20 到 60 之间。然后拿打火机不点火只放气在距离传感器 20cm 处放 2 秒记录峰值一般在 150 以上。把预警阈值定在本底加 40%、火警阈值定在本底加 80% 左右比死抄别人的数字靠谱得多。标定项测量方法典型值固件参数烟雾本底通电预热 10min 后读滤波值20~60作为基线偏移烟雾响应峰值打火机放气 2s150~220决定 SMOKE_FIRE温度本底室温稳定后读 DS18B20与环境一致用于判断探头故障火焰触发距离打火机点火由远及近30~80cm调整模块电位器温度通道还有一个额外用途如果 DS18B20 连续读回 85.0 或者 -55.0基本可以判定是通信失败而非真实温度固件里应该把这类值标记为无效并跳过判据同时点一个故障指示灯。5.2 误报抑制的三个开关第一个开关是预热屏蔽上电后 3 分钟内不参与报警判断只在 LCD 上显示实时值。MQ-2 冷启动阶段读数偏高硬判必然误报。第二个开关是持续时间确认烟雾连续 5 个 100ms 周期越限才升级状态中间任何一次回落就清零计数。这一条能挡掉绝大部分瞬时干扰。实现上用一个静态计数器在 100ms 标志位为 1 时累加即可。第三个开关是变化率门限只有浓度在上升才允许推进状态。油烟的特点是快速冲到高位后维持然后随通风下降真实阴燃是缓慢持续上升。用一个变量保存上一次的滤波值差值小于 2 时不推进状态可以有效区分这两类曲线。5.3 串口帧自检与复位观察上位机侧用一段 Python 把帧解出来比盯着串口助手看十六进制省事得多也方便画趋势图。import serial ser serial.Serial(COM3, 9600, timeout1) buf b while True: buf ser.read(64) while len(buf) 9: i buf.find(b\xaa\x55) # 找帧头 if i 0: buf buf[-1:] break if len(buf) - i 9: break frame buf[i:i 9] buf buf[i 9:] if sum(frame[0:6]) 0xFF ! frame[6]: continue # 校验不过直接丢 smoke, temp, flame, state frame[2:6] print(f烟雾{smoke} 温度{temp} 火焰{flame} 状态{state})联调时按「单传感器逐路验证 → 两两组合 → 三路同时」的顺序推进每加一路观察一次串口输出。如果发现复位后第一条帧校验总是错多半是复位瞬间串口电平不稳产生的垃圾字节接收端靠帧头同步就能滤掉。反过来如果长时间收不到完整帧先量波特率拿示波器看一位的时间宽度9600bps 下应该是 104 微秒左右偏差超过 3% 就要回去检查晶振是不是 11.0592MHz。本文还有配套的精品资源点击获取