LCD1602 液晶显示实验
来介绍一种非常简单且常用的显示装置——LCD1602 液晶显示器,使用它可以显示更多的字符数字。这将有助于我们清晰的观察程序的运行结果,更加方便我们的后续调试和开发。
我们的开发板上集成了一个 LCD1602 液晶显示器接口,将LCD1602 液晶对应插入即可。
本章要实现的功能是:系统运行时,在 LCD1602 液晶上显示字符信息。
本章节可分为如下几部分内容:
一、 LCD1602 介绍
二、 硬件设计
三、 软件设计
四、 实验现象
保姆级烧录教程
一些有趣的LCD1602显示实验
一、LCD1602介绍
1.1 LCD1602简介和引脚定义
LCD1602 液晶也叫 LCD1602 字符型液晶,它能显示 2 行字符信息,每行又能显示 16 个字符。它是一种专门用来显示字母、数字、符号的点阵型液晶模块。它是由若干个
5x7
或者
5x10
的点阵字符位组成,每个点阵字符位都可以用显示一个字符, 每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用,正因为如此,它不能很好的显示图片。其实物图如下所示:
大家手上拿到的 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,背光灯电源负极。
下面给出各引脚功能速览表和其内部原理图:
1.2 LCD1602内部结构
DDRAM(Data Display Random Access Memory
):数据显示存储器。
可读取,可写入。如图5所示,可显示位置为40*2,共计80个字符。即是第一行
00H~27H
,和第二行的
40H~67H
。
CGRAM(Character Generation Random Access Memory):用户字符发生器。
可以自定义用户的字模库(最多八个字符)。
CGROM(Character Generation Read Only Memory):字符发生器。
出厂默认设置的字模库,与标准ASCII码基本一致。并在此基础上加入片假名和部分特殊符号的支持。具体支持范围如图六所示:
读取方式也很简单,例如想获得”A”在字模库中的位置,由图6可知,其二进制位置为
0100 0000B
,转换为16进制是
40H
。
屏幕显示:显示字符数据,16*2。
由图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常用指令
在使用 LCD1602 时,我们要掌握一些常用指令,这些指令对于 LCD1602 初始化是必须的。
(1)清屏指令
功能:
<1> 清除液晶显示器,即将 DDRAM 的内容全部填入”空白”的 ASCII 码
20H
;<2> 光标归位,即将光标撤回液晶显示屏的左上方;
<3> 将地址计数器(AC)的值设为 0。
也就是说 清屏指令是
0x01
(2)模式设置指令
功能:
设定每次写入 1 位数据后光标的移位方向,并且设定每次写入的一个字符是否移动。
I/D:0=写入新数据后光标左移 1=写入新数据后光标右移
S:0=写入新数据后显示屏不移动 1=写入新数据后显示屏整体右移1 个字符
也就是说 写入新数据后光标右移,显示屏不移动的命令是
0x06
(3)显示开关控制指令
功能:
控制显示器开/关、光标显示/关闭以及光标是否闪烁。
D:0=显示功能关 1=显示功能开
C:0=无光标 1=有光标
B:0=光标闪烁 1=光标不闪烁
也就是说 显示开,光标关,闪烁关的指令是
0x0c
(4)功能设定指令
功能:
设定数据总线位数、显示的行数及字型。
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写时序:
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 液晶接口电路,如下图所示:
从上图中可知,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指数字的个数 |
|
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 液晶上显示字符信息。
注意:LCD1602 液晶要正确插入到 LCD1602 接口 J2 位置,插反或者差错都会导致显示不正常。如果出现显示看不清的情况,可调节板子 LCD1602 接口下的 RJ1 电位器。如果 LCD1602 无法显示,可重启电源或复位。
保姆级烧录教程
本章节实验到此已经完毕,考虑到大家的基础差异较大,刚接触51单片机的小伙伴可能对烧录过程有些困惑,作者便在篇末给大家提供保姆级烧录教程,帮助大家快速掌握单片机程序烧录技巧,希望对大家有所帮助!
1. 开发板准备工作(以普中A2开发板为例)
(1)检查单片机芯片型号是否为 STC89C52RC
(2)单片机与计算机建立通信
2. 烧录软件的准备操作
(1)下载并打开上述链接所提供的软件。
下载完成后。鼠标
右键软件图标
,选择
以管理员身份运行
,在弹出的对话框中选择
是
,软件开始运行。(没有管理员选项的直接鼠标左键双击运行也可)。
(2)芯片型号选择
如果芯片型号与图17一致,直接按照图示箭头依次选择即可。不同的话则依据各自型号选择相应系列。
(3)检查串口
检查串口是否有红框圈中的
USB-SERIAL CH340
字样(后面的COM口不用管,每台电脑都可能不一致)。有的话证明你的电脑已经装了CH340驱动;没有的话也没关系,下载下面链接的驱动安装软件,双击运行,按照提示操作即可。
CH340驱动软件下载(win系统)
(4)打开程序文件
按照图示操作,找到已经编译生成的
.hex
文件,单击选中后点击打开。
3. 烧录程序
(1)点击 下载/编程 按钮
点击
下载/编程
按钮,看到软件右侧显示,正在检测单片机。
(2)开关单片机开发板
此时找到图26中,开发板的 POWER 白色按键:
1. 若此时是上电状态,则按两下白色按键,即关机后重新开机。
2. 若此时是断电状态,则按一下白色按键,即重新开机。
(3)烧录成功
软件显示操作成功,此时程序已经烧录进单片机,单片机将按照程序开始执行操作。
一些有趣的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上温度会上升,松开手温度会下降。
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行设置。