二、带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设备:
sudo i2cdetect -y 1
输出入下图所示:
可以看到,目前我们只连接了一个I2C设备,设备号为27。
由于背光的控制位相对独立,我们封装单独的函数来处理,如下:
# 是否开启背光 由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位的控制都封装成函数:
# 补充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发送数据前,根据配置的背光设置来决定背光控制位的值:
# 通过I2C总线写入数据
def writeI2C(addr, data):
# 添加背光控制
temp = addBlenControl(data)
# 写数据到I2C总线
BUS.write_byte(addr ,temp)
准备好了这些工具函数,我们只需要根据LCD1602的指令手册来设置具体的功能,发送要展示的数据即可,完整的代码如下:
#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句话:
Hello World!
Love China!
Great Raspberry!
其实此程序也是一个完整的LCD1602驱动,初始化完成后,我们可以通过其提供额sendData方法来实现各种各样的显示需求。效果如下图所示: