Claude Media
Claude CodeでESP-IDFのファームウェアを開発する手順

Claude CodeでESP-IDFのファームウェアを開発する手順

Claude CodeでESP-IDFのファームウェアを書くときのidf.pyコマンド運用・sdkconfig編集・パーティションテーブル設計・Guru Meditation Errorの読み方をまとめます。

Claude CodeでESP-IDFのファームウェアを開発するとき、鍵になるのはidf.pyというCLIツールとの付き合い方です。GUIのmenuconfigに頼らずsdkconfigを編集する方法、パーティションテーブルをCSVで設計する手順を扱います。クラッシュ時のGuru Meditation Errorをログファイル経由でClaude Codeに読ませる流れも、ESP-IDF公式ドキュメントに沿って説明します。

Claude CodeでESP-IDF開発を始める前に

idf.pyはESP-IDFプロジェクトのビルド・書き込み・デバッグをまとめて扱うフロントエンドで、内部でCMake・Ninja・esptoolを呼び出します。実行できるのはCMakeLists.txtを含むESP-IDFプロジェクトディレクトリ内に限られ、古いMakefile形式のプロジェクトでは動作しません。

同じESP32系の開発でも、Arduino IDEのGUIを避けてArduino CLIをBashツールから呼ぶ構成があります。これはClaude CodeでESP32の開発とシリアルデバッグを進める手順で扱っています。本記事が対象にするのはArduino Core(Arduinoフレームワーク)ではなく、Espressif純正のESP-IDF(FreeRTOSベースのネイティブフレームワーク)です。どちらを選ぶかはプロジェクトの要件次第で、使い分けは後述します。

ESP-IDFの環境をアクティベートしたシェルからidf.pyを実行する点はArduino CLIと共通です。ただし、Claude CodeのBashツールは呼び出しをまたいで環境変数を保持しません(保持されるのは作業ディレクトリだけです)。確実なのは、claudeを起動する前のシェルで. $IDF_PATH/export.sh(Windowsはexport.bat)を実行しておく方法です。こうすればアクティベート済みの環境変数をclaudeプロセスごと引き継ぎ、以降のBashツール呼び出しでidf.pyをそのまま使えます。起動順を制御できない場合は、コマンドごとに. $IDF_PATH/export.sh && idf.py buildのように連結して都度アクティベートします。

idf.pyコマンドをBashツールに任せる

基本の開発ループはターゲット設定・ビルド・書き込み・監視の4コマンドです。

idf.py set-target esp32
idf.py build
idf.py -p /dev/ttyUSB0 flash
idf.py -p /dev/ttyUSB0 monitor

複数のサブコマンドは1回の呼び出しにまとめられます。idf.py -p COM4 clean flash monitorのように書くと、ビルドディレクトリの掃除・ビルド・書き込み・シリアルモニターの起動が、記述順に関係なく正しい順序で実行されます。

set-targetはターゲットチップを切り替えるコマンドですが、実行するとビルドディレクトリを削除し、既存のsdkconfigをsdkconfig.oldとして退避したうえで再生成します。Claude Codeにチップを変更させるときは、この副作用(設定のリセット)を承知のうえで指示する必要があります。

書き込みのたびに権限確認が挟まって流れが止まる場合は、プロジェクトの.claude/settings.jsonに許可ルールを追記しておくとテンポが上がります。設定ファイル全体の書き方はClaude Code settings.json完全ガイドにまとめています。claudeを起動する前にexport.shを済ませておけば、Bashツールに渡るコマンドはidf.py buildのような素の形になるため、許可ルールは次のように書けます。コマンドを都度. $IDF_PATH/export.sh &&で連結する運用にした場合は、許可ルールもその連結後の文字列に合わせて書き直す必要があります。

{
  "permissions": {
    "allow": ["Bash(idf.py *)"]
  }
}

idf.py -p /dev/ttyUSB0 flashのようにオプションを挟む呼び出しはBash(idf.py flash*)にはマッチしないため、コマンド先頭がidf.pyである呼び出しをまとめて許可する形にしています。

sdkconfigをmenuconfigなしで編集する

idf.py menuconfigはターミナル上で動くグラフィカルな設定ツールですが、キー入力を伴うTUIなのでClaude Codeが対話的に操作する対象には向きません。Claude Codeにsdkconfigを編集させるときは、生成済みのsdkconfigを直接書き換えるのではなく、プロジェクトのsdkconfig.defaultsにKconfigオプションを追記する方法が扱いやすくなります。set-targetや初回ビルドのタイミングで、このsdkconfig.defaultsの内容が実際のsdkconfigに反映されます。

注意点は、この反映が効くのはsdkconfigにまだ値が無いオプションだけという点です。すでに生成済みのsdkconfigに同じオプションの値が入っていると、sdkconfig.defaults側を書き換えても上書きされません。set-targetはビルドディレクトリのクリアとsdkconfigの再生成(sdkconfig.oldへの退避)を伴うため、既存プロジェクトで確実に反映させたいときはチップを変えない場合でもset-targetを打ち直すか、sdkconfigを削除してからidf.py buildを実行します。「追記したのに設定が変わらない」と感じたら、まずここを疑います。

ターゲットチップの既定値も同じ仕組みで指定できます。プロジェクトのsdkconfig.defaultsにCONFIG_IDF_TARGET="esp32s3"のように書いておくと、環境変数やCMake変数でIDF_TARGETが指定されていない限りこの値が使われます。

複数の設定パターン(開発用・本番用など)を切り替えたい場合は、CMake Presetsを使う方法もあります。プロジェクトルートにCMakePresets.jsonを置き、プリセットごとにSDKCONFIGのパスを指定しておけば、idf.py --preset production buildのようにビルドごとの設定を切り替えられます。

パーティションテーブルをCSVで設計する

ESP-IDFのパーティションテーブルは、アプリ本体・NVS・OTA用データなど、フラッシュ上の領域をどう区切るかを定義するテーブルです。既定のオフセット0x8000に書き込まれ、テーブル自体の最大サイズは0xC00バイト、最大95エントリまでという制約があります。

プロジェクト固有のカスタムテーブルを使う場合は、menuconfigで「Custom partition table CSV」を選び、プロジェクト内のCSVファイル名を指定します。CSVの各行はカンマ区切りでName, Type, SubType, Offset, Size, Flagsを並べ、#で始まる行はコメントとして無視されます。

# Name,   Type, SubType,  Offset,   Size,  Flags
nvs,      data, nvs,      0x9000,  0x4000
otadata,  data, ota,      0xd000,  0x2000
phy_init, data, phy,      0xf000,  0x1000
factory,  app,  factory,  0x10000,  1M
ota_0,    app,  ota_0,    ,         1M
ota_1,    app,  ota_1,    ,         1M
coredump, data, coredump, ,         64K

Offset列を空欄にすると、直前のパーティションの直後に自動配置されます。appタイプのパーティションはオフセットを0x10000(64KB)境界に、サイズをフラッシュセクタ(4KB)境界に合わせる必要があり、揃っていない場合は変換ツールがエラーを返します。

Type列に使える値はapp(0x00)・data(0x01)のほか、bootloader(0x02)・partition_table(0x03)がESP-IDFの予約領域です。0x40〜0xFEはアプリケーション独自の用途に使える範囲です。dataタイプのSubTypeにはnvs・phy・otaのほか、fat(0x81)・spiffs(0x82)・littlefs(0x83)のようにファイルシステム用の値も用意されています。

CSVから実際にフラッシュへ書き込むバイナリへの変換はgen_esp32part.pyが担い、idf.py buildまたはidf.py partition-tableを実行するとビルドプロセスの一部として自動で行われます。Claude CodeにCSVを編集させたら、idf.py partition-tableでサマリーを出力させ、意図した配置になっているかを確認する一手間を挟むと事故を防げます。

idf.py partition-table

稼働中のデバイスに対して個別のパーティションを読み書き・消去したい場合はparttool.pyが使えます。コマンドラインから直接呼び出せるので、Claude CodeのBashツールとも相性が良い構成です。

python components/partition_table/parttool.py --port /dev/ttyUSB1 \
  read_partition --partition-name=factory --output factory.bin

Flash EncryptionやSecure Bootが有効なデバイスでは、書き込み系の操作(erase_partition・write_partition)がesptool側の安全装置でエラーになります。--esptool-erase-args=forceを付ければ回避できますが、意図しないデバイスへの実行を防ぐため、Claude Codeにこのフラグを常用させない方が安全です。

Guru Meditation Errorをログファイル経由で読ませる

ESP-IDFでは、不正命令やヌルポインタ参照などCPU例外が起きると、パニックハンドラがGuru Meditation Error: Core 0 panic'ed (IllegalInstruction).のようなメッセージとレジスタダンプ・バックトレースをコンソールに出力し、既定では再起動します。

Claude CodeはTTYに張り付いてログを流し見るツールではありません。idf.py monitorはキーボードショートカットによる操作を前提とした対話ツールで、標準入力がTTYに繋がっていない状態(Bashツール経由の実行はこれに当たります)ではこの対話操作が機能しないため、timeoutと組み合わせてファイルへリダイレクトする使い方は前提から外れます。

非対話でクラッシュ情報を回収するなら、コアダンプをフラッシュに保存する設定を使うのが確実です。sdkconfig.defaultsにCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=yを追記してビルド・書き込みしておくと、クラッシュ時にレジスタ・コールスタック・タスク一覧がフラッシュへ保存されます。これはmenuconfigのComponent config > Core dump > Data destinationで「Flash」を選ぶのと同じ設定で、既定値は「None」(保存しない)です。回収はidf.py coredump-infoで読み出すだけで、シリアルポートを監視し続ける必要がありません。

ただしこの仕組みには保存先の領域が要ります。ESP-IDFの既定パーティションテーブルにはcoredumpパーティションが自動で含まれますが、本記事のようにカスタムパーティションテーブルを使っている場合は、dataタイプ・coredumpサブタイプのパーティションを自分で追加しておく必要があります。追加しないままだとCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=yを設定しても保存先が無く、idf.py coredump-infoが読み出すデータを見つけられません。

idf.py coredump-info

より詳しく変数の中身までGDBで調べたい場合はidf.py coredump-debugを使うと、コアダンプをELFファイルとして保存したうえでGDBセッションを開始します。いずれもidf.py coredump-info --help / idf.py coredump-debug --helpで個別のオプションを確認できます。

もう一つの方法が、これまでどおりシリアルモニターの出力を読ませる形です。開発者自身がターミナルでidf.py monitorを対話的に操作してクラッシュ発生後の出力を確認し、その内容をコピーしてログファイルに保存したうえでClaude Codeに読ませます。IDF Monitorを介した出力では、バックトレースに含まれるプログラムカウンタの値が関数名・ファイル名・行番号に自動変換され、次のような注釈付きの出力になります。

Backtrace: 0x400e14ed:0x3ffb5030 0x400d0802:0x3ffb5050
0x400e14ed: app_main at /Users/user/esp/example/main/main.cpp:36
0x400d0802: main_task at /Users/user/esp/esp-idf/components/esp32/cpu_start.c:470

この注釈行のおかげで、Claude Codeにログファイルを読ませるだけで「クラッシュしたファイルと行番号」を特定させられます。バックトレースの先頭行がクラッシュの発生箇所、以降の行が呼び出し元のスタックです。

エラー種別ごとの典型的な原因は次のとおりです。LoadProhibited系はEXCVADDRレジスタの値、InstrFetchProhibitedはPCレジスタの値が手がかりになります。

エラー種別典型的な原因
IllegalInstruction典型的な原因FreeRTOSタスク関数がreturnで終了した(vTaskDelete()を呼ぶ必要がある)、SPIフラッシュのピンを他用途に再設定した
LoadProhibited / StoreProhibited典型的な原因EXCVADDRレジスタが0付近ならNULLポインタの参照、それ以外の不正な値なら未初期化または破損したポインタ
InstrFetchProhibited典型的な原因無効な関数ポインタの呼び出し。PCレジスタが0または0x4から始まらない値になる
LoadStoreAlignment典型的な原因32bit読み書きを4バイト境界外のアドレスに対して行った
Cache error典型的な原因spi_flash APIでフラッシュへの読み書き中にキャッシュが無効化されている区間で、IRAM未配置の割り込みハンドラが動いた

CPU例外以外の要因で再起動するケースも頻出です。

エラー種別典型的な原因
Interrupt Watchdog Timeout典型的な原因割り込みハンドラや長時間の割り込み禁止区間が、割り込みウォッチドッグの制限時間を超えて実行された
IntegerDivideByZero典型的な原因整数のゼロ除算を実行した
Stack overflow(スタック監視)典型的な原因ウォッチポイントに基づくFreeRTOS独自のスタックオーバーフロー検出。CONFIG_FREERTOS_WATCHPOINT_END_OF_STACKで有効化される仕組み
Stack Smashing典型的な原因GCCの-fstack-protector*によるスタックカナリア破壊の検出。ローカル配列への範囲外書き込みが典型
Corrupt Heap典型的な原因ヒープ構造の破損検出(Heap Poisoning)。CORRUPT HEAP:というメッセージが出る
Brownout典型的な原因電源電圧が安全水準を下回ったことを内蔵の検出器が検知。メッセージが電圧降下の速さによっては途中で切れることがある

CONFIG_ESP_SYSTEM_PANICの既定値は「レジスタとバックトレースを表示して再起動」ですが、シリアル出力を確認するツールが実行を止められない環境ではCONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDSを有効にして再起動を数秒遅らせると、ログを回収しやすくなります。

「ログを取って読ませ、直す」というこの反復は、Claude Codeでテスト駆動開発(TDD)を回す手順で扱う失敗からの修正ループと構造が同じで、Stop hookで完了条件を明示する考え方もそのまま流用できます。

ESP-IDFの公式MCPサーバーをClaude Codeに追加する

ESP-IDFにはAI統合用のMCPサーバーが公式に用意されており、idf.py mcp-serverで起動できます。ESP-IDF Installation Manager(EIM)0.8.1以降を使っている場合はeim run経由での起動が推奨されており、シェルでESP-IDF環境をアクティベートしなくても動きます。

Claude CodeのCLIから登録する場合、claude mcp addコマンドを使います。

claude mcp add --transport stdio esp-idf-eim -- eim run "idf.py mcp-server"

eimを経由せず、すでにESP-IDF環境がアクティベートされたシェルから直接起動する場合は次のようになります。

claude mcp add --transport stdio esp-idf -- idf.py mcp-server

このMCPサーバーが提供するツールは、ターゲットチップの設定・プロジェクトのビルド・接続デバイスへの書き込み・ビルド成果物のクリーン・新規プロジェクトの作成の5つで、いずれも自然言語の指示からそのまま呼び出せます。プロジェクトディレクトリは起動時のオプションかIDF_MCP_WORKSPACE_FOLDER環境変数で指定し、複数プロジェクトを行き来する場合はツール呼び出しごとにディレクトリを明示できます。

Bashツール経由でidf.pyコマンドを直接叩く方法と、このMCPサーバー経由でAIにビルド・書き込みを任せる方法は排他ではなく、対話的な指示にはMCPサーバー、スクリプト化した定型作業にはBashツールという使い分けができます。

Arduino CoreとESP-IDFの使い分け

同じESP32でも、Arduino CoreとESP-IDFでは開発体験が大きく異なります。

観点Arduino CoreESP-IDF
ビルドツールArduino CoreArduino CLIESP-IDFidf.py(CMake + Ninja)
設定変更Arduino Coreスケッチのコード内定数が中心ESP-IDFsdkconfig.defaults / menuconfig

パーティション管理やAI統合(MCPサーバー)の対応状況はArduino Core側の実装次第で変わるため、ここでは扱いません。既存のArduinoスケッチ資産を活かしたい、あるいはライブラリの選択肢を優先するなら、Claude CodeでESP32の開発とシリアルデバッグを進める手順で扱うArduino Core構成が手早く動きます。パーティションレイアウトを自分でCSV設計したい、複数ターゲットチップに対応したいといった要件なら、公式ESP-IDF MCPサーバーも使える本記事のESP-IDF構成が適しています。

よくあるつまずき

  • ビルドは通るがフラッシュ書き込みで失敗する: アプリバイナリがどのappパーティションにも収まらない場合、ビルド自体は失敗しますが、一部のパーティションにだけ収まらない場合は警告のみで書き込みまで進み、実機で問題が表面化することがあります。idf.py partition-tableでパーティションサイズを確認します。
  • CSVのオフセットを手で指定するとエラーになる: appタイプのパーティションは0x10000境界に、サイズはフラッシュセクタ(4KB)境界に揃える必要があります。手で計算せず、オフセット列を空欄にしてツールに自動計算させる方が安全です。
  • set-targetを実行したらsdkconfigの変更が消えた: set-targetはビルドディレクトリのクリアとsdkconfigの再生成を伴う仕様です。個別の設定変更はsdkconfigではなくsdkconfig.defaultsに書いておけば、再生成後も反映されます。
  • IllegalInstructionで再起動を繰り返す: SPIフラッシュのピンをGPIOやUARTとして再設定していないか、あるいはFreeRTOSのタスク関数がvTaskDelete()を呼ばずにreturnで終了していないかを確認します。
  • menuconfigをClaude Codeに実行させようとして止まる: menuconfigはキー入力を待つTUIのため、Bashツールで実行すると応答なしのまま止まります。設定変更はsdkconfig.defaultsの直接編集に切り替えます。
  • idf.py coredump-infoが何も読み出せない: CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=yを設定していても、カスタムパーティションテーブルにdataタイプ・coredumpサブタイプのパーティションが無いと保存先がありません。既定のパーティションテーブルには自動で含まれますが、カスタムテーブルでは明示的に追加します。

まとめ

Claude CodeでESP-IDFを開発する構成は、idf.pyにビルド・書き込みを任せる形が基本です。TUIであるmenuconfigの代わりにsdkconfig.defaultsを直接編集し、パーティションテーブルはCSVで設計します。クラッシュ時はidf.py coredump-infoでフラッシュに保存されたコアダンプを読み出せば、シリアルポートに張り付いて監視し続けなくてもGuru Meditation Errorの発生箇所を特定できます。公式のESP-IDF MCPサーバーを使えば、idf.pyコマンドを直接叩かずに自然言語の指示でビルド・書き込みを進める選択肢もあります。

この記事を共有:XはてブLinkedIn