LCD1602 液晶显示实验

        来介绍一种非常简单且常用的显示装置——LCD1602 液晶显示器,使用它可以显示更多的字符数字。这将有助于我们清晰的观察程序的运行结果,更加方便我们的后续调试和开发。

        我们的开发板上集成了一个 LCD1602 液晶显示器接口,将LCD1602 液晶对应插入即可。

        本章要实现的功能是:系统运行时,在 LCD1602 液晶上显示字符信息。

        本章节可分为如下几部分内容:

        一、 LCD1602 介绍

        二、 硬件设计

        三、 软件设计

        四、 实验现象

 保姆级烧录教程

 一些有趣的LCD1602显示实验

点击在线浏览本文档,支持在线评论和互动。

本实验所有文档资料下载(蓝奏云)密码:9drm

本实验所有教学视频及PPT资源下载(百度云)

一、LCD1602介绍

1.1 LCD1602简介和引脚定义

        LCD1602 液晶也叫 LCD1602 字符型液晶,它能显示 2 行字符信息,每行又能显示 16 个字符。它是一种专门用来显示字母、数字、符号的点阵型液晶模块。它是由若干个 5x7 或者 5x10的点阵字符位组成,每个点阵字符位都可以用显示一个字符, 每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用,正因为如此,它不能很好的显示图片。其实物图如下所示:

图1
图1:LCD1602液晶实物图

        大家手上拿到的 LCD1602 外观可能和图1不一样,这是由于不同厂家设计所致,但使用方法是一样的。在上图可以看到有 16 个管脚孔,从左至右管脚编号是 1-16,其功能定义如下所示:

        1 脚:VSS,接地。

        2 脚:VDD,电源正极(4.5~5.5V都可)。

        3 脚:VO,对比度调节。液晶显示偏压信号,用于调整 LCD1602 的显示对比度,一般会外接电位器用以调整偏压信号,注意此脚电压为 0 时可以得到最强的对比度。

        4 脚:RS,数据/指令选择端,1为数据,0为指令。当此脚为高电平时,可以对 1602 进行数据字节的传输操作,而为低电平时,则是进行命令字节的传输操作。命令字节,即是用来对 LCD1602 的一些工作方式作设置的字节;数据字节,即使用以在 1602 上显示的字节。值得一提的是,LCD1602 的数据是 8 位的。

        5 脚:R/W,读/写选择端,1为读,0为写。当此脚为高电平可对 LCD1602 进行读数据操作, 反之进行写数据操作。

        6 脚:E,使能信号,1为数据有效,下降沿执行命令。其实是 LCD1602 的数据控制时钟信号,利用该信号的上升沿实现对 LCD1602 的数据传输。

        7~14 脚:数据输入/输出。8 位并行数据口,而 51 单片机一组 IO 也是 8 位,使得对 LCD1602 的数据读写大为方便。

        15 脚:A,背光灯电源正极。

        16 脚:K,背光灯电源负极。

        下面给出各引脚功能速览表和其内部原理图:

图2:各引脚功能速览表

图3:LCD1602液晶原理图

1.2 LCD1602内部结构

图4:内部结构框图

      DDRAM(Data Display Random Access Memory ):数据显示存储器。

可读取,可写入。如图5所示,可显示位置为40*2,共计80个字符。即是第一行 00H~27H,和第二行的40H~67H

图5:DDRAM(数据显示区)

     CGRAM(Character Generation Random Access Memory):用户字符发生器。

可以自定义用户的字模库(最多八个字符)。

      CGROM(Character Generation Read Only Memory):字符发生器。

出厂默认设置的字模库,与标准ASCII码基本一致。并在此基础上加入片假名和部分特殊符号的支持。具体支持范围如图六所示:

图6:CGRAM+CGROM(字模库)

        读取方式也很简单,例如想获得"A"在字模库中的位置,由图6可知,其二进制位置为 0100 0000B,转换为16进制是 40H

        屏幕显示:显示字符数据,16*2。

图7:屏幕显示区

         由图5和图7对比可知:不是所有的地址都可以直接用来显示字符数据,只有第一行中的 00-0F,第二行中的 40-4F 才能显示,其他地址只能用于存储。

        要显示字符时要先输入显示字符地址,也就是告诉模块在哪里显示字符,例如第二行第一个字符的地址是 40H,那么是否直接写入 40H 就可以将光标定位在第二行第一个字符的位置呢?这样不行,因为写入显示地址时要求最高位 D7 恒定为高电平 1 所以实际写入的数据应该是 01000000B(40H)+10000000B(80H)=11000000B(C0H)

        在 1602 中我们就用前 16 个就行了。第二行也一样用前 16 个地址。

      AC(Aid Carry):辅助进位,即光标位置。

 指向下一位字符的位置,并在显示完毕后,自动加一。

1.3 LCD1602常用指令

图8:LCD1602液晶显示指令集(标红为常用命令)

      在使用 LCD1602 时,我们要掌握一些常用指令,这些指令对于 LCD1602 初始化是必须的。

(1)清屏指令

图9:清屏指令

功能:

<1> 清除液晶显示器,即将 DDRAM 的内容全部填入"空白"的 ASCII 码 20H;

<2> 光标归位,即将光标撤回液晶显示屏的左上方;

<3> 将地址计数器(AC)的值设为 0。

也就是说 清屏指令是0x01

(2)模式设置指令

图10:模式设置指令

功能:

设定每次写入 1 位数据后光标的移位方向,并且设定每次写入的一个字符是否移动。

I/D:0=写入新数据后光标左移 1=写入新数据后光标右移

S:0=写入新数据后显示屏不移动 1=写入新数据后显示屏整体右移1 个字符

也就是说 写入新数据后光标右移,显示屏不移动的命令是0x06

(3)显示开关控制指令

图11:显示开关控制
图11:显示开关控制

功能:

控制显示器开/关、光标显示/关闭以及光标是否闪烁。

D:0=显示功能关 1=显示功能开

C:0=无光标 1=有光标

B:0=光标闪烁 1=光标不闪烁

也就是说 显示开,光标关,闪烁关的指令是 0x0c

(4)功能设定指令

图12:功能设定指令

功能:

设定数据总线位数、显示的行数及字型。

DL:0=数据总线为 4 位 1=数据总线为 8 位

N:0=显示 1 行 1=显示 2 行

F:0=5×7 点阵/每字符 1=5×10 点阵/每字符

也就是说 数据总线8位,显示2行,5*7点阵/字符的命令是0x38

1.4 LCD1602的使用

        要使用 LCD1602,首先需要对其初始化,即通过写入一些特定的指令实现。然后选择要在 LCD1602 的哪个位置显示,并将所要显示的数据发送到 LCD 的 DDRAM。使用 LCD1602 通常都是用于写数据进去,很少使用读功能。LCD1602 操作步骤如下所示:

(1)初始化

发送指令0x38 //八位数据接口,两行显示,5*7点阵

发送指令0x0C //显示开,光标关,闪烁关

发送指令0x06 //数据读写操作后,光标自动加一,画面不动

发送指令0x01 //清屏

(2)写命令(RS=0),设置显示坐标

(3)写数据(RS=1)

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

        下面给出LCD1602 的时序图和时序参数图:

图13:LCD1602 的时序图和时序参数图

        在此,不需要读出它的数据的状态或者数据本身。所以只需要看图13写时序:

  1. 当要写指令字,设置 LCD1602 的工作方式时:需要把 RS 置为低电平,RW 置为低电平,然后将数据送到数据口 D0~D7,最后 E 引脚一个高脉冲将数据写入。也就是:

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;       //写命令置0
	LCD_RW=0;       //1为读,0为写
	P0=Command;    //把命令通过P0口传送(D0~D7连到P0口的情况下)
	LCD_EN=1;      //拉高使能引脚
	LCD_Delay();  //1ms 延时
	LCD_EN=0;     //拉低使能引脚,数据写入完毕
	LCD_Delay();  //1ms 延时
}

2.当要写入数据字,在 1602 上实现显示时:需要把 RS 置为高电平,RW 置为低电平,然后将数据送到数据口 D0~D7,最后 E 引脚一个高脉冲将数据写入。也就是:

/**
* @brief  LCD1602写数据
* @param  Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
    LCD_RS=1;    //写数据置1
    LCD_RW=0;    //1为读,0为写
    P0=Data;     //把数据通过P0口传送(D0~D7连到P0口的情况下)
    LCD_EN=1;    //拉高使能引脚
    LCD_Delay();  //1ms 延时
    LCD_EN=0;     //拉低使能引脚,数据写入完毕
    LCD_Delay(); //1ms 延时
}

        写指令和写数据,差别仅仅在于 RS 的电平不一样而已。

        当要写命令字节的时候,时间由左往右,RS 变为低电平,R/W 变为低电平, 注意看是 RS 的状态先变化完成。然后这时,DB0~DB7 上数据进入有效阶段,接着 E 引脚有一个整脉冲的跳变,接着要维持时间最小值为tpw=400ns 的 E 脉冲宽度。然后E 引脚负跳变,RS 电平变化,R/W 电平变化。这样便是一个完整的LCD1602写命令的时序。

 从图13可以看到,以上给的时间参数全部是 ns 级别的,而 51 单片机的机器周期是 1us,指令周期是 2-4 个机器周期,所以理论上即便在程序里不加延时程序,也可以很好的配合 LCD1602 的时序要求了。但实际上在使能E拉高时要延时1ms左右,不然写入数据会失败,这个要多加注意。

二、 硬件设计

        本实验使用到硬件资源如下:

        (1)LCD1602 液晶

        开发板上集成了一个 LCD1602 液晶接口,下面来看下开发板上 LCD1602 液晶接口电路,如下图所示:

图14:普中A2开发板LCD1602液晶接口电路

        从上图中可知,LCD1602 的 8 位数据口 DB0-DB7 与单片机的 P0.0-P0.7 管脚连接,LCD1602 的 RS、RW、E 脚与单片机的 P2.6、P2.5、P2.7 管脚连接。RJ1 是一个电位器,用来调节 LCD1602 对比度即显示亮度。

注意:这里原理图是使用的 8 位 LCD1602 接口设计,是可以兼容 4 位 LCD1602 的。对于非标准接口的 LCD1602,我们也会通过转接板将其转接为对应开发板接口的。对于 4 位 LCD1602 在传输数据的时候需要将 8 位的数据截成两段,先发送高四位,在发送低四位。其它引脚操作方法不变。

三、 软件设计

        本章所要实现的功能是:在 LCD1602 液晶上显示字符信息。程序框架如下:

        (1)编写 LCD1602 显示函数

        (2)编写主函数

        本章软件的重点是如何对 LCD1602 进行写命令和数据。

        3.1 LCD1602驱动函数

        在第一步 1.4 LCD1602的使用 我们已经了解到了LCD1602的初始化,写命令及写数据操作,现在我们给出写命令和写数据时,11.0592MHz 调用可延时1ms的代码(此代码可由辅助软件计算写出):

//函数定义:
/**
  * @brief  LCD1602延时函数,11.0592MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

        3.2 LCD1602显示函数

        (1) LCD1602设置光标位置

        因为写入显示地址时要求最高位 D7 恒定为高电平 1,所以命令的最高位要全赋为1,而显示位是从0开始的,所以第一行的写入命令是 0x80| (lie-1)

        而LCD1602的存储是每行40个字符,所以第二行第一位是在第一行的基础上加0x40

/**
  * @brief  LCD1602设置光标位置
  * @param  hang 行位置,范围:1~2
  * @param  lie 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char hang,unsigned char lie) //设置光标位置
{
	if(hang==1)
	{
		LCD_WriteCommand(0x80|(lie-1)); //因为写入显示地址时要求最高位 D7 恒定为高电平 1
	}
	else if(hang==2)
	{
		LCD_WriteCommand(0x80|(lie-1+0x40));  //LCD的存储是每行40,所以第二行第一位是在第一行的基础上加0x40
	}
}

        (2) 在LCD1602指定位置上显示一个字符

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  hang 行位置,范围:1~2
  * @param  lie 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char hang,unsigned char lie,char Char)
{
	LCD_SetCursor(hang,lie);
	LCD_WriteData(Char);
}

        (3) 在LCD1602指定位置开始显示所给字符串

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  hang 起始行位置,范围:1~2
  * @param  lie 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char hang,unsigned char lie,char *String)
{
	unsigned char i;
	LCD_SetCursor(hang,lie);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

        (4) 在LCD1602指定位置开始显示所给数字

789 这是最初的数

把各个数分开之后的数

i指数字的个数

i 和标红文字的关系

789/100%10

7

3

10^(3-1)

789/10%10

8

2

10^(2-1)

789/1%10

9

1

10^(1-1)

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}
/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  hang 起始行位置,范围:1~2
  * @param  lie 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char hang,unsigned char lie,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(hang,lie);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');//加'0'是因为要转换成数字格式,不然会显示ASCII码
	}
}

        3.3 主程序

        将上述代码整合在main.c(主程序)里面,得到如下完整代码:

#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;

void LCD_Delay() //11.0592MHz调用可延时1ms
{
	unsigned char i, j;
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}


void LCD_WriteCommand(unsigned char Command) //LCD1602写命令
{
	LCD_RS=0;       //写命令置0
	LCD_RW=0;       //1为读,0为写
	P0=Command;    //把命令通过P0口传送(D0~D7连到P0口的情况下)
	LCD_EN=1;      //拉高使能引脚
	LCD_Delay();  //1ms 延时
	LCD_EN=0;     //拉低使能引脚,数据写入完毕
	LCD_Delay();  //1ms 延时
}
void LCD_Init()  //LCD1602初始化函数
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

void LCD_WriteData(unsigned char Data) //LCD1602写数据
{
    LCD_RS=1;    //写数据置1
    LCD_RW=0;    //1为读,0为写
    P0=Data;     //把数据通过P0口传送(D0~D7连到P0口的情况下)
    LCD_EN=1;    //拉高使能引脚
    LCD_Delay();  //1ms 延时
    LCD_EN=0;     //拉低使能引脚,数据写入完毕
    LCD_Delay(); //1ms 延时
}

void LCD_SetCursor(unsigned char hang,unsigned char lie) //设置光标位置
{
	if(hang==1)
	{
		LCD_WriteCommand(0x80|(lie-1)); //因为写入显示地址时要求最高位 D7 恒定为高电平 1
	}
	else if(hang==2)
	{
		LCD_WriteCommand(0x80|(lie-1+0x40));  //LCD的存储是每行40,所以第二行第一位是在第一行的基础上加0x40
	}
}
void LCD_ShowChar(unsigned char hang,unsigned char lie,char Char)//指定位置显示一个字符
{
	LCD_SetCursor(hang,lie);
	LCD_WriteData(Char);
}

void LCD_ShowString(unsigned char hang,unsigned char lie,char *String)//指定位置开始显示所给字符串
{
	unsigned char i;
	LCD_SetCursor(hang,lie);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

int LCD_Pow(int X,int Y)//返回值=X的Y次方
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}
//在LCD1602指定位置开始显示所给数字
void LCD_ShowNum(unsigned char hang,unsigned char lie,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(hang,lie);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');//加'0'是因为要转换成数字格式,不然会显示ASCII码
	}
}

void main()
{
	LCD_Init();						//LCD初始化
	LCD_ShowChar(1,1,'A');			//在1行1列显示字符A
	LCD_ShowString(1,3,"Hello");	//在1行3列显示字符串Hello
    LCD_ShowNum(1,9,66,2);			//在1行9列显示数字66,长度为2
    LCD_ShowString(2,1,"ZhangFuGong");	//在2行1列显示字符串ZhangFuGong
    LCD_ShowChar(2,13,0xDF);		//在2行13列显示编码为0xDF的字符
	LCD_ShowChar(2,14,'C');			//在2行14列显示字符C
    
	while(1)
	{
	}
}

四、 实验现象

        使用 USB 线将开发板和电脑连接成功后(电脑能识别开发板上 CH340 串口), 把编译后产生的.hex 文件烧入到芯片内,实现现象如下:在 LCD1602 液晶上显示字符信息。

图15:实验现象

注意:LCD1602 液晶要正确插入到 LCD1602 接口 J2 位置,插反或者差错都会导致显示不正常。如果出现显示看不清的情况,可调节板子 LCD1602 接口下的 RJ1 电位器。如果 LCD1602 无法显示,可重启电源或复位。

图16:引脚和板子匹配示意图

保姆级烧录教程

        本章节实验到此已经完毕,考虑到大家的基础差异较大,刚接触51单片机的小伙伴可能对烧录过程有些困惑,作者便在篇末给大家提供保姆级烧录教程,帮助大家快速掌握单片机程序烧录技巧,希望对大家有所帮助!

本教程所需软件下载(win系统)

        1. 开发板准备工作(以普中A2开发板为例)

        (1)检查单片机芯片型号是否为 STC89C52RC

图17:普中A2开发板单片机芯片

        (2)单片机与计算机建立通信

图18:用micro-usb线一端连接单片机

图19:用micro-usb线另一端连接计算机USB口

        2. 烧录软件的准备操作

        (1)下载并打开上述链接所提供的软件。

        下载完成后。鼠标右键软件图标,选择 以管理员身份运行 ,在弹出的对话框中选择 ,软件开始运行。(没有管理员选项的直接鼠标左键双击运行也可)。

图20:鼠标右键图标界面

        (2)芯片型号选择

        如果芯片型号与图17一致,直接按照图示箭头依次选择即可。不同的话则依据各自型号选择相应系列。

图21:芯片型号选择

        (3)检查串口

        检查串口是否有红框圈中的 USB-SERIAL CH340 字样(后面的COM口不用管,每台电脑都可能不一致)。有的话证明你的电脑已经装了CH340驱动;没有的话也没关系,下载下面链接的驱动安装软件,双击运行,按照提示操作即可。CH340驱动软件下载(win系统)

图22:电脑识别开发板上 CH340 串口

        (4)打开程序文件

        按照图示操作,找到已经编译生成的.hex文件,单击选中后点击打开。

图23:打开程序文件

      

图24:找到.hex文件并打开

 3. 烧录程序

        (1)点击 下载/编程 按钮

    点击 下载/编程 按钮,看到软件右侧显示,正在检测单片机。

图25:下载/编程

        

(2)开关单片机开发板

 此时找到图26中,开发板的 POWER 白色按键:

 1. 若此时是上电状态,则按两下白色按键,即关机后重新开机。

 2. 若此时是断电状态,则按一下白色按键,即重新开机。

图26:Power键

        (3)烧录成功

  软件显示操作成功,此时程序已经烧录进单片机,单片机将按照程序开始执行操作。

图27:烧录成功

一些有趣的LCD1602显示实验(基于普中A2开发板)

        1. 温度传感器读取实验(利用DS18B20芯片)

        (1) 实验代码

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
float T;


void Delay(unsigned int xms)
{
    unsigned char i, j;
    while(xms--)
    {
        i = 2;
        j = 239;
        do
        {
            while (--j);
        } while (--i);
    }
}



void LCD_Delay() //11.0592MHz调用可延时1ms
{
    unsigned char i, j;
    i = 2;
    j = 199;
    do
    {
        while (--j);
    } while (--i);
}

/**
* @brief  LCD1602写命令
* @param  Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
    LCD_RS=0;
    LCD_RW=0;
    LCD_DataPort=Command;
    LCD_EN=1;
    LCD_Delay();
    LCD_EN=0;
    LCD_Delay();
}

/**
* @brief  LCD1602写数据
* @param  Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
    LCD_RS=1;
    LCD_RW=0;
    LCD_DataPort=Data;
    LCD_EN=1;
    LCD_Delay();
    LCD_EN=0;
    LCD_Delay();
}

/**
* @brief  LCD1602设置光标位置
* @param  Line 行位置,范围:1~2
* @param  Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
    if(Line==1)
    {
        LCD_WriteCommand(0x80|(Column-1));
    }
    else if(Line==2)
    {
        LCD_WriteCommand(0x80|(Column-1+0x40));
    }
}

/**
* @brief  LCD1602初始化函数
* @param  无
* @retval 无
*/
void LCD_Init()
{
    LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
    LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
    LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
    LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
* @brief  在LCD1602指定位置上显示一个字符
* @param  Line 行位置,范围:1~2
* @param  Column 列位置,范围:1~16
* @param  Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
    LCD_SetCursor(Line,Column);
    LCD_WriteData(Char);
}

/**
* @brief  在LCD1602指定位置开始显示所给字符串
* @param  Line 起始行位置,范围:1~2
* @param  Column 起始列位置,范围:1~16
* @param  String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
    unsigned char i;
    LCD_SetCursor(Line,Column);
    for(i=0;String[i]!='\0';i++)
    {
        LCD_WriteData(String[i]);
    }
}

/**
* @brief  返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
    unsigned char i;
    int Result=1;
    for(i=0;i<Y;i++)
    {
        Result*=X;
    }
    return Result;
}

/**
* @brief  在LCD1602指定位置开始显示所给数字
* @param  Line 起始行位置,范围:1~2
* @param  Column 起始列位置,范围:1~16
* @param  Number 要显示的数字,范围:0~65535
* @param  Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
    unsigned char i;
    LCD_SetCursor(Line,Column);
    for(i=Length;i>0;i--)
    {
        LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
    }
}

sbit OneWire_DQ=P3^7;

/**
* @brief  单总线初始化
* @param  无
* @retval 从机响应位,0为响应,1为未响应
*/
unsigned char OneWire_Init(void)
{
    unsigned char i;
    unsigned char AckBit;
    OneWire_DQ=1;
    OneWire_DQ=0;
    i = 247;while (--i);		//Delay 500us
    OneWire_DQ=1;
    i = 32;while (--i);			//Delay 70us
    AckBit=OneWire_DQ;
    i = 247;while (--i);		//Delay 500us
    return AckBit;
}

/**
* @brief  单总线发送一位
* @param  Bit 要发送的位
* @retval 无
*/
void OneWire_SendBit(unsigned char Bit)
{
    unsigned char i;
    OneWire_DQ=0;
    i = 4;while (--i);			//Delay 10us
    OneWire_DQ=Bit;
    i = 24;while (--i);			//Delay 50us
    OneWire_DQ=1;
}

/**
* @brief  单总线接收一位
* @param  无
* @retval 读取的位
*/
unsigned char OneWire_ReceiveBit(void)
{
    unsigned char i;
    unsigned char Bit;
    OneWire_DQ=0;
    i = 2;while (--i);			//Delay 5us
    OneWire_DQ=1;
    i = 2;while (--i);			//Delay 5us
    Bit=OneWire_DQ;
    i = 24;while (--i);			//Delay 50us
    return Bit;
}

/**
* @brief  单总线发送一个字节
* @param  Byte 要发送的字节
* @retval 无
*/
void OneWire_SendByte(unsigned char Byte)
{
    unsigned char i;
    for(i=0;i<8;i++)
    {
        OneWire_SendBit(Byte&(0x01<<i));
    }
}

/**
* @brief  单总线接收一个字节
* @param  无
* @retval 接收的一个字节
*/
unsigned char OneWire_ReceiveByte(void)
{
    unsigned char i;
    unsigned char Byte=0x00;
    for(i=0;i<8;i++)
    {
        if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
    }
    return Byte;
}

//DS18B20指令
#define DS18B20_SKIP_ROM			0xCC
#define DS18B20_CONVERT_T			0x44
#define DS18B20_READ_SCRATCHPAD 	0xBE

/**
* @brief  DS18B20开始温度变换
* @param  无
* @retval 无
*/
void DS18B20_ConvertT(void)
{
    OneWire_Init();
    OneWire_SendByte(DS18B20_SKIP_ROM);
    OneWire_SendByte(DS18B20_CONVERT_T);
}

/**
* @brief  DS18B20读取温度
* @param  无
* @retval 温度数值
*/
float DS18B20_ReadT(void)
{
    unsigned char TLSB,TMSB;
    int Temp;
    float T;
    OneWire_Init();
    OneWire_SendByte(DS18B20_SKIP_ROM);
    OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
    TLSB=OneWire_ReceiveByte();
    TMSB=OneWire_ReceiveByte();
    Temp=(TMSB<<8)|TLSB;
    T=Temp/16.0;
    return T;
}


void main()
{
    DS18B20_ConvertT();		//上电先转换一次温度,防止第一次读数据错误
    Delay(1000);			//等待转换完成
    LCD_Init();
    LCD_ShowString(1,1,"Temperature:");
    while(1)
    {
        DS18B20_ConvertT();	//转换温度
        T=DS18B20_ReadT();	//读取温度
        if(T<0)				//如果温度小于0
        {
            LCD_ShowChar(2,1,'-');	//显示负号
            T=-T;			//将温度变为正数
        }
        else				//如果温度大于等于0
        {
            LCD_ShowChar(2,1,'+');	//显示正号
        }
        LCD_ShowNum(2,2,T,3);		//显示温度整数部分
        LCD_ShowChar(2,5,'.');		//显示小数点
        LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示温度小数部分
    }
}

        (2) 实验现象

        烧录程序后,LCD1602上显示当前温度。图28中箭头所指为温度传感器,当用手捏住芯片,LCD1602上温度会上升,松开手温度会下降。

图28:显示当前温度

        2. 密码锁实验

        (1) 实验代码

#include <REGX52.H>

unsigned char KeyNum;
unsigned int Password,Count;

void Delay(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}



/**
  * @brief  矩阵键盘读取按键键码
  * @param  无
  * @retval KeyNumber 按下按键的键码值
			如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
  */
unsigned char MatrixKey()
{
	unsigned char KeyNumber=0;
	
	P1=0xFF;
	P1_3=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
	
	P1=0xFF;
	P1_2=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
	
	P1=0xFF;
	P1_1=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
	
	P1=0xFF;
	P1_0=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
	
	return KeyNumber;
}
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
  * @brief LCD1602延时函数,11.0592MHz调⽤可延时1ms
  * @param ⽆
  * @retval ⽆
  */
void LCD_Delay()
{
 unsigned char i, j;
 i = 2;
 j = 199;
 do
 {
 while (--j);
 } while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}


void main()
{
    LCD_Init();
    LCD_ShowString(1,1,"Password:");
    while(1)
    {
        KeyNum=MatrixKey();
        if(KeyNum)
        {
            if(KeyNum<=10)	//如果S1~S10按键按下,输入密码
            {
                if(Count<4)	//如果输入次数小于4
                {
                    Password*=10;				//密码左移一位
                    Password+=KeyNum%10;		//获取一位密码
                    Count++;	//计次加一
                }
                LCD_ShowNum(2,1,Password,4);	//更新显示
            }
            if(KeyNum==11)	//如果S11按键按下,确认
            {
                if(Password==2345)	//如果密码等于正确密码
                {
                    LCD_ShowString(1,14,"OK ");	//显示OK
                    Password=0;		//密码清零
                    Count=0;		//计次清零
                    LCD_ShowNum(2,1,Password,4);	//更新显示
                     Delay(500);
                    LCD_ShowString(1,14,"   ");	
                }
                else				//否则
                {
                    LCD_ShowString(1,14,"ERR");	//显示ERR
                    Password=0;		//密码清零
                    Count=0;		//计次清零
                    LCD_ShowNum(2,1,Password,4);	//更新显示
                    Delay(500);
                    LCD_ShowString(1,14,"   ");	
                }
            }
            if(KeyNum==12)	//如果S12按键按下,取消
            {
                Password=0;		//密码清零
                Count=0;		//计次清零
                LCD_ShowNum(2,1,Password,4);	//更新显示
            }
            if(KeyNum==13)  //如果s13按键按下,退位
            {
                Password/=10;  //密码退位
                if(Count>0)
                {
                    Count--;  //计次减一
                    LCD_ShowNum(2,1,Password,4);
                    LCD_ShowString(1,14,"   ");
                }
                else
                {
                    LCD_ShowNum(2,1,Password,4);
                    LCD_ShowString(1,14,"   ");
                }
            }
    }
}
}
        

        (2) 实验现象

        S1到S9分别是1~9,S10是数字0,S11是确定,S12是重新输入,S13密码退位,正确密码是2345,输入正确会显示0.5s OK,输入错误会显示0.5s ERR

        密码可在代码第231行设置。

图29:密码锁
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐