最近ESP32をいじってて、ふと思ったんだけど、みんなパーティションテーブルってちゃんと意識してる?正直、僕も最初は「なんかよくわからんけどデフォルトで動いてるし、まあいっか」って感じで触ってた。でもね、これを知ってるかどうかで、できることの幅が全然違ってくるんだよね。特に「設定値を再起動しても残したい」とか「OTAアップデートを実装したい」ってなったら、避けては通れない道。
要するに、ESP32のフラッシュメモリっていう一枚の大きな土地を、どういう区画に分けるか決めるのがパーティションテーブルの役割。アプリを置く「居住区」、設定を保存する「倉庫」、ファームウェア更新用の「予備地」みたいに、役割分担をさせるための設計図みたいなものかな。
で、なんでこれがそんなに大事なの?
なんでかっていうと、これをちゃんとやらないと、例えばファームウェアを更新(OTA)しただけで、ユーザーが設定したWi-Fiのパスワードとかが全部消えちゃう、みたいな悲劇が起こりうるから。アプリとデータがごちゃ混ぜになってると、片方をいじっただけでもう片方に影響が出ちゃう。分離させておくのが、やっぱり基本だよね。
デフォルトのパーティション構成だと、`nvs`(設定とかを保存する場所)と`factory`(アプリ本体)の2つがメイン。これでも簡単なことであれば十分なんだけど、ちょっと凝ったこと、例えばOTA(Over-The-Air)アップデートをやろうとすると、もう全然足りない。
じゃあ、どうやって作るの?
「難しそう…」って思うかもしれないけど、実はただのCSVファイルなんだ。びっくりするくらいシンプル。構造はこんな感じ。
# <a href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/partition-tables.html" target="_blank" class="blogHightLight_css nobox">ESP-IDF</a> Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
otadata, data, ota, 0xf000, 0x2000,
ota_0, app, ota_0, 0x20000, 0x200000,
ota_1, app, ota_1, 0x220000, 0x200000,
storage, data, spiffs, 0x420000, 0x200000,
この6つのカラムの意味さえ分かれば、もうこっちのもの。
- Name: パーティションの名前。自分で分かりやすい名前をつければOK(ただし16文字まで)。`nvs`っていう名前だけはシステムが特別扱いするから、基本的にはそのまま使う。
- Type: `app`(アプリ用)か`data`(データ用)の二択。シンプルでしょ。
- SubType: ここが一番のキモ。`data`の中でも「これはNVS用」「これはSPIFFSファイルシステム用」みたいに、具体的な用途を決める場所。`app`なら`factory`(工場出荷時)とか`ota_0`, `ota_1`(OTA用)とかを指定する。
- Offset: パーティションの開始アドレス。…なんだけど、正直ここは空欄にしとくのが一番。そうすればESP-IDFが前のパーティションの終わりから自動で計算してくれる。自分でやると計算ミスでハマる元。マジで。
- Size: パーティションのサイズ。`4K`とか`1M`みたいに書ける。アプリのサイズが将来大きくなることを見越して、特に`app`領域は余裕を持たせておくのがセオリー。
- Flags: 今のところは`encrypted`ってフラグを立てて、フラッシュの暗号化を有効にするかどうかに使うくらいかな。
このCSVファイルを作って、`menuconfig`で「Custom partition table CSV file」にそのファイル名を指定してあげれば、ビルド時に反映される。簡単だよね。
あ、ちなみにESP-IDFのバージョンはv4.2以降を想定してる。古いバージョンだと微妙に違うかもしれないから注意して。Espressifの公式ドキュメントは英語だけど、一番正確だから困ったらそこを見るのが確実。でも、日本のM5StackのコミュニティとかQiitaの記事とかを見ると、もっと具体的な使い方が日本語で議論されてて、そっちの方がとっつきやすいかもね。
どのデータタイプ(SubType)を使えばいいの?
データ用のパーティションを作るとき、`nvs`, `fat`, `spiffs` のどれを使うか迷うと思う。僕なりの使い分けはこんな感じ。
| SubType | どんなとき使う? | メリット | デメリット |
|---|---|---|---|
| nvs | Wi-FiのSSID/パスワード、デバイスの個体設定値とか、数キロバイト程度の少量のキーバリューデータを保存したいとき。 | ・APIがシンプルで使いやすい。 ・暗号化もサポートされててセキュア。 |
・大量のデータとか、頻繁な書き換えには向かない。 ・ファイルっていう概念がない。 |
| spiffs | 画像ファイルやJSONの設定ファイルみたいに、ある程度まとまったサイズのファイルをいくつか置きたいとき。Webサーバーのコンテンツ置き場とか。 | ・ウェアレベリング(書き込み平準化)を自動でやってくれる。 ・ファイル単位で管理できる。 |
・ディレクトリ(フォルダ)が作れない。 ・公式ではもう非推奨で、FAT FSへの移行が推奨されてる。 |
| fat | SDカードみたいに使いたいとき。データロガーでどんどんログを貯めたり、PCでファイルを読み書きしたり。一番汎用性が高いかも。 | ・みんな知ってるFATファイルシステム。 ・ディレクトリも作れる。 ・ウェアレベリングと組み合わせられる。 |
・SPIFFSに比べると、ちょっとだけメモリ使用量が大きい…かな?でも最近のESP32なら誤差みたいなもん。 |
個人的には、ちょっとした設定なら`nvs`、ファイルとして何かを保存したいならもう`fat`でいいかなって思うことが多い。`spiffs`も悪くないんだけど、公式が「今後はFATを使ってね」って言ってるから、これから新しく作るならFATの方が安心感あるよね。
よくあるハマりどころと解決策
カスタムパーティションで絶対一回は通るエラーと、その解決策をメモしとく。
「Failed to find X partition...」
これは、コードから探そうとしたパーティションがCSVファイルに存在しないってこと。大体は`SubType`の指定ミスか、単純なタイポ。CSVファイルを見直せばすぐわかるはず。
「Partition overlapping issue」
これ、一番よく見るやつ。「パーティションが重なってるよ!」って怒られてる。自分で`Offset`を手計算してるとマジでよくやる。前のパーティションのサイズを間違えたりとか。
解決策: だから言ったじゃん!`Offset`は空欄にしとこう!それが一番安全で確実。
「Offset is not aligned to 0x10000」
`app`タイプのパーティション(`ota_0`とか)は、64KB(0x10000)の境界に配置しないといけないっていうルールがある。これも自分で`Offset`を計算してるとやらかしがち。
解決策: やっぱり`Offset`は空欄で!コンパイラは神。
そもそも変更が反映されない
CSVファイルを書き換えてビルドして書き込んだのに、なんか挙動が変わらない…ってとき。パーティションテーブル自体を変更したときは、一回フラッシュを全部消去しないとダメ。
idf.py -p <COM_PORT> erase_flash
これをやってから、もう一回書き込んでみて。パーティションテーブルはメモリの超基本構造だから、これを変えるってのは、いわば更地にして家を建て直すようなもの。前の家の基礎が残ってたらおかしくなるでしょ?それと同じ。
まとめというか、 TL;DR
正直、最初は面倒くさい。でも、一回このパーティションテーブルの考え方を理解しちゃうと、ESP32のフラッシュメモリを本当に隅々まで使い倒せるようになる。外部にEEPROMとかSDカードを追加しなくても、本体のメモリだけで結構いろんなデータを永続化できるから、コスト的にもサイズ的にも有利。
怖がらずに、まずは`Offset`を空欄にした簡単なCSVから試してみるのがおすすめ。一度OTA対応の構成が作れるようになると、もうあなたはESP32中級者以上を名乗っていいと思うよ、マジで。
それで、あなたならどんなカスタムパーティションを作ってみたいですか? データロガー用に巨大なFAT領域を作りますか?それとも、たくさんの小さな設定を管理するために、複数のNVSパーティションを用意しますか?ぜひコメントであなたのアイデアを教えてください!
