logo头像

学如逆水行舟

一起玩转树莓派(11)——使用LCD屏

一起玩转树莓派(11)——使用LCD屏

通过本系列博客前几篇文章的介绍,我们已经体验过了许多传感器元件,它们大多非常简单,可以直接对其进行数据读取或写入,无需复杂的指令配置。本篇博客,我们将介绍一个相对复杂的元件:LCD屏。当今大多数常见的电子设备为了便于用户操作,都会配备一块LCD液晶显示屏,用户通过屏幕可以获取到设备的相关信息方便使用。下面,我们将尝试使用树莓派来在LCD屏上展示信息。

一、LCD 1602

LCD屏是Liquid Crystal Display的简称,即液晶显示屏。LCD 1602是一种点阵式的给付型液晶显示屏,其型号为1602本身也是有意义的,表示其可以显示2行信息,每行可以显示16个字符。LCD1602最多可以显示32个字符,价格上也并不昂贵,十几元钱即可买到。LCD1602有16个引脚,如下图所示:

LCD1602的16个引脚看上去很多,但实际上使用起来并不复杂,我们首先将上图这些引脚的功能来介绍一下。

  • 引脚1:接地引脚
  • 引脚2:接5V电压
  • 引脚3:VE引脚为屏幕对比度调整引脚,接地时对比度最大,接正极电源时对比度最小。
  • 引脚4:RS引脚为功能模式引脚(也被称为寄存器选择引脚),为其加高电平时为数据模式(存取屏幕展示的数据),为其加低电平时为指令模式(读取指令)。
  • 引脚5:RW引脚为读写模式引脚,为其加高电平时为读操作,为其加低电平时为写操作。
  • 引脚6:Enable引脚,此引脚用来触发动作,负跳变时进行数据处理或指令的执行。
  • 引脚7-引脚14:这8个引脚为数据引脚,用来进行数据传输。
  • 引脚15:背光电源引脚。
  • 引脚16:背光接地引脚。

上面所介绍的引脚中,引脚1,引脚2,引脚15和引脚16比较好理解,其都是作为供电功能,无需编程操作。引脚3是一个单独调增对比度功能的引脚,作用也相对独立,无需复杂的指令操作。引脚4和引脚5是比较核心的两个引脚,这两个引脚的高低电平状态组合共有4种,分别会将LCD1602设置为读指令模式,写指令模式,读数据模式和写数据模式。引脚6可以理解为一个触发引脚,通过操作这个引脚的电平跳变来让LCD1602执行具体的功能。引脚7-引脚14用来进行数据的存取或指令的存取。

现在请你务必将上面所介绍的内容完全理解,否则后面的内容可能会更加令你迷惑。对于LCD1206的读数据模式和写数据模式你应该没有什么疑惑,只要通过引脚4和引脚5设置正确的模式后,再通过GPIO来写和读引脚7到引脚14的电平数据,即可得到一个8位的数据。我们核心需要理解的是指令模式,LCD1602的指令集如下:

上图中的RS和R/W就是引脚4和引脚5,其控制模式,与指令本身无关,我们可以先不关心。从DB7到DB0是真正的指令部分。我们下面来逐一介绍。

1. 指令一:0000 0001

清屏指令,响应时间为1.53ms。

2.指令二:0000 001- (最后一位’-‘表示0和1都可以,不被关心)

光标归位指令,执行后光标的位置会回到起点,但是数据寄存器中的数据不会清空。

3.指令三:0000 01[I/D][SH]

光标移动模式设置指令,I/D和SH两个控制为光标或屏幕移动模式。

I/D设置为0:每次读取一个字符后光标左移。

I/D设置为1:每次读取一个字符后光标右移。

SH设置为0:屏幕不移动。

SH设置为1:屏幕移动,方向与I/D的设置一致。

4.指令四:0000 1[D][C][B]

显示模式设置指令,D,C,B这三个位分别设置主显示功能,光标显示功能,光标闪烁功能。

D:设置为0则关闭屏幕,设置为1开启屏幕。

C:设置为0关闭光标,设置为1显示光标。

B:设置为0光标不闪烁,设置为1光标闪烁。

5.指令五:0001 [S/C][R/L]–

设置光标和显示屏移动方向。

S/C设置为0时,R/L设置为0则光标左移,RL设置为1时光标右移。

S/C设置为1时,R/L设置为0则显示内容左移,R/L设置为1则显示内容右移。

6.指令六:001[DL] [N][F]–

功能模式设置指令。

DL:设置为1时采用8位总线读数据,设置为0时采用4位总线读数据。

N:设置为0时是单行显示模式,设置为1时是双行显示模式。

F:设置为0时为5*8的点阵字符,设置为1时为5*11的点阵字符。

7.指令七:01[A5][A4] [A3][A2][A1][A0]

设置下一个字符要显示的位置。A5位设置要定位到的行,A0到A4位定位要显示的位置,取值为0-16之间。

8.指令八:1[A6][A5][A4] [A3][A2][A1][A0]

数据寄存器地址设置。

了解了LD1602显示屏上面的指令用法,我们就可以编程来控制显示屏显示的内容了。

二、带I2C模块的LCD 1602

前面我们说过,LCD1602有16个引脚。原则上我们已经可以使用树莓派来控制显示屏的显示了,但是16个引脚全部连接到树莓派会使接线十分的复杂,而且程序代码的编写也非常繁琐,要对太多的GPIO引脚进行操作,十分不便。本次实验,我们采用的是带I2C模块的LCD元件,I2C模块本身将一些独立的功能进行了封装,通过I2C模块,我们可以以8位数据为标准来传输任何我们想要执行的指令或让LCD显示的字符,非常方便。

如上图所示,有了IC2模块的LCD1602元件,只需要4个引脚即可实现显示功能。下面我们来分析下如何使用此I2C模块。

首先关于I2C通信的相关内容,之前博客已经有详细的介绍,这里不再赘述。我们先来介绍下为何通过4个引脚通过I2C总线传输8位的数据集合实现所有功能。

LCD引脚1与引脚2:用I2C模块的电源引脚和接地引脚代替。

LCD引脚15和引脚16:LCD的这两个引脚功能为控制背光,此逻辑被封装进了I2C模块中,I2C模块每次写入的8位数据中的第4位用来控制背光。

LCD的引脚3:LCD的此引脚用来设置显示的对比度,在I2C模块中,通过一个可调节的电阻来实现此功能,在后面的实验中如果发现屏幕显示不清,可以尝试调节此电阻器。

LCD的引脚4,引脚5和引脚6:这几个功能引脚也被封装进了I2C模块中,I2C模块每次写入的8位数据中的第1位,的2位和第3位分别用来控制这些引脚。

LCD剩下的数据引脚的数据由I2C传输的8位数据中的高4位来对应,在LCD的8位数据模式下,I2C分两次传输一次完整的数据,前传输的4位为LCD所需数据的低4位,后传输的数据为LCD所需数据的高4位。在LCD的4位数据模式下,因为LCD需要获取到完整的8位数据,因此也需要通过两次数据传输,只是此时先传输的数据为LCD所需数据的高4位,后传输的数据为LCD所需数据的低4位,这点需要特别注意。

下面总结了I2C在传输数据时每一位的意义:

第8位 第7位 第6位 第5位 第4位 第3位 第2位 第1位
数据/指令 数据/指令 数据/指令 数据/指令 背光控制位 Enable控制位 RW控制位 RS控制位

三、编码实验

使用I2C模块封装的LCD1602只有4个引脚,接线非常简单,如下:
LCD 树莓派
GND GND
VCC +5V
SDA 树莓派SDA功能引脚
SCL 树莓派SCL功能引脚

将LCD1602与树莓派连接完成后,在树莓派的终端执行如下指令查看I2C设备:

1
sudo i2cdetect -y 1

输出入下图所示:

可以看到,目前我们只连接了一个I2C设备,设备号为27。

由于背光的控制位相对独立,我们封装单独的函数来处理,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
# 是否开启背光 由PCF8574T的低4位中的第4位决定
BLEN = 1
# 补充背光控制位
def addBlenControl(data):
global BLEN
tmpData = data
if BLEN:
# 将第4位背光控制位强制设置1
tmpData = data | 0b00001000
else:
# 将第4位背光控制位强制设置为0
tmpData = data & 0b11110111
return tmpData

同理,可以将Enable位的控制,RS位的控制都封装成函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 补充Enable控制位
def addEnableControl(data, high):
tempData = data
# 第3位控制Enable
if high:
tempData |= 0b00000100
else:
tempData &= 0b11111011
return tempData

# 补充RS控制位
def addRSControl(data, high):
tempData = data
# 第1位控制RS
if high:
tempData |= 0b00000001
else:
tempData &= 0b11111110
return tempData

在向I2C发送数据前,根据配置的背光设置来决定背光控制位的值:

1
2
3
4
5
6
# 通过I2C总线写入数据
def writeI2C(addr, data):
# 添加背光控制
temp = addBlenControl(data)
# 写数据到I2C总线
BUS.write_byte(addr ,temp)

准备好了这些工具函数,我们只需要根据LCD1602的指令手册来设置具体的功能,发送要展示的数据即可,完整的代码如下:

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#coding:utf-8
import time
import smbus
BUS = smbus.SMBus(1)
# LCD屏幕的硬件地址
LCD_ADDR = 0x27
# 是否开启背光 由PCF8574T的低4位中的第4位决定
BLEN = 1

# 补充背光控制位
def addBlenControl(data):
global BLEN
tmpData = data
if BLEN:
# 将第4位背光控制位强制设置1
tmpData = data | 0b00001000
else:
# 将第4位背光控制位强制设置为0
tmpData = data & 0b11110111
return tmpData

# 补充Enable控制位
def addEnableControl(data, high):
tempData = data
# 第3位控制Enable
if high:
tempData |= 0b00000100
else:
tempData &= 0b11111011
return tempData

# 补充RS控制位
def addRSControl(data, high):
tempData = data
# 第1位控制RS
if high:
tempData |= 0b00000001
else:
tempData &= 0b11111110
return tempData

# 通过I2C总线写入数据
def writeI2C(addr, data):
# 添加背光控制
temp = addBlenControl(data)
# 写数据到I2C总线
BUS.write_byte(addr ,temp)

# 发送指令到LCD1602
def sendCommand(comm):
# comm高4位数据传输
# 低4位先清空
buf = comm & 0b11110000
# 先将Enable置为高电平
buf = addEnableControl(buf, 1)
# 设置为指令模式
buf = addRSControl(buf, 0)
# 写入指令
writeI2C(LCD_ADDR ,buf)
time.sleep(0.002)
# 将Enable置为低电平 使产生低电平跳变来执行指令
buf = addEnableControl(buf, 0)
writeI2C(LCD_ADDR ,buf)

# comm低4位数据传输
# 高4位先清空 并将低4位的数据移动到高4位
buf = (comm & 0b00001111) << 4
# 当次指令的低4位用来 做enable re rew的控制
# 先将Enable置为高电平
buf = addEnableControl(buf, 1)
writeI2C(LCD_ADDR ,buf)
time.sleep(0.002)
# 将Enable置为低电平 使产生低电平跳变来执行指令
buf = addEnableControl(buf, 0)
writeI2C(LCD_ADDR ,buf)

# 发送数据到LCD
def sendData(data):
# data高4位数据传输
# 低4位先清空
buf = data & 0b11110000
# 先将Enable置为高电平
buf = addEnableControl(buf, 1)
# 设置为数据模式
buf = addRSControl(buf, 1)
writeI2C(LCD_ADDR ,buf)
time.sleep(0.002)
# 将Enable置为低电平 使产生低电平跳变来执行指令
buf = addEnableControl(buf, 0)
writeI2C(LCD_ADDR ,buf)

# data低4位数据传输
buf = (data & 0b00001111) << 4
# 先将Enable置为高电平
buf = addEnableControl(buf, 1)
# 设置为数据模式
buf = addRSControl(buf, 1)
writeI2C(LCD_ADDR ,buf)
time.sleep(0.002)
# 将Enable置为低电平 使产生低电平跳变来执行指令
buf = addEnableControl(buf, 0)
writeI2C(LCD_ADDR ,buf)

# 初始化方法
def initLCD():
# 启动时,LCD1602为8位模式 I2C传输数据时先传输的为低位数据
# 因此实际上的指令为 0b00100011
# 为指令6 将LCD1602设置为4位总线模式
sendCommand(0b00110010)
time.sleep(0.005)

# 之后的指令都是4位总线模式
sendCommand(0b00110010)
time.sleep(0.005)
# 指令4 设置屏幕开启,无光标,无闪烁
sendCommand(0b00001100)
time.sleep(0.005)
# 指令1 清屏
sendCommand(0b00000001)

# 设置屏幕要展示的文案 x,y确定位置
def printLCD(x, y, str):
# 2行 16 列
if x < 0:
x = 0
if x > 15:
x = 15
if y <0:
y = 0
if y > 1:
y = 1
# 指令7 设置数据要展示的位置
addr = 0b10000000 + 0b00000100 * y + x
sendCommand(addr)
# 开始发送字符数据到LCD1602的数据寄存器
for chr in str:
# ord函数可以获取字符的ascil
sendData(ord(chr))

# 主程序
initLCD()
printLCD(0, 0, 'Hello, world!')
time.sleep(2)
sendCommand(0b00000001)
time.sleep(0.002)
printLCD(0, 0, 'Love China!')
time.sleep(2)
sendCommand(0b00000001)
time.sleep(0.002)
printLCD(0, 0, 'Great Raspberry!')

如上代码所示,所有的指令都采用的二进制的方式,便于对比指令手册进行理解。更多时候我们会采用十六进制数字来编写指令,这样会使代码看的干净很多。上面的示例代码是一个简单的应用程序,运行后可以直接在LCD屏幕上展示3句话:

1
2
3
Hello World!
Love China!
Great Raspberry!

其实此程序也是一个完整的LCD1602驱动,初始化完成后,我们可以通过其提供额sendData方法来实现各种各样的显示需求。效果如下图所示:

专注技术,懂的热爱,愿意分享,做个朋友

QQ:316045346