简介:基于51单片机的烟雾自动报警系统毕业设计资料,适合电子类、自动化类学生及单片机入门者用于课程设计、毕业设计或项目实践。系统以AT89S52为主控芯片,结合AD0809模数转换器、DS18B20温度传感器与MQ-2烟雾传感器,运用多传感器信息融合技术,实现烟雾浓度采集、温度监测和声光报警联动。整个压缩包共四十五个文件,整体大小约二点二兆字节,其中包含两篇毕业论文文档、三份C语言源程序、三份十六进制烧录文件、一张原理图,以及工程配置、备份和日志文件。文档详细阐述设计思路与工作原理,源代码可直接编译与烧录,原理图便于硬件连线和电路检查。目前已有三千七百四十九人学习下载,学习者既能参考论文论证方法,也能根据源码和原理图实际搭建报警系统,从而深入理解单片机在烟雾报警及自动控制领域中的应用。
1. 从烟雾报警看51单片机课程设计:别把项目做成“点亮LED”
烟雾报警在51单片机课程设计里出现频率极高,但大部分提交上来的方案只做到了“烟雾浓度超标就响”,硬件上拉一个蜂鸣器,代码里写死一个比较阈值。真正能拿得出手的自动报警设计,至少要打通“烟雾传感器 -> 信号调理 -> ADC采样 -> 阈值判断 -> 声光报警”这一整条链路,并且要回答两个问题:浓度怎么量化,阈值怎么定才不误报。毕竟厨房炒菜的油烟、宿舍点蚊香、实验室焊锡烟,都有触发报警的可能,一台报警器能不能用,取决于你在这一串环节里做了多少工程化处理。
这篇博文直接以“基于51单片机的自动报警设计(烟雾报警)”为线索,从硬件选型和接法讲起,到ADC驱动与浓度换算,再到Proteus仿真里的阈值标定和去抖,最后给出报警记录存储和烧录后的验证清单。标题里提到的“论文与源代码.zip”本质上就是一份课程设计交付物,我会按真实做项目的顺序,把源代码里最核心的几段逻辑拆开讲清楚,让你拿到任何一份类似项目代码都能快速看懂、改得动。
2. 烟雾采样硬件链路:MQ-2传感器、ADC0809与51单片机的最小系统搭建
2.1 MQ-2的模拟量输出为什么不能直接进单片机P3口
MQ-2是半导体气敏传感器,内部加热丝和氧化锡敏感层构成一个阻值随可燃气体/烟雾浓度变化的元件。它在洁净空气中的阻值较高,在还原性气体(比如烟雾、甲烷、液化气)中阻值会明显下降。配合一个负载电阻,就能把阻值变化转化成电压变化——这是烟雾检测的基本原理。
但MQ-2输出的电压是模拟量,而51单片机内部没有集成ADC模块(除了少数增强型型号如STC12C5A60S2、STC15系列),P3口只能识别高低电平。如果你直接把MQ-2的输出接到P3口,只能得到一个“浓度超过某个未知电平就触发”的粗糙结果,既读不出具体浓度值,也没法调节灵敏度,更谈不上分级报警。这是很多课程设计代码里最明显的硬伤——所谓“报警”只是碰巧在某个浓度下触发了,换一个环境就失效。
正确的做法有两种:一是通过ADC芯片(ADC0809、PCF8591、ADC0832)把模拟电压转成数字量送入单片机;二是用电压比较器(LM393)把模拟量与基准电压比较,直接输出开关量。对课程设计来说,ADC方案能显示浓度值、能调阈值、能画曲线,演示效果和答辩素材都更完整,所以大多数“源程序”里选的是ADC方案。下面重点讲ADC0809的接法和驱动。
2.2 用ADC0809还是PCF8591:课程设计的两种常见接法
ADC0809是8通道8位逐次逼近型ADC,转换时间约100微秒,精度8位,正好与51单片机的数据总线对接。它的数据线D0-D7可以直接接P0口(需要外加10k上拉电阻),通过地址线ADD-A、ADD-B、ADD-C选通IN0-IN7八个模拟输入通道。启动转换靠ST端上升沿(实际用下降沿启动的习惯也有),转换结束后EOC引脚输出低电平,读完数据后OE置高把数据放到总线上。
PCF8591是I2C接口的4通道8位ADC,只需要两根线(SCL、SDA),接线比ADC0809简单得多,但需要写I2C时序。课程设计里ADC0809更常见,因为Proteus仿真库里有现成模型,原理图里连线直观,答辩时也好讲。以下是实际项目中常用的接线关系:
| 信号 | 连接位置 | 说明 |
|---|---|---|
| D0-D7 | P0.0-P0.7 | 需外接10k上拉排阻 |
| ADD-A/B/C | P2.0/P2.1/P2.2 | 000选通IN0通道 |
| START | P2.3 | 上升沿启动转换 |
| EOC | P2.4 | 转换结束标志,低电平有效 |
| OE | P2.5 | 输出使能,高电平读数据 |
| CLK | 单片机ALE引脚 | 或由P1.0输出方波,频率建议500kHz~1MHz |
这里有个容易踩的坑:ADC0809的CLK时钟频率上限约640kHz,如果你直接把51单片机的ALE(晶振12MHz时为1MHz)接过去,可能超过规格。常见做法是ALE引脚先经过一个D触发器(如74LS74)二分频再接到CLK。但在Proteus仿真里,很多教程直接用单片机的P1.0引脚软件翻转产生方波,这样更灵活,频率可控,实物的接线也更清晰。
2.3 蜂鸣器驱动:PNP三极管还是ULN2003
报警输出端的蜂鸣器分为有源和无源两种。有源蜂鸣器内部包含振荡电路,只要通电就会发声,程序里置高低电平就能控制;无源蜂鸣器需要单片机输出一定频率的方波才能发声,常用于播放旋律。烟雾报警器用有源蜂鸣器即可,声音响亮且控制简单。
但51单片机P1口灌电流能力只有20mA左右,直接驱动蜂鸣器容易拉低端口电平甚至烧毁引脚。稳妥的方案是用一个PNP三极管(如8550)做开关:蜂鸣器接在VCC和集电极之间,发射极接VCC,基极通过1k电阻接到单片机P1.0。当P1.0输出低电平时,基极有电流流过,三极管导通,蜂鸣器通电发声;输出高电平则截止。值得注意的是,蜂鸣器线圈是感性负载,关断瞬间会产生反向电动势,最好在蜂鸣器两端反向并联一个二极管(1N4148)吸收尖峰,防止损坏三极管。
这种驱动电路的逻辑是反的——低电平响、高电平停。如果你把代码里报警条件写成if (alarm_flag)直接置P1_0 = 1,蜂鸣器反而不会响,这是非常典型的接线和代码逻辑不匹配的问题。
3. ADC驱动与浓度转换:从寄存器配置到“读到多少算报警”
3.1 用查询方式轮询ADC0809,不引入中断的状态机写法
ADC0809的驱动代码在各类“51单片机源代码”里几乎是一个模子刻出来的,但版本差异很大。有些代码用延时等待EOC,有些用查询方式轮询,有些干脆不管EOC直接读完拉倒。下面给出一个适合课程设计的查询版本,状态清晰,便于在源码基础上改:
#include <reg51.h> #define ADC_PORT P0 // ADC0809数据线接P0口 sbit START = P2^3; // 启动转换信号 sbit EOC = P2^4; // 转换结束标志 sbit OE = P2^5; // 输出使能 sbit ALE = P2^6; // 通道锁存(可与START并联处理) sbit ADD_A = P2^0; // 通道选择位 sbit ADD_B = P2^1; sbit ADD_C = P2^2; void delay_us(unsigned int us) { while (us--) { _nop_(); } } unsigned char read_adc(unsigned char channel) { unsigned char adc_value; // 1. 选择通道:这里固定用IN0,对应ADC0809的ADD-A/B/C为000 ADD_A = 0; ADD_B = 0; ADD_C = 0; // 2. 地址锁存 + 启动转换:先给ALE和START一个高电平脉冲 ALE = 0; START = 0; delay_us(10); ALE = 1; START = 1; // 上升沿写入通道地址并启动转换 delay_us(10); ALE = 0; START = 0; // 下降沿后开始转换 // 3. 等待EOC变低再变高,表示转换完成 while (EOC == 1); // 转换中EOC为高,结束后变低 while (EOC == 0); // 等待EOC重新变高,表示数据就绪 // 4. 打开输出使能,读取数据 OE = 1; delay_us(5); adc_value = ADC_PORT; OE = 0; return adc_value; }这段代码的关键在于时序:ALE和START同时拉高,把通道地址锁存进ADC0809内部并启动一次转换。然后在EOC引脚上先等它从高变低(转换开始),再等它从低变高(转换结束),这是一个完整的查询握手过程。最后OE使能,把转换结果从P0口读进来。
很多人写的代码里少了地址锁存这一步,直接把START拉高拉低就开始等EOC,这在Proteus里偶尔能跑通,但实物上通道选择不稳定,可能采到的是随机通道的数据。另外注意,读取完成后要把OE置低,否则ADC0809的输出数据线一直占用P0总线,影响后续其他外设操作。
3.2 把AD值换算成电压与浓度:分段线性标定
ADC0809的参考电压VREF一般接+5V,8位分辨率对应0~255的数字量。如果MQ-2输出接在IN0通道,那么:
电压值 (V) = adc_value × 5.0 / 255
例如读回128,电压就是2.51V左右。这个换算很简单,难在电压与烟雾浓度之间不是线性关系。MQ-2在低浓度段灵敏度高,高浓度段趋于饱和,如果硬用一条直线拟合,低浓度区误差会很大。
标定的具体做法在Proteus仿真里没法完全复现,因为虚拟MQ-2模型通常用电压源代替,根本不模拟气敏特性。所以实物调试时的常规做法是:测量几个已知点的电压,比如洁净空气时输出电压约0.5V,点燃一截香烟靠近后电压升到1.8V,浓烟笼罩时可能在3.5V以上,然后用分段线性插值建立电压到浓度等级的映射:
unsigned char smoke_level(unsigned int adc_value) { unsigned int voltage_x10 = adc_value * 50 / 255; // 放大10倍,避免浮点数 if (voltage_x10 < 10) return 0; // 0~1.0V:正常 if (voltage_x10 < 20) return 1; // 1.0V~2.0V:轻度烟雾 if (voltage_x10 < 35) return 2; // 2.0V~3.5V:中度烟雾 return 3; // 3.5V以上:重度报警 }这里的voltage_x10用整数放大避免引入浮点运算,这是51单片机编程的一个重要习惯——Keil C51如果没有开启FPU模拟,浮点运算会占用大量代码空间和执行时间,用整数运算处理标定点足够满足8位精度场景。
3.3 两级报警阈值:预警与报警怎么设才不误报
许多课程设计的源码里只有一个阈值,比如if (voltage > 2.0) 报警。这样的设计在实验室里演示能过,但实际使用中烟雾浓度是缓慢上升的,到达阈值附近时AD值会在边界反复抖动,蜂鸣器响一下停一下,非常烦人。工程上的做法是设计两级阈值:预警阈值和报警阈值,并配合迟滞区间。
| 参数 | 取值 | 行为 |
|---|---|---|
| 预警阈值 | 2.0V(AD值≈102) | LED黄灯常亮,蜂鸣器不响 |
| 报警阈值 | 3.0V(AD值≈153) | 蜂鸣器以2Hz频率鸣叫,红灯闪烁 |
| 恢复阈值 | 1.8V(AD值≈92) | 浓度回落并持续10秒后解除报警 |
这里的恢复阈值低于报警阈值,形成约1.2V的迟滞区。也就是说,只有当浓度从3.0V以上跌回1.8V以下时才解除报警,避免在报警点附近反复触发。代码里用一个状态变量记录当前状态:
unsigned char alarm_state = 0; // 0=正常 1=预警 2=报警 void update_alarm(unsigned int voltage) { switch (alarm_state) { case 0: if (voltage > 153) alarm_state = 2; // 直接报警 else if (voltage > 102) alarm_state = 1; // 进入预警 break; case 1: if (voltage > 153) alarm_state = 2; // 烟雾加重,升级 else if (voltage <= 92) alarm_state = 0; // 回到正常 break; case 2: if (voltage <= 92 && smoke_persist_count > 100) alarm_state = 0; // 持续正常才复位 break; } }smoke_persist_count是一个在主循环里周期性累加的计数器,只有连续多次采样都低于恢复阈值时才清零,否则不解除报警。这就是“持续确认”机制——它的作用不是减慢反应速度,而是防止偶发的传感器尖峰误复位报警。对于课程设计答辩,这个变量是最值得拿出来讲的设计点之一。
4. Proteus仿真与阈值标定:烧进芯片前先改这3个参数
4.1 最小仿真电路:用ADC0809与电位器模拟烟雾浓度变化
Proteus仿真在51单片机课程设计里几乎是标配,因为很多学校不要求实物焊接,只要能在仿真环境里跑通并演示交互。烟雾报警系统的仿真电路核心就这么几个器件:AT89C51单片机、ADC0809、电位器、逻辑探针或虚拟终端、蜂鸣器(Proteus里有SOUNDER模型)。
电位器在这里替代MQ-2的电压输出,通过旋转改变分压点进出ADC0809的IN0通道。这个设计有一个很实用的调试价值:你可以精确地把电位器旋转到某个位置,让输出电压正好等于报警阈值点,用于验证代码里的阈值判断逻辑是否正确。而如果用真实的MQ-2模块,你很难精确控制它在某一时刻输出电压恰好是多少。
连接电路时注意一点:ADC0809在Proteus里默认的CLK时钟如果悬空,转换永远不会完成,EOC一直为高。你需要在属性里给ADC0809设置时钟频率,或者给它一个时钟源。实际仿真时我习惯用一个独立方波发生器(如DIGITAL CLOCK)产生500kHz的时钟信号接到CLK引脚,启动和读数的结果更稳定。
4.2 阈值标定的3个必调参数
Proteus跑通后,你会发现一个奇怪的现象:同样的代码、同样的电路,换一台电脑或者换个Proteus版本,报警点似乎不太一样。这不是玄学,而是三个参数没有固定下来的结果。
参数一:ADC0809的参考电压VREF。仿真里VREF默认接+5V,但如果你把电位器上端接的是VCC而VREF接的是另一个电源,两者之间有压差,AD值和电压就不再是对应关系。建议在仿真里把VREF+接VCC、VREF-接地,让满量程正好等于5V。
参数二:代码里的报警阈值。上面的示例代码里写的是voltage > 153对应3.0V。但注意,这个AD值是按5V参考电压算出来的。如果你在仿真里改了VREF,或者实际电路里用3.3V给传感器供电,这个数字就不对了。所以标定的第一步就是用电压表测量电位器分压,再把AD值打印出来,确定实际对应关系。
// 调试用:在串口打印AD值和电压 void debug_adc(unsigned char ch) { unsigned char val = read_adc(ch); unsigned int voltage_x10 = val * 5 * 10 / 255; // 单位0.1V SBUF = val; // 通过串口发送原始AD值 while (!TI); TI = 0; SBUF = voltage_x10 / 10 + '0'; // 发送电压整数位 while (!TI); TI = 0; SBUF = '.'; while (!TI); TI = 0; SBUF = voltage_x10 % 10 + '0'; // 发送电压小数位 while (!TI); TI = 0; }参数三:主循环采样周期。烟雾浓度变化是慢过程,不需要每毫秒都读一次ADC。一般建议每秒采样5~10次,即主循环里延时100~200ms再读一次。采样太快会让蜂鸣器控制节奏混乱,采样太慢则报警响应迟缓。很多课程设计源码里直接在read_adc后加了延时,却没有意识到这个延时本身就是采样周期的设定。
4.3 烟雾浓度缓变场景下的去抖与持续确认
去抖是报警系统里最容易忽略的细节。PCB上电源纹波、MQ-2敏感层受气流扰动、烟头在传感器旁晃一下,都会让AD值产生瞬时尖峰。如果不对连续多次采样取平均值或中值,误报是必然的。
常见做法是滑动平均滤波:维护一个长度为8的环形缓冲区,每次采样写入新值并丢弃最旧值,取平均值作为当前浓度。这个方案的优点是响应快、没有明显滞后,且能有效抑制随机尖峰。
#define FILTER_SIZE 8 unsigned char filter_buffer[FILTER_SIZE]; unsigned char filter_index = 0; unsigned int filter_sum = 0; unsigned char adc_filter(unsigned char new_value) { filter_sum -= filter_buffer[filter_index]; // 去掉最旧的值 filter_buffer[filter_index] = new_value; // 写入新值 filter_sum += new_value; filter_index = (filter_index + 1) % FILTER_SIZE; return (unsigned char)(filter_sum / FILTER_SIZE); }注意filter_sum是16位整数,8个8位数的加和最大为2040,不会溢出。这个函数每次调用返回平均值,主循环里把它当作当前浓度去做阈值判断。配合前面说的持续确认计数器(需要连续100次采样低于恢复阈值才解除报警),双管齐下,误报概率会大幅下降。
5. 配数码管显示浓度值并用EEPROM记录报警事件:收尾前的最后一步
5.1 数码管显示当前浓度与阈值设置模式
纯蜂鸣器报警的代码拿到答辩现场会显得单薄,因为评委看不出“现在浓度是多少”“离报警还有多远”。加入两位数码管后,实时显示当前AD值或电压值,效果会直观很多。动态扫描显示的代码本身不复杂,但要注意两个细节:一是段码表要包含0~9和熄灭状态,二是扫描频率要足够高(每位数码管至少每秒刷新50次),否则肉眼看会闪烁。
P0口接段选(a~g、dp),P2.0和P2.1接位选(共阴数码管) 显示逻辑:P2.0=1选通十位,P0送十位段码;延时2ms;P2.1=1选通个位,P0送个位段码在这个基础上,还可以用按键切换“显示浓度值”和“显示阈值”两种模式,长按按键进入阈值调节状态,按一次加0.1V,这个交互逻辑在答辩演示时非常加分,而且代码复杂度增加不大。
5.2 用STC内部EEPROM保存报警次数与最近一次报警浓度
很多51课程设计的源程序只把报警状态放在RAM里,一旦断电就丢。而STC系列单片机内部自带EEPROM(如STC89C52有4KB),地址从0x2000开始,按字节读写。虽然AT89C51没有EEPROM,但仿真的上位机或实际烧录的STC芯片都支持,可以把这个功能作为“扩展点”写进论文的创新性说明里。
void eeprom_write(unsigned int addr, unsigned char dat) { ISP_CONTR = 0x83; // 设置等待时间,使能ISP/IAP ISP_CMD = 0x02; // 字节写命令 ISP_ADDRL = addr & 0xff; ISP_ADDRH = (addr >> 8) & 0xff; ISP_DATA = dat; ISP_TRIG = 0x46; // 触发命令,先写46h ISP_TRIG = 0xB9; // 再写B9h,启动IAP操作 ISP_CONTR = 0x00; // 关闭IAP功能 }注意这段代码依赖STC的IAP寄存器,不同系列地址略有差异,烧录前务必查对应数据手册的ISP/IAP章节。EEPROM写入次数有限(约10万次),不要在报警状态变化时频繁写入,一般只在“由不报警变为报警”的时刻记录一次,并维护一个计数器做掉电保护。
5.3 最后的验证清单:从烧录到演示的6步确认
拿到代码和电路后,按这份清单走一遍,基本不会在答辩现场翻车:
- 上电先测基准电压:万用表测MQ-2模块输出端,正常应在0.3V~0.8V之间,如果超过1.5V说明传感器没预热或模块电位器调得太高。
- 观察串口输出AD值:轻吹传感器表面,AD值应当有明显变化,变化量太小说明传感器老化或负载电阻阻值不合适。
- 触发报警:用打火机放气(不点火)接近MQ-2,蜂鸣器应在1~3秒内响应,响应过快说明阈值设置太低,容易误报。
- 测试恢复:远离烟雾源后,报警应在10秒内解除,超过20秒说明持续确认的计数器和采样周期配合有问题。
- 断电重启:确认EEPROM中记录的报警次数在上电后能正确读回,这是扩展功能的必测项。
- 按键调节:如果做了阈值调节功能,调低到当前环境浓度以下,看报警是否立即触发;调高后看是否迅速停止。这一步直接演示了系统的实时性和可控性。
以上六步走完,你的“基于51单片机的自动报警设计(烟雾报警)”就不仅是一个能亮的电路,而是有明确标定方法、有抗干扰策略、有数据记录的完整工程了。论文部分可以照这条实际调试路径来写实验数据,包括不同距离下的响应时间、阈值标定结果和误报消除前后的对比。源码里最值得保留的三个模块是:ADC查询读取的完整时序、滑动平均滤波器、带迟滞的两级报警状态机。
本文还有配套的精品资源,点击获取