51单片机_LCD1602液晶显示

51单片机_LCD1602液晶显示

仿真电路图:

一、 LCD1602 简介

普中开发板原理图:

DDRAM(显示数据RAM)地址映射

LCD1602内部有80字节的DDRAM,但只有前32字节对应到实际显示位置:

行号 物理位置 DDRAM地址(十六进制) 实际显示地址(写入命令) 说明
第1行 1-16字符 0x00 - 0x0F 0x80 + 列号 (0x80-0x8F) 标准显示区域
第1行 17-40字符 0x10 - 0x27 0x90 - 0xA7 不显示,但可用于滚动
第2行 1-16字符 0x40 - 0x4F 0xC0 + 列号 (0xC0-0xCF) 标准显示区域
第2行 17-40字符 0x50 - 0x67 0xD0 - 0xE7 不显示,但可用于滚动

实际使用的显示地址表格

第一行显示地址(从0开始计数):

列位置 实际地址 命令值 备注
第1列 0x00 0x80 第一行起始
第2列 0x01 0x81
第3列 0x02 0x82
第4列 0x03 0x83
第5列 0x04 0x84
第6列 0x05 0x85
第7列 0x06 0x86
第8列 0x07 0x87
第9列 0x08 0x88
第10列 0x09 0x89
第11列 0x0A 0x8A
第12列 0x0B 0x8B
第13列 0x0C 0x8C
第14列 0x0D 0x8D
第15列 0x0E 0x8E
第16列 0x0F 0x8F 第一行末尾

第二行显示地址(从0开始计数):

列位置 实际地址 命令值 备注
第1列 0x40 0xC0 第二行起始
第2列 0x41 0xC1
第3列 0x42 0xC2
第4列 0x43 0xC3
第5列 0x44 0xC4
第6列 0x45 0xC5
第7列 0x46 0xC6
第8列 0x47 0xC7
第9列 0x48 0xC8
第10列 0x49 0xC9
第11列 0x4A 0xCA
第12列 0x4B 0xCB
第13列 0x4C 0xCC
第14列 0x4D 0xCD
第15列 0x4E 0xCE
第16列 0x4F 0xCF 第二行末尾

地址确定原理

为什么地址是0x80+列号?

LCD1602的指令集中规定:

  • 设置DDRAM地址的命令格式:1 A6 A5 A4 A3 A2 A1 A0
  • 二进制:1xxxxxxx,所以最高位始终为1
  • 十六进制:0x8x(第一行)或0xCx(第二行)

地址相关的重要指令

指令 二进制 十六进制 功能说明
清屏 0000 0001 0x01 清除显示,地址归0
归位 0000 0010 0x02 地址归0,显示回原位
进入模式 0000 0110 0x06 地址自动递增,显示不移
显示开关 0000 1100 0x0C 显示开,光标关,闪烁关
功能设置 0011 1000 0x38 8位数据,2行显示,5×8点阵
地址设置 1xxx xxxx 0x8x或0xCx 设置DDRAM地址

指令集(11条指令):

表1

(1)指令1:清屏。指令码01H,光标复位到地址00H。

(2)指令2:光标复位。光标复位到地址00H。

(3)指令3:输入方式设置。

其中,I/D表示光标的移动方向,高电平右移,低电平左移;S表示显示屏上所有文字是否左移或右移,高电平表示有效,低电平表示无效。

(4)指令4:显示开关控制。

其中,D用于控制整体显示的开与关,高电平表示开显示,低电平表示关显示;C用于控制光标的开与关,高电平表示有光标,低电平表示无光标;B用于控制光标是否闪烁,高电平闪烁,低电平不闪烁。

(5)指令5:光标或字符移位控制。

其中,S/C表示在高电平时移动显示的文字,低电平时移动光标。

(6)指令6:功能设置命令。

其中,DL表示在高电平时为4位总线,低电平时为8位总线;N表示在低电平时为单行显示,高电平时双行显示;F表示在低电平时显示5×7的点阵字符,高电平时显示5×10的点阵字符。

(7)指令7:字符发生器RAM地址设置。

(8)指令8:DDRAM地址设置。

(9)指令9:读忙信号和光标地址。

其中,BF为忙标志位,高电平表示忙,此时模块不能接收命令或数据,如果为低电平则表示不忙。

(10)指令10:写数据。

(11)指令11:读数据。

常用指令:

0x80+0xdd:dd为地址,这条命令用于设置显示起点坐标

0x0c :开显示,无光标,光标不闪烁 。一般做带键盘输入的才加入光标,如计算器。常用的计量显示不显示光标。

0x06 :写一个数据,地址指针加1,由1602地址表可以看出,实际上就是设置成从左往右写数据而已。

0x38 : 设置显示模式,16x2显示 5x7点阵,8位数据接口。端口不够用时,这个命令也可以换用4位数据接口的。

0x01 :清屏。

表2

二、显示原理

如上图,每个字符由 5X8 点阵组成(也可选用 5X10),想要实现显示,只需如下图:

例:以 5X7 点阵为例, 显示字符 A

0 代表灭,1 代表亮

只需将想要显示的字符的对应位置1,就能显示该字符

LCD1602 固化了字模寄存器,即 CGROM 和 CGRAM,存储了192个常用字符的字模。

字模库:

该表 行是低四位,列是高四位

想要显示哪个字符,只需查表,换算为十六进制,写入LCD1602即可。

例: 想要显示字符 A

需要向 LCD1602 写入 0x41(0100 0001)

三、显示位置

LCD1602 实际有80个字节的DDRAM,

只不过 LCD1602 只有 16X2 个位置,后面很多位置显示不出来,可以使用 指令5"光标或显示移动指令" 使字符慢慢移动到可见的显示范围内,看到字符的移动效果。

所以LCD1602的实际显示位置是,第一行:00 ~ 0F,第二行:40 ~ 4F

注意:

如图, 指令8,D7位恒为 1,

在实际向LCD1602传入数据显示地址时,需要 < 地址+0x80 >

例:向LCD1602的第一行第一列写数据,传入的地址应为: (0x00+0x80)

三、LCD1602 操作

①写操作时序图:

②读操作时序图:

③时序时间参数:

1、忙检测

为什么需要忙检测??

单片机和 LCD1602 的工作速度存在差异,单片机速度快,所以单片机向 LCD1602 传数据时,LCD1602 可能正在处理上一次的数据,处在忙状态,为了防止数据丢失或出错,所以单片机就需要等待,待 LCD1602 处理完时在进行下一次数据的传送。

忙检测代码:

由指令9,需判断 BF 的状态

c 复制代码
/****************** 忙检测 *********************
LCD_Data 与 0x80 进行或运算,判断 bit7 位状态

若 LCD_Data 的 bit7 位是 0,则 LCD 不忙
若 LCD_Data 的 bit7 位是 1,则 LCD 忙
***********************************************/
void LCD_Check_Busy(){

	uchar temp;

	LCD_Data = 0xff  // 十六进制:1111 1111
	LCD_RS = 0;  // 0 指令
	LCD_RW = 1;	 // 1 读

	do{
		LCD_E = 1;		  // 拉高
		temp = LCD_Data;  // 将 LCD 状态保存在 temp 中,用于判忙
		LCD_E = 0;		  // 负跳变使能 
	}while(temp & 0x80);  // 结果为 1,LCD 忙,继续循环;结果为 0,LCD 不忙,可以进行后面的操作
}

综合练习:

1)二行1列显示数字6

c 复制代码
#include <REGX52.H>			// 加载头文件
#include "Delay.h"

sbit RS = P2^6;         // 数据命令选择位声明
sbit RW = P2^5;         // 读写选择位声明
sbit EN = P2^7;         // 使能位声明

#define LCD_DATA P0

void main()
{
    // 初始化LCD(必须)
    Delay(15);          // 等待LCD稳定
    

    // 初始化指令
    RS = 0; RW = 0;
    LCD_DATA = 0x38;    // 8位数据,2行显示,5x8点阵
    EN = 1; Delay(1); EN = 0; Delay(1);
    
    LCD_DATA = 0x0C;    // 显示开,光标关
    EN = 1; Delay(1); EN = 0; Delay(1);
    
    LCD_DATA = 0x06;    // 地址自动+1
    EN = 1; Delay(1); EN = 0; Delay(1);
    
    LCD_DATA = 0x01;    // 清屏
    EN = 1; Delay(1); EN = 0; Delay(2); // 清屏需要更长时间
    
    while(1)
    {
        // 1. 设置显示位置(第二行第一位)
        RS = 0; RW = 0;             // 写指令
        LCD_DATA = 0xC0;            // 第二行第一位的地址
        EN = 1; Delay(1); EN = 0; Delay(1);
        
        // 2. 显示字符'6'
        RS = 1; RW = 0;             // 写数据
        LCD_DATA = '6';             // 字符'6'的ASCII码是0x36
        EN = 1; Delay(1); EN = 0; Delay(1);
        
        Delay(1000);  // 延时1秒,避免太快重复写入
    }
}

2) 模块化编码

main.c

c 复制代码
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"

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_ShowSignedNum(1,12,-88,2);	        //在1行12列显示有符号数字-88,长度为2
	LCD_ShowHexNum(2,1,0xA5,2);			    //在2行1列显示十六进制数字0xA5,长度为2
	LCD_ShowBinNum(2,4,0xA5,8);			    //在2行4列显示二进制数字0xA5,长度为8
	LCD_ShowChar(2,13,0xDF);			 	//在2行13列显示编码为0xDF的字符
	LCD_ShowChar(2,14,'C');					//在2行14列显示字符C
	while(1)
	{
	}
}

LCD1602.c

c 复制代码
#include <REGX52.H>

//引脚定义
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_E=P2^7;
#define LCD_DataPort P0

/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()		//@12.000MHz 1ms
{
	unsigned char i, j;

	i = 2;
	j = 239;
	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_E=1;
	LCD_Delay();
	LCD_E=0;
	LCD_Delay();
}

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

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init(void)
{
	LCD_WriteCommand(0x38);
	LCD_WriteCommand(0x0C);
	LCD_WriteCommand(0x06);
	LCD_WriteCommand(0x01);
}

/**
  * @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
	{
		LCD_WriteCommand(0x80|(Column-1)+0x40);
	}
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned 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,unsigned 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('0'+Number/LCD_Pow(10,i-1)%10);
	}
}

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

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	unsigned char SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData('0'+SingleNumber);
		}
		else
		{
			LCD_WriteData('A'+SingleNumber-10);
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(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('0'+Number/LCD_Pow(2,i-1)%2);
	}
}

LCD1602.h

c 复制代码
#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_Init(void);
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,unsigned char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

Delay.c

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

Delay.h

c 复制代码
#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

3)实现正计时显示

只需要修改 main.c ,其余部分同上

c 复制代码
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"

unsigned int hour = 0;
unsigned int minute = 0;
unsigned int second = 0;

/**
 * 更新时间函数
 */
void UpdateTime()
{
    second++;
    
    if(second >= 60)
    {
        second = 0;
        minute++;
    }
    
    if(minute >= 60)
    {
        minute = 0;
        hour++;
    }
    
    if(hour >= 24)
    {
        hour = 0;
    }
}

/**
 * 主函数
 */
void main()
{
    // LCD初始化
    LCD_Init();
    
    // 显示初始界面
	LCD_ShowString(1, 1, "F_timing:");
    
    while(1)
    {
		// 显示小时
		LCD_ShowNum(2, 1, hour, 2);
		// 显示分钟
		LCD_ShowNum(2, 4, minute, 2);
        // 显示秒数
        LCD_ShowNum(2, 7, second, 2);
			
		// 显示冒号
	    LCD_ShowChar(2, 3, ':'); 
        LCD_ShowChar(2, 6, ':'); 
			
        // 延时1秒
        Delay(1000);
        
        // 更新时间
        UpdateTime();
    }
}

附加内容: