分析一段LCD1602显示字符代码
#include "public.h"
#include "lcd1602.h"
extern void lcd1602_write_cmd(u8 cmd);
extern void lcd1602_write_data(u8 dat);
//管脚定义
sbit LCD1602_RS=P2^6;//数据命令选择
sbit LCD1602_RW=P2^5;//读写选择
sbit LCD1602_E=P2^7; //使能信号
#define LCD1602_DATAPORT P0 //宏定义LCD1602数据端口
void main()
{
u8 i=0;
u8 gshowbufa[16]="Hello World";
u8 gshowbufb[27]="welcome to the world of mcu";
lcd1602_init();
lcd1602_show_string(0,0,gshowbufa);
lcd1602_show_string(0,1,gshowbufb);
lcd1602_write_cmd(0x07);//每写一个数据屏幕就要右移一位,就相对于数据来说就是左移了
while(1)
{
lcd1602_write_cmd(0x80);
for(i=0;i<16;i++)
{
lcd1602_write_data(gshowbufa[i]);
delay_ms(500);//滚动速度调节
}
}
}
以上是一段LCD1602显示字符的主函数,其功能为:
在第一行最左侧显示"Hello Word"
在第二行最左侧显示“welcome to the world of mcu",并且字符从左往右循环滚动。
要想让LCD1602显示字符需要经过四个过程:写命令(选择功能)、写数据(装填字符)、初始化(设定功能)、显字符(屏幕显示)。这四个过程分别对应四个函数,之所以没有把这四个函数一起放在上面,是因为
太多了,会翻到怀疑人生
所以,为了便于理解,也便于查阅,我会把这四个函数以及引脚定义统一放在第三节,并一一解释它们的作用。我们先来了解一下,这个LCD1602到底是何方神圣。
一、LCD1602是什么?
LCD,也就是液晶显示屏;02代表可显示两行字符,16代表每行16个字符,因此,LCD1602实际上就是能显示2*16=32个字符的液晶显示屏。

可以看到,LCD上有32个方块,代表显示字符的区域;每个区域中又有若干个小黑片(实际上是5*7个),这个小黑片就是LCD显示的起点。这就是我们要讲的第一个问题
液晶显示原理
当液晶两端不给电压时,近似为透明(完全透光);当液晶两端给电压时,近似为黑灰(完全不透光),这种电压变化会引起液晶的透明度也就是亮度变化,因此很多单片机会在LCD1602旁边设置一个可调电阻,用于调整液晶两端电压,从而控制液晶对比度。

认识字符显示区域的小黑片之后,我们再把视角拉远,看看一个字符区域是怎么工作的。
字符显示原理
LCD1602字符区域有5*7和5*10两种,可以通过初始化设置是哪种,这个我们会在初始化函数中再次见到它。现在主要看5*7,一块字符区域可以看作是7行5列,当行方向通低压,列方向通高压时,对应的小黑片(也就是像素)就会显示

以字母“A”为例,将A按水平方向切成七份,水平方向从上到下依次给低电平(也称作扫描),垂直方向按预先设定好的方式给高电平,连起来就是

基于这样的原理,一块显示区域需要5+7=12个引脚,即使把行方向的7个引脚全部复用,32个显示区域也需要2*7+32*5=174个引脚,单片机看到这么多引脚直接燃尽了。
为此,人们想出在LCD1602中专门再做一个处理器,专门给LCD提供高低压信号,以减轻单片机的引脚负担,这个处理器就叫做HD44780U,相当于计算机中的显卡,它就在LCD1602背面,稍大的圆盘。

HD44780U有16个COM口和40个SEG口,分别连接字符区域的行方向和列方向。这样,两排字符的行方向通过复用,16个COM口是完全足够的,但是列方向需要16*5=80个引脚,而HD44780U只有40个SEG口,剩下的40个引脚就管不了了吗?
当然不是,所以大圆盘旁边配备了一个帮手HD44100H,它也有40个SEG口,刚好补足前者在SEG的空缺,两者连起来,就可以控制整个LCD1602上的字符显示。

我们梳理一下,LCD1602显示的基本单位是电压控制透明度的小液晶,HD44780U在HD44100H的辅助下,控制整块屏幕32个字符区域显示的内容。当然,这只是LCD1602内部的一套规则,单片机要想控制它,必须借助8个数据引脚,这就是下一节的主角D7~D0。
二、单片机如何控制LCD1602?
LCD1602共有16个引脚,我将它们分成四部分:电源引脚、控制引脚、数据引脚、背光板

电源引脚:VSS,VDD分别是LCD1602的电源和接地端;V0是LCD1602两端电压,通过控制V0可以调节液晶显示的对比度。总之,这三个端口是不需要在代码中专门设定的,所以了解一下就行。

控制引脚:RS(寄存器选择)、RW(读写控制)、E(使能),这三个引脚在写指令函数中扮演重要角色,这里就先不提,因为它们不是这一节的主角。
背光板引脚:A、K,这部分与电源引脚一样,不需要在代码中专门设定,只是控制背光板亮灭。
数据引脚:D0~D7,这八个引脚与LCD1602的主控芯片相连,单片机通过向D0~D7输入数据来控制LCD1602的主控芯片,从而控制液晶屏的显示。
单片机控制LCD1602显示无非是两个问题:显示什么?在哪显示?
“显示什么”需要用到LCD1602的一个寄存器
CGRAM(字符发生存储器)
CGRAM预先把240个常用字符对应的高低压信号存起来,并且用ASCII码作为这些信号的索引。比如显示“B”,我们向数据引脚输入0100 0010,对应于下图的“B”(注意最上一行是高四位,最左一行是低四位)

“在哪显示”需要LCD1602的另一个寄存器
DDRAM(显示数据存储器)
为了更精准地让指定区域显示指定字符,HD44780U为每块字符区域都设计了一个字节DDRAM。所谓DDRAM,就相当于计算机的显存,先指定某一块字符区域,再向其输送字符信息。一共两行,每行40个字节,共80个字节。两行显示方式下,DDRAM地址范围为:第一行00H~27H,第二行40H~67H。
LCD1602显示最核心的CGRAM和DDRAM我们都了解了一遍,可以明确其显示的逻辑顺序是:
查ASCII表确定字符——设置数据存储器地址(DDRAM)——输入ASCII码——CGRAM生成字符
在熟练掌握LCD1602内部运作规律后,我们再回头看看开头没有写出但十分重要的四大函数。
三、写命令、写数据、显字符、初始化
这四个函数按重要性排序,先讲讲使用频率最高的写命令函数
1、写命令
void lcd1602_write_cmd(u8 cmd)
{
LCD1602_RS=0;//选择命令
LCD1602_RW=0;//选择写
LCD1602_E=0;
LCD1602_DATAPORT=cmd;//准备命令
delay_ms(1);
LCD1602_E=1;//使能脚E先上升沿写入
delay_ms(1);
LCD1602_E=0;//使能脚E后负跳变完成写入
}
写命令首先要将RS和RW置0,这是因为所有指令(除了写数据和读数据外)的前两位都置0;然后再给E一个上升沿,相当于按下了“确认键”,确认执行这个指令。以下是LCD1602的指令表:

清屏不用多说,我们看一下几个常用指令该如何使用:

I/D(光标移动方向,高右移,低左移);
S(所有文字是否移动,高是,低否)

D(是否开启显示功能,高开,低关);
C(是否有光标,高是,低否);
B(光标是否闪烁,高是,低否)

S/C(光标移动方式,高平光标独立移动,低平光标与显示一起移动);
R/L(光标移动方向,低平向左移动,高平向右移动)

DL(控制数据接口宽度,高平8位,低平4位);
N(显示行数,高平显示两行,低平显示一行);
F(点阵大小,高平5*10点阵,低平5*7点阵)
2、写数据
void lcd1602_write_data(u8 dat)
{
LCD1602_RS=1;//选择数据
LCD1602_RW=0;//选择写
LCD1602_E=0;
LCD1602_DATAPORT=dat;//准备数据
delay_ms(1);
LCD1602_E=1;//使能脚E先上升沿写入
delay_ms(1);
LCD1602_E=0;//使能脚E后负跳变完成写入
}
RS高电平选择数据,低电平选择指令;RW高电平读,低电平写。
因此,RS置1,RW置0就表示写数据。这个函数关键问题是
这个数据是什么?
这个问题好像有点奇怪,但是是必要的。写数据中的数据,实际上就是字符数据。比如说,我想显示字母“A”,首先指定地址,下一步就是lcd1602_write_data("A")。ASCII表已有的字符,输入时会直接转换为ASCII码,然后CGRAM会生成对应字符。但是要注意,写数据函数只管显示字符,不管显示位置,因此需要下面这个函数来指定字符显示位置。
3、显字符
void lcd1602_show_string(u8 x,u8 y,u8 *str)
{
u8 i=0;
if(y>1||x>15)return;//行列参数不对则强制退出
if(y<1) //第1行显示
{
while(*str!=' ')//字符串是以' '结尾,只要前面有内容就显示
{
if(i<16-x)//如果字符长度超过第一行显示范围,则在第二行继续显示
{
lcd1602_write_cmd(0x80+i+x);//第一行显示地址设置
}
else
{
lcd1602_write_cmd(0x40+0x80+i+x-16);//第二行显示地址设置
}
lcd1602_write_data(*str);//显示内容
str++;//指针递增
i++;
}
}
else //第2行显示
{
while(*str!=' ')
{
if(i<16-x) //如果字符长度超过第二行显示范围,则在第一行继续显示
{
lcd1602_write_cmd(0x80+0x40+i+x);
}
else
{
lcd1602_write_cmd(0x80+i+x-16);
}
lcd1602_write_data(*str);
str++;
i++;
}
}
}
x表示字符显示的起始位置,y表示字符显示行位置(第一行或第二行)。由于第一行显示与第二行显示代码整体上是一样的,所以我们缩小范围,看看第一行显示时的核心代码。
while(*str!=' ')//字符串是以' '结尾,只要前面有内容就显示
{
if(i<16-x)//如果字符长度超过第一行显示范围,则在第二行继续显示
{
lcd1602_write_cmd(0x80+i+x);//第一行显示地址设置
}
else
{
lcd1602_write_cmd(0x40+0x80+i+x-16);//第二行显示地址设置
}
lcd1602_write_data(*str);//显示内容
str++;//指针递增
i++;
}
C语言中字符串以' '结尾,它是字符串的终止标志。所以,while(*str!=' ')的含义是执行到字符串结尾,循环的次数就是字符串长度。接下来看一次循环中都发生了什么:
若设定x=0,即字符串从最左边开始显示,此时if条件满足,执行if语句中的
lcd_1602_write_cmd(0x80+i+x);
x=0忽略不计,i=0,在第二节DDRAM部分了解过,0x80表示第一行第一列的地址,再看下一段
lcd1602_write_data(*str);
*str表示字符串str的指针,此时指向str的第一个字符。结合上面的写函数,就是在第一行第一列(0x80)显示str字符串的第一个字符。接下来是赋值。
str++;
i++;
表明指针从字符串第一位指向了第二位,同时i++表明显示地址从0x80变到0x81,右移一位。
我们来总结一下这个函数的流程:
判断字符是否结尾——设置显示地址——显示字符——地址+1,指针+1——设置下一个地址和下一个字符
4、初始化
void lcd1602_init(void)
{
lcd1602_write_cmd(0x38);
lcd1602_write_cmd(0x0c);
lcd1602_write_cmd(0x06);
lcd1602_write_cmd(0x01);
}
初始化只有四条指令,我们结合前面指令表对应着看看都初始化了哪些功能。
0x38——0011 1000

DL置1,数据接口宽度为8;N置1,显示两行字符;F置0,点阵大小为5*7
0x0c——0000 1100

D置1,开启显示功能;C置1,显示光标;B置0,光标不闪烁
0x06——0000 0110

I/D置1,光标向右移动;S置0,文字不移动
初始化函数相当于为LCD显示设定了功能,即8位数据接口,2行显示,5*7规格的带光标的显示屏
四、写在最后
感谢你能看到这里,LCD1602虽然时至今日仍然很受欢迎,但与当今热门的显示屏相比已经算是垂暮老人了,市面上好一点的计算机,显存能达到25GB,而LCD1602的显存DDRAM只有80B。除了存储劣势外,LCD1602还极易出问题,比如LCD亮但只有一排白方块,或者干脆显示乱码。只有真正去了解这款上世纪80年代的产物,才能体会到当初人们不断追求更高更快设备的野心和追求。LCD1602我们尚可驾驭,那么更快更强的显示器我们一定会得心应手。







