【Raspberry Pi】I2CアダプターなしでLCD1602Aに文字を表示する

# はじめに
Qiita初投稿…ども…
というわけでタイトル通り、意外とありそうでなかった
 __「I2CアダプターなしでLCD1602に文字を表示」__ 
に挑戦したいと思います。
備忘録も兼ねてますので、拙いかもですがどうかご容赦を！:rolling_eyes:

#### ○成果物
・ラズパイ5でのLCD操作プログラム
（こんな感じで表示ができます）
![IMG_0802(1)(1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/58463a8a-a960-ebe7-fd88-bbe7c676c85f.gif)

# 動機
1. ラズパイ5の使い方を学びたい！
2. LCD(1602A)を使いたい、でもI2Cアダプターがない！
（Arduinoスタートセットの一部）
3. 最近の生成AIの実力を知りたい！

という3つの思いから、I2Cアタプターを使わずにラズパイ5のGPIOを直接使って~~GPIOの無駄遣い~~、LCD1602Aを操作したいと思います。
また、プログラミングの部分は長文読解に強いと言われる生成AI __「[Claude](https://claude.ai/)」__ くんと協力することで、最近の生成AIの力を測りたいと思います。


# ライブラリなど
- gpiozero 2.0
- Python 3.11.2

:::note warn
警告
本記事では文量の関係により、ライブラリのインストールなどの手順は記載しませんのでご注意ください。
諸兄が記事にされている内容で特に難しいことはないはずと思います。
:::

# LCD1602A とは
言わずもがなの16文字x2列の液晶ディスプレイです。
外観はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/e22b4e67-61b4-dc47-60c1-2b87b113d271.png)

Arduinoのスタートセットとして購入し、データシートには文字コードやタイミングチャートがなかったので、秋月電子通商さんのものを参考にしました。
（拝借がマズかったらご指摘ください…）

#### ■タイミングチャート
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/84c5321b-ba4c-ac8f-3234-27a25e7f38e0.png)

#### ■文字コード表

![無題 1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/f47262e3-7b3d-3ef8-87f5-1d3fe1c9f911.jpeg)



■プログラム作成手順
##### 1.Claudeにお願いする
ではまずClaudeくんにLCD1602Aをコントロールするためのプログラムを作成するようお願いしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/8fb8af8a-d7c3-f0c6-5d60-3598a037641b.png)

簡単なお願いに対して、結構しっかりしたコードを作成してくれました。

```python:python
import RPi.GPIO as GPIO
import time

class LCD1602A:
    def __init__(self, rs, e, d4, d5, d6, d7):
        self.RS = rs
        self.E = e
        self.D4 = d4
        self.D5 = d5
        self.D6 = d6
        self.D7 = d7
        
        GPIO.setmode(GPIO.BCM)
        GPIO.setup([self.RS, self.E, self.D4, self.D5, self.D6, self.D7], GPIO.OUT)
        
        self.initialize()
    
    def initialize(self):
        self.write_cmd(0x33)  # 8ビットモードに初期化
        self.write_cmd(0x32)  # 4ビットモードに設定
        self.write_cmd(0x28)  # 2ライン表示、5x8ドットフォント
        self.write_cmd(0x0C)  # ディスプレイON、カーソルOFF
        self.write_cmd(0x06)  # エントリーモード設定
        self.write_cmd(0x01)  # ディスプレイクリア
        time.sleep(0.002)     # クリア後の待機時間
    
    def write_cmd(self, cmd):
        GPIO.output(self.RS, GPIO.LOW)
        self.write_four_bits(cmd >> 4)
        self.write_four_bits(cmd & 0x0F)
    
    def write_char(self, char):
        GPIO.output(self.RS, GPIO.HIGH)
        self.write_four_bits(ord(char) >> 4)
        self.write_four_bits(ord(char) & 0x0F)
    
    def write_four_bits(self, data):
        GPIO.output(self.D4, bool(data & 0x01))
        GPIO.output(self.D5, bool(data & 0x02))
        GPIO.output(self.D6, bool(data & 0x04))
        GPIO.output(self.D7, bool(data & 0x08))
        self.pulse_enable()
    
    def pulse_enable(self):
        GPIO.output(self.E, GPIO.HIGH)
        time.sleep(0.0005)
        GPIO.output(self.E, GPIO.LOW)
        time.sleep(0.0005)
    
    def clear(self):
        self.write_cmd(0x01)
        time.sleep(0.002)
    
    def set_cursor(self, row, col):
        offsets = [0x00, 0x40, 0x14, 0x54]
        self.write_cmd(0x80 | (offsets[row] + col))
    
    def print(self, text):
        for char in text:
            self.write_char(char)
    
    def close(self):
        GPIO.cleanup()

# 使用例
if __name__ == "__main__":
    try:
        # GPIOピン番号は実際の接続に合わせて変更してください
        lcd = LCD1602A(rs=20, e=21, d4=6, d5=13, d6=19, d7=26)
        
        lcd.clear()
        lcd.print("Hello, World!")
        lcd.set_cursor(1, 0)
        lcd.print("Raspberry Pi")
        
        time.sleep(5)  # 5秒間表示
    finally:
        lcd.close()
```
作成されたコードに合わせ、下のイメージのように配線してプログラムを実行してみました。（ラズパイ5のモデルがなかったのでラズパイ4Bで代用しているのはご愛嬌）

![LCD1602A_and_RasPi_ブレッドボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/b9b4e26a-67c1-54ad-1c87-88de8c9d9fd0.png)

すると、ラズパイ5がRPiに対応していないことに由来しそうなエラーが出たので、gpiozeroを使って書き直すようお願いしました。

```python:python(修正後1)
from gpiozero import OutputDevice
import time

class LCD1602A:
    def __init__(self, rs, e, d4, d5, d6, d7):
        self.RS = OutputDevice(rs)
        self.E = OutputDevice(e)
        self.D4 = OutputDevice(d4)
        self.D5 = OutputDevice(d5)
        self.D6 = OutputDevice(d6)
        self.D7 = OutputDevice(d7)
        
        self.initialize()
    
    def initialize(self):
        self.write_cmd(0x33)  # 8ビットモードに初期化
        self.write_cmd(0x32)  # 4ビットモードに設定
        self.write_cmd(0x28)  # 2ライン表示、5x8ドットフォント
        self.write_cmd(0x0C)  # ディスプレイON、カーソルOFF
        self.write_cmd(0x06)  # エントリーモード設定
        self.write_cmd(0x01)  # ディスプレイクリア
        time.sleep(0.002)     # クリア後の待機時間
    
    def write_cmd(self, cmd):
        self.RS.off()
        self.write_four_bits(cmd >> 4)
        self.write_four_bits(cmd & 0x0F)
    
    def write_char(self, char):
        self.RS.on()
        self.write_four_bits(ord(char) >> 4)
        self.write_four_bits(ord(char) & 0x0F)
    
    def write_four_bits(self, data):
        self.D4.value = bool(data & 0x01)
        self.D5.value = bool(data & 0x02)
        self.D6.value = bool(data & 0x04)
        self.D7.value = bool(data & 0x08)
        self.pulse_enable()
    
    def pulse_enable(self):
        self.E.on()
        time.sleep(0.0005)
        self.E.off()
        time.sleep(0.0005)
    
    def clear(self):
        self.write_cmd(0x01)
        time.sleep(0.002)
    
    def set_cursor(self, row, col):
        offsets = [0x00, 0x40, 0x14, 0x54]
        self.write_cmd(0x80 | (offsets[row] + col))
    
    def print(self, text):
        for char in text:
            self.write_char(char)
    
    def close(self):
        self.clear()
        self.RS.close()
        self.E.close()
        self.D4.close()
        self.D5.close()
        self.D6.close()
        self.D7.close()

# 使用例
if __name__ == "__main__":
    try:
        # GPIOピン番号は実際の接続に合わせて変更してください
        lcd = LCD1602A(rs=11, e=24, d4=22, d5=18, d6=16, d7=12)
        
        lcd.clear()
        lcd.print("Hello, World!")
        lcd.set_cursor(1, 0)
        lcd.print("Raspberry Pi")
        
        time.sleep(5)  # 5秒間表示
    except Exception as e:
        print(f"エラーが発生しました: {e}")
    finally:
        if 'lcd' in locals():
            lcd.close()
```
この間、約1分程度。
短い時間でいい感じに修正してくれました。

##### 2.動作確認
さて、本当ならここで中身のチェックですが、動きそうな雰囲気なのでテストしてみましょう。
テストコードはこんな感じ。
```python:python
import time
import sys
import select

# LCD動作確認
def test_LCD():
        lcd = None
        try:
            # GPIOピン番号は実際の接続に合わせて変更すること(GPIO17 = 17)
            lcd = LCD1602A(rs=17, e=8, d4=25, d5=24, d6=23, d7=18)
            
            lcd.clear()
            print_onDebug("1行目を表示")
            lcd.print("Hello, World!")
            lcd.set_cursor(1, 0)
            print_onDebug("2行目を表示")
            lcd.print("Raspberry Pi")
            
            print("Press Enter to exit...")
            
            while True:
                # 標準入力に何か入力があるかチェック
                if select.select([sys.stdin,], [], [], 0.0)[0]:
                    # 入力があればループを抜ける
                    break
                
                time.sleep(0.1)  # CPU使用率を下げるための短い待機
        
        except Exception as e:
            print(f"エラーが発生しました: {e}")
        finally:
            if lcd:
                lcd.clear()
                lcd.print("Goodbye!")
                time.sleep(2)  # "Goodbye!"を2秒間表示
                lcd.close()
            print("プログラムを終了します。")
```
Claudeくんが生成してくれた使用例のそのまんまですね。
なおここで、プログラムにデバッグモード変数を導入し、デバッグモード時のみログ出力するヘルパー関数を導入しています。

```python:python
DEBUG_IS = True

def print_onDebug(text):
    if DEBUG_IS:
        print(f"DebugLog: {text}")
```
テストプログラムを実行してみたところ…

![IMG_0802(1)(1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/58463a8a-a960-ebe7-fd88-bbe7c676c85f.gif)

###### 動きました！！すごい！！（ほぼ何もしてない）
ちゃんと動くか半信半疑でしたが、まさかここまで作れるとは驚きです。
プログラマーが淘汰される未来も近そう…。

##### 3.中身のチェック
では最後にプログラムの中身のチェックをしましょう。
タイミングチャートおよび文字コード表とにらめっこして確認したところ…

1. ASCII以外の文字列には非対応
![無題 2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3838080/f171df65-75a0-e642-120e-85abc63be45b.jpeg)
➡ write_charメソッドの改修が必要そう


2. write_four_bitsのEピン立ち上げ順序がおかしい
（本来ならD4~7ピンの立ち上げ前？）
```python:python
    def write_four_bits(self, data):
        self.D4.value = bool(data & 0x01)
        self.D5.value = bool(data & 0x02)
        self.D6.value = bool(data & 0x04)
        self.D7.value = bool(data & 0x08)
        self.pulse_enable() # ⇐ ここ
```
➡ write_four_bitsの改修が必要そう

という点が気になるものの、おおむね動作には問題ありませんでした。
改めて本当にすごい。


# まとめ

というわけで、今回は生成AIと協力し、ラズパイ5のGPIOを使ってLCDを操作してみました。
少し課題はあるものの、ほぼ正確なコードを作成してくることがわかり、汎用部品の操作プログラムを作成するような仕事は生成AIに取って代わられるような雰囲気をチョー感じます。
Claudeくんの「言いたいことを汲み取る能力」にもビックリです。

# 今後
次回以降は以下の課題に対応していきたいと思います。

1. ASCII以外の文字に対応する
2. write_four_bitsのEピン立ち上げ順序を修正
3. 長文の表示機能を追加
4. 部品モジュールとして使用する際の処理・API追加

# 最後に
初投稿をご覧いただきありがとうございました！
これからも組み込みやIoTに関する記事を投稿していく予定ですので、よろしくお願いいたします。:blush: