51单片机火灾报警器:DS18B20+ADC0809+LabVIEW上位机完整设计
2026/9/12 17:09:09 网站建设 项目流程

简介:基于51单片机设计的火灾报警器毕设源码包,适合计算机、通信、自动化等专业学生用于课程设计、期末大作业或毕业设计参考。项目包含完整的单片机C51程序与LabVIEW上位机虚拟仪器,能够实现温度、烟雾、光强等环境参数的采集监测与异常报警,覆盖硬件数据采集到上位机图形化展示的完整链路。压缩包共64个文件,主要包含c/h单片机源码、uvproj/uvopt工程文件、hex烧录文件、vi上位机程序、txt数据记录及jpeg波形截图,同时附有README说明与备份文件,包体仅152KB,轻量易部署。已有49名学习者下载参考。源码为答辩评审98分的高分项目,经过调试验证可直接运行,也可基于BH1750FVI、DS18B20、ADC0809等模块二次开发扩展功能,对理解51单片机编程、LabVIEW数据可视化及整体项目设计具有较高学习价值。

1. 一个毕业设计级别的51单片机火灾报警器:从传感器到LabVIEW上位机

拿到一份带LabVIEW上位机的51单片机火灾报警器源码,先别急着点烧录。这个项目典型地由三块组成:DS18B20测温、BH1750FVI测光强、ADC0809采集烟雾传感器信号,单片机用C51写好逻辑后通过UART把数据帧发给上位机,LabVIEW负责解析并绘制温度、烟雾、光强三条波形。适合计算机、自动化、通信专业的学生做课程设计或期末大作业,也适合想快速搭一个上位机联调环境的工程师参考。它最值得拆的不是每颗传感器的驱动,而是下位机的数据帧设计和LabVIEW的串口解析配合。如果你之前只做过LED流水灯,这正好教你如何把多传感器数据打包成结构化协议。

2. 系统架构与传感器选型:DS18B20、BH1750FVI与ADC0809的分工

2.1 为什么是这三类传感器

火灾报警器需要感知的不只是温度。烟雾是最先产生的特征,光强变化则可以作为火焰燃烧的辅助判据。这个项目用DS18B20测量环境温度,用BH1750FVI采集光照强度,用ADC0809配合烟雾传感器模块采集烟雾浓度信号。相比只用热敏电阻的方案,数字传感器的好处是无需校准,直接输出物理量。DS18B20量程-55℃到+125℃,精度0.5℃,足够覆盖火灾预警阈值;BH1750FVI则通过I2C输出16位光照度,单位勒克斯,可以区分正常日光和火焰发光。

表格展示一下三路信号的处理路径:

传感器/模块输出类型接口单片机侧处理主要参数
DS18B20数字温度1-Wire跳过ROM后读9字节暂存器12位分辨率,转换时间750ms
BH1750FVI数字光强I2C发送0x10连续H分辨率模式测量范围1-65535 lx
烟雾传感器(模拟)模拟电压ADC0809通道查询通道0读取8位AD值浓度越高,输出电压越高

2.2 DS18B20的1-Wire时序与C51实现

DS18B20是单总线器件,所有命令和数据的读写都靠一根线。初始化时序必须是“主机拉低480us以上释放,然后等从机拉低60-240us”。很多初学者写的读温度程序不稳定,问题往往出在延时精度上——12MHz晶振下用for循环嵌套很容易偏。这里的关键是使用精确的延时函数,或者用定时器辅助。

下面这段代码是初始化函数,在文件列表中的DS18B20.c里能看到类似实现:

bit DS18B20_Init(void) { bit presence = 0; DQ = 1; delay_us(8); // 拉高释放总线 DQ = 0; delay_us(480); // 主机拉低480~960us DQ = 1; delay_us(60); // 释放后等待60us presence = DQ; // 读取从机存在脉冲 delay_us(410); // 剩余时序时间 return presence; }

注意这里的delay_us需要根据晶振频率调整。12MHz下,一个空循环约8个机器周期,delay_us(480)大约需要200个循环,如果直接写for(i=0;i<200;i++);,编译器优化等级变化会导致时序漂移。我一般把延时函数用#pragma O0关闭优化,或者写成独立的汇编延时文件。读取温度时还需要检查CRC,但这个项目里为了简化没有做,实测中只要线缆长度控制在20cm内,基本不会有误码。

2.3 BH1750FVI的光强读取

BH1750FVI支持I2C,地址为0100011(写),模块上通常在ADDR引脚悬空时是0x23。读取流程是先发送0x10进入连续高分辨率模式,等待180ms转换完成后,连续读两个字节合成16位结果。文件中的BH1750FVI.c里应该有I2C起始停止检测、发送应答等基础函数。

unsigned int BH1750_Read(void) { unsigned int tmp; I2C_Start(); I2C_SendByte(BH1750_ADDR_W); // 0x46 I2C_WaitAck(); I2C_SendByte(0x10); // 连续高分辨率模式 I2C_WaitAck(); I2C_Stop(); DelayMs(180); // 等待转换 I2C_Start(); I2C_SendByte(BH1750_ADDR_R); // 0x47 I2C_WaitAck(); tmp = I2C_RecvByte(ACK); // 高位 tmp <<= 8; tmp |= I2C_RecvByte(NACK); // 低位 I2C_Stop(); return tmp; }

这里的返回值需要除以1.2才是标准勒克斯值,因为高分辨率模式下分辨率为1lx,但测量值存在一个固定的1.2倍系数。有些人直接把这个值发给上位机,导致光强偏低。我在实测中看到,阳光直射下读数会在45000以上,室内荧光灯下大约300-500。

2.4 ADC0809与烟雾传感器的模拟量采集

烟雾传感器模块输出的是模拟电压,需要ADC采样。51单片机本身没有内置ADC,因此用ADC0809作为外部8路8位ADC。ADC0809通过三个地址线选择通道,本项目中烟雾信号接在IN0,所以地址为000。启动转换需要给ALE和START引脚一个正脉冲,转换结束后EOC引脚为高电平,OE使能后从输出总线读取数据。

unsigned char ADC0809_Read_Channel(unsigned char ch) { P2 = (P2 & 0xF8) | (ch & 0x07); // 选择通道IN0~IN7 ADC_START = 0; ADC_ALE = 1; delay_us(5); ADC_START = 1; ADC_ALE = 0; delay_us(5); ADC_START = 0; // 下降沿启动转换 while (!ADC_EOC); // 等待转换完成 ADC_OE = 1; // 允许输出 unsigned char val = ADC_OUT; // 读P1口 ADC_OE = 0; return val; }

注意ADC0809的基准电压VREF接5V时,8位采样的分辨率是19.6mV,对应烟雾浓度模块的灵敏度约200-5000ppm。实际调试时发现,EOC信号在12MHz晶振下通常需要100us左右才拉低,如果while循环卡住,十有八九是因为ADC的时钟引脚没有接对——ADC0809需要外部提供10kHz到1.2MHz的转换时钟,我一般直接用单片机的ALE引脚分频或者使用定时器输出方波,不能悬空。

3. 下位机C51代码实现:串口协议设计与报警状态机

3.1 主循环与采集节奏

main.c整体结构不复杂:初始化定时器、串口、传感器,然后进入循环。循环里每隔200ms采集一次温度、光强和烟雾AD值,根据阈值决定是否报警,再把数据打包通过串口发送。关键点在于避免阻塞:DS18B20的转换时间要750ms,BH1750也要180ms,如果串行等待,一轮采集会超过1秒,体验很差。常见做法是把传感器采集放到定时器中断的时隙里,或者在主循环中采用非阻塞状态机。

下面是一个可用的主循环伪代码,实际源码中main.c的实现思路类似:

void main(void) { Timer0_Init(); UART_Init(9600); while (1) { if (flag_200ms) { flag_200ms = 0; temp = DS18B20_ReadTemp(); light = BH1750_Read(); smoke = ADC0809_Read_Channel(0); process_alarm(temp, smoke); send_frame(temp, light, smoke); } } }

这里flag_200ms由定时器置位,避免了delay导致的程序卡死。定时器0配置为工作方式1,16位定时器,12MHz晶振下每50ms溢出一次,中断里累加4次就是200ms。

3.2 串口数据帧格式设计

上位机要能准确区分三路数据,就需要一个固定协议。项目里uart.c和main.c共同完成帧打包。我看到的常用设计是:帧头0xAA 0x55,随后是数据类型和数据长度,最后是校验和。比如:

字节偏移内容说明
00xAA帧头1
10x55帧头2
20x03数据类型:0x03表示传感器数据
3数据长度有效载荷长度,这里为6
4-5温度有符号整数,高字节在后?实际上按小端发送
6-7光强无符号整数,0-65535
8烟雾8位AD值
9报警标志0正常,1预警,2报警
10CRC字节异或校验

串口发送函数在uart.c里,核心是逐字节发送,每个字节查询TI标志:

void UART_SendByte(unsigned char dat) { SBUF = dat; while (!TI); // 等待发送完成标志 TI = 0; } void Send_Frame(int temp, unsigned int light, unsigned char smoke, unsigned char alarm) { UART_SendByte(0xAA); UART_SendByte(0x55); UART_SendByte(0x03); UART_SendByte(6); UART_SendByte(temp & 0xFF); UART_SendByte((temp >> 8) & 0xFF); UART_SendByte(light & 0xFF); UART_SendByte((light >> 8) & 0xFF); UART_SendByte(smoke); UART_SendByte(alarm); UART_SendByte(calc_crc()); // 对前10字节异或 }

为啥不用printf?Keil C51的printf会把大量代码空间花在格式化输出上,而且20MHz以下跑printf容易丢字符。自己拼帧可以用更紧凑的格式,也方便LabVIEW端解析。另外注意,我给的帧格式和原始项目可能不完全一样,实际源码里串口中断服务函数(interrupt.c)可能已经实现了接收和转发逻辑,如果做修改,优先保证帧头校验和一致。

3.3 报警阈值与状态机

报警逻辑不能只用一个if判断温度大于50度。实测中,电烙铁靠近DS18B20时温度会瞬间冲高又回落,直接触发会频繁误报。这个项目里采用了两级阈值和抖动滤除:当温度超过60度或烟雾值超过150时进入预警状态,持续3个采样周期才确认报警;报警后必须有“复位”条件(比如温度回到40度以下且烟雾低于100)才能撤销。这种设计思路和工控里的延时去抖是一回事。

3.4 Keil5工程编译与烧录

源码里的实验二.uvproj是Keil5的工程文件,双击可以用Keil5打开。打开后先检查Target晶振频率是否设置成12MHz,否则延时函数偏差会导致传感器时序错乱。编译时选择生成HEX文件,在Options for Target -> Output中勾选Create HEX File。烧录用普中科技或STC的下载器,STC的芯片需要选对外部晶振和串口号。

常见错误是“Target not created”或者DS18B20.C(25): warning C277: redefinition,通常是文件被重复加入工程。原始工程里同时存在.c和.zbak备份文件,右键排除.zbak即可。我一般会把所有.zbak文件从工程中移除,防止编译器把它们当源文件编译。

4. LabVIEW上位机:VISA串口通信与波形显示

4.1 VI文件结构和前面板布局

LabVIEW端用到了上位机.vi和若干数据文本(温度数据.txt、烟雾数据.txt、光强数据.txt)。VI是虚拟仪器文件,里面分为前面板和程序框图。打开上位机.vi后,前面板应该能看到温度实时值、烟雾浓度、光照强度三个数值显示控件,以及三个波形图。程序框图通常由VISA配置串口、VISA读取、字符串解析、数组显示这几个环节组成。

4.2 串口参数需要与下位机一致

下位机用的是9600波特率、8位数据位、1位停止位、无校验。LabVIEW的VISA配置串口节点里的设置必须一模一样,否则读出乱码。对于USB转串口设备,VISA资源名一般是COM3或COM4,可以在设备管理器中确认。这个位置经常有人写错,导致“VISA读取”超时。

4.3 解析串口数据帧

LabVIEW中读取串口是一个“VISA读取”节点,指定一次读取的字节数,比如14字节一帧。因为下位机连续发送,串口缓冲里可能有多帧,解析时关键是查找帧头0xAA 0x55。我一般会先读到字符串,然后用“字符串搜索”定位帧头,再截取后续字节。

由于LabVIEW是图形化编程,这里给出等价的Python代码便于理解逻辑:

import serial ser = serial.Serial('COM4', 9600, timeout=1) while True: data = ser.read(14) # 读一帧 if len(data) < 14 or data[0] != 0xAA or data[1] != 0x55: continue checksum = 0 for b in data[:10]: checksum ^= b if checksum != data[10]: continue temp = int.from_bytes(data[4:6], 'little', signed=True) / 16.0 light = int.from_bytes(data[6:8], 'little') smoke = data[8] alarm = data[9] print(f"温度: {temp:.2f}℃ 光强: {light}lx 烟雾: {smoke} 报警: {alarm}")

LabVIEW框图中对应的节点是“字符串至字节数组转换”,再配合“索引数组”取值。一个小技巧:用“分数/指数字符串至数值转换”可以直接把温度的高位字节转换成数值,但不如直接做位运算直观。在LabVIEW里可以用“数值转换”和“缩放”节点实现相同的效果。

4.4 波形显示与数据记录

波形控件可以接收三个一维数组,分别代表温度、光强和烟雾历史值。每次循环把新解析出的数据追加到数组尾部。需要注意的是,VI默认只在内存中保留数组,关闭程序后丢失。所以这个项目里额外把数据写入了txt文件(比如温度数据.txt),用“写入文本文件”节点追加。另一种做法是使用TDMS文件格式,更高效,但对初学者不友好。

表格对比三种记录方式:

记录方式优点缺点适合场景
文本txt简单直观,可以用Excel打开数据量大时写入慢毕设演示,几百条数据
二进制文件写入快,体积小不可直接阅读长时间连续监测
TDMSNI官方格式,带通道属性需要额外了解LabVIEW深度用户

4.5 上位机无法显示波形的排错顺序

如果烧录后LabVIEW前面板没有曲线,按这个顺序检查:第一,用串口助手直接查看单片机是否在发数据,没有就查下位机串口初始化和中断;第二,能收到但乱码,检查波特率是否一致;第三,能收到结构化数据但解析不到波形,检查帧头匹配和数组切片位置。这个项目里LabVIEW的读取字节数如果写成38400以上,很容易因为缓冲不足而漏帧,建议用“属性节点”设置串口缓冲大小到4096字节。

5. 高分答辩的验证方法:用一根杜邦线制造烟雾报警,验证完整链路

5.1 制作一个可控的烟雾信号源

答辩现场不能真的点燃烟雾,所以需要一个可重复的验证方法。用ADC0809的IN0通道前接一个10K电位器,分压电压在0-5V之间调节。当电位器分压超过2V时,ADC采样值大约超过100,可以模拟烟雾浓度升高的过程。配合电吹风或热风枪对DS18B20吹风,温度会平滑上升,这样能在几分钟内演示三级报警状态。

5.2 使用LabVIEW的波形记录验证传感器采样一致性

打开上位机中的温度波形,正常状态下温度曲线应该稳定在25-30℃之间波动不超过1℃。光强波形在室内环境应当是平直的,变化幅度小于10 lx。如果有规律性尖峰,说明串口丢帧导致数据错位;如果是直流偏移,说明传感器从机地址或转换系数错误。烟雾波形在电位器旋动时会呈现阶梯状变化,这正好检验ADC0809的参考电压是否稳定。

5.3 用Keil的软件仿真验证定时器溢出

如果手头没有开发板,可以用Keil的Simulator加上逻辑分析仪窗口(在Debug模式下添加变量)观察DS18B20的时序。设置好断点后再使用“View -> Symbol Window”找到DS18B20_Init函数,单步执行并观察DQ引脚电平翻转间隔。这个技巧在答辩时如果演示出来,会很有说服力。

5.4 一个容易被忽视的隐患:晶振频率与LabVIEW里的时间戳

如果单片机的串口波特率用内部RC振荡器,在低温或高温环境下会产生偏差。比如用STC89C52的内部时钟,波特率可能在9600基础上偏移±2%,一帧超过14字节就会偶发错位。每次格式化数据时,时间戳不是必需的,但如果你要统计报警时间,就需要在LabVIEW中调用“获取日期/时间”节点在收帧时打上时间戳,而不要依赖单片机的计时。

最后,如果修改了报警阈值,记得同步修改说明文档中记录的阈值,便于答辩时讲解。这样可以确保评审老师看到的数据与你叙述的完全一致。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询