星期六, 3月 05, 2016

為 16x2 LCD 顯示模組客製 I²C 介面

自製 I²C 介面接線:Raspberry Pi 與 LCD1602
在組成以微控制器為基礎的裝置人機介面時,簡易文字型液晶顯示模組是一項極經濟又容易取得使用的顯示元件選擇。以 16x2 LCD 模組為例,網購容易又便宜,有些一片百元不到,有些價格甚至比運費都還低,它們又多被製成同樣的規格(模組接腳與內部結構指令)讓組接介面與程式規劃得以更簡便。
現在市面上販售的模組許多是並列介面(某些會加裝 I²C 介面,價格稍高一些),如同前文所考量的,我希望使用 I²C 介面而讓出 GPIO 信號接腳以做其他的外部感測或控制等用途,所以就自己來為並列介面的模組客製一個 I²C 介面吧~
當然,我也可以省事地直接買一片有 I²C 介面的模組用就好,但自己客製有動手做的趣味,有需要時也有能力來為其他組件修改介面。


了解 16x2 LCD 模組
若輸入關鍵字 LCD1602 在網路上搜尋,會發現有許多不同廠商供應類似模組的規格資料。儘管有許多廠商製造,但它們的組成結構與規格內容幾乎完全一樣。模組主要由一個液晶控制晶片與一片液晶面板加上驅動電路所組成。液晶面板可顯示上下 2 列文字,每列各 16 個字符,而採用的液晶控制晶片多與 HD44780 相容,模組有 16 個信號接腳供 MCU/MPU 用來控制與傳輸資料,資料傳輸介面可被程式化使用 8 位元或 4 位元。
LCD1602 模組接腳描述

接線
此次,同上回做 3x4 數字按鍵時一樣,使用 MCP23008 做為 I²C 介面 I/O 擴充元件,因為只有 8 位元的 I/O 可用,所以規劃採用 LCD1602 模組的 4 位元資料介面。同樣以 Raspberry Pi 做使用範例,將各部件腳位接線完成,以下僅將 Raspberry Pi, MCP23008, 與 LCD1602 之間的主要信號接線列出,其他詳見本文頂端的接線圖所示。
RPi           MCP23008      LCD1602
--------      --------      --------
#5 (SCL) <--> #1 (SCL)
#3 (SDA) <--> #2 (SDA)
              #10(GP0) <--> #11(D4)
              #11(GP1) <--> #12(D5)
              #12(GP2) <--> #13(D6)
              #13(GP3) <--> #14(D7)
              #14(GP4) <--> #5 (R/W)
              #15(GP5) <--> #4 (RS)
              #17(GP7) <--> #6 (E)


程式
參閱 HD44780 的資料表得知該模組通電後初始會被重設為 8 位元資料介面,MCU/MPU 需要對其指令暫存器寫入功能指令變更為 4 位元資料介面,此外程式也可能必須重設顯示、游標或閃爍等功能。下圖為可用的指令集:
LCD1602 指令集

在規劃程式寫入指令/資料暫存器前,可先參閱模組或控制晶片的時序資料以了解信號交換的協定。我以 python 撰寫程式,無法精準地控制信號位準高低時間,但時序要符合該協定的要求,資料才能正確的寫入。
4 位元資料介面傳輸時序圖例

完成的程式碼:
#!/usr/bin/python
from mcp23008 import MCP23008
import time

class LCD1602:
    PIN_RS= 0b00100000
    PIN_RW= 0b00010000
    PIN_E= 0b10000000
    def __init__(self, address):
        self.mcp= MCP23008(address)

    def setE(self, byte):
        byte = byte | 0b10000000
        self.mcp.writeByte('GPIO', byte)
        time.sleep(0.0005)
        byte = byte & 0b01111111
        self.mcp.writeByte('GPIO', byte)
        time.sleep(0.0005)

    def write(self, data):
        self.mcp.writeByte('GPIO', data)
        self.setE(data)

    def write4bits(self, rs, data):
        self.byte= data >> 4
        if rs:
            self.byte= self.byte | self.PIN_RS
        self.mcp.writeByte('GPIO', self.byte)
        self.setE(self.byte)
        self.byte = data & 0b00001111
        if rs:
            self.byte= self.byte | self.PIN_RS
        self.mcp.writeByte('GPIO', self.byte)
        self.setE(self.byte)

    def setCursor(self, row, col):
        if row == 0:
            self.write4bits(0, 0x80 + col)
        elif row == 1:
            self.write4bits(0, 0xc0 + col)

    def display(self, strMsg):
        for c in strMsg:
            self.write4bits(1, ord(c))

    def init(self):
        self.mcp.writeByte('IODIR', 0b00000000)
        self.mcp.writeByte('GPPU', 0b11111111)
        self.mcp.writeByte('GPIO', 0b01111111)
        time.sleep(0.05)
        self.write(0b00000011)
        time.sleep(0.001)
        self.write(0b00000011)
        time.sleep(0.001)
        self.write(0b00000011)
        time.sleep(0.001)
        self.write(0b00000010)
        time.sleep(0.001)
        self.write4bits(0, 0b00101000)
        time.sleep(0.001)
        self.write4bits(0, 0b00001111)
        time.sleep(0.001)
        self.write4bits(0, 0b00000001)
        time.sleep(0.001)
        self.write4bits(0, 0b00000110)
        time.sleep(0.001)

# test the LCD1602 module...
lcd= LCD1602(0x20)
lcd.init()
lcd.display("Hello, LCD1602!")
lcd.setCursor(1, 0)
lcd.display("from S.H. Lee ")


完成、測試,有圖為證


沒有留言:

張貼留言