简介:基于51单片机的烟雾报警自动设计毕业资料包,面向单片机学习者、电子竞赛备赛学生与毕业设计选题者,也适合课程设计与项目实践。资源以51单片机为控制核心,结合模数转换,配合DS18B20温度传感器与MQ-2烟雾传感器,展示多传感器信息融合及声光报警系统完整设计链路。包内共45个文件、约2.2MB,主要包含论文文档、单片机源程序、原理图文件、工程文件、烧录文件以及备份归档文件;其中论文文档提供毕业设计参考,源程序覆盖主控逻辑与传感器驱动,烧录文件便于直接验证,原理图适合仿真改版。已有3749人学习下载。通过该包可获得可直接参考的论文范文、完整可编译代码及配套原理图,适合从方案论证、硬件搭建到程序调试的完整学习链路,尤其便于快速理解烟雾报警系统的硬件选型、软件流程、程序烧录与整体调试方法。
1. 基于51单片机的烟雾报警器:先解决误报,再谈报警
如果把 MQ-2 烟雾传感器的输出直接接到比较器上,超过阈值就拉响蜂鸣器,厨房炒菜和浴室水蒸气会让报警器毫无价值地叫起来。这个基于 AT89S52、AD0809 和 DS18B20 的烟雾报警设计,把烟雾浓度、环境温度、报警输出三条链路放到同一个 51 程序里做联合判断,而不是单纯比大小。资源包里有两篇毕业设计论文、Keil C51 源码工程和 Proteus 仿真文件,从 AD0809 的通道切换到 DS18B20 的 1-Wire 时序都给出了完整实现。适合正在做 51 单片机课程设计或想理清多传感器采集逻辑的人。下面按硬件接线、代码实现、信息融合、仿真排错四个方向拆开。
2. 传感器信号链与 AT89S52 接线:从 MQ-2 模拟量到 AD0809 数字量
2.1 先看 MQ-2 的测量模型
MQ-2 属于半导体气敏电阻,内部加热丝把敏感层加热到 200℃ 左右,表面吸附还原性气体后电导率升高,等效电阻 Rs 下降。典型电路里,MQ-2 与负载电阻 RL 串联分压,传感器模块的 AOUT 引脚输出 Vout = Vcc × RL / (Rs + RL)。烟雾浓度越高,Rs 越小,Vout 越高。真实模块上 RL 常用 10kΩ,Vcc 接 5V。这个输出不是线性关系,在低浓度段变化明显,再往上逐渐饱和,所以不要指望用一个固定比例换算成“浓度百分比”。
因为 AT89S52 内部没有 ADC,所以必须外接 AD0809。AD0809 是 8 位逐次逼近型转换器,满量程 5V 时分辨率约 19.6mV。8 位精度对烟雾报警够用,因为报警点通常设在 1V~3V 区间,不需要 12 位。把 MQ-2 的 AOUT 接到 AD0809 的 IN0,地线尽量靠近,避免数字信号串扰。模块输出端与 IN0 之间加一个 100nF 电容到地,可以滤掉加热丝带来的低频扰动,这是实际项目里常见做法。
| 信号 | 传感器/器件 | 单片机引脚 | 说明 |
|---|---|---|---|
| 烟雾浓度 | MQ-2 AOUT | AD0809 IN0 | 0~5V 模拟输入 |
| AD 数据 | AD0809 D0~D7 | P1.0~P1.7 | 并口输出 |
| 通道选择 | AD0809 A/B/C | P2.0/P2.1/P2.2 | 000=IN0 |
| 启动 | AD0809 START/ALE | P2.3/P2.4 | 高电平启动 |
| 转换结束 | AD0809 EOC | P3.5 | 低电平表示完成 |
| 输出使能 | AD0809 OE | P2.5 | 高电平读数据 |
| 温度 | DS18B20 DQ | P3.7 | 1-Wire 数据线 |
| 报警 | 蜂鸣器驱动 | P2.6 | 高电平触发 |
| 指示 | LED 报警灯 | P2.7 | 低电平点亮 |
这个表格里的引脚定义直接对应源码里的sbit声明。先画出引脚映射再写代码,是为了避免三个外设抢 P1 口。ADC 数据总线、按键、显示模块最容易冲突,声明宏之前确认一下每根引脚用途。
#define ADC_DATA P1 sbit ADDA = P2^0; // 通道地址最低位 sbit ADDB = P2^1; sbit ADDC = P2^2; sbit START = P2^3; sbit ALE_AD = P2^4; sbit OE = P2^5; sbit EOC = P3^5; sbit BEEP = P2^6; sbit LED_ALARM = P2^7; sbit DQ = P3^7;这段定义把所有外设的 IO 都抽出来了。参数说明:ADDA/ADDB/ADDC 是 AD0809 的通道地址,IN0 时为 000,源码里固定读 0 通道;START和ALE_AD在代码中常用于启动转换;P1 只读不写,避免与蜂鸣器、LED 的引脚输出互相干扰。
2.2 AD0809 的时钟与启动时序
AD0809 需要外部时钟,典型范围是 10kHz~1.2MHz,常用 500kHz 左右。AT89S52 的 ALE 引脚默认输出晶振频率的 1/6,12MHz 晶振下是 2MHz,直接给 AD0809 偏快。常见做法是用 74LS74 做一次二分频,ALE 接 D 触发器输入端,Q 输出接 AD0809 的 CLK,得到 1MHz,这个频率仍在可接受范围。Proteus 仿真时直接放一个 500kHz 时钟源接 CLK 也可以,硬件上再用二分频。
启动一次转换的标准顺序是:先把通道地址写到 A/B/C,然后 ALE 延时后拉低锁存地址,START 给出一个大于 100ns 的高电平脉冲,之后等待 EOC 由高变低,再拉高 OE 读取数据。对应的 C 代码在 3.1 节给出。要强调一个容易错的位置:EOC 是开漏输出,51 的 P3.5 是准双向口,可以直接读,但需要在外部接一个 4.7kΩ 上拉到 Vcc。仿真里不接也经常能通过,真实电路不加上拉,经常卡在while (EOC == 0)里。
2.3 DS18B20 温度读数的边界条件
DS18B20 在这套设计里承担的是温度补偿和异常高温判定。1-Wire 总线只需要一根数据线,但要严格遵守初始化、ROM 指令、功能指令三段时序。总线空闲时被 4.7kΩ 上拉到高电平,主机先拉低 480μs 再释放,随后读到的低电平表示设备应答。温度转换需要等待 750ms(12 位分辨率),如果程序里用delay_ms(750)会阻塞报警扫描。更稳妥的做法是启动转换后先做几轮 ADC 扫描,再回来读温度,让两种传感器并行工作。
DS18B20 的测温范围是 -55℃~+125℃,默认 12 位分辨率时值乘以 0.0625 就是实际温度。在 51 上算小数容易让新手懵,实际做法是读取高字节和低字节拼成int,再判断符号位。温度越高,MQ-2 的基线输出会略有漂移,这就是第 4 章要做温度补偿的原因。
2.4 报警输出:蜂鸣器别直接接引脚
蜂鸣器工作电流约 20mA,AT89S52 的灌电流能力比拉电流强,直接驱动很容易让单片机发热。常见方案是用 S8050 三极管把蜂鸣器放在集电极,发射极接地,基极通过 1kΩ 接到 P2.6,高电平导通。如果是蜂鸣器模块,很多已经带驱动三极管,只要接 IO 就能响。显示部分用共阳数码管加 74HC245 驱动或者 LCD1602,资源里的工程在beep.c里已经包含了蜂鸣器控制,仿真时只需要确认发光二极管限流电阻串在正确的一端。
3. Keil C51 下的核心代码:AD0809 采集、DS18B20 读取与阈值判断
3.1 AD0809 驱动与电压换算
先给一段可以直接放进 Keil 工程的基础采集函数:
unsigned char read_ad0809(unsigned char ch) { unsigned char ad_val; ADDA = ch & 0x01; // 通道地址低三位 ADDB = (ch >> 1) & 0x01; ADDC = (ch >> 2) & 0x01; ALE_AD = 1; // 锁存通道地址 START = 1; // 启动转换 _nop_(); _nop_(); // 保持至少 100ns START = 0; ALE_AD = 0; while (EOC == 0); // 等待转换完成 OE = 1; ad_val = ADC_DATA; // 读取 8 位结果 OE = 0; return ad_val; } unsigned int get_smoke_mv(void) { unsigned int ad = read_ad0809(0); // IN0 通道 return (unsigned int)(ad * 5000UL / 255); }read_ad0809先设置通道,再给 ALE 和 START 一个高电平脉冲。EOC 是 AD0809 发给单片机的状态信号,转换开始后 EOC 变高,转换结束变低,所以用while (EOC == 0)等待。读数据前必须拉高 OE,否则数据总线是高阻态。get_smoke_mv把 0~255 的采样值换算成 0~5000mV,这一步对后面设报警阈值很关键,否则阈值无从谈起。
参数说明:ch传 0 表示 IN0。如果硬件里把 MQ-2 接到了 IN1,就改成 1。5000UL是 5V 满量程,如果实际供电 4.8V,换算时应该改 4800,不要照抄。采样值不是真实电压,只有经过这个线性映射才能和传感器曲线对标。如果发现读取结果总是满量程或者全 0,先检查 EOC 是否接对,再把ADC_DATA改成整型,加一个短暂延时再看。
3.2 DS18B20 的 1-Wire 时序代码
DS18B20 的代码比 ADC 容易踩坑,因为每个写时隙、读时隙都要求微秒级精确。
bit ds18b20_reset(void) { bit presence; DQ = 0; delay_us(500); // 复位脉冲 480us 以上 DQ = 1; delay_us(60); presence = DQ; // 应答脉冲,0=存在 delay_us(420); return presence; } unsigned char ds18b20_read_byte(void) { unsigned char i, value = 0; for (i = 0; i < 8; i++) { value >>= 1; DQ = 0; // 启动读时隙 _nop_(); _nop_(); DQ = 1; if (DQ) value |= 0x80; // 读到 1 delay_us(60); } return value; } void ds18b20_write_byte(unsigned char byte) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); DQ = byte & 0x01; // 先写最低位 delay_us(60); DQ = 1; byte >>= 1; } }ds18b20_reset是总线上唯一的握手信号,主机拉低 480μs 再释放,从机会在 15~60μs 内拉低总线表示设备在线。读到presence为 0 表示连接正常,为 1 表示没接对或线上短路。read_byte与write_byte的每个时隙至少 60μs,周期约 120μs。延时函数在 12MHz 晶振下需要微调,如果换到 11.0592MHz,delay_us里的空循环次数要重新校准。
温度读取完整流程:
int read_temperature(void) { unsigned char temp_msb, temp_lsb; int temp; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM 匹配 ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(750); ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存寄存器 temp_lsb = ds18b20_read_byte(); temp_msb = ds18b20_read_byte(); temp = (temp_msb << 8) | temp_lsb; if (temp & 0x8000) // 负温补码 { temp = ~temp + 1; return -temp * 100 / 16; } return temp * 100 / 16; // 返回温度*100,保留两位 }这里把温度乘了 100,是为了在 LCD 或数码管上直接显示 XX.XX 度,避免引入浮点库。0xCC 是跳过 ROM 指令,适用于总线上只有一个 DS18B20;如果有多个设备,需要按序列号匹配。delay_ms(750)是阻塞延时,课程设计演示可以接受,实际产品建议换成定时器扫描。
3.3 基础阈值判断
建议先把采到的烟雾电压打印出来,观察正常环境和报警环境下的数值差异。把 500mV 设为预警,1000mV 设为报警,都比直接拍一个 2000mV 更有余地。基础判断代码很短:
unsigned int smoke = get_smoke_mv(); if (smoke > 1000) { BEEP = 1; LED_ALARM = 0; // 低电平点亮 } else { BEEP = 0; LED_ALARM = 1; }这是最朴素的阈值报警,能应付大部分答辩演示。但一旦把环境温度从 20℃ 升到 40℃,MQ-2 的输出电压会跟着上涨,可能在没火的情况下直接触发。BEEP = 1是高电平驱动;如果用的是低电平有效的蜂鸣器模块,需要取反。LED 那一路我是按灌电流接法列的低电平点亮,实际板子方向不同就反着写。
4. 多传感器信息融合与报警状态机:单阈值误报的解法
4.1 MQ-2 的温度漂移与水蒸气干扰
MQ-2 对乙醇、甲烷、油烟和香烟烟雾都有响应,热的水蒸气也会让电导率变化。把 AD0809 采集到的电压直接和固定阈值比,最容易遇到两个问题:梅雨季节湿度高,电压持续偏高,误报警;冬天开暖气,温度升高引起基线漂移,预警指示灯常亮。要修正这些,就得引入 DS18B20 的温度读数,再做动态阈值。
一个常用做法是:threshold = base + (temp - 25) * temp_coeff。temp_coeff是温度补偿系数,单位 mV/℃。不同厂家的 MQ-2 模块差异很大,需要开机稳定 5 分钟后用加热源改变环境温度来标定。常见做法是取 10~50mV/℃ 范围内的整数,在代码里用宏定义,方便实验时快速修改。
| 条件 | 烟雾电压 | 温度 | 输出 |
|---|---|---|---|
| 正常 | < base | < 70℃ | 不报警 |
| 烟雾 | > threshold | 任何 | 报警 |
| 高温异常 | 任意 | ≥ 70℃ | 报警 |
| 预警 | > base 且 < threshold | < 70℃ | LED 闪烁 |
这个矩阵控制了一个事实:温度超过 70℃ 时,无论有没有烟雾电压,都要触发报警。因为在火灾现场,温度陡增往往早于烟雾扩散。用 DS18B20 作为第二确认信号,可以有效防止厨房水汽单独触发。
4.2 用变化率识别快速燃烧
静态阈值只判断“现在有多大”,但火灾初期的特征不只数值高,还有上升速度快。每次采样时存下上一次的电压值,计算差值:delta = smoke - last_smoke。如果delta > 100mV且持续 50ms,说明浓度在快速上涨,即使当前值还没到报警阈值,也可以先进入预警状态。慢速漏气不会触发这个条件,漏气交给阈值判断,这样既能响应快速火情,又不会对缓慢漂移过度敏感。
#define BASE_MV 300 #define ALARM_MV 1000 #define TEMP_ALARM 70 #define DELTA_FAST 100 unsigned int last_smoke = 0; unsigned char state = 0; void alarm_process(void) { unsigned int smoke = get_smoke_mv(); int temp = read_temperature() / 100; unsigned int threshold; if (temp > 25) threshold = ALARM_MV + (temp - 25) * 20; else threshold = ALARM_MV; if (smoke > threshold || temp >= TEMP_ALARM) state = 2; else if (smoke > threshold - 200 || (smoke > last_smoke && smoke - last_smoke > DELTA_FAST)) state = 1; else state = 0; last_smoke = smoke; set_alarm_output(state); }threshold随温度线性抬高,让同样浓度的烟雾在高温环境下用更高的电压触发,这是对基线漂移的补偿。threshold - 200是预警带,比正式报警阈值低 200mV;smoke - last_smoke > DELTA_FAST捕获电压跳跃。条件里加了smoke > last_smoke,避免无符号变量计算负数造成溢出。set_alarm_output根据状态控制蜂鸣器和 LED,是预处理逻辑和最终输出之间的隔离层。
4.3 报警状态机:防抖与手动恢复
火苗闪烁、人挡在传感器前面,都会让电压瞬间升高又回落。如果每次都立刻响铃,体验很差。把“持续多少毫秒”纳入条件,用定时器中断做时基,每 10ms 调用一次alarm_tick(),只有状态连续保持 500ms 才切换。
#define STATE_NORMAL 0 #define STATE_WARN 1 #define STATE_ALARM 2 unsigned char state = STATE_NORMAL; unsigned char stable_ticks = 0; void alarm_tick(void) { unsigned char new_state = judge_state(); if (new_state == state) { if (stable_ticks < 50) stable_ticks++; if (stable_ticks == 50) { apply_output(state); } } else { stable_ticks = 0; state = new_state; } }这段代码比直接判断阈值多了一个稳定计时器。50 个 tick 对应 500ms,如果出现一个毛刺,状态马上回到正常,计时器清零,报警输出不会成立。state只在变化时更新,输出只写一次,避免频繁拉负载。stable_ticks是 unsigned char,最大 255,50 次没问题;如果时基改为 1ms,这里就要用 unsigned int,否则会溢出。
5. Proteus 仿真与实际排错:从源码包到稳定报警的验证
5.1 仿真连线与元件放置
打开资源里的烟雾传感器.DSN可以看到完整连线。如果从零搭,顺序是:先放 AT89S52、AD0809、DS18B20、LCD1602 或数码管、蜂鸣器、LED。AD0809 的 CLK 在仿真里直接放一个 500kHz 时钟源,地址线 A/B/C 接 P2.0~P2.2,数据线接 P1,EOC 接 P3.5,OE 接 P2.5。MQ-2 在 Proteus 中没有集成模型时,用一个 10kΩ 电位器替代,中心抽头接 AD0809 的 IN0,上端接 5V,下端接地。旋转电位器就能模拟烟雾浓度升高。
编译源码时,在 Keil 的 Options for Target - Output 里勾选 Create HEX File。烧录步骤:双击 AT89S52 元件,在 Program File 里选择beep.hex或sense.hex,时钟频率设 12MHz。播放仿真后,先旋转电位器观察 ADC 读数变化,再按住按键模拟手动测试报警输出。
5.2 几个常见的仿真坑
第一个坑是 AD0809 的 EOC 不加上拉,仿真里有时能通过,换真实芯片会一直卡在等待转换完成。第二个坑是 DS18B20 时序太短,会在读温度时返回 85℃。这个 85℃ 是 DS18B20 的复位值,说明 ROM 指令没被识别,调大delay_us的循环参数再试。第三个坑是蜂鸣器直接接在 P2.6,驱动能力不够,仿真里虽然响但实际音量小,加三极管后要共地。
| 故障现象 | 检查点 | 处理 |
|---|---|---|
| ADC 电压全 0 | EOC 是否上拉 / OE 是否拉高 | 检查延时与位定义 |
| 温度恒为 85℃ | 1-Wire 时序太短 | 增大延时,用示波器看时隙 |
| 电压满量程 255 | 模拟输入悬空 | 给 IN0 接确定电平 |
| 报警不恢复 | 状态机计数溢出 | 把 ticks 类型改为 unsigned int |
5.3 下载后验证的关键一招
把 hex 烧到实际板子后,不要直接拿打火机测试。先用万用表量 AD0809 的 VREF 和 VCC,确认都是 5V。再用手捏住 MQ-2 的网罩,看输出电压有没有缓慢变化;没有变化就检查模块上的电位器是否调到合适位置。如果上述正常,再用打火机气体靠近传感器,观察报警 LED 是否在预设的 500ms 防抖之后点亮。通过调整BASE_MV、ALARM_MV和温度补偿系数这三个宏,让它在正常空气下稳定运行 5 分钟不报警,报警测试两次都能在 1 秒内响应,这套基于 51 单片机的自动报警装置才算真正进入可交付状态。
本文还有配套的精品资源,点击获取