ESP32で設定が保存されない・不安定になる原因|NVS(不揮発メモリ領域)の詰まりと対策

ESP32のNVS問題 Uncategorized
ESP32のNVS領域が小さいことでうまく作動しない問題

Preferences.putBytes() が0バイト書き込み(wrote=0)で失敗する“気づかない落とし穴”と、安定運用の対策

ESP32で開発していると、次のような「原因が見えにくい不具合」に出会うことがあります。

  • 設定を保存したはずなのに、再起動すると元に戻る
  • 昨日まで動いていたのに、突然不安定になる
  • Wi-Fiやセンサーなど別の問題に見えるが、根本原因が分からない
  • ログを見ると、保存処理が成功したり失敗したり揺れる

こうした“うまく動かない”の正体として見落とされがちなのが、NVS(Non-Volatile Storage)です。Arduino環境では Preferences(Preferences.h) がこの領域を使っており、ここが詰まったり条件を満たせなくなると、設定保存が失敗して挙動が破綻します。

この記事では、ESP32-C3環境で実際に遭遇した「Preferences.putBytes() が wrote=0 を返して保存に失敗する」事例をもとに、なぜ起きるのかどう直すのが筋が良いかを整理します。Wi-Fi設定に限らず、ESP32の“設定永続化”全般に刺さる内容です。


この記事でわかること

  • ESP32で「設定が保存されない」「不安定」「突然おかしい」原因がNVSにあるケース
  • Preferences.putBytes()wrote=0 になる理由(単純な最大サイズ超過だけではない)
  • NVS統計(nvs_get_stats)で見える「状態依存の揺らぎ」
  • 運用で強い設計:NVSは最小、設定本体はLittleFS/SPIFFSへ
  • 安定保存の実装方針:CRC/ヘッダ、デバウンス、tmp→rename(原子的更新)

NVSとは?ESP32の「意識しない領域」

ESP32のフラッシュ(例:4MB)は、内部でパーティション(区画)に分割されます。代表的には次のような領域があります。

  • プログラム領域(app)
  • ファイルシステム領域(SPIFFS / LittleFS)
  • NVS領域(nvs)
  • OTA管理領域(otadata)など

ここで重要なのは、次のズレです。

フラッシュ全体が4MBあっても、NVSは別区画(例:20KB)として固定される。
FSが空いていても、NVSが厳しい状態なら設定保存が壊れます。

この「4MBあるのに?」という直感とのズレが、NVS問題が気づきにくい最大の理由です。


いま起きている問題(現象)

A) Preferences.putBytes() が「0バイト書き込み」で失敗する

ログでは次の形で出ます。

putBytes total=8199 → putBytes wrote=0 → FAILED
putBytes total=16391 → putBytes wrote=0 → FAILED

つまり、保存したいデータ(JSON+ヘッダ+CRC)は組めているのに、書き込みAPIが0を返してストレージに載らない

B) “同じサイズ”でも成功したり失敗したりが揺れる

さらに厄介なのが再現の揺らぎです。

  • 4095(=4103 total)が失敗したログ成功したログが混在
  • 8191(=8199 total)も成功失敗が混在

単純な「最大サイズ超過」なら同じサイズは常に失敗するはずですが、実際は状況によって結果が変わるため、原因特定が難しくなります。

C) NVS統計で used/free が変動し、namespace も複数

例:

used_entries=430 free_entries=200 total_entries=630 namespace_count=4
(別ログ)used_entries=280 free_entries=350 namespace_count=1

NVS領域がそこそこ埋まっている状態があり、状態次第で成功/失敗が揺れている可能性が見えます。

D) NVSパーティションが小さい(20KB)

ログ例:

label=nvs size=20480 bytes (20 KB)

ESP32-C3でNVS 20KBはかなり小さめで、数KB〜十数KBのBLOBや更新頻度があるとすぐ厳しくなります。


根本原因(なぜそうなるか)

原因1:NVSは「巨大なデータを頻繁に上書き」する用途に向かない

NVSはKey-Value形式の永続領域ですが、内部的にはページ管理をしており、更新のたびに新しいエントリを使い、GC(ガベージコレクション)で整理します。

そのため、

  • 同じキーを繰り返し putBytes で上書き
  • サイズが大きい(数KB〜十数KB)
  • 領域が小さい(例:20KB)

という条件が重なると、「連続した空き」や「GCのための作業余裕」が確保できず、書き込みが失敗しやすくなります。

原因2:「空きがある」≠「BLOBが書ける」

nvs_get_statsfree_entries が残っていても、BLOBに必要な内部条件(ページ/連続性/移動用の余裕など)が満たせないことがあります。だから空きがあるのに wrote=0という事象が起き得ます。

原因3:断片化+GC条件で“閾値付近”が揺れる

試験傾向として、

  • 16KB級はほぼ保存不可
  • 8KB級も状況次第で成功/失敗が揺れる

が出ています。これは典型的に、断片化やGCタイミングなど内部状態で結果が変わっているサインです。

原因4:伸びるデータ(ログ/履歴/一覧)をNVSに混ぜると地雷化する

設定JSONに、ログ・履歴・配列など「伸びる要素」が混ざると、NVSの用途(小さく安定した設定)から逸脱します。すると、ある日突然「保存できない」「設定が消える」「挙動が変」として表面化します。


対策はどれが筋が良いか(結論)

あなたの方針の中で、運用の確実性が最も高いのは次です。

✅ 推奨:NVSは“最小”、設定本体はLittleFS/SPIFFSへ移す

理由:

  • NVSの断片化やGC条件に左右されない
  • ファイルは大きくても扱いやすい(数十KBでも現実的)
  • tmp→rename の原子的更新ができ、電源断にも強い
  • 保存頻度を落とせばフラッシュ寿命にも優しい

今回の putBytes wrote=0根本から回避でき、将来の設定拡張にも耐性が出ます。


具体的な設計・実装方針(安定運用のテンプレ)

解決策A:設定ファイルをLittleFSに保存(推奨)

方針

  • cfg_blob.binHeader{crc,len} + JSON を保存(現行方式を踏襲)
  • 保存は「値が変わった時だけ」
  • 変更が続く場合は「最後の変更から10秒後に1回だけ保存」(デバウンス)
  • 書き込みは cfg_blob.tmp に書いて rename(原子的更新)

得られる効果

  • NVS容量・断片化・GC由来の失敗が消える
  • 大きな設定でも安定
  • 電源断耐性(途中で落ちても壊れにくい)

解決策B:NVSは“小さいものだけ”に限定

NVSには、固定長で小さい値だけを保存します(例)。

  • configured=true/false(初期設定済みフラグ)
  • bootCount
  • last_ok_crc(最後に成功した設定のCRC)
  • fail_count など

NVSに入れるべきでないもの

  • 巨大JSON
  • ログ
  • 履歴・一覧(増える配列)
  • 可変長の大きいBLOBを頻繁に上書きするもの

解決策C:ログはFS/サーバへ

ログの保存先の優先順位は現実的にこうなります。

  1. サーバへ送る(HTTPでPOST)
  2. 端末内に残すならFSでローテーション
  3. SDがあるならSDへ

NVS拡張(パーティション変更)はどう扱うべきか

結論:拡張は“保険”にはなりますが、設計としては二番手です。

  • 問題は容量だけでなく、BLOB更新+断片化+GC条件が絡む
  • NVSを増やしても、肥大化すれば再発し得る
  • パーティション変更は運用上やや重い(環境によってフルフラッシュが必要)

おすすめは、

  1. まず設計としてFSへ退避
  2. それでも必要ならNVS増量は最後

ログから読み取れる「現状の限界値」(試験結果の要約)

試験ログの傾向は次の通りです。

  • json_len=8191 (total=8199) は成功することがある(ただし揺らぐ可能性)
  • json_len=16383 (total=16391) は失敗しやすい

つまり現状のNVS環境では、16KB級のBLOBは実質保存不可で、8KB級も内部状態次第で揺れる可能性があります。これは「設定が肥大化した瞬間に突然死する」地雷になり得ます。


最終提案(おすすめの最終形)

✅ 運用で強い構成

  • 設定本体:LittleFS(CRC+len+json)
  • NVS:フラグ/小さい値だけ
  • 保存:変更時のみ、10秒デバウンス
  • ログ:サーバ or FSローテーション

これで、

  • putBytes wrote=0 問題を根本から回避
  • 将来の機能追加(設定拡張)にも耐性
  • 設定の信頼性(CRC)も担保

が成立します。


チェックリスト:ESP32が「思うように動かない」時にNVSを疑うサイン

  • Preferencesで保存した値が、再起動で戻る/一部だけ反映されない
  • putBytes / putString の戻り値をチェックしていない
  • 大きいJSONや履歴・一覧をNVSに入れている
  • UI操作(特にスライダー)で頻繁に保存している
  • 同じサイズの保存が成功したり失敗したり「揺れる」
  • NVSが20KB級の小さな構成

おわりに:原因が見えない不具合ほど、NVSが犯人になりやすい

ESP32には、開発者が意識しないまま踏む「小さな領域」があります。それがNVSです。

フラッシュ4MBの感覚で設計していると、NVS数十KBに引っかかって突然不安定になります。しかも表面症状がWi-Fiやセンサーなど別の問題に見えるため、原因に辿り着けない。

だからこそ、運用で強い方針として「NVSは最小、設定本体はFSへ」が効きます。

もしあなたが今、ESP32で「設定が保存されない」「突然おかしい」「原因不明の揺らぎ」に悩んでいるなら、まずはNVS統計(nvs_get_stats)と、Preferencesの戻り値を確認してみてください。意外なほどあっさり原因が見えることがあります。

コメント

タイトルとURLをコピーしました