手把手教你写LCD1602驱动代码
写在前面:本文是本人某种意义上写的第一篇blog,如有错误请留言指正。具体内容以官方手册内容为准1。
首先我们要知道LCD1602屏幕驱动的背后也是有一块类似显卡的芯片HD44780U在作为驱动,当然这块上世纪的芯片当然不能和最新的4090相比,它的显存只有可怜的80bytes,但是这对于很多应用场景来说已经够用了,这也是这块芯片能够经久不衰的原因。此处附上DataSheet下载网址,现在就让我们正式开始这块芯片的学习吧。
HD44780U以下简称44780
44780的内部资源
-
44780中最重要的就是它的寄存器和存储器了,寄存器分别是指令寄存器IR和数据寄存器DR,存储器又分为DDRAM,CGROM和CGRAM了,下面一一为大家解释他们的含义。
-
IR寄存器叫指令寄存器,只能通过MCU发出指令才能改变IR,它相当于一个指挥者,指挥着这块芯片的所有行为,信息可以包含屏幕的显示与否,光标的显示与否,还有RAM的地址信息等
-
DR寄存器叫数据寄存器,它负责数据在MCU和44780之间的传递,MCU需要读取或者写入信息到44780的时候才用到。DR自动地完成了数据在DDRAM和CGRAM中的传输,意思是我们如果想要往DDRAM或者CGRAM中写入或者读取数据,只需要访问或者修改DR即可。我们一般能够使用44780自带的字库就已经够了,所以只需要往DDRAM中写入数据,除非需要自己往44780中写入数据或者读取状态信息。
-
DDRAM全称叫Display Data RAM,顾名思义它的作用是实际显示字符。所有在DDRAM中的8-bit character codes 都会以对应character 显示出来。它是是一个80 x 8bit的RAM,所以最多能够显示80个字符,但是实际上不能显示如此之多,需要外部的硬件驱动扩展,这点我们之后再讨论。
-
对应的CGROM全称叫Character Generator ROM。CGROM中每一个8位地址都确定了对应的5 x 8 bit 或者 5 x 10 bit 数据,此外ROM为只读存储器,它在出厂时就已经确定,一共有208个5 x 8 bit 的数据和32个5 x 10 bit的数据,用户不能更改一般我们买到的都是欧版和日版。
-
用户可以更改的时CGRAM,CGRAM实际上是有16个5 x 8 bit 的物理存储单元,和CGROM合计256个单元。但是我们只能使用其中的8个物理存储单元因为对于CGRAM的地址来说低字节的第三位是无效位,详细分析请看在CGRAM中自定义数据。
此处附上欧版CGRAM和CGROM的字库
-
-
44780中还有两个重要的内部结构分别是BF和AC,尤其AC是芯片运行至关重要的结构。
- BF叫Busy Flag 是44780的忙信号,它在不忙的时候是0,在忙的时候是1。那什么时候忙什么时候不忙呢:读者有没有记得在之前说过DR寄存器负责数据的传输,这个过程是芯片内部自动执行,这个过程是MCU不可控制的,所以需要一个标志来表示结束信号。此外,根据芯片手册,当芯片处于忙状态时是不会接受下一条指令的,甚至如果这个时候再发送指令到IR,芯片不仅不会接受而且会花上成倍的时间去调整,可谓是得不偿失,所以大家在写指令之前一定要先确认44780处于空闲状态。
- AC叫Address Counter,它记录着DDRAM的地址和CGRAM的地址信息,AC初始是0,当读取DDRAM和CGRAM的数据后,AC根据后面介绍的设定自动加一或者减一。AC的改变可以用指令进行,当执行写地址指令后,AC的地址会变成在IR地址中包含的地址信息。这一点在刚开始引起了我的不解,现在仍旧想到有两种方式解释:一是AC中有两个独立的寄存器分别存储DDRAM和CDRAM的地址信息,二是AC通过一种译码方式确定了地址是属于DDRAM还是CGRAM,毕竟DDRAM是7位地址,CCGRAM是六位地址。总之无论是哪种方式,外部看来结果都一样,在操作之前都要通过写地址指令来确定位置。
44780的常规指令操作
Let’s take a quick look of these instructions,读者有可能和我刚开始一样云里雾里,但是没关系我会解释的。
这些指令可以被分成4类,分别是写命令,写数据,读命令,读数据
- 我们先来看看写命令和写数据,他们都是属于往44780中写的范畴,一个是往IR中写一个是往DR中写,要完整无误地进行操作,首先还是得看看44780的写操作时序
由图中可以看出,在改变RS、RW状态后,需要一个短暂的tAS来建立地址,手册中给的是40ns,但是12MHz单片机最短延迟时间是1us,所以就按照1us来处理。为了方便,因为所有的操作延迟时间都低于1us,所以就按照1us来延迟,这样我们得到了第一个延迟时间点,接下来给出使能信号enable,图中显示enable分别有一个rise time和 fall time,以及高电平保持时间不小于PWEH,为了保险起见此处再次延时1us,然后将数据送入总线BUS,此时enable还是保持有效状态,数据也有一个最短建立时间tDSW,所以再次延时1us,注意此时的延时1us并不是代表1us后数据就已经能够发挥作用,而是代表这个时候才能被44780所读取,所以在1us后再改变的话,44780读到的数据是改变之后的那一个。真正能把数据锁存住 的信号是enable的下降沿,当enable的下降沿到来时这个指令周期内BUS上的数据如何变化都和44780无关了。刚学的时候,很容易混淆操作延迟时间和指令延时时间。操作延迟时间很短,大多在ns级别,而指令延迟时间却在us级别,需要在所有的引脚电平变化完成后再额外指令操作延时,这一点在大多数教程中是没有明确指出的。
写指令代码示例如下
void LCD1602_COMMAND(uchar cmd)
{
while(Check_Busy());
LCD_RS=0;
LCD_RW=0;
_nop_();//Address set-up time
LCD_EN=1;
_nop_();
BUS=cmd;
_nop_();//Data set-up time
LCD_EN=0;
BUS = 0; 当enable下降沿到来时,数据已经被所存住,所以这个时候改变也不会影响
Delay_us(500);//instruction implement time
}
读指令类似,只需要将RS变成1即可
- 接下来就是读命令和读指令操作了,依旧,我们先来看看读操作的时序图
由于读操作确实不太常用,而且编程思想和写操作类似,所以在次就简单描述。在配置完RS和RW状态后,仍然需要一段延时来建立地址,之后就是使能信号的置一,与写操作不同的是:此处使能信号之后的延时并不是可选的了,而是必须的。因为需要等待tDDR的Data delay time之后才能读取到有效的数据,所以此处必须延时1us。由于延时了1us,所以PWEH也顺利保持,之后只需要直接置enable为0即可。
读数据的示例如下
uchar Read_Data()
{
uchar data_result;
while(Check_Busy());
LCD_RS=1;
LCD_RW=1;
BUS=0xff; //读取数据之前要先置一
_nop_();//Address set-up time
LCD_EN=1;
_nop_();//Data delay time,
data_result = BUS;
LCD_EN=0;
return data_result;
}
读指令的示例如下
值得注意的是读指令不需要检查忙信号,因为读指令比较特殊,在BF为1的时候也能执行,否则怎么知道BF到底为多少?哈哈哈。另一方面来说Check busy函数本身就调用了读指令函数,这会导致递归调用而且没有终止条件,这个错误是致命的。
uchar Read_Busyflag_and_AD()
{
uchar BF_AD_result;//读指令不需要checkbusy
LCD_RS=0;
LCD_RW=1;
BUS=0xff;
_nop_();//Address set-up time
LCD_EN=1;
_nop_();//Data delay time
BF_AD_result = BUS;
LCD_EN=0;
return BF_AD_result;
}
读指令返回的结果有两部分,一部分是BF的状态,一部分是AC的当前值,也就是执行完上一条指令之后的值。
bit Check_Busy()
{
bit result;
result = (bit)(Read_Busyflag_and_AD()&0x80);
return result;//BF=1,the next instruction will not be accepted.
}
-
有了最基础的4个指令之后,就可以开始写更详细的上层功能实现了,由于这一部分网络上资源较多,所以为了不占用太多篇幅,我就挑选一个来详细说明。今天是12月4号,那就挑选第四个吧,我们看第四个:Cursor or display shift
指令的前两位代表RS和RW,由于是写指令所以都为0,接下来3位的也全部是0,直到DB4才出现从高位到低位的首个1,细心的同学可能已经发现,44780正是通过这种判断首个高位出现的位置的方式来判断到底这个指令属于哪一个类别。DB3是选择移动的是cursor还是display,如果为1,则每执行一次这个命令,背景整个移动一次,如果为0则背景不动,光标移动。注意一点:光标移动没有改变DDRAM中的数据,实际上光标移动的本质就是AC发生了变化,没有写数据的指令,DDRAM当然不会变了。
在CGRAM中自定义数据
这部分内容属于了解即可,如果不是为了特殊需求,CGROM中的字库完全足够使用
- 44780在上电之后需要初始化,而初始化所需要执行的指令除了设置光标,清屏等个性化操作之外,有一条十分重要的指令,在instructions图中有说到,那就是Function set指令,该指令必须放在所有指令之前执行,并且执行一次后不能修改。我们先来看看这条指令的详细说明
该指令DL选择数据位长度,可以为4位或者8位,一般都是8位,除非IO口不够或者MCU是4位的。
N位选择屏幕显示的模式:可以为一行显示和两行显示,正常来说都是两行。
F位选择显示在屏幕上的字体格式,有5 x 8 bit和5 x 10 bit,选择好后这个44780芯片的工作就按照选择的模式进行了
- 我们之前说到,CGRAM只有8个编码的位置能够写自定义数据,那么如何找到这8个位置呢?仍然以5 x 8 bit为例,5 x 10 bit类似。
我们想要自定义数据,本质上就是要在CGRAM中写入数据对应的信息,而CGRAM根据characters图片可知是在地址编码的前16位(00000000-00001111),但是根据上图可知第3位为无效位‘*’,所以实际上只有8个逻辑地址能够让用户自己写入数据,在写入之前,需要先设定想写入的位置。比如1号(00000001)位置,那么就需要将AC的地址设置为CGRAM,然后往对应的CGRAM地址写入8位的数据,但其中仅低5位有效。每一个逻辑地址对应的8个CGRAM地址都需要写入一个8位的数据,所以总共写8次,对应到这个情况就是往(001000-001111)每个位置都写一次。数据为1对应的像素亮,为0对应的像素灭。
Set_CGRAM_Address(0x40);// store the chars into the CGRAM
for (int i=0; i<8; i++) Write_Data(cc1[i]);
由于第三位无效,所以在引用0号位置的时候,可以往DDRAM中写入0x00或者0x80,都会显示同一个字符。
5 x 10 bit 的自定义数据写入和8 bit类似,读者可以根据下图自行写入
虽然这个模式不常用,但是读者如果在写入时要注意characters code 的无效位为0,3位,characters pattern的后5个字节也无关。
使用时的注意事项
- 要在合适的工作电压下使用,这样才能保证44780上电后的初始化正常执行。如果电压不稳定,导致自动初始化没有正常完成,此时44780工作会发生异常。解决方法是手动多次初始化( function set)
写在最后:感谢能读到这里,希望没有浪费您宝贵的时间,如果觉得还不错,不要吝啬手中的赞呦~。
LCD1602的驱动代码稍后放在了sycamoremoon’s github,需要的同学可以自取。
之后还会有其他的内容上传,希望我的小小努力能够给大家带来帮助
这是我的网站,内容会随着时间逐渐丰富的…