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

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();
}
}

附加内容:



1034

被折叠的 条评论
为什么被折叠?



