RaspberrypiでLCD1602Aのi2c通信

ラズパイでLCDを制御してハローワールドを表示する。
使用ライブラリはpigpio
i2c接続するLCDはosoyoo製の物を使うが、たぶん他社製品でも似たようなもの
とりあえずpythonで実行してみる

プログラムを実行する前にpigpiodの起動を忘れないこと

$ sudo pigpiod

サンプルコード
製品ページwgetコマンドはosoyoocomとなっていて、「.」が抜けているので
追加しないとエラーになります
マニュアル osoyooのマニュアルがどこにあるのかわからなかったので
似たような製品を秋月で調べて参考にしました

サンプルコードを参考にpigpioで動くように改変

import time
import pigpio   # pigpioのインポート

I2C_ADDR  = 0x3f    # i2cアドレス i2cdetect -y 1 とかで調べておく
LCD_WIDTH = 16      # 表示文字数

LCD_BACKLIGHT  = 0x08   # 00001000 バックライトの点灯

ENABLE = 0b00000100     # 書き込みを可能にするビット?よくわからん

LCD_CHR = 1     # データ送信モード
LCD_CMD = 0     # コマンド送信モード

LCD_LINE_1 = 0x80   # 1-Lineアドレス(上段)
LCD_LINE_2 = 0xC0   # 2-Lineアドレス(下段)

# タイミング調整用の定数
E_PULSE = 0.0005
E_DELAY = 0.0005

# pigpio初期設定
pi = pigpio.pi()
lcd = pi.i2c_open(1, I2C_ADDR, 0)

# 「ハロー ワールド!」表示用の配列
# 文字コードはマニュアル参照
hello = [202, 219, 176, 32, 220, 176, 217, 196, 222, 33]

def lcd_init():             # LCDの初期設定
    lcd_byte(0x33,LCD_CMD)  # 110011
    lcd_byte(0x32,LCD_CMD)  # 110010
    lcd_byte(0x06,LCD_CMD)  # 000110 カーソルの移動方向
    lcd_byte(0x0C,LCD_CMD)  # 001100 ディスプレイ ON, カーソル OFF
    lcd_byte(0x28,LCD_CMD)  # 101000
    lcd_byte(0x01,LCD_CMD)  # 000001 ディスプレイのクリア
    time.sleep(E_DELAY)

def lcd_byte(bits, mode):   # データ書き込み
    bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
    bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT

    # 上位ビット
    pi.i2c_write_byte(lcd, bits_high)
    lcd_toggle_enable(bits_high)

    # 下位ビット
    pi.i2c_write_byte(lcd, bits_low)
    lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
    time.sleep(E_DELAY)
    pi.i2c_write_byte(lcd, (bits | ENABLE))
    time.sleep(E_PULSE)
    pi.i2c_write_byte(lcd, (bits & ~ENABLE))
    time.sleep(E_DELAY)

def lcd_string(message,line):
  message = message.ljust(LCD_WIDTH," ")

  lcd_byte(line, LCD_CMD)

  for i in range(LCD_WIDTH):
    lcd_byte(ord(message[i]),LCD_CHR)

def lcd_jp(line):   # 日本語表示用
    lcd_byte(line, LCD_CMD)

    for i in hello:
        lcd_byte(i, LCD_CHR)

def main():
  lcd_init()

  while True:
    lcd_string("Hello!",LCD_LINE_1)
    lcd_string("World!",LCD_LINE_2)

    time.sleep(3)

    lcd_jp(LCD_LINE_1)
    lcd_jp(LCD_LINE_2)

    time.sleep(3)

if __name__ == '__main__':

  try:
    main()
  except KeyboardInterrupt:
    pass
  finally:
    lcd_byte(0x01, LCD_CMD)

実行すると

Hello!
World!

3秒後

ハロー ワールド!
ハロー ワールド!

3秒後

以降ループ

と、LCDに表示される

送信データの制御について

LCD1602のデータシートにはi2c通信についてほとんど記載が無いので
どのようにデータを制御しているのか分かりませんでした
なのでサンプルコードとにらめっこして考えた結果
おそらく下記のようになると思います (推測です)

i2c制御フォーマット (S,A,Pは省略)

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
S7 S6 S5 S4 S3 S2 S1 S0 D7 D6 D5 D4 BL EN RW MO
S7 S6 S5 S4 S3 S2 S1 S0 D3 D2 D1 D0 BL EN RW MO

8 - 15 : スレーブアドレス用
0 - 7 : コマンド、データ書き込み用ビット

D4 - D7 : 上位ビット
D0 - D3 : 下位ビット
BL : バックライトのON(1)、OFF(0)
EN : 書き込みの有効(1)、無効(0)
RW : 読み込み(1)、書き込み(0)
MO : データ送信モード(1)、コマンド送信モード(0)

LCDへ書き込むためにはENの書き込み有効化したあと無効化する必要があり
上位ビットを書き込んだあとに下位ビットを書き込む必要がある
つまり、最低4回書き込むことになる
サンプルコードではEN有効を書き込む前にEN無効を書き込んでいるので
計6回書き込んでいる(必要があるのかどうかはわからない)

例、 1-Line(上段)にAを書き込む場合

# 表示するラインを設定
10001100 # 1-Line(0x80)の上位ビットを書き込み有効 # 140
10001000 # 1-Line(0x80)の上位ビットを書き込み無効 # 136
00001100 # 1-Line(0x80)の下位ビットを書き込み有効 # 12
00001000 # 1-Line(0x80)の下位ビットを書き込み無効 # 8

# 表示する文字を送信
01001101 # A(01000001)の上位ビットを書き込み有効 # 77
01001001 # A(01000001)の上位ビットを書き込み無効 # 73
00011101 # A(01000001)の下位ビットを書き込み有効 # 29
00011001 # A(01000001)の下位ビットを書き込み無効 # 25

自分で考えてこうなるのでは?と思っているだけなので
合っているかどうかはわかりません
思った通りの表示はしているので大丈夫だと思いますが参考程度に

教訓

サンプルコードを改変して表示させるのが楽