目录
初始化
根据手册可以得知初始化过程:
- 延时 15ms
- 写指令 38H(不检测忙信号)
- 延时 5ms
- 以后每次写指令,读/写数据操作均需要检测忙信号
- 写指令 38H:显示模式设置
- 写指令 08H:显示关闭
- 写指令 01H:显示清屏
- 写指令 06H:显示光标移动设置
- 写指令 0CH:显示开及光标设置
代码如下:
//模块初始化
void LCD1602_Unit()
{
//延时15ms
Delay15ms();
//写指令,不检测忙信号
LCD_Select_Data(0x38);
//延时5ms
Delay5ms();
//检测忙信号
//写指令38H,显示模式设置
LCD_Select_Cmd(0x38);
//写指令08H:显示关闭
LCD_Select_Cmd(0x08);
//写指令01H,显示清屏
LCD_Select_Cmd(0x01);
//写指令06H,显示光标移动设置
LCD_Select_Cmd(0x06);
//写指令0CH,显示开及光标设置
LCD_Select_Cmd(0x0c);
}
写操作时序分析
分析
由手册可以知道,RS为寄存器选择,高电平选择数据寄存器,低电平选择指令(地址)寄存器;而写操作,分为写数据和写地址
- 以
选择数据寄存器
为例子,分析上图可以知道,当选择数据寄存器
时,RS始终为高电平
; R/W
在tsp1
之前和thd1
之后,即可为高电平
,也可为低电平
,而在tsp1
和thd1
之间时,始终为低电平
,则让R/W
始终为低电平
。- E的变化为0 1 0,同时DB0-DB7(简称DB)在
E上升沿
之前,而查看手册可以知道,tsp2
,对于DB而言是代表着数据建立时间
,故而,在E=1之前
,需要先赋值
,再延时
一段时间,然后在E从1变为0,再次延时tf,不放心就延时两次 - 注意时序是周期性的,
写地址和写数据函数如下
//选择地址
void LCD_Select_Cmd(char cmd)
{
check_busy();//检测忙信号
RS=0;
RW=0;
EN=0;
_nop_();//tf
databuffer=cmd;
_nop_();
EN=1;
_nop_();//tf
_nop_();//tf
EN=0;
_nop_();
}
//使用数据寄存器
void LCD_Select_Data(char ch)
{
check_busy();//检测忙信号
//选择数据寄存器
RS=1;
RW=0;
EN=0;
_nop_();
databuffer=ch;
_nop_();
EN=1;
_nop_();
_nop_();
EN=0;
_nop_();
}
读操作时序分析
时序图如下
分析
- RS可高可低
- R/W在读取过程中是高电平,在读取前和读取之后,可高可低
- E的变化过程是0 1 0
- DB0-DB7依然是可高可低
- 与写操作时序不同点:Valid Data时期一定是在E为高电平时
函数代码
void check_busy()
{
char tmp = 0x80;
databuffer = 0x80;
while(tmp & 0x80){
//一直读取databuffer
RS=0;
RW=1;
EN=0;
_nop_();
EN=1;
_nop_();
_nop_();
tmp=databuffer;
EN=0;
_nop_();
}
}
时序参数
小问题:
- 如何确定字符的显示位置:根据手册可以知道,写入显示地址时,要求最高位D7恒定为高电平1,所以第五位,实际写入的应当是0x80+0x05
- 如何检测忙信号:根据手册可以知道D7位,为忙标志位,当为1时,代表忙,此时不能接收命令或数据。所以通过循环读取检测忙信号
LCD1602,显示一个字符实例
#include "reg52.h"
#include <intrins.h>
#define databuffer P0
sbit RS = P1^0;
sbit RW = P1^1;
sbit EN = P1^2;
sbit led1 = P3^7;
void Delay15ms() //@11.0592MHz
{
unsigned char i, j;
i = 27;
j = 226;
do
{
while (--j);
} while (--i);
}
void Delay5ms() //@11.0592MHz
{
unsigned char i, j;
i = 9;
j = 244;
do
{
while (--j);
} while (--i);
}
void check_busy()
{
char tmp = 0x80;
databuffer = 0x80;
//循环检测忙信号
while(tmp & 0x80){
//一直读取databuffer
RS=0;
RW=1;
EN=0;
_nop_();
EN=1;
_nop_();
_nop_();
tmp=databuffer;
EN=0;
_nop_();
}
}
//选择地址
void LCD_Select_Cmd(char cmd)
{
check_busy();
RS=0;
RW=0;
EN=0;
//数据建立延时
_nop_();
//数据建立
databuffer=cmd;
//E上升沿
_nop_();
EN=1;
_nop_();
_nop_();
EN=0;
_nop_();
}
//使用数据寄存器
void LCD_Select_Data(char ch)
{
check_busy();
//选择数据寄存器
RS=1;
RW=0;
EN=0;
//数据建立延时
_nop_();
//数据建立
databuffer=ch;
//E上升沿
_nop_();
EN=1;
_nop_();
_nop_();
EN=0;
_nop_();
}
//模块初始化
void LCD1602_Unit()
{
//延时15ms
Delay15ms();
//写指令,不检测忙信号
LCD_Select_Data(0x38);
//延时5ms
Delay5ms();
//检测忙信号
//写指令38H,显示模式设置
LCD_Select_Cmd(0x38);
//写指令08H:显示关闭
LCD_Select_Cmd(0x08);
//写指令01H,显示清屏
LCD_Select_Cmd(0x01);
//写指令06H,显示光标移动设置
LCD_Select_Cmd(0x06);
//写指令0CH,显示开及光标设置
LCD_Select_Cmd(0x0c);
}
void main(){
char position = 0x80+0x05;
//初始化
LCD1602_Unit();
//选择地址
LCD_Select_Cmd(position);
//发送数据
LCD_Select_Data('C');
}
进阶:显示一个字符串,关键代码如下
void show_string(int col,int rol,char* str)
{
//col行,rol列,str需要显示的字符串
switch(col){
case 1:{
LCD_Select_Cmd(0x80+rol);
while(*str){
LCD_Select_Data(*str);
str++;
}
}
break;
case 2:{
LCD_Select_Cmd(0x80+0x40+rol);
while(*str){
LCD_Select_Data(*str);
str++;
}
}
break;
}
}