树莓派基础实验29:I2C LCD1602实验

一、介绍

   众所周知,虽然液晶显示器和其他显示器大大的丰富了人机交互,但他们有一个共同的弱点。当它们连接到控制器时,需要占用大量的IO口,但是一般的控制器没有那么多的外部端口,也限制了控制器的其他功能。因此,开发具有I2C组件的LCD1602来解决该问题,LCD1602是一种只用来显示字母、数字、符号等的点阵型液晶模块。

   字符型液晶显示模块是由字符型液晶显示屏LCD 、控制驱动主电路HD44780/KS0066及其扩展驱动电路HD44100或与其兼容的IC, 少量阻、容元件结构件等装配在PCB板上而成。

  I2C总线是由PHLIPS发明的一种串行总线。它是一种高性能的串行总线,具有多主机系统所需的总线控制和高速或低速设备同步功能。I2C LCD1602上的蓝色电位器用于调整背光,以获得更好的显示效果。I2C使用两个双向极漏开路线,串行数据线(SDA)和串行时钟线(SCL),通过电阻上拉。使用的典型电压为5V或3.3V,但允许使用其他电压的系统。

  其它I2C总线实验可以查看前面的PCF8591相关实验,如:
  树莓派基础实验12:PCF8591模数转换器实验

二、组件

★Raspberry Pi主板*1

★树莓派电源*1

★40P软排线*1

★I2C LCD1602模块*1

★面包板*1

★跳线若干

三、实验原理

树莓派基础实验29:I2C LCD1602实验_第1张图片
LCD1602正面
树莓派基础实验29:I2C LCD1602实验_第2张图片
LCD1602背面
树莓派基础实验29:I2C LCD1602实验_第3张图片
LCD1602电路图

   树莓派的GPIO端口数量有限,可通过IO扩展芯片增加GPIO的数量,使得树莓派可以适应更多的应用。本实验中的LCD1602模块有16个管脚,为节省GPIO端口,就使用了一款通过I2C总线扩展IO的芯片,PCF8574。单个PCF8574可扩展8个IO,一个I2C总线最多可挂载8个PCF8574,所以树莓派最多可扩展64个IO。

   本实验中的编程原理比较复杂,所以一定要程序和硬件原理结合起来看才易理解。如果不想深度学习底层原理及驱动程序,掌握LCD1602的函数使用方法就可以了,但若想灵活运用LCD1602,最好了解一下。

   本文是在网上查阅了很多中外资料,汇集诸多大神的智慧,10几天(当然,每天还是要上班的)才整理汇编而成,但仍有很多不懂和错误之处,特别是程序中有一长串“????”注释的地方,请大神们留言指出!

3.1 LCD1602的存储器

  LCD1602里面存储器有三种:CGROM、CGRAM、DDRAM。
  DDRAM(Display Data RAM)就是显示数据RAM,用来寄存待显示的字符代码。共80个字节,其地址和屏幕的对应关系如下,如图:

树莓派基础实验29:I2C LCD1602实验_第4张图片
数据地址和屏幕对应的关系

  DDRAM其实就是我们平时说的PC机的显存,如果说我们想要在屏幕上显示我们想要显示的,直接把需要的字符代码送入显存就可以了,很简单就能够在屏幕上显示我们想要显示的。相同的LCD1602总共存在80个字节的显存,就是DDRAM。遗憾的是LCD1602显示不出来这么多的字符,正是因为这样,不是每一个写在DDRAM上的字符都能够在显示器上显示出来,一次只能显示16个字符。正是因为这样,我们在程序中可以利用下面的“光标或显示移动指令”使字符慢慢移动到可见的显示范围内,看到字符的移动效果。

  那么如何在液晶上显示字符呢,就是把要写入的字符给DDRAM。举个例子,我现在想在屏幕上显示“A”,我就把我要的字符“A”的字符代码41H写入DDRAM的00H地址处然后得到。那我们应该怎么去写入呢,我们在后面进行进一步的阐述。我们下面将要介绍的是A的字模,如图:

树莓派基础实验29:I2C LCD1602实验_第5张图片
A的字模

  上面的图左侧显示的就是“A”的字模数据,上面的图右侧显示“○”代表0,用“■”代表 1。这样我们就能够显示出“A”这个字形。

  在LCD1602模块上固化了字模存储器,就是CGROM和CGRAM,HD44780内置了192个常用字符的字模,存于字符产生器CGROM(Character Generator ROM)中,另外还有8个允许用户自定义的字符产生RAM,称为CGRAM(Character Generator RAM),留给自定义的位置只有8个地址,也就是最多自定义8个符号或者图形。

  下图(字模表)说明了CGROM和CGRAM与字符的对应关系。从ROM和RAM的名称我们也可以知道,ROM是早已固化在LCD1602模块中的,只能读取;但是RAM即可以读又可以写。

树莓派基础实验29:I2C LCD1602实验_第6张图片
字模表

  若是只要求在屏幕上显示CGROM中已经拥有的字符,那就仅仅需要在DDRAM中写入它的字符代码就可以了;若是想显示的是CGROM中不存在的字符,例如美元的符号,那就只能先在CGRAM中规定,下一步再在DDRAM中写入我们之前自己定义的字符就可以。

树莓派基础实验29:I2C LCD1602实验_第7张图片
字形和光标

  上面这个图说明的是5×8点阵和5×10点阵字符的字形和光标的位置。这里我们采用的是5×8点阵,那么定义这样一个字符需要8个字节,每个字节的前3个位没有被使用。

树莓派基础实验29:I2C LCD1602实验_第8张图片
设置CGRAM地址指令

  上面这个图说明的是设置CGRAM地址指令。从这个指令的格式中我们可以看出,它共有aaaaaa这6位,一共可以表示64个地址,即64个字节。一个5×8点阵字符共占用8个字节,那么这64个字节一共可以自定义8个字符。也就是说,上面这个图的6位地址中的DB5DB4DB3用来表示8个自定义的字符,DB2DB1DB0用来表示每个字符的8个字节。这DB5DB4DB3所表示的8个自定义字符(0--7)就是要写入DDRAM中的字符代码。

3.2 管脚

   加装了I2C转接版的LCD1602,能够同时显示16x02即32个字符。(16列2行)1602字符型LCD通常有16条引脚线的LCD:

   引脚    符号   功能说明
1 VSS 一般接地
2 VDD 接电源(+5V)
3 V0 晶显示器对比度调整端,接正电源时对比度最弱,接地电源时对比度最高(对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度)。
4 RS RS为寄存器选择,高电平(1)时选择数据寄存器、低电平(0)时选择指令寄存器。
5 R/W R/W为读写选择,高电平(1)时进行读操作,低电平(0)时进行写操作。
6 E E(或EN)端为使能(enable)端,写操作时,下降沿使能。读操作时,E高电平有效
7 DB0 低4位三态、 双向数据总线 0位(最低位)
8 DB1 低4位三态、 双向数据总线 1位
9 DB2 低4位三态、 双向数据总线 2位
10 DB3 低4位三态、 双向数据总线 3位
11 DB4 高4位三态、 双向数据总线 4位
12 DB5 高4位三态、 双向数据总线 5位
13 DB6 高4位三态、 双向数据总线 6位
14 DB7 高4位三态、 双向数据总线 7位(最高位)(也是busy flag)
15 BLA 背光电源正极
16 BLK 背光电源负极

3.3 LCD1602的基本操作及时序

   本系列模块内部具有两个 8 位寄存器:指令寄存器(IR)和数据寄存器(DR)。用户可以通过 RS 和 R/W 输入信号的组合选择指定的寄存器,进行相应的操作。下表中列出了组合选择方式:

RS R/W 操作说明
0 0 写入指令寄存器(清除屏等)
0 1 读busy flag(DB7),以及读取位址计数器(DB0~DB6)值
1 0 写入数据寄存器(显示各字型等)
1 1 从数据寄存器读取数据

LCD1602的基本操作:
 1. 读状态:输入RS=0,RW=1,E=高脉冲。输出:D0—D7为状态字。
 2. 读数据:输入RS=1,RW=1,E=高脉冲。输出:D0—D7为数据。
 3. 写命令:输入RS=0,RW=0,E=高脉冲。输出:无。(写完置E=高脉冲)
 4. 写数据:输入RS=1,RW=0,E=高脉冲。输出:无。
注意:E(或EN)端为使能(enable)端,写操作时,下降沿使能。读操作时,E高电平有效。

读操作时序图:


树莓派基础实验29:I2C LCD1602实验_第9张图片
读操作时序图

写操作时序图:


树莓派基础实验29:I2C LCD1602实验_第10张图片
写操作时序图

时序时间参数:


树莓派基础实验29:I2C LCD1602实验_第11张图片
时序时间参数

3.4 LCD1602的指令说明

1602液晶模块内部的控制器共有11条控制指令:


树莓派基础实验29:I2C LCD1602实验_第12张图片
LCD1602控制命令表

   1602液晶模块的读写操作、屏幕和光标的操作都是通过指令编程来实现的。

指令1:清显示,指令码01H,光标复位到地址00H位置。
说明:清除屏幕显示内容。光标返回屏幕左上角。执行这个指令时需要一定时间。
指令2:光标复位,光标返回到地址00H。
说明:光标返回屏幕左上角,它不改变屏幕显示内容。
指令3:光标和显示模式设置

树莓派基础实验29:I2C LCD1602实验_第13张图片
进入模式设置指令

I/D=1:写入新数据后光标右移。
I/D=0:写入新数据后光标左移。
S=1:显示移动。
S=0:显示不移动。
说明:这里的设置是0x06。

指令4:显示开关控制。

树莓派基础实验29:I2C LCD1602实验_第14张图片
显示开关控制指令

D=1:显示开,D=0:显示关。
C=1:光标显示,C=0:光标不显示。
B=1:光标闪烁,B=0:光标不闪烁。
说明:这里的设置是显示开,不显示光标,光标不闪烁,设置字为0x0c。

指令5:光标或显示移位

树莓派基础实验29:I2C LCD1602实验_第15张图片
光标或显示移动指令

树莓派基础实验29:I2C LCD1602实验_第16张图片
光标或显示移动指令说明

说明:在需要进行整屏移动时,这个指令非常有用,可以实现屏幕的滚动显示效果。初始化时不使用这个指令。

指令6:功能设置命令

树莓派基础实验29:I2C LCD1602实验_第17张图片
工作方式设置指令

×:不关心,也就是说这个位是0或1都可以,一般取0。
DL:设置数据接口位数。
DL=1:8位数据接口(D7—D0)。
DL=0:4位数据接口(D7—D4)。
N=0:一行显示。
N=1:两行显示。
F=0:5×8点阵字符。
F=1:5×10点阵字符。
说明:因为是写指令字,所以RS和RW都是0。LCD1602只能用并行方式驱动,不能用串行方式驱动。而并行方式又可以选择8位数据接口或4位数据接口。这里我们选择4位数据接口(D3—D0)。我们的设置是4位数据接口,两行显示,5×8点阵,即0b00101000也就是0x28。(注意:NF是10或11的效果是一样的,都是两行5×8点阵。因为它不能以两行5×10点阵方式进行显示,换句话说,这里用0x28或0x2c是一样的)。

指令7:字符发生器CGRAM地址设置。

树莓派基础实验29:I2C LCD1602实验_第18张图片
设置CGRAM地址指令

指令8:DDRAM地址设置。

树莓派基础实验29:I2C LCD1602实验_第19张图片
设置DDRAM地址指令

说明:这个指令用于设置DDRAM地址。在对DDRAM进行读写之前,首先要设置DDRAM地址,然后才能进行读写。前面我们说过,DDRAM就是LCD1602的显示存储器。我们要在它上面进行显示,就要把要显示的字符写入DDRAM。同样,我们想知道DDRAM某个地址上有什么字符,也要先设置DDRAM地址,然后将它读出到单片机。

指令9:读忙信号和光标地址

树莓派基础实验29:I2C LCD1602实验_第20张图片
读忙信号和地址计数器AC

BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据。如果为低电平表示不忙。
说明:这个指令用来读取LCD1602状态。对于单片机来说,LCD1602属于慢速设备。当单片机向其发送一个指令后,它将去执行这个指令。这时如果单片机再次发送下一条指令,由于LCD1602速度较慢,前一条指令还未执行完毕,它将不接受这新的指令,导致新的指令丢失。因此这条读忙指令可以用来判断LCD1602是否忙,能否接收单片机发来的指令。当BF=1,表示LCD1602正忙,不能接受单片机的指令;当BF=0,表示LCD1602空闲,可以接收单片机的指令。RS=0,表示是指令;RW=1,表示是读取。这条指令还有一个副产品:即可以得到地址记数器AC的值(address counter)。LCD1602维护了一个地址计数器AC,用来记录下一次读写CGRAM或DDRAM的位置。需要强调的是:这条指令我一次也没有执行成功。很多网友似乎也是这样。好在我们有另外的办法,也就是延时。通过查看每条指令的执行时间,再经过一些试验,可以确定指令的延时。这样就可以在上一条指令执行完毕后再执行下一条指令了。
指令10:写数据。

树莓派基础实验29:I2C LCD1602实验_第21张图片
写数据到CGRAM或DDRAM指令

说明:RS=1,数据;RW=0,写。指令执行时,要在DB7—DB0上先设置好要写入的数据,然后执行写命令。

指令11:读数据。

树莓派基础实验29:I2C LCD1602实验_第22张图片
从CGRAM或DDRAM读数据指令

说明:RS=1,数据;RW=1,读。先设置好CGRAM或DDRAM的地址,然后执行读取命令。数据就被读入后DB7—DB0。

3.5 初始化

  如果电路电源能满足内部RESET电路的如下要求, 初始化可自动完成:


树莓派基础实验29:I2C LCD1602实验_第23张图片
自动初始化

  如果电路电源不能满足内部RESET电路的要求的话,需要用初始化程序来实现初始化,有8位总线和4位总线两种模式。

8位数据传输模式:


树莓派基础实验29:I2C LCD1602实验_第24张图片
8位总线模式初始化

本次实验中使用4位数据传输模式:


树莓派基础实验29:I2C LCD1602实验_第25张图片
4位总线初始化参数示例

3.6 DDRAM地址

1602字符液晶显示可分为上下两部分各16位进行显示,处于不同行时的字符显示地址如下:

显示字符 1 2 3 4 ...... 12 13 14 15 16
第一行地址 00H 01H 02H 03H ...... 0BH 0CH 0DH 0EH 0FH
第二行地址 40H 41H 42H 43H ...... 4BH 4CH 4DH 4EH 4FH

   按照上面指令8格式所示,由于地址为7位,在写入地址时,第8位D7恒为1。当我们想在指定位置写入内容时,要先指定地址,如在第一行第一位写入,地址位是00H,再加上DB7的1,即80H(0010000000),第二行第一位是40H,再加上DB7的1,即C0H(0011000000),依次类推。

四、实验步骤

  第1步:连接电路。连接电源打开树莓派,显示屏就会亮,同时在第一行显示一排黑方块。如果看不到黑方块或黑方块不明显,请调节可调电阻,直到黑方块清晰显示。如果调节可调电阻还看不到方块,则可能你的连接有问题了,请检查连接,包括检查显示屏的引脚有没有虚焊。

树莓派 T型转接板 LCD1602
SCL SCL SCL
SDA SDA SDA
5V 5V VCC
GND GND GND
树莓派基础实验29:I2C LCD1602实验_第26张图片
LCD1602实验电路图
树莓派基础实验29:I2C LCD1602实验_第27张图片
LCD1602实验实物接线图

  第2步:PCF8591模块采用的是I2C(IIC)总线进行通信的,但是在树莓派的镜像中默认是关闭的,在使用该传感器的时候,我们必须首先允许IIC总线通信。

打开I2C总线通信

  第3步:查询LCD1602的地址。得出地址为0x27。

pi@raspberrypi:~ $  ls /dev/i2c-*
/dev/i2c-1
pi@raspberrypi:~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                        

  第4步:编写驱动程序。这里先编写一个LCD1602.py文件,后面再编写一个python程序引入这个库文件,调用这个文件中的函数实现更复杂的功能。
  LCD1602.py文件就相当于是LCD1602模块的驱动程序,单独编写是为了便于重用。
  该程序也可以单独运行,会在第一行显示“Hello”,在第二行显示“world!”。

#!/usr/bin/env python

import time
import smbus  #SMBus (System Management Bus,系统管理总线) 在程序中导入“smbus”模块

BUS = smbus.SMBus(1)   #创建一个smbus实例
# 0 代表 /dev/i2c-0, 1 代表 /dev/i2c-1 ,具体看使用的树莓派那个I2C来决定
def write_word(addr, data):
    global BLEN   #该变量为1表示打开LCD背光,若是0则关闭背光
    temp = data
    if BLEN == 1:
        temp |= 0x08  #0x08=0000 1000,表开背光
        #buf |= 0x08等价于buf = buf | 0x08(按位或)
    else:
        temp &= 0xF7  #0xF7=1111 0111,表关闭背光
        #buf &= 0xF7等价于buf = buf & 0xF7(按位与)
    BUS.write_byte(addr ,temp)  #这里为什么又一次写入8位??????
    #write_byte(int addr, char val)发送一个字节到设备

def send_command(comm):
    # Send bit7-4 firstly
    buf = comm & 0xF0   #与运算,取高四位数值
    #由于4位总线的接线是接到P0口的高四位,传送高四位不用改
    buf |= 0x04    #buf |= 0x04等价于buf = buf | 0x04(按位或)0x04=0000 0100
    # RS = 0, RW = 0, EN = 1 
    #为什么这样写入代表RS = 0, RW = 0, EN = 1,低4位在这里有何意义????????
    write_word(LCD_ADDR ,buf)  #为什么这里又是8位写入?????
    time.sleep(0.002)
    buf &= 0xFB    #buf &= 0xFB等价于buf = buf & 0xFB(按位与)0xFB=1111 1011
    # Make EN = 0,EN从1——>0,下降沿,进行写操作
    #为什么这样写入代表Make EN = 0????????
    write_word(LCD_ADDR ,buf)

    # Send bit3-0 secondly
    buf = (comm & 0x0F) << 4  #与运算,取低四位数值,
    #由于4位总线的接线是接到P0口的高四位,所以要再左移4位
    buf |= 0x04               
    # RS = 0, RW = 0, EN = 1 写入命令
    write_word(LCD_ADDR ,buf)
    time.sleep(0.002)
    buf &= 0xFB               # Make EN = 0
    write_word(LCD_ADDR ,buf)

def send_data(data):
    # Send bit7-4 firstly
    buf = data & 0xF0
    buf |= 0x05               # RS = 1, RW = 0, EN = 1 写入数据
    write_word(LCD_ADDR ,buf)
    time.sleep(0.002)
    buf &= 0xFB               # Make EN = 0
    write_word(LCD_ADDR ,buf)

    # Send bit3-0 secondly
    buf = (data & 0x0F) << 4
    buf |= 0x05               # RS = 1, RW = 0, EN = 1 写入数据
    write_word(LCD_ADDR ,buf)
    time.sleep(0.002)
    buf &= 0xFB               # Make EN = 0
    write_word(LCD_ADDR ,buf)

def init(addr, bl):  #LCD1602初始化
    global LCD_ADDR  #该变量为设备地址
    global BLEN      #该变量为1表示打开LCD背光,若是0则关闭背光
    LCD_ADDR = addr
    BLEN = bl
    try:
        send_command(0x33) # 必须先初始化为8行模式   110011 Initialise
        time.sleep(0.005)
        send_command(0x32) # 然后初始化为4行模式   110010 Initialise
        time.sleep(0.005)
        send_command(0x28) # 4位总线,双行显示,显示5×8的点阵字符。
        time.sleep(0.005)
        send_command(0x0C) # 打开显示屏,不显示光标,光标所在位置的字符不闪烁
        time.sleep(0.005)
        send_command(0x01) # 清屏幕指令,将以前的显示内容清除
        time.sleep(0.005)
        send_command(0x06) # 设置光标和显示模式,写入新数据后光标右移,显示不移动
        BUS.write_byte(LCD_ADDR, 0x08)  #这里这样写入0x08是什么意思??????
    except:
        return False
    else:
        return True

def clear():
    send_command(0x01) # 清屏


def write(x, y, str):
    if x < 0:   #LCD1602只有16列,2行显示,小于第0列的数据要做修正
        x = 0
    if x > 15:  #LCD1602只有16列,2行显示,大于第15列的数据要做修正
        x = 15
    if y <0:    #LCD1602只有16列,2行显示,小于第0行的数据要做修正
        y = 0
    if y > 1:   #LCD1602只有16列,2行显示,大于第1行的数据要做修正
        y = 1

    # 移动光标
    addr = 0x80 + 0x40 * y + x  
    #第一行第一位的地址为0x00,加上D7恒为1,所以第一行第一位的地址为0x80
    #第二行第一位是0x40,加上D7恒为1,所以第二行第一位的地址为0x80加上0x40,最后为0xC0
    send_command(addr)       #设置显示位置

    for chr in str:
        send_data(ord(chr))  #发送显示内容
        #ord()函数以一个字符(长度为1的字符串)作为参数,
        #返回对应的 ASCII 数值,或者 Unicode 数值

if __name__ == '__main__':
    init(0x27, 1)  #在树莓派终端上使用命令'sudo i2cdetect -y 1'查询设备地址为0x27
    # 第二个参数1表示打开LCD背光,若是0则关闭背光
    write(4, 0, 'Hello')  #4,0参数指显示的起始位置为第4列,第0行
    write(7, 1, 'world!') #7,1参数指显示的起始位置为第7列,第1行
    #‘Hello’为要显示的字符串
            

  第5步:编写控制程序。先是静态显示内容:第一行显示“Greetings!!”,第二行显示“Welcome here!”,持续2秒。之后动态滚动显示“Thank you for buying Raspberry! _”。
  

#!/usr/bin/env python
import LCD1602
import time

def setup():
    LCD1602.init(0x27, 1)   # init(slave address, background light)
    LCD1602.write(0, 0, 'Greetings!!')
    LCD1602.write(1, 1, 'Welcome here!')
    time.sleep(2)

def loop():
    space = '                '
    greetings = 'Thank you for buying Raspberry! ^_^'
    greetings = space + greetings
    while True:
        tmp = greetings
        for i in range(0, len(greetings)):
            LCD1602.write(0, 0, tmp)    #当要显示的字符串过长时,会自动在LCD的第二行显示
            tmp = tmp[1:]   #每次循环去掉字符串首位字符,实现字幕向左移动的效果
            time.sleep(0.8)
            LCD1602.clear()

def destroy():
    pass    

if __name__ == "__main__":
    try:
        setup()
        while True:
            loop()
    except KeyboardInterrupt:
        destroy()

你可能感兴趣的:(树莓派基础实验29:I2C LCD1602实验)