1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code からラズパイ Pico 2 を使ってみた(LED 点灯 → 2.8 インチ液晶表示まで)

1
Last updated at Posted at 2026-08-09

はじめに

Claude Code(CLI の AI エージェント)から、USB で繋いだ Raspberry Pi Pico 2 を
そのまま操作できるのか試した記録です。

普段 Pico をいじるときは Thonny を開いてコードを貼って実行、という流れですが、
「エージェントに任せたらどこまでいけるのか」が気になったので、
Thonny を使わず、Claude Code のターミナル操作だけで
LED 点滅から 2.8 インチタッチ液晶の表示までやってみます。

ゴールはこの 3 段階です。

  1. Pico が USB で見えていることを確認する
  2. オンボード LED を点滅させる
  3. Waveshare Pico-ResTouch-LCD-2.8 に文字と図形を表示する

環境

項目 値
ホスト macOS (Apple Silicon)
ボード Raspberry Pi Pico 2 (RP2350)
ファーム MicroPython
表示 Waveshare Pico-ResTouch-LCD-2.8(320×240 / ST7789VW / 抵抗膜タッチ)
転送ツール mpremote 1.28.0

Waveshare Pico-ResTouch-LCD-2.8(Amazon)
Raspberry Pi Pico 2(Amazon)


STEP 0: そもそも Pico は見えているのか

「USB で繋いでいるけど確認できる?」とだけ投げたところ、
Claude Code はシリアルポートの一覧から調べ始めました。

ls -la /dev/cu.*
/dev/cu.usbmodem11301
/dev/cu.usbmode********0001

usbmodem が 2 つある。どちらが Pico か分からないので、VID/PID で特定します。

system_profiler は空振りした

最初に試した system_profiler SPUSBDataType は、
Claude Code のサンドボックス下では出力が空になりました。
代わりに ioreg を使います。

ioreg -p IOUSB -w0 -l | grep -E '"(USB Vendor Name|USB Product Name|idVendor|idProduct)"'
"idProduct" = 5
"USB Product Name" = "Board in FS mode"
"USB Vendor Name" = "MicroPython"
"idVendor" = 11914

11914 = 0x2E8A は Raspberry Pi のベンダ ID、5 = 0x0005 は
「MicroPython Board in FS mode」。つまり MicroPython ファームで通常起動中です。

ここ、地味に便利なのが VID/PID でモードまで分かること。

状態 VID:PID 見え方
MicroPython 通常起動 2E8A:0005 シリアルポートとして見える
BOOTSEL(書き込み)モード 2E8A:0003 /Volumes/RPI-RP2 としてマウントされる

ls /Volumes に RPI-RP2 が無かったので、BOOTSEL でもないと確定できました。

ポート名との対応はこれで取れます。

ioreg -r -c IOSerialBSDClient -l | grep -E '"(IOCalloutDevice|IOTTYDevice)"'

結論: Pico = /dev/cu.usbmodem11301


STEP 0.5: mpremote を入れる

Pico にコードを送る手段として mpremote(MicroPython 公式ツール)を使います。

pipx install mpremote
installed package mpremote 1.28.0

mpremote を選んだ理由は、run が Pico に保存せず実行だけしてくれる点です。

mpremote connect /dev/cu.usbmodem11301 run foo.py

エージェントに試行錯誤させると何十回も転送することになるので、
フラッシュを汚さずに回せるのは大きいです。
確定したファイルだけ cp で置けばいい。


STEP 0.9: 最初のつまずき — ポートが掴まれている

いざ実行したらこれ。

mpremote: failed to access /dev/cu.usbmodem11301 (it may be in use by another program)

lsof で犯人を特定します。

lsof /dev/cu.usbmodem11301
COMMAND   PID            USER   FD   TYPE DEVICE  NODE NAME
Python  57365 userxxx1    7u   CHR    9,9   805 /dev/cu.usbmodem11301
ps aux | grep -i thonny
.../thonny/plugins/rp2040/rp2040_back.py {'port': '/dev/cu.usbmodem11301', ...}

Thonny が起動しっぱなしでポートを握っていました。

シリアルポートは排他アクセスなので、Thonny を切断(または終了)しないと
mpremote は繋がりません。地味ですが、Claude Code から Pico を触るときの
第一の落とし穴だと思います。

対策として、ラッパスクリプトに lsof チェックを仕込んで、
繋がらないときに「誰が掴んでいるか」まで出るようにしました。

if holder=$(lsof -t "$PORT" 2>/dev/null) && [[ -n "$holder" ]]; then
  echo "ERROR: $PORT は他プロセスが使用中です (PID: $holder)" >&2
  ps -o pid=,comm= -p $holder >&2
  exit 1
fi

STEP 1: LED を点滅させる

import sys
import time

from machine import Pin


def onboard_led():
    try:
        return Pin("LED", Pin.OUT)
    except (TypeError, ValueError):
        return Pin(25, Pin.OUT)


def main(count=10, interval=0.3):
    led = onboard_led()
    for i in range(count):
        led.value(1)
        time.sleep(interval)
        led.value(0)
        time.sleep(interval)
        print("  blink", i + 1, "/", count)

Pin("LED") を優先しているのは、Pico W 系だと LED が CYW43 側にぶら下がっていて
GPIO 番号では届かないためです。Pico / Pico 2 は GP25 直結なので
Pin(25) でも動きますが、エイリアスで書いておくとボードを跨いで動きます。

./tools/pico.sh run src/01_led_blink.py

STEP 2: 2.8 インチ液晶に表示する

モジュールの素性

Waveshare Pico-ResTouch-LCD-2.8 は Pico に直挿しできる液晶シールドです。

項目 値
コントローラ ST7789VW
解像度 320 × 240
パネル IPS / 262K colors
タッチ XPT2046(抵抗膜)
バス SPI1(実測書き込み上限 60MHz)
その他 microSD スロット

直挿しなので配線は不要ですが、GPIO は結構持っていかれます。

LCD 側 Pico GPIO
LCD_DC GP8
LCD_CS GP9
LCD_CLK GP10
MOSI GP11
MISO GP12
LCD_BL GP13
LCD_RST GP15
TP_CS GP16
TP_IRQ GP17
SD 関連 GP5, GP18〜GP22

ちなみに、Web 検索の要約には RST: GP12 と出てきましたがこれは誤りでした。
GP12 は MISO で、RST は GP15。エージェントに調べさせるときも、
ピンアサインみたいな一次情報は公式のピン表に当てさせた方が安全です。

公式 Wiki が 403 を返す

WebFetch で Waveshare Wiki を取ろうとしたら弾かれました。

The server returned HTTP 403 Forbidden.

User-Agent を付けた curl なら通ります。

curl -sL -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..." \
  "https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8" -o wiki.html

公式デモコードもここから取れます。

curl -sL -o demo.zip "https://files.waveshare.com/upload/5/5a/Pico-ResTouch-LCD-X_X_Code.zip"
unzip -q demo.zip -d demo
# → demo/Pico-ResTouch-LCD-X_X_Code/python/2inch8/main_2inch8.py

落とし穴 1: Pico 2 だとタッチ座標がずれる

公式デモの冒頭にこれがあります。

PICO_RP2040 = True
PICO_RP2350 = not PICO_RP2040

Pico 2 で動かすなら False にする必要があります。 較正式が別物だからです。

# RP2040
X_Point = 320 - int((get[1] - 430) * 320 / 3270)
# RP2350
X_Point = 320 - int((get[1] - 4500) * 320 / 3360)

オフセットが 430 と 4500。桁が違うので、間違えると盛大にずれます。
手で切り替えるのは事故のもとなので、実行時に判定するようにしました。

def is_rp2350():
    import os
    return "RP2350" in os.uname().machine.upper()

落とし穴 2: 色が入れ替わる("color BRG" の正体)

公式デモの色定数がこれ。

self.RED   = 0x07E0   # 素の RGB565 なら GREEN
self.GREEN = 0x001f   # 素の RGB565 なら BLUE
self.BLUE  = 0xf800   # 素の RGB565 なら RED

デモ内にも #color BRG というコメントがあります。なぜこうなるのか。

原因は エンディアンの不一致です。

  • MicroPython の framebuf.RGB565 は、16bit 値をリトルエンディアンでバッファに書く
  • ST7789 は SPI で受け取った 2 バイトをビッグエンディアンとして解釈する

つまり、バッファに 0xF800(赤)と書くと、メモリ上は 00 F8 の順に並び、
LCD 側は 0x00F8 として読む。結果、赤のつもりが青になります。

なので 値を作る時点でバイトスワップしておくのが正解です。

def rgb565(r, g, b):
    v = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
    return ((v & 0xFF) << 8) | (v >> 8)   # ← バイトスワップ

RED   = rgb565(255, 0, 0)
GREEN = rgb565(0, 255, 0)
BLUE  = rgb565(0, 0, 255)

これで rgb565(255, 0, 0) がちゃんと赤になり、
マジックナンバーを覚えなくてよくなります。

落とし穴 3: フレームバッファが 150KB ある

320 × 240 × 2byte = 153,600 byte。

ボード SRAM 余裕
Pico (RP2040) 264KB かなり厳しい
Pico 2 (RP2350) 520KB 余裕あり

このサイズの全画面バッファを持てるのは、Pico 2 の実利がはっきり出るところです。

落とし穴 4: SPI を 3 つのデバイスで共有している

LCD・タッチ・SD カードが全部 SPI1 にぶら下がっています。
LCD は 60MHz で回しますが、XPT2046 は 5MHz 程度まで落とさないと読めません。

公式デモはタッチ読み取りのたびに SPI() オブジェクトを作り直していますが、
SPI.init(baudrate=...) で速度だけ変える方が素直です。

self.spi.init(baudrate=5_000_000)   # タッチ読み取り
# ... XPT2046 とやりとり ...
self.spi.init(baudrate=60_000_000)  # LCD へ戻す

表示してみる

./tools/pico.sh cp src/lcd_2inch8.py :lcd_2inch8.py
./tools/pico.sh run src/02_lcd_hello.py

IMG_4344_noexif.jpg


やってみた感想

電子工作の醍醐味は、電子部品を作成したプログラムで動かすところだと思います。
AIを使うと、接続だけすることであとはほぼ自動で作成できました。
趣味として電子工作を楽しむには、味気ない感じがしました。
プログラムを作成して、動作したときの感動が薄いです。
電子工作を楽しむのか、AIを活用することを楽しむのか、どちらを重視するかだと思いました。
また、公式ドキュメントを探したり、調べたりもしないので、こういった古来からのスキルが不要になった点は、良くも悪くもという感じがしました。
とは言っても、今後AIによるこうい開発が進んで行くのであれば、不要なっていくスキルなのかもしれません。

Claude Code に任せてよかったところ

  • system_profiler が空 → ioreg に自分で切り替えた
  • WebFetch が 403 → curl に UA を付けて回避した
  • mpremote が繋がらない → lsof で Thonny を特定した

エラーが出たときに別の手段へ乗り換える動きは、
対話でいちいち指示するより速かったです。

気をつけたところ

  • ピンアサインは検索結果の要約が間違っていた(RST を GP12 と誤答)。
    一次資料に当てさせるのが大事
  • Thonny は閉じておく

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?