Python × Pico 電子工作 | #08 LCDに文字を表示しよう

今回は LCD1602(16 文字 × 2 行の液晶ディスプレイ)に文字を表示します。前回(#07)の 7セグメント LED は数字 1 文字を出すのに 8 本のピン が必要でしたが、LCD なら I2C 通信 を使って VCC・GND・SDA・SCL の 4 本だけ で繋がります。しかも 文字列 が表示できるので、センサー値・メッセージ・カウンタなどを直接読めるディスプレイになります。

  • I2C 通信の仕組み(SDA・SCL の 2 本で複数デバイスと通信)
  • LCD1602 の配線(VCC・GND・SDA・SCL の 4 本だけ)
  • pico_lcd_init ・pico_lcd_print ・pico_lcd_clear の使い方
  • Hello World、2 行表示、カウンタ、スプラッシュメッセージなどの応用

PycoBlocks(MicroPythonモード) を開き、左カテゴリ「Pico 電子工作 / 表示」の「LCD初期化」「LCDに表示」「LCDをクリア」 ブロックが今回の主役です。I2C アドレスは 0x27(ELEGOO 互換 LCD のデフォルト)を前提に話を進めます。

LCDに文字を表示しよう

I2C 通信の仕組み:2 本の信号線で何台ものデバイスと通信

I2C(Inter-Integrated Circuit、アイ・スクエア・シー)は、Philips(現 NXP)が開発したシリアル通信 規格です。1 対 1 ではなく 1 台のマスター(Pico)と複数のスレーブ(LCD・温湿度センサー・加速度センサーなど)が 同じ 2 本の線(SDA・SCL)を共有して通信できるのが最大の特徴です。

信号線正式名称役割Pico 側
SDASerial DAtaデータ本体(読み書き)GP0(I2C0)・GP2(I2C1)など
SCLSerial CLockマスターが叩くクロック信号GP1(I2C0)・GP3(I2C1)など
VCC—電源(3.3V または 5V)3V3 ピン(または VBUS)
GND—共通グランドGND ピン

下の図のように、LCD と Pico は VCC・GND・SDA・SCL の 4 本だけ でつながります。7セグメント LED が a〜g+コモンで 8 本必要だったのと比べると、配線が一気にスッキリします。

I2C 通信で Pico と LCD1602 を VCC・GND・SDA・SCL の 4 本だけで接続する図
I2C は VCC・GND・SDA・SCL の 4 本だけで LCD とつながる。SDA(データ)と SCL(クロック)の 2 本の信号線を共有して通信する。

📝 I2C アドレスとは

同じ 2 本の線に 複数のデバイスをぶら下げて 通信するために、各デバイスには 7 ビットの ID(アドレス) が割り当てられている。Pico は「アドレス 0x27 さん、こんにちは」とアドレス指定で呼びかけ、その時だけ該当デバイスが応答する仕組み。LCD1602 の I2C バックパックは 0x27 または 0x3F が標準(基板上のジャンパで切替可能)。

7セグメント LED との比較:前回の 7セグでは a〜g の 7 本+コモン 1 本=合計 8 本 もの配線が必要でした。LCD(パラレル接続なら 8 本+電源で 10 本以上)を I2C なら 4 本だけで繋げます。残りの GPIO がそっくり空く ので、ボタン・センサー・サーボなどを同時に使う設計の自由度が一気に上がります。

ステップ1:LCD に「Hello, Pico!」を表示しよう

まずは LCD の 1 行目に 「Hello, Pico!」 を表示してみましょう。ブロックは 「LCD初期化」(SDA・SCL ピンと使う変数名を指定)と 「LCDに表示」(行と文字列を指定)の 2 つだけで動きます。

LCD1602 を I2C で Pico に接続した実体配線図
配線:LCD-VCC→Pico-3V3、LCD-GND→Pico-GND、LCD-SDA→Pico-GP0、LCD-SCL→Pico-GP1。I2C バックパック付きの LCD ならピン 4 本だけで動きます。コントラスト調整用の青いツマミ(LCD 裏面)も忘れずに回して、文字が見える明るさに合わせます。(実体配線図は Fritzing の部品画像をもとに作図・CC BY-SA 3.0)
LCD に「Hello, Pico!」を表示するブロック
ブロック構成:LCD初期化 SDA:GP0 SCL:GP1 変数:lcd → LCD 変数:lcd 1行目に表示:「Hello, Pico!」。
from machine import Pin, I2C
from lcd1602 import LCD1602
_i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
lcd = LCD1602(_i2c, 0x27)
lcd.move_to(0, 0)
lcd.putstr((str("Hello, Pico!") + " " * 16)[:16])

コードのポイント:I2C(0, sda=Pin(0), scl=Pin(1)) で I2C0 バス を初期化(Pico には I2C0・I2C1 の 2 系統あります)。LCD1602(_i2c, 0x27) でアドレス 0x27 の LCD を制御するオブジェクトを作り、move_to(列, 行) でカーソル移動 → putstr で文字列を書き込みます。+ " " * 16 と [:16] は 16 文字に揃える(短い文字列には空白を足し、長い文字列は切り捨て)ためのおまじないで、ブロック側が自動でやってくれます。パソコンの Python なら ljust(16) と書けますが、MicroPython の文字列に ljust はありません(実行すると AttributeError で止まります)。そのため空白を足してから切る形になっています。

lcd1602.py について:MicroPython 標準ライブラリには LCD1602 のドライバが含まれていないので、事前に lcd1602.py を Pico にコピー しておく必要があります。Thonny の「ファイル」ペインから Pico の / ルートに保存すれば、from lcd1602 import LCD1602 が使えるようになります。ドライバは RPi-PICO-I2C-LCD や micropython-lcd1602 で検索すれば GitHub に多数公開されています。

ステップ2:2 行をフル活用して情報を増やそう

LCD1602 の「1602」は 16 文字 × 2 行 を意味します。1 行目だけでなく 2 行目 も使えば、「タイトル+値」「センサー名+計測値」のような 2 段組レイアウト が可能です。

LCD の 1 行目と 2 行目に別々の文字列を表示するブロック
ブロック構成:LCD初期化 → 1行目:「Hello, Pico!」 → 2行目:「I2C is amazing」。
from machine import Pin, I2C
from lcd1602 import LCD1602
_i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
lcd = LCD1602(_i2c, 0x27)
lcd.move_to(0, 0)
lcd.putstr((str("Hello, Pico!") + " " * 16)[:16])
lcd.move_to(0, 1)
lcd.putstr((str("I2C is amazing") + " " * 16)[:16])

行の指定:ブロックの「行」プルダウンは 「1行目」「2行目」 と日本語ですが、内部的には move_to(0, 0)(1 行目)/move_to(0, 1)(2 行目)と 0 始まり です。コンピューターの世界では「最初」は 1 ではなく 0(リストの先頭インデックスも 0)。これは Python の流儀です。

16 文字を超える場合:I2C is amazing は 14 文字なのでギリギリ収まりますが、I2C communication is great のような 16 文字を超える文字列は 後ろが切り捨て られます。長い文を表示したいときは スクロール表示 を自作するか、2 行に分割 して工夫します。

ステップ3(コラム):I2C アドレスとセンサー連携への発展

I2C は 同じ 2 本の線 に 複数のデバイス をぶら下げられるのが最大の魅力です。LCD1602 だけでなく、気圧センサー(BMP280)・加速度センサー(MPU6050)・RTC(DS3231)・OLED(SSD1306)・ADC 拡張(ADS1115) など、Pico の GPIO を使い切ったあと でも I2C なら追加デバイスをぶら下げ続けられます。

💡 LCD が反応しないとき:I2C アドレスを確認しよう

もし LCD が反応しないときは アドレスが 0x27 でない 可能性がある。下のコードを Thonny で実行 すると、I2C バスに繋がっているデバイスのアドレスを 一覧表示 できる。
from machine import Pin, I2C
_i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
devices = _i2c.scan()
print('Found I2C devices:', [hex(d) for d in devices])
# 例:Found I2C devices: ['0x27']

i2c.scan() は 0x08〜0x77 のすべてのアドレスに「いる?」と呼びかけ、応答したデバイスのアドレスをリストで返します。結果が ['0x3F'] なら、コードの 0x27 を 0x3F に書き換えてください。

⚠️ I2C バスの注意点

  • プルアップ抵抗:SDA・SCL は オープンドレイン信号なので、本来はプルアップ抵抗が必要です。ただし I2C バックパック付き LCD や Pico の内蔵プルアップでほぼ動くため、本記事では省略しています。
  • 電圧レベル:Pico は 3.3V ロジックですが、LCD1602 のバックパックは 5V 推奨 のものもあります。VCC を VBUS(5V) に繋ぐ場合は SDA・SCL のロジックが 5V↔3.3V のミスマッチになる可能性があります。多くの I2C バックパックには レベル変換 が入っているので動きますが、心配なら 3V3 接続を選ぶのが安心です。
  • I2C0 と I2C1 の使い分け:Pico には I2C0(GP0/GP1) と I2C1(GP2/GP3) の 2 系統あります。「センサーは I2C0、LCD は I2C1」のように バスを分けて 衝突を避ける設計もできます。

カスタム文字:LCD1602 には 8 個の自作キャラクター を登録できる機能があり、ハート♥・温度計🌡・スマイル☺ などのオリジナル絵文字を表示できます。lcd.custom_char(位置, パターン配列) のようなドライバ拡張で対応可能。発展編で扱います。

演習課題

課題3-8-1:I2C 通信の長所と短所を整理しよう

I2C 通信を使う メリット と デメリット を、前回までで学んだ パラレル通信(GPIO 個別出力で 7セグや LED を制御するやり方) と比較しながら、それぞれ 2 つずつ 挙げてみよう。

ヒント:配線本数・速度・対応デバイス数・複雑さ・ノイズ耐性・コストなど、色々な観点があります。

▶ 模範解答と解説を見る
# I2C のメリット
# 1. 配線が少ない(SDA + SCL の 2 本だけで何台でも繋げる)
# 2. デバイスのアドレス指定で個別制御できる(衝突しない)
# (他にも:標準化されているので互換デバイスが豊富、配線がシンプルでミスが少ない)

# I2C のデメリット
# 1. パラレル通信より遅い(標準モード 100kHz、高速モード 400kHz)
# 2. プルアップ抵抗が必要、長距離配線は苦手(ノイズに弱い)
# (他にも:同一バスにアドレス重複のデバイスは繋げない、初期化が複雑)

# 使い分けの目安:
# - リアルタイム性・速度重視  → パラレル (LED 直接駆動など)
# - 複数デバイス・配線節約  → I2C (LCD、センサー類)
# - 中距離・高速通信      → SPI (SD カード、TFT 液晶)

解説: 解説:I2C の最大の魅力は 「ピンを節約できる」 こと。Pico の GPIO は 26 本 しかないので、センサーを 5 種類使うだけでパラレル接続だと GPIO が枯渇しますが、I2C なら 2 本+電源 2 本 で 5 種類全部繋がります。速度の比較:

  • パラレル GPIO 直接駆動:CPU クロック近く(数 MHz)
  • I2C 標準モード:100 kHz(≒ 1 ピン 100k 回/秒)
  • I2C 高速モード:400 kHz
  • SPI(次回以降):数十 MHz まで可能
LCD の更新は人間が読む速度(毎秒 1〜10 回程度)で十分なので、I2C 標準モードでも余裕です。

課題3-8-2:LCD で自己紹介しよう(カウンタ表示)

1 行目に「Count:」と固定表示し、2 行目に 0 から 9 まで 0.5 秒ごとに数字をカウントアップ しよう。ブロックの「LCD に表示」の 値スロット には 変数ブロック もそのまま入れられます(内部で str() 変換される)。「N 回繰り返す」ブロックを使うとシンプルに書けます。

▶ 模範解答と解説を見る

ブロックの組み合わせ例:

1 行目に Count:、2 行目に 0→9 のカウンタを表示するブロック
ブロック構成:LCD初期化 → 1行目:「Count:」 → 変数 i を 10 回繰り返す → 2行目:変数 i → 0.5秒待つ。
from machine import Pin, I2C
from lcd1602 import LCD1602
import utime
_i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
lcd = LCD1602(_i2c, 0x27)
lcd.move_to(0, 0)
lcd.putstr((str("Count:") + " " * 16)[:16])
for i in range(10):
    lcd.move_to(0, 1)
    lcd.putstr((str(i) + " " * 16)[:16])
    utime.sleep(0.5)

解説: Python の工夫:(str(i) + " " * 16)[:16] で 整数 i を文字列に変換 してから 16 文字に揃えます。この空白埋めをしないと、前回表示した 「9」の右側に残っていた文字 が消えず、「Count:9」の後に「Count:10」を表示したいときに 「9」の右隣に「0」 が残ってしまうので注意。発展:

  • 10 → 20 のカウントアップ:range(10, 20) に変えるだけ。
  • カウンタを 100 まで増やす:range(100) ・間隔を 0.1 秒に短縮するとデジタル時計の秒表示風になる。
  • 可変抵抗(#05)でカウントの速度を変える:val = pot.read_u16() を読み取り、utime.sleep(val / 65535 * 2) のように待ち時間を計算する。

課題3-8-3:起動メッセージ → 本表示への切替を作ろう

ゲーム機やデジタル機器によくある 「スプラッシュ画面」 風の演出を作ってみよう。1.5 秒間「Welcome! / PycoBlocks」と表示 したあと、画面をクリアして 「Pico is ready! / Press RESET...」 を 2 秒表示し、ずっとループ させよう。「LCDをクリア」 ブロックの使い方がポイント。

▶ 模範解答と解説を見る

ブロックの組み合わせ例:

スプラッシュメッセージとメイン表示を切り替えるループブロック
ブロック構成:LCD初期化 → ずっと繰り返す → クリア → Welcome!/PycoBlocks → 1.5秒 → クリア → Pico is ready!/Press RESET... → 2秒。
from machine import Pin, I2C
from lcd1602 import LCD1602
import utime
_i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
lcd = LCD1602(_i2c, 0x27)
while True:
    lcd.clear()
    lcd.move_to(0, 0)
    lcd.putstr((str("  Welcome!") + " " * 16)[:16])
    lcd.move_to(0, 1)
    lcd.putstr((str(" PycoBlocks") + " " * 16)[:16])
    utime.sleep(1.5)
    lcd.clear()
    lcd.move_to(0, 0)
    lcd.putstr((str("Pico is ready!") + " " * 16)[:16])
    lcd.move_to(0, 1)
    lcd.putstr((str("Press RESET...") + " " * 16)[:16])
    utime.sleep(2)

解説: レイアウトのコツ:1 行目 ' Welcome!' と 2 行目 ' PycoBlocks' の 先頭に空白を入れる ことで、16 文字中央に文字が来るように調整しています。LCD はモノスペース(等幅)なので、空白を数えれば狙った位置に文字を置けます。clear() の役割:lcd.clear() は 画面全体を空白で埋める 関数で、前回表示の残りカスを消すために使います。発展:

  • 3 画面以上のスライドショー:「画面1 → 待機 → 画面2 → 待機 → 画面3 → 待機」のように切替メッセージを増やせば、デジタルサイネージ風の演出ができる。
  • ボタン(#03)で切替:btn.value() == 0 で押下を検出して画面を進めれば、説明スライドのリモコンができる。
  • センサー値表示:可変抵抗(#05)や温度センサー(#10 で扱う DHT11)の値を LCD に表示すれば、本格的なモニタ装置に。

まとめ

  • I2C 通信は SDA・SCL の 2 本+電源 2 本= 4 本だけで複数デバイスと通信できる省ピン規格。
  • LCD1602 の I2C アドレスは 0x27 または 0x3F が標準。i2c.scan() で確認できる。
  • pico_lcd_init(変数作成)→ pico_lcd_print(行と文字を指定)の順で書く。pico_lcd_clear で全消去。
  • 表示は 16 文字 × 2 行。ブロック側で自動的に + " " * 16 → [:16] の 16 文字揃えが入るので残カス対策済み。
  • Pico は I2C0(GP0/GP1)と I2C1(GP2/GP3)の 2 系統 あり、バスを分けて衝突回避もできる。

次の記事は #09「超音波で距離を測ろう」。HC-SR04 超音波センサーで距離を測り、今回学んだ LCD と組み合わせて距離計 を作ってみます。電圧レベル(5V → 3.3V 変換)の話も登場します。

トップへ戻る