简介:这是一份基于AT89C51单片机的温度烟雾火灾报警系统设计方案,面向单片机初学者与电子设计竞赛参赛者。系统具备火情探测、灯光报警、蜂鸣器报警和阈值调节等核心功能。烟雾采集选用MQ-2传感器,输出模拟信号,经ADC0832转换后送单片机;温度采集用DS18B20,通过单总线与单片机相连。显示设备采用数码管,实时显示烟雾浓度值和温度值。报警模块由蜂鸣器和LED组成,检测元素超标即鸣叫。此外,系统提供三个按键调节报警阈值,并增加一个手动报警按键。压缩包约4.93MB,包含设计相关文件,便于对照原理图与代码理解流程。目前已有354人学习浏览,覆盖传感器选型、数据转换、显示与报警联动等关键环节,对掌握单片机火灾监测应用有参考价值。
1. AT89C51 双路传感器的火警数据链路解析
当年实验室的防火监控需要在可见烟雾出现前就预警,温度探头和烟雾传感器往往很难在同一个 MCU 上有序工作。MQ-2 的模拟输出需要外接 ADC,而 DS18B20 又是一条完全不同的单总线协议,再加上数码管轮流刷新,很多团队在调试时节拍会乱——显示一卡,温度跳变,报警又误触发。这套系统把 AT89C51 作为主控,让 ADC0832 承担烟雾信号的模数转换,DS18B20 直接上报温度,数码管显示实时状态,蜂鸣器和 LED 负责多级警报。在入门 51 平台上,这种组合能充分利用端口资源,是性价比最高的经典做法。对于正在做课设、嵌入式入门或者是想把实物完整跑起来的人来说,这套线路覆盖了传感器采集、总线时序、按键阈值校准和报警联动四个核心环节,值得从实物可复现的角度逐步拆解。
2. 硬件连接:AT89C51 怎么同时挂上 ADC0832 和 DS18B20
在入门 51 上,同时使用 ADC0832 和 DS18B20 最大的问题是端口资源占用。多数人设计时会先拨 P0 做数码管段选,P2 做位选,再把 P1 口的一部分划给传感器。常见做法是 P1.0 接 MQ-2 的模拟输出到 ADC0832 的 CH0,P1.1 接 DS18B20 的数据线,另外 P1.3~P1.5 接三个按键,P2.7 接蜂鸣器,P2.0、P2.1 接 LED。这种安排保证了 ADC0832 时钟和数据线不会与数码管动态扫描互相干扰。
2.1 MQ-2 的模拟输出与 ADC0832 的采样电路
MQ-2 的敏感体是二氧化锡,还原性气体浓度升高时导电性变强,输出到负载电阻的电压随之抬高。这个输出电压一般在 0~5V 之间摆动,普通 51 内部没有 ADC 无法直接读取,所以要把信号送到 ADC0832 的 CH0 通道。ADC0832 是 8 位串行逐次逼近型 ADC,256 级分辨率对 MQ-2 这种趋势性信号完全够用:烟雾浓度从 200ppm 升到 1000ppm 时,电压差异远大于一个 LSB。转换时钟由单片机机器周期分频得到,一般取 250kHz 上下比较稳妥。
实际接线时有两个细节容易出问题。第一,MQ-2 的 DO 引脚是内部比较器输出的数字信号,很多设计者误把 DO 直接接单片机,结果烟雾浓度线性区域表现很差,正确做法是取 AO 模拟输出接 ADC0832 的 CH0。第二,ADC0832 的 CS 要拉低才能启动转换,片选若和数码管位选共用一个端口,扫描瞬间会产生采集噪声;改到独立 IO 后基本能消除。下面给出一份建议接线:
| 信号 | MCU 端口 | 说明 |
|---|---|---|
| ADC0832.DI/DO | P1.0 | 数据输入输出复用脚 |
| ADC0832.CLK | P1.1 | 串行时钟输入 |
| ADC0832.CS | P1.2 | 片选,低电平有效 |
| DS18B20.DQ | P3.7 | 单总线数据线,需接 4.7k 上拉 |
| 蜂鸣器 | P2.7 | 经 NPN 三极管驱动 |
| LED1(烟雾报警) | P2.0 | 烟雾超阈值点亮 |
| LED2(温度报警) | P2.1 | 温度超阈值点亮 |
| 按键 S1~S3 | P1.3~P1.5 | 阈值加减与确认 |
表里把 ADC0832 和 DS18B20 的端口彻底分开,数码管又占用 P0 和 P2 的高位,这种分配在后续调试时可以避免总线驱动电平竞争。
2.2 DS18B20 单总线接法在强电环境下要注意什么
DS18B20 的一线总线协议全靠单根线上的时序高低电平传递,上拉电阻必须选 4.7k。报警器周围如果存在继电器或其他感性负载模块,线上毛刺很容易把复位脉冲误判成数据帧,所以布局里要尽量把传感器线缩短,并在 VCC 和 GND 之间加一个 0.1uF 去耦电容。我做过一次实物调试时踩过这个坑:扎带把 DS18B20 数据线与继电器驱动线绑在一起,断电瞬间温度读数直接变成 -55 或 125 两个极端值。而 AT89C51 的 P3.7 上电默认状态是准双向口,读引脚前要先置 1,这个特性在代码里必须时刻记住,后面时序部分会再展开。
如果打板而不是洞洞板飞线,建议把 DS18B20 用 3PIN 排针引出,地线和信号线间距至少 2mm。传感器离主板超过 30cm 时,屏蔽线情况下总线电容应控制在 200pF 以内,否则上升沿变缓,程序可能一直卡在转换等待。
注意:DS18B20 的 VCC 不要从 MQ-2 的加热回路取电,加热丝瞬间压降会导致温度芯片复位。
2.3 显示、按键和报警执行电路参数推荐
数码管选用 4 位共阴即可,P0 口经 200Ω 排阻限流做段选,P2.3~P2.6 做位选,通过 8550 三极管驱动公共端。蜂鸣器选择 5V 有源蜂鸣器,经 S8050 接到 P2.7,严禁直接接 IO 口,AT89C51 拉电流上限约 20mA,直接驱动会拉低端口电平甚至烧毁引脚。按键部分若用板上微动,直接接 10k 上拉,并配 100nF 电容滤波,程序内再补一次消抖。这套设计的供电能力完全依托 51 基本盘展开:P0 段选 + P2 位选 + P1 传感器输入,各外设之间没有共用同一组总线的尴尬。
若使用外部电源适配器,建议 7805 输入侧留一个 470uF 电解电容。MQ-2 加热丝启动瞬间电流可达 150mA 以上,电压跌落会造成单片机复位,这是实物调试中非常隐蔽的偶发故障。
3. 固件实现:AD 值、温度值到稳定显示的转换过程
硬件链路搭建完成后,固件核心分为三块:ADC0832 的时序读取、DS18B20 的时序读取、数码管动态扫描刷新。三者放在同一个主循环里时要特别注意时序占用,因为 DS18B20 转换期间要求数据线保持稳定,而 ADC0832 的串行时钟又不能被中断拖得太久。以下是我在 12MHz 晶振下的实现思路。
3.1 ADC0832 驱动与连续采样消抖
#include <reg52.h> #include <intrins.h> sbit ADC_CS = P1^2; sbit ADC_CLK = P1^1; sbit ADC_DIO = P1^0; /* 读取 ADC0832 指定通道的 8 位转换结果 */ unsigned char read_adc0832(unsigned char channel) { unsigned char i, dat1 = 0, dat2 = 0; ADC_CS = 1; ADC_CLK = 0; ADC_CS = 0; /* 片选拉低,启动转换 */ ADC_DIO = 1; ADC_CLK = 1; _nop_(); ADC_CLK = 0; /* 第一个下降沿写入起始位 */ ADC_DIO = 1; ADC_CLK = 1; _nop_(); ADC_CLK = 0; /* 第二个下降沿写入 SGL/DIFF 位 */ ADC_DIO = channel; /* 0 选 CH0,1 选 CH1 */ ADC_CLK = 1; _nop_(); ADC_CLK = 0; /* 第三个下降沿写入通道号 */ ADC_DIO = 1; /* 释放数据线,准备读取 */ ADC_CLK = 1; _nop_(); ADC_CLK = 0; for (i = 0; i < 8; i++) { ADC_CLK = 1; ADC_CLK = 0; if (ADC_DIO) dat1 |= (0x80 >> i); /* 高位在前收集数据 */ } for (i = 0; i < 8; i++) /* 读取后半段校验数据 */ { dat2 >>= 1; if (ADC_DIO) dat2 |= 0x80; ADC_CLK = 1; ADC_CLK = 0; } ADC_CS = 1; return dat1; }这段代码的核心时序是:CS 拉低后连续送四个时钟,依次写入起始位、通道选择位、单端输入标志位;随后方向从 DI 切换为 DO,在后续 8 个时钟的下降沿逐位读出结果。dat1 保存第一次读到的 8 位数据,dat2 保存第二组读出的数据,两者若相差超过 1,说明通信线上有干扰,我一般会丢弃本次结果并重新触发采样。ADC0832 是 8 位精度,返回 0~255 对应 0~5V 电压,程序里做阈值比较时直接使用这个原始 AD 值即可。
配合 MQ-2 使用有个常见问题:传感器上电前 30 秒输出电压会先冲高到接近 5V,然后缓慢回落到静态值。如果单片机上电后立刻采样,容易误判为高浓度烟雾触发报警。解决办法是上电后先延时 60 秒再进入采样循环,或者连续读 10 次去掉最大最小值取平均。
3.2 DS18B20 单总线时序驱动
sbit DQ = P3^7; void ds18b20_reset(void) { DQ = 0; delay_us(480); /* 主机拉低 480us 以上产生复位脉冲 */ DQ = 1; delay_us(60); /* 释放总线,等待器件应答 */ } unsigned char ds18b20_read_bit(void) { unsigned char bit; DQ = 0; _nop_(); /* 拉低 1us 后释放 */ DQ = 1; _nop_(); bit = DQ; /* 在 15us 窗口内采样 */ delay_us(50); return bit; } unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; if (ds18b20_read_bit()) dat |= 0x80; } return dat; } void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); DQ = dat & 0x01; /* 数据位从低位开始写入 */ delay_us(60); DQ = 1; dat >>= 1; } } unsigned char get_temperature(void) { unsigned char low, high; ds18b20_reset(); ds18b20_write_byte(0xCC); /* 跳过 ROM,单点测温 */ ds18b20_write_byte(0x44); /* 启动温度转换 */ while (!DQ); /* 转换完成 DQ 被释放拉高 */ ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); /* 读取暂存器数据 */ low = ds18b20_read_byte(); high = ds18b20_read_byte(); /* 12 位分辨率下拼接整数部分,忽略小数位 */ return (high << 4) | (low >> 4); }DS18B20 的通信分为初始化、ROM 命令、功能命令三个层次。rst后通过拉低 480us 产生复位脉冲,器件会回一个 60~240us 的低电平应答;接着写 0xCC 跳过 ROM 寻址,0x44 启动转换,等待 DQ 引脚释放后重新复位,再发 0xBE 读取暂存器前两个字节。返回的是带符号补码,比如 0x0032 代表 50°C,0xFFCE 代表 -50°C,判断温度超限时直接与阈值比较。
提示:DS18B20 的延时函数不要用带中断的 delay,建议用
_nop_()组合或关闭中断的定时查询,否则中断服务函数会破坏单总线时序。
3.3 数码管 4 位动态扫描与数据缓冲
unsigned char code seg_code[10] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; unsigned char disp_buf[4]; unsigned char disp_pos = 0; void timer0_isr(void) interrupt 1 { TH0 = 0xFC; TL0 = 0x18; /* 12MHz 下定时 2ms */ P0 = 0x00; /* 消隐,防止拖影 */ P2 &= 0x0F; P0 = seg_code[disp_buf[disp_pos]]; P2 |= (0x10 << disp_pos); /* 选中当前位 */ if (++disp_pos >= 4) disp_pos = 0; }显示部分放在定时器中断里,每 2ms 切换一位数码管,四位数码管 8ms 完成一轮刷新,人眼看不到闪烁。主循环只需维护 disp_buf 数组:显示温度时,disp_buf[0] 和 disp_buf[1] 放温度十位和个位,disp_buf[2] 放 0x40 作为分隔符号;显示 AD 值时,把 0~255 映射成两位十进制。P2 高四位用作位选,切换前先 P0=0 消隐。这样做的收益是显示刷新与采样时序解耦,DS18B20 转换期间定时器中断照常发生,但因为中断服务函数只有显示逻辑,不会碰到 DQ 引脚,所以不会破坏单总线时序。
3.4 按键消抖与长按连续调阈值
按键 S1 加一、S2 减一、S3 确认是这类系统最常见的交互方式。阈值初始化时烟雾阈值设为 150,温度阈值设为 50°C。按键消抖不做软件延时死等,而是利用主循环扫描周期,连续两次读到同一电平才认为稳定,10ms 后再进入分支。长按 S1 或 S2 超过 60 次扫描后,把计数器阈值设为 3,实现连续快速加减,不需要一次次按。
主循环骨架如下:
while (1) { smoke_val = read_adc0832(0); temp_val = get_temperature(); /* ADC 值在 0~255 之间,与阈值直接比较 */ if (smoke_val > smoke_limit) led_smoke_on(); if (temp_val > temp_limit) led_temp_on(); if (smoke_val > smoke_limit || temp_val > temp_limit) buzzer_on(); else buzzer_off(); scan_key(); /* 消抖 + 长按检测 */ update_display_mode(); /* 3 秒交替显示 */ }主循环中不建议把 ADC 和 DS18B20 采样放进中断。尤其是 DS18B20 的 while 等待,一旦中断优先级配置不当,很可能导致传感器应答位丢失。更好的做法是把采样放在主循环顺序执行,数码管刷新放在定时器中断里,两者互不打扰。
4. 阈值调节与手动报警:从按键到报警状态的联动
阈值按键和手动报警按键是这套设计里最能体现交互细节的部分。如果按键逻辑写得太简单,会出现设置状态下误触报警、手动报警无法撤销等问题。这一章把这些交互边界逐一理清。
4.1 阈值设置的进入、修改与保存
阈值调整不应在正常监控模式下直接响应 S1/S2,否则现场人员擦拭面板就会误改参数。常见做法是同时按住 S3 超过 1 秒进入阈值设置模式,数码管第一位显示 S 代表烟雾阈值,后两位显示当前 AD 阈值;再按 S3 切换到 T 代表温度阈值;S1/S2 修改数值;长按 S3 退出并写入保存。如果用 24C02 EEPROM 保存阈值,写操作约 5ms,期间按键扫描会被阻塞,所以保存动作应在退出设置时统一执行,而不是每按一次 S1 写一次 EEPROM。
阈值修改后要检查合法性:烟雾阈值范围限制在 30~250,温度阈值限制在 30~85°C。低于下限会导致开机瞬间误报警,高于上限则报警功能形同虚设。不少课设同学把阈值随意设在 0 或 255,恰好踩中 MQ-2 上电脉冲的峰值,这种问题在初版代码里非常常见。
4.2 手动报警与自动报警的优先级设计
手动报警按钮的存在意义是:系统阈值未超标但人已闻到焦味时,提供一个强制的报警入口。单片机读取到该按键拉低后,应立即进入独立报警分支,不依赖任何阈值判断。S3 必须长按 2 秒才能消除,避免误触或小孩乱按后无法复位。报警状态的真值表如下:
| 场景 | 蜂鸣器 | LED 状态 |
|---|---|---|
| 温度超限 | 鸣叫 | 红色 LED 亮 |
| 烟雾超限 | 鸣叫 | 黄色 LED 亮 |
| 温度和烟雾同时超限 | 鸣叫 | 双 LED 亮 |
| 手动报警 | 鸣叫,不依赖阈值 | 双 LED 闪烁 |
| 无异常 | 关闭 | 绿色 LED 慢闪 |
手动报警分支的优先级最高,自动报警判断在其之后执行。消除手动报警的唯一入口是 S3 长按 2 秒,期间数码管显示H提示手动报警状态。这个设计比简单的一键消除更符合实际安防产品习惯——消除操作需要刻意且明确。
4.3 显示模式自动轮换与缓冲区写入
显示内容分为温度模式和烟雾模式,主循环通过一个 3 秒计数器切换。在阈值设置界面下,显示缓冲区强制写入设置值而非实时采样值,避免用户调阈值时看到数字不停跳动。计数器声明时必须加 volatile,否则 keil 优化后可能出现判断条件被内联掉导致显示永不切换的情况。实际实现中我用一个 16 位计数器,每次主循环自增,达到阈值就取反显示标志并清零。显示缓冲区只在主循环中写入,定时器中断只读取,这样不会出现数据竞争。
5. 实物调试的关键动作:从时序到阈值漂移的排错顺序
5.1 上电先量静态电平,再抓总线时序
拿到实物板子后不要急着烧录程序。先测三组电压:DS18B20 的 DQ 引脚静态电平应接近 5V,低于 2V 说明传感器未接好或已经短路;MQ-2 的 AO 引脚静态电压应在 0.3~1V 之间,刚上电会偏高,等 60 秒再量;蜂鸣器驱动三极管基极电压应为 0V 或用万用表二极管档确认无击穿。之后用示波器观察 DQ 线上的复位脉冲,正常能看到 480us 低电平和紧随的 80us 应答脉冲。没有示波器时,在 ds18b20_reset 后读 DQ,读到 1 直接返回 0xFF 错误码,通过串口区分通信断裂与传感器断开。
5.2 MQ-2 预热漂移与基准校准
MQ-2 数据手册给出的预热稳定时间是 24 小时,实际做实物时不可能等那么久。我一般在上电 2 分钟后连续采样 10 次取平均作为基准值,比如静态 AD 值 45,就把报警阈值设为基准值加固定增量 80,也就是 125。增量的大小按环境调整:厨房油烟大放 100,机房要求灵敏放 60。基准值每 30 秒做一次滑动平均更新,以消除凌晨和中午温湿度不同带来的基线漂移。
5.3 三条高频故障路径和对应解法
整理实物调试中最常见的问题。第一,ADC0832 的 CS 与数码管位选共用引脚,扫描瞬间转换结果跳变,表现为烟雾值每隔几百毫秒出现一个尖峰,解决办法是把 CS 挪到独立空闲 IO。第二,DS18B20 数据线过长或走线宽带不足,温度读数隔几次跳变一次,这是上升沿变缓导致采样窗口错位,缩短线长或换双绞线可解决。第三,蜂鸣器启动瞬间电源跌落导致单片机复位,现象是报警一响数码管熄灭又重启,检查 7805 输出端是否放置 100uF 电解电容,没有的话补上并把蜂鸣器供电单独加 10uF 去耦。
最后做一次完整的报警链路验证:用打火机气体靠近 MQ-2,3~5 秒内蜂鸣器应鸣叫,黄色 LED 点亮;用电烙铁靠近 DS18B20 加热,温度超过设定值时红色 LED 点亮,蜂鸣器再次鸣叫;按下手动报警键,无论当前采样值高低都应立即进入报警状态。整个过程中数码管上的 ADC 值和温度值实时变化,这正是这套基于 AT89C51 的设计最直接的验收标准。
本文还有配套的精品资源,点击获取