2025年8月12日火曜日

I2C接続1602キャラクタLCD(HD44780+PCF8574A)の操作

 前回は、1602 LCD をビットパラレルモード(4ビットデータ通信)で直接制御しました。これは LCD の各制御線(RS、EN、D4〜D7など)を Raspberry Pi Pico W の GPIO に1本ずつ接続して動かす方法で、仕組みを理解するにはとても勉強になります。

しかし、この方法では接続に使うピンが多くなり、GPIOの数が限られている小型マイコンでは少し不便に感じることもあります。

そこで今回は、**PCF8574A という I/O エクスパンダ(拡張チップ)**を使って、I²C(アイ・スクエアド・シー)通信で LCD を制御する方法を紹介します。この方法を使うと、LCD の操作に必要な信号を **わずか2本の通信線(SDA と SCL)**にまとめられるため、配線がとてもスッキリし、他のセンサーやデバイスとの接続も簡単になります。

初心者の方でも、I²C の基本を学びながら、LCD 表示の実用的な使い方にステップアップできる内容になっています。マイコンの入出力ピンを節約しつつ、LCD にメッセージを表示したい方にぴったりの方法です。

PCF8574A と I²C って何?

PCF8574A
PCF8574A は NXP 製の “I/O エクスパンダ” と呼ばれる IC で、

  • 8 本の GPIO を I²C でまとめて増設できる

  • 各ピンは入力にも出力にも使える “擬似オープンドレイン” 方式

  • アドレスピン(A2–A0)の組み合わせで 0x38 – 0x3F の 8 通りの I²C アドレスを選択可能

今回の 1602 LCD では、PCF8574A の 8 本を下図のように割り当てるのが定番です。

PCF8574A ピンLCD 信号役割
P0RSデータ/コマンド切替
P1RW読み書き切替(通常は GND = 書き込み専用)
P2ENイネーブルパルス
P3BLバックライト ON/OFF
P4–P7D4–D74 ビットデータバス

I²C(Inter‑Integrated Circuit)
I²C は 1980 年代に Philips(現 NXP)が開発した 2 線式のシリアル通信規格で、

  1. SDA(データ線) と SCL(クロック線) だけで通信

  2. 7 ビットまたは 10 ビットの アドレスでデバイスを識別

  3. マスター(Pico W) がクロックを生成し、スレーブ(PCF8574A など) に読み書き要求を出す

  4. 複数デバイスを同じ SDA/SCL に “バス” でぶら下げられる(プルアップ抵抗が必須)

初心者が最初につまずきやすいポイントは「プルアップ抵抗がないと信号が正しく High にならない」ことですが、多くの市販 I²C‑LCD モジュールには 4.7 kΩ 付近の抵抗が実装済みなので安心です。

回路図

PicoWのI2Cポートは、id=1,SDA=GP14,SCL=GP15を利用

Raspberry Pi Pico W + MicroPython で I²C バスをスキャンし、
見つかったデバイスのアドレスを 16 進数表記でターミナルに表示するだけの最小サンプルです。
(LCD には表示せず、シリアル REPL 上に出力します)

# i2c_address_scan.py
#
# I2C バスをスキャンして接続されているデバイスのアドレスを表示する
# ───────────────────────────────────────────
from machine import I2C, Pin

# I²C1 (SDA=GP14, SCL=GP15, 100 kHz) を使用
i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=100_000)

# スキャン実行
devices = i2c.scan()

# 結果を表示
if devices:
    print("検出した I²C デバイスのアドレス:")
    for addr in devices:
        print("  •", hex(addr))
else:
    print("I²C デバイスが見つかりません。配線やプルアップ抵抗を確認してください。")


>>> %Run -c $EDITOR_CONTENT

MPY: soft reboot
検出した I²C デバイスのアドレス:
  • 0x27
>>>

LCDに表示するコード

from machine import I2C, Pin
from utime import sleep

# ── I²C 設定(I2C1, SDA=GP14, SCL=GP15)──────────────
i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=100_000)

# ── LCD/PCF8574A 関連定数 ──────────────────────────
LCD_ADDR = 0x27     # モジュールの I²C アドレス(基板により 0x3F の場合も)
LCD_EN   = 0x04     # Enable ビット
LCD_BL   = 0x08     # バックライト ON
CMD      = 0x00     # コマンドモード
CHR      = 0x01     # 文字モード
LINE1    = 0x80     # 1 行目の先頭アドレス
LINE2    = 0xC0     # 2 行目の先頭アドレス

buf = bytearray(2)  # I²C 送信用バッファ(2 バイト)

# ── 4 ビット・データ送信関数 ────────────────────────
def lcd_write(bits, mode):
    # 上位 4 ビット
    data = (bits & 0xF0) | mode
    buf[0] = data | LCD_EN | LCD_BL   # EN=1
    buf[1] = data | LCD_BL           # EN=0
    i2c.writeto(LCD_ADDR, buf)
    sleep(0.0001)

    # 下位 4 ビット
    data = ((bits << 4) & 0xF0) | mode
    buf[0] = data | LCD_EN | LCD_BL
    buf[1] = data | LCD_BL
    i2c.writeto(LCD_ADDR, buf)
    sleep(0.0001)

# ── LCD 初期化 ─────────────────────────────────────
def lcd_init():
    lcd_write(0x33, CMD)   # 初期化シーケンス 1
    lcd_write(0x32, CMD)   # 初期化シーケンス 2
    lcd_write(0x06, CMD)   # エントリーモード設定
    lcd_write(0x0C, CMD)   # 表示 ON, カーソル/ブリンク OFF
    lcd_write(0x28, CMD)   # 2 行, 5×7 ドット
    lcd_write(0x01, CMD)   # 画面クリア
    sleep(0.002)           # クリア後は 1.52 ms 以上待機

# ── 文字列表示 ─────────────────────────────────────
def lcd_print(text):
    for ch in text:
        lcd_write(ord(ch), CHR)

# ── メイン処理 ─────────────────────────────────────
lcd_init()            # LCD を初期化
lcd_write(LINE1, CMD) # 1 行目の先頭にカーソル移動
lcd_print("Hello World!")  # 「Hello World!」を表示
lcd_write(LINE2, CMD) # 1 行目の先頭にカーソル移動
lcd_print("RaspberryPiPicoW")  # 「Hello World!」を表示

# 以上で完了。以降コードが続かない限り、表示はそのまま残ります。


アドレスを自動検出して表示するコード

# Raspberry Pi Pico W + MicroPython
# I²C 接続 1602 LCD(HD44780 + PCF8574A)
# 「Hello World!」を 1 行目に表示するだけの最小サンプル
#
# ──────────────────────────────────────────────
from machine import I2C, Pin
from utime import sleep_ms, sleep_us

# ── I²C 設定(I2C1 を GP14/GP15 で使用) ─────────
i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=100_000)

# ── PCF8574(A) のアドレスを自動検出 ─────────────
_PCF_ADDRS = list(range(0x20, 0x28)) + list(range(0x38, 0x40))
found = [a for a in i2c.scan() if a in _PCF_ADDRS]
if not found:
    raise OSError("PCF8574(A) が見つかりません。配線とプルアップ抵抗を確認してください。")
LCD_ADDR = found[0]      # 見つかった最初のデバイスを採用

# ── LCD / PCF8574A ビット定義 ───────────────────
LCD_EN = 0x04            # Enable
LCD_BL = 0x08            # Back‑Light 常時 ON
CMD    = 0x00            # コマンドモード
CHR    = 0x01            # 文字モード
LINE1  = 0x80            # 1 行目アドレス
LINE2  = 0xC0            # 2 行目アドレス

_buf = bytearray(2)      # 2 バイトバッファ(EN=1 → EN=0

# ── 4 ビットデータ送信 ──────────────────────────
def _lcd_write(nibble, mode):
    # 上位 4 ビット
    data = (nibble & 0xF0) | mode | LCD_BL
    _buf[0] = data | LCD_EN   # EN = 1
    _buf[1] = data            # EN = 0
    i2c.writeto(LCD_ADDR, _buf)
    sleep_us(50)

    # 下位 4 ビット
    data = ((nibble << 4) & 0xF0) | mode | LCD_BL
    _buf[0] = data | LCD_EN
    _buf[1] = data
    i2c.writeto(LCD_ADDR, _buf)
    sleep_us(50)

# ── LCD 用ラッパ ────────────────────────────────
def _cmd(value):  _lcd_write(value, CMD)
def _char(value): _lcd_write(value, CHR)

# ── 初期化シーケンス ────────────────────────────
def lcd_init():
    sleep_ms(50)          # 電源 ON 待ち
    _cmd(0x33)            # 8‑bit → 8‑bit
    _cmd(0x32)            # 8‑bit → 4‑bit
    _cmd(0x06)            # エントリーモード:左→右、自動インクリメント
    _cmd(0x0C)            # 表示 ON, カーソル OFF, ブリンク OFF
    _cmd(0x28)            # 2 行, 5×7 ドット
    _cmd(0x01)            # ディスプレイクリア
    sleep_ms(2)

# ── 文字列表示(行番号は 0 or 1) ────────────────
def lcd_print(text, line=0, pos=0):
    addr = (LINE1 if line == 0 else 0xC0) + pos
    _cmd(addr)
    for ch in text:
        _char(ord(ch))

# ──────────────────────────────────────────────
# メイン
lcd_init()
lcd_print("Hello World!", line=0, pos=0)
lcd_print("RaspberryPiPicoW", line=2, pos=0)
# ここで終了。LCD の内容は電源を切るまで保持されます。


処理の流れ

  1. 🔌 I²C バスをセットアップ

    • I2C(1, sda=GP14, scl=GP15, freq=100 kHz) で Pico W の I²C1 を初期化。

  2. 🔍 PCF8574A のアドレスをスキャン

    • i2c.scan() でバス上の全デバイスを検索し、
      0x20–0x27 または 0x38–0x3F の範囲にある最初のアドレスを LCD_ADDR として確定。

  3. 🪛 LCD 制御用ビット定義を準備

    • LCD_ENLCD_BLCMDCHRLINE1LINE2 などの定数を用意。

    • 2 バイトの送信用バッファ _buf を確保。

  4. ✉️ 4 ビットデータ送信関数 _lcd_write() を実装

    • 上位 4 ビット → EN パルス → 下位 4 ビット → EN パルス。

    • 各トグル後に sleep_us(50) で 50 µs 待機。

  5. 🛠️ コマンド/データ送信ラッパを定義

    • _cmd(value) はコマンドモード、 _char(value) は文字モードで _lcd_write() を呼び出す。

  6. ⚙️ LCD 初期化 lcd_init() を実行

    • 電源投入待ち(50 ms)。

    • 8‑bit → 8‑bit → 4‑bit の初期化シーケンス。

    • 表示設定(エントリー、表示 ON、2 行モード)。

    • 画面クリア後、2 ms 待機。

  7. 🖊️ 文字列表示 lcd_print() を定義

    • 行番号(0 or 1)と桁位置を元に DDRAM アドレスを計算し、 _cmd() でカーソル移動。

    • 文字列を 1 文字ずつ _char() で送信。

  8. 🚀 メイン処理

    • lcd_init() で LCD を初期化。

    • lcd_print("Hello World!", 0, 0) で 1 行目に「Hello World!」。

    • lcd_print("RaspberryPiPicoW", 1, 0) で 2 行目に「RaspberryPiPicoW」。

  9. 💤 スクリプト終了後も表示は保持

    • ループを回さないためプログラムは終了するが、LCD の内容は電源を切るまでそのまま残る。


0 件のコメント:

コメントを投稿

Google Spread Sheetの利用

  以前に ESP32 で作っていたものを、Raspberry Pi Pico W + MicroPythonで再現してみました。 1.Googleスプレッドシートの設定 1.Google Drive → 右クリック → Google スプレッドシート 2.作成して共有をクリック