基于 51 单片机的 DS18B20 温度测量与 LCD1602 显示系统详
一、引言
在嵌入式开发领域,利用单片机结合各类传感器实现数据采集与显示是常见且实用的应用场景。本文将深入剖析一个基于 51 单片机,使用 DS18B20 温度传感器采集环境温度,并通过 LCD1602 液晶显示屏实时展示温度值的系统设计,完整代码已附上供大家参考学习。
二、硬件选型与连接
- 单片机:选用 51 系列单片机作为核心控制单元,其具有丰富的外设资源、易于上手编程等优点,为整个系统提供稳定的运算与控制能力。
- 温度传感器 DS18B20:这是一款高精度的数字温度传感器,单总线通信方式使得与单片机连接极为简洁,仅需一个数据引脚(文中连接至 P1^4),大大简化了硬件布线复杂度,能快速、精准地测量环境温度。
- LCD1602 液晶显示屏:用于直观呈现温度数据及欢迎信息等内容。它与单片机通过特定引脚相连,其中 RS(P2^0)用于控制读写操作是命令还是数据,RW(P2^1)设定读写方向,EN(P2^2)作为使能信号,数据传输则利用 P0 口。
- 具体仿真图如下:
三、软件编程剖析
-
首先是一系列的预处理指令:
#include<reg52.h>
引入 51 单片机的头文件,让编译器获取单片机特殊功能寄存器等相关定义,方便后续编程操作。- 通过
#define
宏定义了uchar
和uint
,分别代表无符号字符型和无符号整型,增强代码可读性,使数据类型声明更为简洁直观。 -
引脚定义与全局变量声明:
- 清晰地将与 LCD1602 和 DS18B20 交互的引脚进行宏定义,如
sbit RS=P2^0;
等,方便在程序中对这些引脚进行控制操作。同时声明了一系列用于存储温度值、显示分量及标志位的全局变量,像temperature
存放原始温度数据,temperatureflag
指示温度正负,以及用于拆分温度显示的wendu
、wendubai
等变量。 -
关键函数实现:
- 延时函数
Delay(uint z)
:采用双重循环结构,通过传入不同参数z
实现灵活的延时时间控制,在与硬件设备如 LCD1602 交互时,为确保数据或命令被稳定接收与执行提供必要的等待时间。 - LCD1602 操作函数:
WriteCommand(uchar command)
和WriteData(uchar information)
函数分别负责向 LCD1602 写入命令和数据。在写入过程中,严格按照其时序要求,先设置 RS、RW 引脚状态,将数据或命令送上 P0 口,再通过拉高、拉低 EN 引脚触发数据传输,中间穿插延时确保操作完成。LCD1602Init()
函数完成对 LCD1602 的初始化配置,包括设置显示模式(162 行,57 点阵,8 位数据接口)、初始状态(关闭显示、清屏、光标设置等),为后续正常显示奠定基础。LCD1602Display()
函数用于在屏幕上显示欢迎信息,精准设置 DDRAM 地址,将预先定义的欢迎字符串逐字符写入并配合延时保证显示效果。- DS18B20 相关函数:
DS18B20_Delay(uint i)
是针对 DS18B20 通信的简易延时函数,满足其时序中的时间等待需求。DS18B20Init()
负责传感器初始化,通过对 DQ 引脚的拉高、拉低操作模拟初始化序列,最后读取存在脉冲判断初始化是否成功并返回结果,这是后续温度读取的前置条件。DS18B20_WriteOneByte(uchar j)
和DS18B20_ReadOneByte()
实现向传感器写入一个字节数据以及读取一个字节数据的功能,写操作时按位依次传输,读操作时巧妙利用移位和判断 DQ 引脚电平来获取数据。DS18B20_ReadTemperature()
整合前面函数,完成从初始化传感器、发送温度转换命令、再次初始化读取温度数据,并对数据进行处理,包括判断正负、转换为实际摄氏度并乘以 10 保留一位小数,最终返回处理后的温度值。- 温度处理与显示函数:
TemperatureFenJie()
依据读取到的温度值及正负标志位,将温度拆分为百位、十位、个位、小数部分以及设置相应符号位,为精准显示做准备。TemperatureDisplay()
根据设定好的 DDRAM 地址,将拆分后的温度各分量以 ASCII 码形式依次写入 LCD1602,完成温度直观展示。-
主函数
main()
:作为程序入口,按部就班地初始化 LCD1602 和 DS18B20,先显示欢迎信息,短暂延时后清屏,随后进入无限循环。在循环内,不断更新并显示温度信息,即先在第一行写入温度提示文本,接着分解并在第二行显示最新温度值,如此往复实现实时温度监测显示功能。 -
完整代码如下:
#include<reg52.h> // 包含单片机52系列头文件 #define uchar unsigned char // 定义无符号字符类型 #define uint unsigned int // 定义无符号整数类型 sbit RS=P2^0; // 定义LCD1602的RS控制引脚到P2.0 sbit RW=P2^1; // 定义LCD1602的RW控制引脚到P2.1 sbit EN=P2^2; // 定义LCD1602的EN使能引脚到P2.2 sbit DQ=P1^4; // 定义DS18B20的数据引脚到P1.4 uint temperature,t; // 定义温度变量和延时计数器 uint temperatureflag; // 定义标志位用于指示温度正负 uint wendu,wendubai,wendushi,wenduge,xiaoshudian,xiaoshu,fuhaowei; // 定义显示温度的各个分量及符号位 // 定义在LCD上显示的欢迎信息字符串 uchar code table[]="Welcome To"; uchar code table1[]="Our System !"; uchar code table2[]="temperature is:"; // 延时函数,通过双重循环实现一定时间的延时 void Delay(uint z){ uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } // 写命令给LCD1602 void WriteCommand(uchar command){ RS=0; // 设置为写入命令模式 RW=0; // 设置为写入方向 P0=command; // 将命令放到数据总线P0上 Delay(5); // 等待一段时间确保命令被接收 EN=1; // 拉高使能信号以执行命令 Delay(5); // 等待一段时间确保命令被执行 EN=0; // 拉低使能信号完成命令周期 } // 写数据给LCD1602 void WriteData(uchar information){ RS=1; // 设置为写入数据模式 RW=0; // 设置为写入方向 P0=information; // 将数据放到数据总线P0上 Delay(5); // 等待一段时间确保数据被接收 EN=1; // 拉高使能信号以执行数据写入 Delay(5); // 等待一段时间确保数据被正确写入 EN=0; // 拉低使能信号完成数据写入周期 } // 初始化LCD1602配置 void LCD1602Init(){ WriteCommand(0x38); // 设置16*2行,5*7点阵,8位数据接口 WriteCommand(0x08); // 关闭显示 WriteCommand(0x01); // 清屏 WriteCommand(0x06); // 光标右移,不移动显示内容 WriteCommand(0x0c); // 显示开,光标关 } // 在LCD1602上显示欢迎信息 void LCD1602Display(){ uchar j; WriteCommand(0x80+0x00); // 设置DDRAM地址为第一行起始位置 for(j=0;j<10;j++){ WriteData(table[j]); // 写入第一个欢迎信息字符串 Delay(5); // 确保每个字符都被正确显示 } WriteCommand(0x80+0x40); // 设置DDRAM地址为第二行起始位置 for(j=0;j<11;j++){ WriteData(table1[j]); // 写入第二个欢迎信息字符串 Delay(5); // 确保每个字符都被正确显示 } } // DS18B20延时函数 void DS18B20_Delay(uint i){ while(i--); // 简单的循环延时 } // DS18B20初始化函数,返回初始化是否成功 bit DS18B20Init(){ uchar x=0; DQ = 1; // 拉高DQ线开始初始化序列 DS18B20_Delay(8); // 等待稳定 DQ = 0; // 拉低DQ线发出复位脉冲 DS18B20_Delay(80); // 复位脉冲持续时间 DQ = 1; // 结束复位脉冲 DS18B20_Delay(4); // 等待响应 x=DQ; // 读取存在脉冲,判断初始化是否成功 DS18B20_Delay(20); // 稳定时间 return x; // 返回初始化结果 } // 向DS18B20写入一个字节的数据 void DS18B20_WriteOneByte(uchar j){ uchar i=0; for (i=8; i>0; i--){ // 循环8次,写入8位数据 DQ = 0; // 拉低DQ线准备写入一位数据 DQ = j&0x01; // 写入最低位数据 DS18B20_Delay(5); // 等待写入完成 DQ = 1; // 结束写入一位数据 j>>=1; // 右移数据准备写入下一位 } } // 从DS18B20读取一个字节的数据 uchar DS18B20_ReadOneByte(){ uchar i=0; uchar j = 0; for (i=8;i>0;i--){ // 循环8次,读取8位数据 DQ = 0; // 拉低DQ线准备读取一位数据 j>>=1; // 准备存储读取的一位数据 DQ = 1; // 拉高DQ线允许传感器发送数据 if(DQ) // 如果DQ是高电平 j|=0x80; // 存储这一位数据 DS18B20_Delay(4); // 等待读取完成 } return j; // 返回读取到的数据 } // 读取DS18B20温度值 uint DS18B20_ReadTemperature(){ uchar L=0; uchar H=0; DS18B20Init(); // 初始化DS18B20 DS18B20_WriteOneByte(0xcc); // 跳过ROM操作,直接与器件通信 DS18B20_WriteOneByte(0x44); // 发送温度转换命令 DS18B20Init(); // 再次初始化以确保通信正确 DS18B20_WriteOneByte(0xcc); // 跳过ROM操作 DS18B20_WriteOneByte(0xbe); // 发送读寄存器命令 L=DS18B20_ReadOneByte(); // 读取低字节温度数据 H=DS18B20_ReadOneByte(); // 读取高字节温度数据 temperature = H; // 高字节左移8位后与低字节合并成完整温度值 temperature = temperature << 8; temperature = temperature | L; if(temperature>0x0fff){ // 判断温度是否为负值 temperature = ~temperature+2; // 如果是负值,则取补码转换为正值 temperatureflag=1; // 设置温度标志位表示负值 } else{ temperatureflag=0; // 温度为正值则设置标志位为0 } temperature = temperature*0.0625*10; // 将温度值转换为摄氏度并乘以10保留小数点后一位 return temperature; } // 分解温度值为个、十、百位以及符号 void TemperatureFenJie(){ wendu=DS18B20_ReadTemperature(); // 读取温度值 if(temperatureflag==1){ // 如果温度为负值 fuhaowei=0x2d; // 符号位设置为'-'(ASCII) wendubai=wendu/1000; // 分解温度值为百位 wendushi=wendu%1000/100; // 分解温度值为十位 wenduge=wendu%100/10; // 分解温度值为个位 xiaoshudian=0x2e; // 设置小数点(ASCII) xiaoshu=wendu%10; // 分解温度值为小数部分 } else{ // 如果温度为正值 fuhaowei=0x2b; // 符号位设置为'+'(ASCII) if(wendu==0){ // 特殊处理零的情况 fuhaowei=0x20; // 符号位设置为空格(ASCII) } wendubai=wendu/1000; // 分解温度值为百位 wendushi=wendu%1000/100; // 分解温度值为十位 wenduge=wendu%100/10; // 分解温度值为个位 xiaoshudian=0x2e; // 设置小数点(ASCII) xiaoshu=wendu%10; // 分解温度值为小数部分 } } // 在LCD1602上显示分解后的温度值 void TemperatureDisplay(){ WriteCommand(0x80+0x40); // 设置DDRAM地址为第二行起始位置 WriteData(fuhaowei); // 写入温度符号 WriteData(0x30+wendubai); // 写入百位数字(加上0x30转换为ASCII码) WriteData(0x30+wendushi); // 写入十位数字 WriteData(0x30+wenduge); // 写入个位数字 WriteData(xiaoshudian); // 写入小数点 WriteData(0x30+xiaoshu); // 写入小数部分 WriteData(0xdf); // 写入特殊字符'C'前的度符号 WriteData(0x43); // 写入'C' } // 主函数 void main(){ uchar num; LCD1602Init(); // 初始化LCD1602 LCD1602Display(); // 显示欢迎信息 Delay(500); // 短暂延时 WriteCommand(0x01); // 清屏 DS18B20Init(); // 初始化DS18B20 while(1){ // 无限循环 WriteCommand(0x80+0x00); // 设置DDRAM地址为第一行起始位置 for(num=0;num<15;num++){ // 循环写入温度提示信息 WriteData(table2[num]); Delay(5); // 确保每个字符都被正确显示 } TemperatureFenJie(); // 分解当前温度值 TemperatureDisplay(); // 显示温度值 } }
四、系统运行流程总结
系统启动后,单片机首先初始化 LCD1602 液晶显示屏,展示欢迎界面给用户,稍作停顿后清屏。紧接着初始化 DS18B20 温度传感器,之后便进入持续工作状态。在主循环中,不停地向 LCD1602 写入温度提示信息,同时实时读取并处理 DS18B20 的温度数据,将温度值拆解后展示在屏幕上,让用户能时刻了解当前环境温度状况。
五、拓展与优化思考
- 精度提升:目前温度值乘以 10 保留一位小数显示,若需更高精度,可调整转换算法,充分利用 DS18B20 更高分辨率模式。
- 功能扩展:可添加按键模块,实现温度阈值设置,当环境温度超出设定范围时触发报警功能,让系统更加智能实用。
通过对该系统的深入学习,相信大家能对 51 单片机与常见传感器、显示屏的结合应用有更扎实的掌握,为进一步的嵌入式开发项目积累宝贵经验。
作者:嵌入式lower