はじめに
Claude Code(CLI の AI エージェント)から、USB で繋いだ Raspberry Pi Pico 2 を
そのまま操作できるのか試した記録です。
普段 Pico をいじるときは Thonny を開いてコードを貼って実行、という流れですが、
「エージェントに任せたらどこまでいけるのか」が気になったので、
Thonny を使わず、Claude Code のターミナル操作だけで
LED 点滅から 2.8 インチタッチ液晶の表示までやってみます。
ゴールはこの 3 段階です。
- Pico が USB で見えていることを確認する
- オンボード LED を点滅させる
- 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
やってみた感想
電子工作の醍醐味は、電子部品を作成したプログラムで動かすところだと思います。
AIを使うと、接続だけすることであとはほぼ自動で作成できました。
趣味として電子工作を楽しむには、味気ない感じがしました。
プログラムを作成して、動作したときの感動が薄いです。
電子工作を楽しむのか、AIを活用することを楽しむのか、どちらを重視するかだと思いました。
また、公式ドキュメントを探したり、調べたりもしないので、こういった古来からのスキルが不要になった点は、良くも悪くもという感じがしました。
とは言っても、今後AIによるこうい開発が進んで行くのであれば、不要なっていくスキルなのかもしれません。
Claude Code に任せてよかったところ
-
system_profilerが空 →ioregに自分で切り替えた -
WebFetchが 403 →curlに UA を付けて回避した -
mpremoteが繋がらない →lsofで Thonny を特定した
エラーが出たときに別の手段へ乗り換える動きは、
対話でいちいち指示するより速かったです。
気をつけたところ
- ピンアサインは検索結果の要約が間違っていた(RST を GP12 と誤答)。
一次資料に当てさせるのが大事 - Thonny は閉じておく
