RaspberryPiで16x2 I2C LCDモジュールを制御

#概要
Raspberry Piでストロベリーリナックス社製LCD(LCDコントローラはST7032i)を制御するプログラムを作成しました。LCDは16文字×2行表示ですが、コントローラ自体はDDRAMに80文字まで文字を保持できるので、16文字×4行を画面切り替えによって制御できるようにしました。たった64文字を制御するだけですが、私にとっては難しくて大変でした。


##動作動画
youtubeにアップした動作概要動画へのリンクです。まずはファンクションキーに割り当てたアイコンの表示／非表示動作、次にカーソルを矢印キーで移動して画面の切り替え動作及びカーソルがZ字型に移動する動作、次にカーソルに文字を入力する動作、最後にエンターキーで1・2行目と3・4行目を2秒おきに切り替える動作でCtrl+C押下で切り替え動作から入力モードへ戻ります。

[![RaspberryPiでLCD制御](https://img.youtube.com/vi/909Lnp_i6cc/0.jpg)](https://youtu.be/909Lnp_i6cc)

#構成
下記のように接続します、I/FはI2Cです。

|PinNo| LCD  |      RaspberryPi         |
|:---:|:------------|:------------------|
|1    |RST             |3.3V  |
|2    |SCL             |SCL1  |
|3    |SDA            |SDA1 |
|4    |VSS             |GND  |
|5    |VDD             | 3.3V|

#機能仕様
1. Deleteキーでカーソル位置の文字を消去、消去後のカーソル(点滅、以下同様)は移動させない
2. カーソルを矢印キーでリアルタイムに動かす
3. 任意の文字キー入力でカーソル位置に文字を表示させる、入力後のカーソルは右に移動させる
4. F1～F9キーに9種類のアイコンを割り当て押すたびに表示／非表示のトグル動作をさせる
5. カーソルを2行目から3行目に移動させる時に画面が1・2行目表示から3・4行目表示に切り替わる
6. エンターキーを押したら2秒おきに1・2行目と3・4行目表示を繰り返す、カーソルはOFFにする
7. 1行目の右端で右キーを押すと2行目の左端のようにZ字型に遷移する
8. Backspaceキーでカーソルの1つ左の文字を削除する、カーソルは左に移動ただしカーソルがはじの場合はZ字に遷移

#ソース

- getch()関数はpythonの標準入力であるinput()関数とは違い、文字入力をするとEnterキー入力なしで処理してくれる関数です。詳しくは参考ページを参照してください。Linuxディストリビューションで動作するもののようです。
- メイン処理のwhile文でキー入力をポーリングしています。入力される文字コードはaなら97、Enterなら13などif文で条件分岐させていますが、矢印キーやファンクションキーは特殊なようで例えば左矢印なら[27,91,68]と1回のキー入力で文字コードが3回入力されたり、F1キーなら[27,91,49,49,126]が入力される等特殊になっています。また、これらの特殊なキーは環境によって同じキーボードでも入力コードがかわってしまったので注意が必要です。理由はわかりません。
- slide_show()関数で2秒おきの画面切り替えをしていますが、sleep関数でカウントしている最中はgetch()でキー入力ができず解除処理ができないので、Ctrl+Cでキーボード割り込みを発生させて解除しています。
- st7032iは、カーソルの位置(アドレスカウンターACに格納されているアドレス)に文字が入力されるので、基本的な制御方法としてはACに書き込みたい位置のアドレスをwrite(Set DDRAM Address Instruction)してから、文字コードを書き込み(Write Data to RAM Instruction)となります。16×4行の位置とアドレスの関係は下のほうに記載します。


```LCD.py
import sys
from time import sleep
import smbus
import tty
import termios

def getch():
    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        return sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd,termios.TCSADRAIN,old)



def init():
    trials = 5
    for i in range(5):
        try:
            sleep(0.04)
            bus.write_i2c_block_data(st7032i,ctr_inst,[0x39,0x39,0x14,0x70|(contrust&0x0f),0x5c|(contrust&0x30)>>4,0x6c])
            sleep(0.2)
            bus.write_byte_data(st7032i,ctr_inst,0x0d)

        except IOError:
            if i==trials-1:
                sys.exit()


def view_icon(x):
    global icon
    global position

    bus.write_byte_data(st7032i,ctr_inst,0x39)

    if x == 0:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x40)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        else:
            bus.write_byte_data(st7032i,ctr_inst,0x40)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 1:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x42)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        else:
            bus.write_byte_data(st7032i,ctr_inst,0x42)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 2:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x44)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        else:
            bus.write_byte_data(st7032i,ctr_inst,0x44)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 3:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x46)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        else:
            bus.write_byte_data(st7032i,ctr_inst,0x46)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 4:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x47)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        elif icon[x] == 1:
            bus.write_byte_data(st7032i,ctr_inst,0x47)
            bus.write_byte_data(st7032i,ctr_data,0x08)
            icon[x] = 2
        elif icon[x] == 2:
            bus.write_byte_data(st7032i,ctr_inst,0x47)
            bus.write_byte_data(st7032i,ctr_data,0x18)
            icon[x] = 3
        elif icon[x] == 3:
            bus.write_byte_data(st7032i,ctr_inst,0x47)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 5:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x49)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        else:
            bus.write_byte_data(st7032i,ctr_inst,0x49)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 6:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x4b)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        else:
            bus.write_byte_data(st7032i,ctr_inst,0x4b)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 7:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x4d)
            bus.write_byte_data(st7032i,ctr_data,0x1e)
            icon[x] = 1
        elif icon[x] == 1:
            bus.write_byte_data(st7032i,ctr_inst,0x4d)
            bus.write_byte_data(st7032i,ctr_data,0x1a)
            icon[x] = 2
        elif icon[x] == 2:
            bus.write_byte_data(st7032i,ctr_inst,0x4d)
            bus.write_byte_data(st7032i,ctr_data,0x12)
            icon[x] = 3
        elif icon[x] == 3:
            bus.write_byte_data(st7032i,ctr_inst,0x4d)
            bus.write_byte_data(st7032i,ctr_data,0x02)
            icon[x] = 4
        elif icon[x] == 4:
            bus.write_byte_data(st7032i,ctr_inst,0x4d)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0
    elif x == 8:
        if icon[x] == 0:
            bus.write_byte_data(st7032i,ctr_inst,0x4f)
            bus.write_byte_data(st7032i,ctr_data,0x10)
            icon[x] = 1
        else:
            bus.write_byte_data(st7032i,ctr_inst,0x4f)
            bus.write_byte_data(st7032i,ctr_data,0x00)
            icon[x] = 0

    row = position/16
    col = position%16
    if row == 0:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x00|col&0x0f)
    elif row == 1:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x40|col&0x0f)
    elif row == 2:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x10|col&0x0f)
    elif row == 3:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x50|col&0x0f)


def clear_icon():
    bus.write_byte_data(st7032i,ctr_inst,0x39)
    bus.write_i2c_block_data(st7032i,0x80,[0x40,0x40,0x00, 0x80,0x42,0x40,0x00, 0x80,0x44,0x40,0x00, 0x80,0x46,0x40,0x00, 0x80,0x47,0x40,0x00])
    bus.write_i2c_block_data(st7032i,0x80,[0x49,0x40,0x00, 0x80,0x4b,0x40,0x00, 0x80,0x4d,0x40,0x00, 0x80,0x4f,0x40,0x00])


def clear():
    global position
    bus.write_byte_data(st7032i,ctr_inst,0x01)#clear Display
    sleep(0.001)
    clear_icon()
    sleep(0.001)
    return_home()


def move_left():
    global position

    if position%16 > 0 and  position%16 <= 15:
        position -= 1
        bus.write_i2c_block_data(st7032i,ctr_inst,[0x38,0x10])
        #print position
    elif position == 16:
        position -= 1
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x0f)
    elif position == 32:
        position -= 1
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x4f)

        bus.write_byte_data(st7032i,ctr_inst,0x38)
        for i in range(16):
            bus.write_byte_data(st7032i,ctr_inst,0x1c)

    elif position == 48:
        position -= 1
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x1f)
    else:
        return -1


def move_right():
    global position

    if position%16 >=0 and position%16 < 15:
        position += 1
        bus.write_i2c_block_data(st7032i,ctr_inst,[0x38,0x14])
        #print position
    elif position == 15:
        position += 1
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x40)
    elif position == 31:
        position += 1
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x10)

        bus.write_byte_data(st7032i,ctr_inst,0x38)
        for i in range(16):
            bus.write_byte_data(st7032i,ctr_inst,0x18)

    elif position == 47:
        position += 1
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x50)
    else:
        return -1


def move_up():
    global position

    if position/16 <> 0:
        position -=16
    else:
        return -1

    row = position / 16
    col = position % 16
    if row == 0:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x00|(col&0x0f))
    elif row == 1:

        bus.write_byte_data(st7032i,ctr_inst,0x38)
        for i in range(16):
            bus.write_byte_data(st7032i,ctr_inst,0x1c)

        bus.write_byte_data(st7032i,ctr_inst,0x80|0x40|(col&0x0f))
    elif row == 2:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x10|(col&0x0f))
    #print position




def move_down():
    global position

    if position/16 <> 3:
        position += 16
    else:
        return -1

    row = position / 16
    col = position % 16
    if row == 1:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x40|(col&0x0f))
    elif row == 2:

        bus.write_byte_data(st7032i,ctr_inst,0x38)
        for i in range(16):
            bus.write_byte_data(st7032i,ctr_inst,0x18)

        bus.write_byte_data(st7032i,ctr_inst,0x80|0x10|(col&0x0f))

    elif row == 3:
        bus.write_byte_data(st7032i,ctr_inst,0x80|0x50|(col&0x0f))
    #print position


def delete():
    bus.write_byte_data(st7032i,ctr_data,0x20)#write space
    bus.write_byte_data(st7032i,ctr_inst,0x38)
    bus.write_byte_data(st7032i,ctr_inst,0x10)#move cursor to left


def write_char(c):
    if c >= 0x00 and c <= 0xff:
        bus.write_byte_data(st7032i,ctr_data,c)
        bus.write_i2c_block_data(st7032i,ctr_inst,[0x38,0x10])#cursor left shift
        move_right()
    else:
        return -1


def backspace():
    move_left()
    delete()


def slide_show():
    global position
    print "Now presentation mode, Please press \"Ctrl+C\" to return edit mode."
    return_home()
    bus.write_byte_data(st7032i,ctr_inst,0x0c)#display on/off -> cursor off
    while True:
        try:
            sleep(2)
            move_down()
            move_down()
            sleep(2)
            move_up()
            move_up()
        except KeyboardInterrupt:
            print "Now edit mode"
            return_home()
            bus.write_byte_data(st7032i,ctr_inst,0x0d)#display on/off -> cursor on
            break

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def return_home():
    global position
    bus.write_byte_data(st7032i,ctr_inst,0x02)
    sleep(0.001)
    position = 0


st7032i = 0x3e
ctr_inst = 0x00#(C0=0, RS=0)
ctr_data = 0x40#(C0=0, RS=1)
contrust = 36#(0-63)
bus = smbus.SMBus(1)
icon = [0,0,0,0,0,0,0,0,0]
position = 0
state = 0

clear_icon()

init()

clear()

write_string("Hello World!    This program is written by      Python. :)")
return_home()

print "ready now. Please press Ctrl+C to stop quit program."

while True:

    key = ord(getch())

    if key == 3:
        break
    elif key == 13:
        print "Enter"
        slide_show()
    elif key == 127:
        print "DEL"
        delete()
    elif key == 8:
        print "BS"
        backspace()
    elif key == 27:
        key = ord(getch())
        if key == 91:
            key = ord(getch())
            if key == 68:
                print "left"
                move_left()
            elif key == 67:
                print "right"
                move_right()
            elif key == 65:
                print "up"
                move_up()
            elif key == 66:
                print "down"
                move_down()
            elif key == 49:
                key = ord(getch())
                if key == 49:
                    key = ord(getch())
                    if key == 126:
                        print "F1"
                        view_icon(0)
                elif key == 50:
                    key = ord(getch())
                    if key == 126:
                        print "F2"
                        view_icon(1)
                elif key == 51:
                    key = ord(getch())
                    if key == 126:
                        print "F3"
                        view_icon(2)
                elif key == 52:
                    key = ord(getch())
                    if key == 126:
                        print "F4"
                        view_icon(3)
                elif key == 53:
                    key = ord(getch())
                    if key == 126:
                        print "F5"
                        view_icon(4)
                elif key == 55:
                    key = ord(getch())
                    if key == 126:
                        print "F6"
                        view_icon(5)
                elif key == 56:
                    key = ord(getch())
                    if key == 126:
                        print "F7"
                        view_icon(6)
                elif key == 57:
                    key = ord(getch())
                    if key == 126:
                        print "F8"
                        view_icon(7)
            elif key == 50:
                key = ord(getch())
                if key == 48:
                    key = ord(getch())
                    if key == 126:
                        print "F9"
                        view_icon(8)
                elif key == 49:
                    key = ord(getch())
                    if key == 126:
                        print "F10"
                        view_icon(9)
                elif key == 51:
                    key = ord(getch())
                    if key == 126:
                        print "F11"
                elif key == 52:
                    key = ord(getch())
                    if key == 126:
                        print "F12"

    else:
        print "character:{0},    code:{1}".format(chr(key),key)
        write_char(key)


    sleep(0.005)
```
###文字表示座標の考え方

- 16文字×4行の配置とアドレスの関係を下記に示します。LCDの行数は表の行に、LCDの横の文字数は表の列にアドレスと管理用のPosition番号を記載しています。例えば1行目の1文字目はDDRAMアドレス0x00、3行目の2文字目は0x11といった感じです。
- わかりづらいですが、1行目の16文字目アドレス0x0Fの次の0x10アドレスは3行目の1文字目といったように、アドレス上は1行目→3行目→2行目→4行目になっています。これは本来、下記のようにst7032iのDDRAMアドレスの並びは40文字×2行なので、3行目以降の概念はないのですが17～32列目を3・4行目にしているからです。
>st7032データシートP15抜粋
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530581/866a7ede-e8e0-ca35-86d1-2933872a761a.png)

- このLCDはアドレスカウンターの値をREADできないのでPositionという変数で座標を保持しています。DDRAMアドレスは16進数、Position変数は10進数です。

|行数(※) |           |1  |2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|
|:--------------------:|:---------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|1(0000)               |DDRAM|00|01|02|03|04|05|06|07|08|09|0A|0B|0C|0D|0E|0F|
|                      |Position    | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|
|2(0100)               |DDRAM|40|41|42|43|44|45|46|47|48|49|4A|4B|4C|4D|4E|4F|
|                      |Position    |16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
|3(0001)               |DDRAM|10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F|
|                      |Position    |32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|
|4(0101)               |DDRAM|50|51|52|53|54|55|56|57|58|59|5A|5B|5C|5D|5E|5F|
|                      |Position    |48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|

※DDRAMアドレス上位4bits


#参考
- [ストロベリーリナックス社製 16x2 LCD](https://strawberry-linux.com/catalog/items?code=27001)
- [Pythonで1文字ずつ入力受け取り](https://torina.top/detail/428/)
- [Pythonでキーボードイベント取得](https://qiita.com/tobesan/items/d9f4bd543c3a85b49bb2)
- [RaspberryPiで学ぶ電子工作 講談社](http://bluebacks.kodansha.co.jp/books/9784062578912/appendix/)