从前面的分析中知道,在HD44780控制芯片忙的时候,是不能对其进行写入操作的,所以在写入指令或数据时都需要进行判忙的操作,其时序如下图所示(8位数据模式)。

从上图中可看到,当HD44780在执行内部操作时,其数据的最高位DB7为高电平,表示忙,只有内部操作结束时,DB7才为低电平,表示空闲,这时才能对HD44780进行写入操作。为了方便,一般通过C语言把以上“判忙”这一过程封装成一个函数,如下所示(C51,以下同)。

bit Busy(void)
{
unsigned char value;
LCD_EN = 1; //使能端拉高电平
_nop_(); //调用汇编指令延时一个空指令周期
value = DataPort; //读取数据
LCD_EN = 0; //忙信号结束,拉低使能端电平
_nop_();  //调用汇编指令延时一个空指令周期
return (bit)(value & 0x80);//返回1为忙,0为不忙
}

一般情况下,判忙是为了等待,即忙则等待,直到不忙为止,因此,还可以进一步封装成一个“忙等待”函数,如下所示。

void WaitForEnable(void)
{
DataPort = 0xff; //数据线电平拉高
LCD_RS = 0; //选择指令寄存器
LCD_RW = 1; //选择读方式
while(Busy()); //忙等待
}

有了忙等待函数,就可以封装出其他函数了。接下来封装”写指令“函数,写指令可根据需要进行判忙或不判忙选择,依据前面给出的写操作时序,函数代码如下所示。

void LcdWriteCommand(unsigned char cmd, unsigned char busy)
{
if(busy)            //若busy为1则判忙,否则不判忙
WaitForEnable();   //忙等待
LCD_RS = 0; //选择指令寄存器
LCD_RW = 0; //选择写方式
_nop_();     //调用汇编指令延时一个空指令周期
LCD_EN = 1; //使能端拉高电平
_nop_();     //调用汇编指令延时一个空指令周期
DataPort = cmd; //把命令数据送到数据线上
_nop_();     //调用汇编指令延时一个空指令周期
LCD_EN = 0; //拉低使能端电平,完成写入
_nop_();     //调用汇编指令延时一个空指令周期
}

以同样的方式,可以封装出一个”写数据“的函数,写数据都需要先判忙,函数代码如下所示。

void LcdWriteData(unsigned char data)
{
WaitForEnable();  //忙等待
LCD_RS = 1; //选择数据寄存器
LCD_RW = 0; //选择写方式
_nop_();     //调用汇编指令延时一个空指令周期
LCD_EN = 1; //使能端拉高电平
_nop_();     //调用汇编指令延时一个空指令周期
DataPort = data; //把显示数据送到数据线上
_nop_();     //调用汇编指令延时一个空指令周期
LCD_EN = 0; //拉低使能端电平,完成写入
_nop_();     //调用汇编指令延时一个空指令周期
}

有了写指令的函数之后,”初始化“函数也可以写了,依据前面的初始化过程,函数代码如下所示(8位数据模式)。

void LcdInit(bit N, bit ID, bit S, bit D, bit C, bit B)
{
unsigned char cmd = 0x30;
LcdWriteCommand(cmd, 0);     //写第一次
Delay_nms(5); //延时5ms
LcdWriteCommand(cmd, 0);  //写第二次
Delay_nms(1); //延时1ms
LcdWriteCommand(cmd, 0); //写第三次
if(N)
cmd |= 0x08; //双行
else
cmd &= ~0x08; //单行
LcdWriteCommand(cmd, 1); //确定显示行数及字形大小,检测忙信号
LcdWriteCommand(0x08, 1); //关闭显示,检测忙信号
LcdWriteCommand(0x01, 1); //清屏,检测忙信号
cmd = 0x04;
if(ID)
cmd |= 0x02; //AC递增
else
cmd &= ~0x02; //AC递减
if(S)
cmd |= 0x01; //画面移动
else
cmd &= ~0x01; //光标移动
LcdWriteCommand(cmd, 1); //配置进入模式,检测忙信号
cmd = 0x08;
if(D)
cmd |= 0x04; //开启显示
else
cmd &= ~0x04; //关闭显示
if(C)
cmd |= 0x02; //显示光标
else
cmd &= ~0x02; //不显示光标
if(B)
cmd |= 0x01; //开启闪烁
else
cmd &= ~0x01; //关闭闪烁
LcdWriteCommand(cmd, 1); //屏幕显示、光标显示、闪烁等配置,检测忙信号
LcdWriteCommand(0x02, 1); //光标复位
}

接下来需要封装一个”光标定位“函数,用于确定欲显示字符的起始位置,函数代码如下所示。

void LocateXY(char posX, char posY)
{
unsigned char temp;
temp = posX & 0x0f; //屏蔽高4位,限定横坐标X的范围为0~15
posY &= 0x01; //屏蔽高7位,限定纵坐标Y的范围为0~1
if(posY)
temp |= 0x40; //第二行显示,地址码+0x40,因第二行起始地址为0x40
temp |= 0x80; //设定DDRAM地址的指令DB7恒为1(即0x80)
LcdWriteCommand(temp, 1); //把命令temp写入LCD中,检测忙信号
}

接下来封装一个“显示单字符”函数,用于在确定位置显示一个字符,函数代码如下所示。

void DisplayOneChar(unsigned char x, unsigned char y, unsigned char data)
{
LocateXY(x, y); //定位欲显示字符的位置
LcdWriteData(data);  //将要显示的数据data写入LCD
}

最后可以封装一个“显示字符串”函数,用于一次性显示多个字符,函数代码如下所示。

void DisplayString(unsigned char x, unsigned char y, unsigned char const *ptr)
{
unsigned char i, j=0;
while(ptr[j] > 31)
j++;     //ptr[j]>31时为ASCII码,j累加,计算出字符串长度
for(i=0; i<j; i++)
{
DisplayOneChar(x++, y, ptr[i]); //显示单个字符,同时x坐标递增
if(x == 16)
{
x = 0;
y ^= 1; //当每行显示超过16个字符时换行继续显示
}
}
}

一般应用拥有以上函数就够了,但也可以根据需要再封装一下其他函数,这里再封装一个获取当前光标所在地址(AC)的函数,即”读取光标位置“函数,依据前面给出的读操作时序,函数代码如下所示。

unsigned char LcdReadAC(void)
{
unsigned char value;
WaitForEnable(); //忙等待
DataPort = 0xff; //数据线电平拉高
LCD_RS = 0; //选择指令寄存器
LCD_RW = 1; //选择读方式
_nop_(); //调用汇编指令延时一个空指令周期
LCD_EN = 1; //使能端拉高电平
_nop_(); //调用汇编指令延时一个空指令周期
value = DataPort; //读取数据
LCD_EN = 0; //拉低使能端电平
_nop_(); //调用汇编指令延时一个空指令周期
return (value & 0x7f); //返回低7位的AC值
}

以同样的方式,可以封装出一个”读数据“的函数,函数代码如下所示,其参数为欲读取的内容所在地址。

unsigned char LcdReadData(unsigned char AC)
{
unsigned char value;
LcdWriteCommand((AC | 0x80), 1); //定位欲读取的地址
WaitForEnable();       //检测忙信号
DataPort = 0xff;       //数据线电平拉高
LCD_RS = 1;      //选择数据寄存器
LCD_RW = 1;      //选择读方式
_nop_();          //调用汇编指令延时一个空指令周期
LCD_EN = 1;      //使能端拉高电平
_nop_();          //调用汇编指令延时一个空指令周期
value = DataPort;      //读取数据
LCD_EN = 0;      //拉低使能端电平
_nop_();          //调用汇编指令延时一个空指令周期
return value;         //返回读取到的内容
}

接下来再封装一个“光标移动”函数,用于移动光标,其参数为0时光标左移,为1时光标右移,函数代码如下所示。

void CursorMove(bit dir)
{
if(dir)
LcdWriteCommand(0x14, 1); //光标右移
else
LcdWriteCommand(0x10, 1); //光标左移
}

以同样的方式,可以再封装出一个”画面移动“的函数,用于移动画面,其参数为0时画面左移,为1时画面右移,函数代码如下所示。

void ImageMove(bit dir)
{
if(dir)
LcdWriteCommand(0x1c, 1); //画面右移
else
LcdWriteCommand(0x18, 1); //画面左移
}

最后还需要封装一个上面初始化函数中用到的毫秒级延时函数,函数代码如下所示,该函数的延时时间与单片机相关,这里是51单片机12MHz晶振为例,若用其他单片机应酌情更改。

void Delay_nms(unsigned int ms)
{
unsigned int x,y;
for(x=ms; x>0; x--)
for(y=110; y>0; y--);
}

实际使用时,上面的所有函数可写在一个C语言文件,然后把相关的定义放在头文件中,主程序只需要把该文件添加进工程,即可调用所有的函数了。头文件一般作如下定义。

//========================引脚宏定义========================
sbit LCD_RS = P1^0; //RS脚输出高电平
sbit LCD_RW = P1^1; //RW脚输出高电平
sbit LCD_EN = P2^5; //EN脚输出高电平
//========================端口宏定义========================
#define DataPort P0 //P0为数据端口
//========================初始化参数的宏定义========================
#define SINGLE 0 //单行显示
#define DOUBLE 1 //双行显示
#define INC 1 //AC递增
#define DEC 0 //AC递减
#define SHIFT 1 //画面滚动
#define NOSHIFT 0 //画面不动
#define OPEN 1 //打开显示
#define CLOSE 0 //关闭显示
#define SHOW 1 //显示光标
#define NOSHOW 0 //不显示光标
#define BLINK 1 //光标闪烁
#define NOBLINK 0 //光标不闪烁
#define LEFT 0 //向左移动
#define RIGHT 1 //向右移动
//===========================函数声明============================
void Delay_nms(unsigned int ms); //延时n毫秒
bit Busy(void); //判忙
void WaitForEnable(void); //忙等待
void LcdWriteData(unsigned char data); //写数据
unsigned char LcdReadData(unsigned char AC); //读某地址的数据
void LcdWriteCommand(unsigned char cmd, unsigned char busy); //写命令
unsigned char LcdReadAC(void); //读当前地址
void LcdInit(bit N, bit ID, bit S, bit D, bit C, bit B);//初始化
void LocateXY(char posx, char posy); //定位显示位置
void DisplayOneChar(unsigned char x, unsigned char y, unsigned char data); //显示单字符
void DisplayString(unsigned char x, unsigned char y, unsigned char const *ptr);//显示字符串
void CursorMove(bit dir); //光标移动
void ImageMove(bit dir); //画面滚动

在使用时,根据实际的连接情况,只需要更改上述头文件中的“引脚宏定义”和“端口宏定义”部分,其余均不需要改动。

LCD1602液晶屏(续)的更多相关文章

  1. 为树莓派3B添加LCD1602液晶屏

    树莓派3B针脚说明 LCD1602接线说明 VSS,接地VDD,接5V电源VO,液晶对比度调节,接电位器中间的引脚,电位器两边的引脚分别接5V和接地.RS,寄存器选择,接GPIO14RW,读写选择,接 ...

  2. 51单片机 | 实现SMC1602液晶屏显示实例

    ———————————————————————————————————————————— LCD1602 - - - - - - - - - - - - - - - - - - - - - - - - ...

  3. 简析LCD1602液晶驱动及在Arduino上的实例实现

    这几日在倒腾新到的Arduino,比起普通单片机来,感觉写程序太简单了.不过和外设打交道还是没那么容易,比如今天要说的看似简单的LCD1602液晶,却费了我一整天才基本搞懂,不过还是有一个小问题没有实 ...

  4. FPGA nios软核编写液晶屏LCD12864驱动程序源码以及注意事项,本人亲自踩坑,重要!!!

    LCD12864引脚如下: FPGA开发板得提供,3.3v电压,5v电压,普通io都是3.3v电压 DB:数据脚,得用双向io,因为程序里面需要读取液晶的应答(普通io3.3v可以) E:?输出引脚即 ...

  5. Arduino 1602液晶屏实验和程序

    在Arduino IDE中, 项目->加载库->管理库中搜索LiquidCrystal,然后安装即可 1.接线图 2.引脚图 3.最简单程序 #include <LiquidCrys ...

  6. s3c2440液晶屏驱动 (内核自带) linux-4.1.24

    自带有一部分驱动的配置信息,只要修改这部分就能支援 不同的液晶屏 - /arch/arm/mach-s3c24xx/mach-smdk2440.c 另一部分在 /drivers/video/fbdev ...

  7. 拓普微小尺寸TFT液晶屏-高性价比

    智能模块(Smart LCD)是专为工业显示应用而设计的TFT液晶显示模块. 模块自带主控IC.Flash存储器.实时嵌入式操作系统,客户主机可把要存储的数据(如背景图.图标等)存储到屏的flash中 ...

  8. 液晶屏MIPI接口与LVDS接口区别(总结)

    液晶屏接口类型有LVDS接口.MIPI DSIDSI接口(下文只讨论液晶屏LVDS接口,不讨论其它应用的LVDS接口,因此说到LVDS接口时无特殊说明都是指液晶屏LVDS接口),它们的主要信号成分都是 ...

  9. ARM40-A5应用——fbset与液晶屏参数的适配【转】

    转自:https://blog.csdn.net/vonchn/article/details/80784579 ARM40-A5应用——fbset与液晶屏参数的适配 2018.6.18 版权声明:本 ...

  10. 所谓的液晶屏驱动IC是单独的IC还是在屏内就集成

    所谓的液晶屏驱动IC是单独的IC还是在屏内就集成 时间:2016-12-05    作者:admin   其实无论什么液晶屏,想要正常工作必须包括两个人:玻璃屏+驱动IC:但是现在有一些液晶厂商他们不 ...

随机推荐

  1. 痞子衡嵌入式:Farewell, 我的写博故事2022

    -- 题图:苏州荷塘月色 2022 年的最后一天,写个年终总结.困扰大家三年之久的新冠疫情终于在 12 月全面放开了,痞子衡暂时还没有阳,计划坚持到总决赛.对于 2023 年,痞子衡还是充满期待的,慢 ...

  2. [R语言] ggplot2入门笔记3—通用教程如何自定义ggplot2

    通用教程简介(Introduction To ggplot2) 代码下载地址 以前,我们看到了使用ggplot2软件包制作图表的简短教程.它很快涉及制作ggplot的各个方面.现在,这是一个完整而完整 ...

  3. Nexus私有maven库部署和使用

    原文地址:Nexus私有maven库部署和使用 - Stars-One的杂货小窝 前段圣诞节前后,Jitpack网站突然崩溃了,无法下载依赖,然后过了一个星期才解决了,好在没啥紧急的Android开发 ...

  4. SPOJ PHONELST - Phone List | UVA11362 Phone List | LibreOJ10049. 「一本通 2.3 例 1」Phone List

    简要题意 \(t\) 组数据,每组数据给定 \(n\) 个长度不超过 \(10\) 的数字串,判断是否有两个字符串 \(A\) 和 \(B\),满足 \(A\) 是 \(B\) 的前缀,若有,输出 N ...

  5. 超详细版本vue+capacitor(自定义capacitor插件)编写移动端应用

    我的环境 Node v16.13.0 npm v8.1.0 mac的话需要安装Xcode windows的话需要Java 8 JDK和Android Studio软件 本文以安卓开发为例 找一个自己喜 ...

  6. java入门与进阶P-6.1+P-6.2

    字符类型 字符型char在Java语言中占用 2 个字节,char类型的字面量必须使用半角的单引号括起来,取值范围为[ 0 - 65535 ],char 和 short 都占用 2 个字节,但是 ch ...

  7. Windows静态库和动态库区别

    个人建议:能使用静态库的就不要使用动态库,能使用隐式调用的就不要用显示调用. 注意:     (1)动态库中的.lib文件叫做导入库,对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符 ...

  8. Stochastic Methods in Finance (1)

    Bootcamp Topics related to measure theory. 略去,详见测度论专栏中的文章 Expectations 令 \(X\) 为 \((\Omega, \mathcal ...

  9. Spring6 DI 依赖注入--Bean属性赋值

    Spring6基于XML实现Bean 管理(属性赋值) IOC和DI有什么区别:DI是IOC中的具体实现,DI表示依赖注入或注入属性,注入属性要在创建对象的基础之上完成 依赖注入方法 bean属性赋值 ...

  10. spring cloud alibaba Nacos集群部署 Linux

    参考:https://www.cnblogs.com/dw3306/p/12961353.html 1.官网:    https://nacos.io/zh-cn/docs/cluster-mode- ...