ESP32でClaude Codeを使う手順 — Arduino CLIでシリアルデバッグまで
ESP32でClaude Codeを使うには、Arduino IDEの代わりにArduino CLIをBashツールから呼びます。ビルド・書き込み・シリアル出力の読み取りと、対応SoCの種類を説明します。
ESP32でClaude Codeを使うなら、Arduino IDEの代わりにArduino CLIを使います。Claude Codeはターミナルで動くため、IDEのボタン操作ではなく、Bashツールからarduino-cliのコマンドを呼んでコンパイル・書き込み・シリアル出力の確認までを回す形になります。使えるESP32の種類は、安定版のコアでESP32・S2・S3・C3・C5・C6・H2・P4の8種です(詳細は次の節)。
環境構築からデバッグループ、症状別の切り分けまでを順に説明します。より本格的なマルチタスク制御やdevicetreeによるハードウェア記述が必要になったら、Claude CodeでZephyr RTOSをwestビルドで開発する手順も参考になります。
Claude CodeでESP32開発を始める前に
Claude Codeではファイル編集はEditツール、ビルドや書き込みはBashツールが担当します。
対象になるのはArduino Core for ESP32です。最新リリースは3.3.12(2026年9月18日公開)で、ESP-IDF v5.5.5への更新を含みます。安定版として使えるSoCは、ESP32・ESP32-C3・ESP32-C5・ESP32-C6・ESP32-H2・ESP32-P4・ESP32-S2・ESP32-S3の8種です。ESP32-C2とESP32-C61も対応しますが、ArduinoをESP-IDFのコンポーネントとして使うか、静的ライブラリを作り直す必要があります。
対応OSはWindows・Linux・macOSの3つです。Espressifの「Getting Started」ページが対応IDEとして挙げているのはArduino IDEだけで、Arduino CLIは載っていません。それでもCLIでコアを入れられるのは、IDEのボードマネージャーに登録するのと同じJSONのURLをCLI側にも指定できるからです。
sdkconfigの直接編集やパーティションテーブルのCSV設計まで任せたい場合は、Espressif純正のESP-IDFを使います。その構成はClaude CodeでESP-IDFのファームウェアを開発する手順で扱っています。M5Stack Cardputer-Advなら、cwc-makersプラグインの/maker-setupで書き込みまで任せられます。
開発機がDevContainerやCodespacesの場合は注意が必要です。ESP32はUSBシリアル経由で書き込むため、リモートのコンテナ環境ではUSBパススルーの設定がない限りボードに直接アクセスできません。Claude Code CodespacesでDevContainer開発を始める手順で扱っている構成は、コンパイルのチェックまでに留めるか、USBデバイスを転送できるローカル環境と組み合わせるのが現実的です。
Arduino CLIでESP32のビルド環境を整える
最初の作業は、ESP32のボード定義をArduino CLIに登録することです。ESP32はArduino公式のデフォルトインデックスに含まれない3rdパーティコアなので、Espressifが公開するボードマネージャーのJSONを追加します。
arduino-cli config init
arduino-cli core update-index \
--additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
arduino-cli core install esp32:esp32 \
--additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json毎回--additional-urlsを書きたくない場合は、config initで生成された設定ファイルに同じURLを書き込みます。以降のコマンドでは、設定ファイルのURLが自動で使われます。
board_manager:
additional_urls:
- https://espressif.github.io/arduino-esp32/package_esp32_index.jsonボードをUSB接続したら、認識状況を確認します。Claude Codeに結果を読ませるなら、全コマンド共通の--jsonを付けると出力が構造化されます。
arduino-cli board list
arduino-cli board list --json正しく認識されていれば、シリアルポートとFQBN(Fully Qualified Board Name)が一覧に表示されます。汎用のESP32開発ボードのFQBNはesp32:esp32:esp32です。これはボード定義ファイル上でボードIDがesp32(表示名は「ESP32 Dev Module」)として登録されていることに由来し、<パッケージ名>:<アーキテクチャ>:<ボードID>の形式でそのまま組み立てられます。
手元のボードが認識されない場合は、arduino-cli board listall esp32で対応ボード名とFQBNの候補を検索できます。Arduino CLIの説明では、一覧にUnknownと出ても、コアとFQBNを正しく指定すれば書き込みは通るとされています。
Claude Codeで回すデバッグループの1周
- 1
スケッチを編集する
Editツールで
.inoを直します。ここはClaude Codeの得意な作業です。 - 2
コンパイルして構文とライブラリの不整合を潰す
エラーの全文がそのままBashツールの出力に返るので、Claude Codeが読んで直せます。ボードがなくても回せる唯一の工程です。
- 3
書き込む
uploadを実行します。 - 4
シリアル出力をファイルに落とす
timeout付きで一定秒数だけ読み、serial.logに保存します。 - 5
ログを読ませて原因を絞る
スタックトレースや再起動の理由を拾い、最初の工程に戻ります。
Claude Codeにコンパイルと書き込みを任せる
スケッチの作成と編集はClaude CodeのEditツールで行い、ビルドと書き込みはBashツール経由でArduino CLIに委ねます。
arduino-cli sketch new blink_esp32
arduino-cli compile --fqbn esp32:esp32:esp32 blink_esp32
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32 blink_esp32compileは-b(--fqbnのエイリアス)、uploadは-p(ポート)と-bの組み合わせで、どちらもArduino CLIのサブコマンドが共通して受け付けるオプションです。uploadは--build-pathなどで指定したビルド済みの成果物を書き込むだけでコンパイルは行いません。コードを変更したらcompileから実行し直します。
2つを1回の呼び出しにまとめたいときは、compileに-pと-uを付けます。-tを足すと、書き込んだバイナリの検証までが同じコマンドで済みます。
arduino-cli compile -b esp32:esp32:esp32 -p /dev/ttyUSB0 -u -t blink_esp32書き込みが失敗して「Failed to connect to ESP32: Timed out waiting for packet header」のようなエラーになる場合、ESP32がダウンロードモードに入れていないことが原因です。ENピンでリセットする間、GPIO0をLOWに保つ必要があり、多くの開発ボードはBOOTボタンでこれを代行します。
許可ルールを先に書いて確認ダイアログを減らす
書き込みコマンドを実行するたびにClaude Codeの権限確認が挟まって流れが止まる場合は、プロジェクトの.claude/settings.jsonに許可ルールを追記しておくとテンポが上がります。
{
"permissions": {
"allow": [
"Bash(arduino-cli board list *)",
"Bash(arduino-cli compile *)",
"Bash(arduino-cli upload *)",
"Bash(arduino-cli monitor *)"
]
}
}書き方で結果が変わる点が2つあります。末尾の*の前に置くスペースは規則の一部で、compile *は単語の境界を守ったうえでcompileだけの実行にも一致します。スペースを省いたcompile*は、compileで始まる別の語にも一致します。もう1つは、timeoutで包んだtimeout 10 arduino-cli monitor ...が、timeoutを取り除いた形で照合される点です。monitorの規則がそのまま効きます。
&&や;でつないだ複合コマンドは、各部分がそれぞれ規則に一致しなければ通りません。>によるファイル出力は、出力先が作業ディレクトリの内側かどうかなど、編集の規則で別途検査されます。また、プロジェクトのpermissions.allowは、そのフォルダーを信頼するまで効きません。
シリアル出力をファイルに落としてClaude Codeに読ませる
Claude Codeは対話中のターミナルに張り付いてログを流し見るツールではないため、シリアルモニターも「一定時間だけ実行してファイルに落とし、そのファイルを読ませる」形にするとデバッグループに組み込みやすくなります。
timeout 10 arduino-cli monitor -p /dev/ttyUSB0 --config 115200 --timestamp > serial.log--configはシリアルポートの設定を<ID>=<値>の形式、または公式の例のようにボーレートだけを渡す形式で指定します。スケッチ側のSerial.begin()に指定した値と一致させないと、文字化けか無出力になります。どの設定項目があるかは--describeで一覧できます。--timestampは受信した行ごとに時刻を付けるので、再起動までの経過時間を読ませたいときに便利です。-q(--quiet)を付けると、モニターの入出力だけが表示されます。モニターの入出力以外の表示が出ないので、ログが読みやすくなります。
終了させる手段を置く理由は、Bashツールの待機時間にあります。フォアグラウンド実行の既定の上限は2分(BASH_DEFAULT_TIMEOUT_MSの120000ミリ秒)で、monitorは自分では終わらないため、上限まで待たされます。
macOSの標準環境にはtimeoutコマンドが入っていません。その場合はバックグラウンドで起動して、一定時間後に止める形にします。ポート名はboard listの出力どおりに指定します。
arduino-cli monitor -p <ポート> --config 115200 > serial.log & sleep 10; kill $!何も出ないときはUSBコネクタの種類を疑う
シリアルに何も出ない場合、ESP32の新しい世代ではUSB接続がUARTとUSB-CDCの2系統に分かれていることを疑います。どちらのコネクタにケーブルを挿したかで、必要な設定が変わります。
どのコネクタにつないだかで設定が変わる
USBコネクタ
USB-CDCを使う側です。「USB CDC On Boot」を有効(-D ARDUINO_USB_CDC_ON_BOOT=1)にし、「USB Mode」を「Hardware CDC and JTAG」(-D ARDUINO_USB_MODE=1)に合わせます。コードはSerial.print()で書きます。
UARTコネクタ
「USB CDC On Boot」を無効(-D ARDUINO_USB_CDC_ON_BOOT=0)にします。CDCを有効にしたままUARTへ出したいときだけ、Serial0.print()を使います。
ケーブルもコードも間違っていないのに出力だけが届かないときは、この対応が食い違っています。ESP32(無印)はUARTしか持たないため、この切り替えは存在しません。ESP32-S3のボード定義(3.3.12のboards.txt)には「USB Mode」と「USB CDC On Boot」のメニューがあり、無印ESP32の定義にはありません。
USB-CDCには別の弱点もあります。デバイスがきつい再起動ループに入っていると、CDCの初期化が間に合わず、出力を取りこぼすことがあります。初期化まわりの不具合を追うときに、ログがまったく取れない原因になります。
CLIで指定するときは、メニュー名をboards.txtのIDに置き換える
IDEの「Tools」メニューにある設定は、CLIでは--board-options(カンマ区切りで複数指定可)に<ID>=<値>で渡します。IDとして使える文字列は、3.3.12のboards.txtにあるメニューIDと値です。
| Toolsメニュー | 渡す値の例 | 補足 |
|---|---|---|
| Core Debug Level | 渡す値の例DebugLevel=verbose | 補足none error warn info debug verbose |
| Partition Scheme | 渡す値の例PartitionScheme=huge_app | 補足「Huge APP」(OTAなし・3MBアプリ領域) |
| Upload Speed | 渡す値の例UploadSpeed=115200 | 補足高速で失敗するなら下げる |
| USB CDC On Boot | 渡す値の例CDCOnBoot=cdc | 補足ESP32-S3の例。無効はdefault |
| USB Mode | 渡す値の例USBMode=hwcdc | 補足ESP32-S3の例。defaultはTinyUSB |
| PSRAM | 渡す値の例PSRAM=opi | 補足ESP32-S3の例。QSPIはenabled |
詳細なログが欲しいときは、Core Debug Levelを上げます。None・Error・Warning・Info・Debug・Verboseの6段階があり、原因が絞り込めない再起動ループの調査ではVerboseまで上げてからserial.logを読ませると、スタックトレースの該当行を拾いやすくなります。
arduino-cli compile -b esp32:esp32:esp32s3 --board-options "DebugLevel=verbose,CDCOnBoot=cdc" blink_esp32こうした「実行してログを取り、Claude Codeに読ませて直す」の反復は、Claude Codeでテスト駆動開発(TDD)を回す手順で扱う失敗テストからの修正ループと構造が近く、Stop hookなどで完了条件を明示する考え方もそのまま流用できます。
Arduino IDEとArduino CLIをどう使い分けるか
| 用途 | Arduino IDE | Arduino CLI(Claude Code) |
|---|---|---|
| 初回のボード認識確認 | Arduino IDE◎ 設定画面で状態が見える | Arduino CLI(Claude Code)△ board listの出力を読む必要がある |
| ライブラリの探索 | Arduino IDE◎ Library Managerで一覧できる | Arduino CLI(Claude Code)○ lib searchでも同等の情報は取れる |
| 繰り返しのビルド・書き込み | Arduino IDE△ クリック操作が挟まる | Arduino CLI(Claude Code)◎ コマンド1行で再現できる |
| CIやリモート開発機でのビルド確認 | Arduino IDE× GUI前提で組み込みにくい | Arduino CLI(Claude Code)◎ USBポートがなくてもコンパイルまでは検証可能 |
書き込みまで自動化したいならArduino CLI、初回のボード確認やライブラリ探索だけならArduino IDEのGUIの方が早いこともあります。Claude apps gatewayをCIやリモート開発機から使うにはで触れているような、物理ポートを持たないリモート実行環境では、arduino-cli compileによるビルド確認までを担当させ、実機への書き込みとシリアル確認はUSBが挿さったローカル環境に分けるとワークフローが破綻しません。
複数枚のESP32を同時に扱う(メッシュ通信のテストなど)場合は、ボードごとにシリアルポートが異なるため、ログの取得とコンパイルを並行して走らせたくなります。Claude Codeのサブエージェント完全活用で紹介されているサブエージェントの並列実行パターンは、ボードごとに独立したログファイルへ書き込ませる用途に応用できます。PlatformIOでビルドとnativeテストまでを任せる構成は、Claude CodeでPlatformIOのファームウェアを開発する手順にあります。
症状から原因を切り分ける
症状別に見るつまずき
書き込めない・ボードが見えない
書き込みの失敗は、次の順に切り分けます。
- USBケーブルを替える(充電専用ケーブルはデータ線がありません)
- USBハブを避けてPCに直接つなぐ
- 電源供給を確認する
- TX/RXピンに何も接続されていないか確認する
それでも通らないときは、RSTとGNDの間に10μFのコンデンサをはんだ付けする方法があります。ボード自体が認識されない場合は、USBドライバの有無と、ケーブル・ポート・電源・ボードの故障を順に疑います。
起動直後に再起動を繰り返す
ESP32-S3が最小構成のスケッチでも再起動するときは、PSRAMの種別がIDEの設定と食い違っています。シリアルには
assert failed: do_core_init startup.c:326 (flash_ret == ESP_OK)のような行が出ます。モジュールのシールド上部にWROOM-1かWROOM-2かが書かれ、左下の隅に型番コードがあります。コードの末尾がR2ならQSPI、R8ならOPIのPSRAMです。PSRAMのない型番(N4・N8・N16・H4)は変更不要で、WROOM-2は常にOPIです。
もう1つの原因はパーティションです。公式は、選び方を誤るとシステムがクラッシュすると説明しています。フラッシュサイズの設定が実機と違うときも、パーティションの選択で問題が出ます。
パーティションを変えたあとに起動しなくなった場合は、Toolsメニューの「Erase All Flash Before Sketch Upload」を切り替えます。Enabledにするとフラッシュ全体を消してから書き込みます。古いデータを残さず書き込みたいときに使い、既定はDisabledです。
周辺機能の初期化で失敗する
E (588) SPIFFS: mount failedのようなエラーには、SPIFFS.begin(true)でformatOnFailを有効にして再フォーマットを許可すると解消することがあります。SDカードのマウント失敗は、配線の接触不良が原因のものが大半です。
SD_MMCライブラリではD3ピンを含む全データピンに外付け10kΩのプルアップが必要で、D3は1ビットモードで使わない場合も同じです。BLEやBluetoothでESP-IDFのAPIを直接使うスケッチは、メモリの扱いに注意が要ります。Arduinoコアは、BLEやBluetooth Classicのライブラリが使われていないと、既定でBTDM用のメモリ(約36KB)を解放します。この解放を避けるには、
esp32-hal-alloc-ble-mem.h(Classicならesp32-hal-alloc-bt-classic-mem.h)をincludeして、使用中のフラグを立てます。この挙動はコアのソースesp32-hal-bt.cに書かれています。
まとめ
ESP32では、ビルドと書き込みはArduino CLIでClaude Codeに任せられても、BOOTボタンとUSBコネクタの選択は人の手元に残ります。この2点を指示に書き、ログをファイルに落とす構成にしておけば、原因の切り分けまでをセッションの中で回せます。基板側の検証はKiCadのkicad-cliによるERCとDRCで同じ型が使えます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- docs.espressif.com/projects/arduino-esp32/en/latest/getting_started.html
- docs.espressif.com/projects/arduino-esp32/en/latest/troubleshooting.html
- docs.espressif.com/projects/arduino-esp32/en/latest/guides/tools_menu.html
- docs.espressif.com/projects/arduino-esp32/en/latest/installing.html
- arduino.github.io/arduino-cli/1.5/getting-started
- arduino.github.io/arduino-cli/1.5/commands/arduino-cli_compile
- arduino.github.io/arduino-cli/1.5/commands/arduino-cli_upload
- arduino.github.io/arduino-cli/1.5/commands/arduino-cli_monitor
- code.claude.com/docs/en/settings
- code.claude.com/docs/en/permissions
- code.claude.com/docs/en/env-vars
- github.com/espressif/arduino-esp32/blob/3.3.12/boards.txt