51单片机_LCD1602液晶显示

51单片机_LCD1602液晶显示

仿真电路图:

image-20260108160422876

一、 LCD1602 简介

image-20260108163947829

image-20260108164014687

普中开发板原理图:

image-20260108164635212

image-20260108164728518

image-20260108165210595

image-20260108165639470

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

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

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

实际使用的显示地址表格

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

列位置实际地址命令值备注
第1列0x000x80第一行起始
第2列0x010x81
第3列0x020x82
第4列0x030x83
第5列0x040x84
第6列0x050x85
第7列0x060x86
第8列0x070x87
第9列0x080x88
第10列0x090x89
第11列0x0A0x8A
第12列0x0B0x8B
第13列0x0C0x8C
第14列0x0D0x8D
第15列0x0E0x8E
第16列0x0F0x8F第一行末尾

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

列位置实际地址命令值备注
第1列0x400xC0第二行起始
第2列0x410xC1
第3列0x420xC2
第4列0x430xC3
第5列0x440xC4
第6列0x450xC5
第7列0x460xC6
第8列0x470xC7
第9列0x480xC8
第10列0x490xC9
第11列0x4A0xCA
第12列0x4B0xCB
第13列0x4C0xCC
第14列0x4D0xCD
第15列0x4E0xCE
第16列0x4F0xCF第二行末尾

地址确定原理

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

LCD1602的指令集中规定:

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

地址相关的重要指令

指令二进制十六进制功能说明
清屏0000 00010x01清除显示,地址归0
归位0000 00100x02地址归0,显示回原位
进入模式0000 01100x06地址自动递增,显示不移
显示开关0000 11000x0C显示开,光标关,闪烁关
功能设置0011 10000x388位数据,2行显示,5×8点阵
地址设置1xxx xxxx0x8x或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即可。
image-20260108165651108

例: 想要显示字符 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 操作

①写操作时序图:

在这里插入图片描述

②读操作时序图:

在这里插入图片描述

③时序时间参数:

img

1、忙检测
为什么需要忙检测??

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

忙检测代码:

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

/****************** 忙检测 *********************
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

#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

#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

#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

#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


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

Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

image-20260108192008266

3)实现正计时显示

只需要修改 main.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();
    }
}

image-20260108194620147

附加内容:

img

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值