スキップしてメイン コンテンツに移動

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_EN, LCD_BL, CMD, CHR, LINE1, LINE2 などの定数を用意。

    • 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 の内容は電源を切るまでそのまま残る。


コメント

このブログの人気の投稿

Raspberry Pi Pico Wを使ってみよう

   Raspberry Pi Pico W(ラズベリーパイ ピコ ダブリュー) は、英国 Raspberry Pi 財団が 2022 年に発売した Wi-Fi 搭載マイコンボードです。従来の「Raspberry Pi」と聞くと Linux が動くシングルボードコンピュータ(SBC)を思い浮かべがちですが、Pico W は マイクロコントローラ (MCU)に分類され、いわゆる “組み込み開発” を手軽に始められるデバイスです。搭載 MCU はデュアルコア Arm Cortex-M0+(133 MHz 動作)の RP2040 。ここに Infineon 製 CYW43439 チップが追加され、 2.4 GHz IEEE 802.11 b/g/n Wi-Fi(BLE 対応 FW も提供中) が使えるのが最大の特徴です。  開発言語は MicroPython や CircuitPython が真っ先に紹介されることが多いのですが、 公式 Pico SDK を使えば C/C++ でも本格的に開発 できます。SDK は CMake ベースで Windows/macOS/Linux いずれでも利用可能。さらに、Arduino Core RP2040 が整備されたことで Arduino IDE 2.x からも “スケッチ感覚” で書き込みが可能 になりました。したがって、 「まずは Python でサッと試す」 「より高速化や省メモリ化が必要になったら C/C++ へ移行」 といった二段構えの学習ルートが取れるのが魅力です。 メニュー(基礎編) 01. Raspberry Pi Pico Wの開発環境を整える。 02. スイッチで 発光ダイオード(LED) を点灯・消灯させる 03. タイマーで発光ダイオード(LED)を1秒ごとに点滅させる 04. シリアル通信(オウム返し ) 05. シリアル通信(発光ダイオードの点灯・消灯) 06. サーボモーターを動かしてみよう(SG90制御入門) 07. DCモータを動かす(PWM) 08. IRリモートでRGBLED点灯 09. アナログ電圧を測定する(ADCの基本) 10. GPIO割り込み処理 11. リレーを駆動してLEDを制御する 12. DFPlayer で MP3 再生 13. 7 セグ 4 ...

スイッチで発光ダイオード(LED)を点灯・消灯させる

 スイッチを使って発光ダイオード(LED)を制御してみましょう。今回は、スイッチをオンにすると LED が点灯し、オフにすると消灯するように動作させます。もちろん、スイッチと LED を直接接続するのではなく、適切な回路を介して制御します。 1.回路 GPIO16 はデジタル入力として使用し、内部プルアップを有効にします。GPIO15 は出力として設定します。なお、接続には 1kΩ(1キロオーム)の抵抗を使用します。 【ソースコード】 # スイッチで LED を ON / OFF する簡単な例 #   GPIO15 : LED(出力)— LOW で消灯、HIGH で点灯 #   GPIO16 : スイッチ(入力)— 内部プルアップ抵抗を使用 # # ※ 配線例 #   LED のアノード → 1 kΩ 抵抗 → GPIO15 #   LED のカソード → GND #   スイッチ片側   → GPIO16 #   スイッチ反対側 → GND # # ★ 動作 #   スイッチを押す(GPIO16 が LOW)   → LED 点灯 #   スイッチを離す(GPIO16 が HIGH) → LED 消灯 from machine import Pin    # GPIO 制御ライブラリ import utime               # 時間関連(今回は使用しないが拡張用に読み込み) led = Pin ( 15 , Pin.OUT)                 # GPIO15 を出力モードに設定(LED 用) sw   = Pin ( 16 , Pin.IN, Pin.PULL_UP)     # GPIO16 を入力モード+内部プルアップ有効 while True:     if sw. value () == 1 :      # プルアップなので押していないときは 1   ...

シリアル通信(オウム返し)

 Raspberry Pi Pico Wは単体で使うのも便利ですが、パソコンや他のマイコンなど、さまざまな機器と通信できるとさらに活用の幅が広がります。 そこで今回は「シリアル通信」について紹介します。 まずは、パソコンから送られてきたデータをPico Wが受け取り、同じデータをそのままパソコンに送り返す、いわゆる「オウム返し(エコー)」を実装してみましょう。 シリアル通信とは? シリアル通信は、マイコンとパソコンや他の機器がデータを1ビットずつ順番に送受信する通信方式です。Raspberry Pi Pico WではUART(Universal Asynchronous Receiver/Transmitter)というハードウェアを使って実現します。 接続回路  id      TX       RX        baudrate     parity    stopbit  0  GPIO0 GPIO1   9600  なし  1  1  GPIO4 GPIO5   9600  なし  1 今回はUART1(GPIO4:TX、GPIO5:RX)を使用します。 ソースコード例 from machine import UART, Pin # UART1ポートを9600bpsで初期化(ボーレートを指定) sirial = UART ( 1 , 9600 ) # UARTの詳細設定: 9600bps 、データビット8、パリティ無し、ストップビット1で初期化 sirial. init ( 9600 , bits = 8 , parity = None, stop = 1 ) # UARTで文字列を送信する(※MicroPythonではバイト列で送るのが正しいため b '' にしたほうが安全) sirial. write (b 'Hello World \n\r ' )  # 起動時に「Hello World」を送信 # メインループ while True:     # 受信バッファにデータが存在するかチェック     if sirial. any () > 0 : ...