51单片机实战:液晶显示器のLCD1602

文章框架

文章框架



前言

好吧,最终我还是决定把LCD和串口通信分开写。

首先祝各位新春快乐,鸡年大吉。上班的事业有成,上学的天天向上。过大年呢,还真没啥心情码字。

借着爆竹声咱扯会儿LCD(液晶显示器,Liquid Crystal Display)。今天要实战的这款俗称为LCD1602,尤其注意这个1602,他说明了这款显示器的显示能力:每行16个字符,共2行,乃字符显示器(仅ASCII)。


参数

显示容量 芯片工作电压 工作电流 模块最佳工作电压
16×2 Char 4.5~5.5V 2.0mA(5.0V) 5.0V

引脚

Vss Vdd VO RS R/W E D0~7 BLA BLK
接地 正极 对比度调节 数据(H)/命令(L)选择端 读(H)/写(L)选择端 使能(Enable)信号 数据口 背光电源正极 背光电源负极

LCD上也有一个单片机,用于控制屏幕显示。我们并不是直接操作那块屏幕,而是与那个单片机交互。
其中D0~7这8个数据口就是用于交互的,为并行传输

指令

因为我们要和LCD内嵌的单片机交互,所以需要指令。下面所列的东西都是当RS为低电平时发送的(若为高电平,就识别为数据)。

  • 数据指针
第一行 第二行
0x80 0xC0 (0x80+0x40)
  • 显示相关

模式:

指令 功能
0x38 设置16×2显示,5x7点阵,8位数据接口

方式:

0 0 0 0 1 D C B
\ \ \ \ \ Display,1:开显示 Cursor,1:显示光标 Blink,1:光标闪烁
0 0 0 0 0 1 N S
\ \ \ \ \ \ Next,1:读/写一个字符后,指针自动加1 Shift,1:写字符时,相对字符静止的屏幕移动

清屏:

指令 功能
0x01 数据指针清0且所有指针清空
0x02 仅数据指针清0
  • 操作
指令 功能
0x10 光标左移
0x14 光标右移
0x18 整体左移
0x1c 整体右移

电平

简单说下,逻辑电路中只有高电平和低电平,也就是程序里面的1和0。但是在物理层面上,它需要一个具体的表现,然后整理成标准。
TTL就是本例中要用到的一种电平,单片机和LCD通过引脚传递电信号,从而达到1和0的传递。
TTL中的低电平(0)表现为0V,高电平(1)表现为5V


实例

界内显示

  • 电路

电路

在Proteus里,1602就是LM016L,除了没有背光灯电源外用法一致(VEE是对比度调节,本例不用)。
RP1为上拉电阻,用于提高电压。由于这款单片机的P0引脚组的电压低于5V,所以需要上拉至5V,达到TTL的标准。
那个圆形的刻着Volts字样的东西是电压表,连接两个没被上拉电压的P0引脚,具体数值看后面的演示图。

  • 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int

//coefficient of 1602 display(16 row 2 col. 5*7px per Char)
#define LCD_CLEAR 0x01 //宏定义:清屏
#define DISPLAY_MODE_1602 0x38 //宏定义:1602显示模式

#define DISPLAY_OFF 0x08 //宏定义:关显示
#define DISPLAY_ON_NO_CURSOR 0x0c //宏定义:开显示且无光标
#define DISPLAY_ON_WITH_CURSOR_NO_BLINK 0x0e //宏定义:开显示且有光标但不闪烁
#define DISPLAY_ON_WITH_CURSOR_BLINK 0x0f //宏定义:开显示且有光标且闪烁

#define AUTO_BACK_STEP 0x04 //宏定义:读/写时指针自动减1
#define AUTO_NEXT_STEP 0x06 //宏定义:读/写时指针自动加1
#define AUTO_DISPLAY_MOVE_LEFT 0x07 //宏定义:字符相对静止,整屏左移
#define AUTO_DISPLAY_MOVE_RIGHT 0x05 //宏定义:字符相对静止,整屏右移

#define ALL_MOVE_LEFT 0x18 //宏定义:屏幕左移
#define ALL_MOVE_RIGHT 0x1c //宏定义:屏幕右移
#define CURSOR_MOVE_LEFT 0x10 //宏定义:光标左移
#define CURSOR_MOVE_RIGHT 0x14 //宏定义:光标右移

#define FIRST_ROW 0x80 //宏定义:第一行头地址
#define SECOND_ROW FIRST_ROW+0x40 //宏定义:第二行头地址


uchar code fst[] = "Hello World!"; //第一行要显示的数据数组
uchar code sec[] = ""; //第二行要显示的数据数组
uchar num; //字符计数
sbit enable = P0^5; //使能端
sbit RS = P0^7; //数据/命令切换
sbit RW = P0^6; //读/写切换

sbit anode = P0^0; //连接电压表阳极
sbit cathode = P0^1; //连接电压表阴极

//粗制的延时器,没走一次这个函数大约为1ms,适用于11.0592MHz及附近
void delay(uint z)
{
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}

//写命令
void writeCmd(uchar cmd)
{
RS = 0; //切换为写命令模式
P2 = cmd;
delay(1); //注意
enable = 1; //执行!
delay(1); //注意
enable = 0; //执行完毕!
}

void writeDat(uchar dat){
RS = 1; //切换为数据模式
P2 = dat;
delay(1); //注意
enable = 1;
delay(1); //注意
enable = 0;
}

//初始化函数
void init(){
anode = 1;
cathode = 0;

RW = 0; //写模式,本例只往LCD写数据
enable = 0;
writeCmd(DISPLAY_MODE_1602); //发送命令:1602模式
writeCmd(DISPLAY_ON_WITH_CURSOR_BLINK); //发送命令:开始显示并闪烁光标
writeCmd(AUTO_NEXT_STEP); //发送命令:数据指针自动加1
writeCmd(LCD_CLEAR); //发送命令:清屏
}

void main(){
init();
writeCmd(FIRST_ROW); //发送命令:开始从第一行写入
for(num=0;num<=12;num++){
writeDat(fst[num]); //发送数据,每次一字节
}
while(1);
}

好,说一下上面代码中标//注意的地方,全都是delay(1)
为了什么呢,不是蛋疼,是因为单片机给LCD传送信号时,数据是要放在数据线上的,要是LCD还没读完单片机给它发的啥就把内容撤走的话,就会造成数据丢失。
就是这个道理,为了传输稳定,所以延时一小会儿。这个延时的数值需要大家自己去试,并不一定所有的情况都延时大约1ms就够的。

  • 效果

GIF.gif

越界显示

本例用于显示字符数超过16个的情况。
代码改动

1
2
uchar code fst[] = "1234567890ABCDEF";  //第一行要显示的数据数组
uchar code sec[] = "1234567890ABCDEFGHIJK"; //第二行要显示的数据数组
  • 初始化函数
1
2
3
4
5
6
7
8
9
10
11
void init(){
anode = 1;
cathode = 0;

RW = 0;
enable = 0;
writeCmd(DISPLAY_MODE_1602);
writeCmd(DISPLAY_ON_NO_CURSOR); //换成不闪的,虽然跟这个新需求没什么联系,就是给你演示下效果
writeCmd(AUTO_NEXT_STEP);
writeCmd(LCD_CLEAR);
}
  • 主函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void main(){
init();
writeCmd(FIRST_ROW); //发送命令:开始从第一行写入
for(num=0;num<=16;num++){
writeDat(fst[num]);
}
writeCmd(SECOND_ROW); //发送命令:开始从第二行写入
for(num=0;num<=20;num++){
writeDat(sec[num]);
}
while(1){
//向左移动三次,每次间隔500ms
for(num=0;num<=3;num++) {
writeCmd(ALL_MOVE_LEFT); //发送命令:整屏左移
delay(500);
}
delay(1000); //暂停大约1s
//向右移动三次,每次间隔500ms
for(num=0;num<=3;num++) {
writeCmd(ALL_MOVE_RIGHT); //发送命令:整屏右移
delay(500);
}
delay(3000); //暂停大约3s后开始下一轮
}
}

效果
效果
移屏只移3个字符距离,所以并没有把第二行的K显示出来。


结语

这次单讲LCD的入门应用,送给不爱看春晚的你。前两天搞定了科二考试,年后准备科三了。《扯单》系列的一周目大概还差两三篇就完结了,下集预告:串口应用。好了,看完文章实践实践后就该打麻将打麻将,该放炮仗放炮仗吧!总之大家吃好玩好。