ESP32 ソフトウェア 回路設計

ESP32のFlashにデータを保存する方法|NVS・Preferencesを解説

投稿日:

ESP32でプログラムを作っていると、

「電源を切っても設定値を残したい」
「Wi-Fiの設定を保存したい」
「最後に設定した値を次回起動時にも使いたい」

という場面があります。

通常の変数はRAM上に保存されるため、ESP32の電源をOFFすると内容は失われます。

たとえば、

int temperature = 25;

と変数へ値を保存しても、これは電源OFF後まで保持される設定データにはなりません。

そこでESP32では、Flash Memoryへデータを保存する方法として**NVS(Non-Volatile Storage)**を利用できます。

Arduino-ESP32を使用している場合には、NVSを扱いやすくしたPreferences Libraryも用意されています。EspressifはPreferencesを従来のArduino EEPROM Libraryに代わる方法として案内しています。

この記事では、

NVSとは何か
Preferencesとの違い
実際に値を保存・読み出す方法
Flashの書き換え寿命
NVSへ保存するのに向いているデータ

まで解説します。

なぜ普通の変数ではデータを保存できない?

まずESP32のメモリについて簡単に整理します。

プログラム実行中に使用する変数の多くはRAM上に置かれます。

イメージとしては、

ESP32

RAM
├─ 変数
├─ Stack
└─ Heap

Flash
├─ Firmware
├─ NVS
└─ File Systemなど

という構成です。

RAMは高速に読み書きできますが、電源がなくなると内容が失われる揮発性メモリです。

一方、Flash Memoryは電源を切っても内容を保持できます。

したがって、

電源ON
  ↓
設定値を変更
  ↓
電源OFF
  ↓
次回起動
  ↓
前回の設定値を復元

という動作を実現するには、設定値を不揮発メモリへ保存する必要があります。

ESP32のNVSとは?

NVSはNon-Volatile Storageの略です。

ESP-IDFに用意されている、Flash Memory上へデータを保存するための仕組みです。

NVSではデータを、

Key → Value

というKey-Value形式で管理します。

たとえば、

"volume" → 8
"temp"   → 25
"mode"   → 1

というように、データへ名前を付けて保存できます。

さらにNVSではNamespaceを使ってKeyをグループ分けできます。

Namespace : "settings"

├─ "volume" → 8
├─ "temp"   → 25
└─ "mode"   → 1

別のNamespaceなら、同じKey名を使用しても衝突しません。ESP-IDFのNVS APIでも、nvs_open()でNamespaceを指定し、取得したHandleを使ってnvs_get_*()nvs_set_*()などを操作する構造になっています。

製品Firmwareでも、

"system"
"network"
"sensor"
"user"

など、用途ごとにデータを整理できます。

NVSにはどんなデータを保存できる?

NVSでは整数だけでなく、複数種類のデータを扱えます。

代表的には、

  • 8~64bitの整数
  • String
  • Blob(Binary Data)

などです。

Arduino-ESP32のPreferencesでは、さらにAPIとして、

putBool()
putInt()
putUInt()
putFloat()
putDouble()
putString()
putBytes()

などが用意されています。

そのため、

温度設定値
音量
動作モード
Calibration値
初期設定完了フラグ
Wi-Fi関連の設定

など、小さな設定値を保存する用途に向いています。

NVSとPreferencesは何が違う?

ここは混同しやすいポイントです。

NVSとPreferencesは別々の保存媒体ではありません。

Arduino-ESP32のPreferences Libraryは、内部でESP32のNVSを利用しています。Espressifの公式Arduino-ESP32 Documentationでも、PreferencesはESP32のNVSを利用してデータを保存すると説明されています。

関係を簡単にすると、

Arduino-ESP32
     │
Preferences
     │
    NVS
     │
Flash Memory

となります。

一方、ESP-IDFを直接使用する場合は、

ESP-IDF
   │
 NVS API
   │
Flash Memory

という使い方ができます。

つまり、

PreferencesはNVSをArduinoから扱いやすくしたAPI

と考えると分かりやすいでしょう。

ArduinoでPreferencesを使って値を保存する

Arduino-ESP32では、Preferences Libraryを使用すると比較的簡単にNVSへ値を保存できます。

まず、

#include <Preferences.h>

Preferences preferences;

とします。

そしてNamespaceを開きます。

preferences.begin("settings", false);

"settings"がNamespace名です。

第2引数のfalseはRead/Write Modeを意味します。trueを指定するとRead Onlyになります。

値を保存する場合は、

preferences.putInt("temp", 25);

とします。

これで、

Namespace : settings
Key       : temp
Value     : 25

として保存できます。

処理が終わったら、

preferences.end();

でNamespaceを閉じます。

保存した値を読み出してみる

次回起動時には、保存した値を読み出せます。

たとえば、

preferences.begin("settings", true);

int temperature = preferences.getInt("temp", 20);

preferences.end();

とします。

ここでは"temp"というKeyから値を取得しています。

第2引数の20は、Keyが存在しなかった場合に返すDefault値です。

つまり初回起動などでまだデータが保存されていなくても、

保存済み
   ↓
保存値を使用

未保存
   ↓
Default値を使用

という処理にできます。

PreferencesではNamespaceをRead Onlyで開いても値の読み出しは可能です。値を書き込む場合にはRead/Write Modeで開く必要があります。

電源OFFしても本当に値は残る?

残ります。

PreferencesはESP32のNVSを利用しているため、保存したデータはRestartや電源断をまたいで保持されます。

たとえば、

初回起動
temp = 20

   ↓

ユーザー操作
temp = 28

   ↓
NVSへ保存

   ↓
電源OFF

   ↓
再び電源ON

   ↓
NVSから読み出す

temp = 28

という動作を作れます。

設定値を保存する機器では非常によく使う処理です。

初回起動かどうかを判定することもできる

製品では、

工場出荷状態

一度設定された状態

を区別したいことがあります。

PreferencesではisKey()を使って、特定のKeyが存在するか確認できます。Espressifの公式Tutorialでも、この方法を利用して初回起動時にDefault設定を作成する例が紹介されています。

たとえば、

preferences.begin("settings", false);

if (!preferences.isKey("init")) {
    preferences.putInt("temp", 25);
    preferences.putBool("init", true);
}

preferences.end();

とすれば、"init"が存在しない場合だけ初期値を書き込めます。

これを利用すると、

初回起動
   ↓
Factory Defaultを書き込む
   ↓
init = true

以降の起動
   ↓
保存済み設定を読み出す

という構成にできます。

製品Firmwareでは使いやすい方法です。

NVSは何でも保存する場所ではない

ここからが重要です。

NVSは便利ですが、Flashへ保存できるからといって、あらゆるデータをNVSへ書けばよいわけではありません。

EspressifはNVSについて、主に頻繁には変化しないConfiguration Dataの保存を推奨しています。反対に、頻繁かつ大量に更新するLogging用途などには適さないと説明しています。

たとえば、

○ 向いている

温度設定値
Wi-Fi設定
Calibration値
動作モード
ユーザー設定
初期化済みフラグ

に対して、

△~× 向いていない

高速Sampling Data
毎秒更新するLog
大量の画像Data
大きなFile
連続的な履歴Data

という違いがあります。

Arduino-ESP32のPreferences Documentationでも、多数の小さな値の保存に適しており、大量データならLittleFSなどのFile Systemを検討するよう案内されています。

Flashには書き換え寿命がある

NVSの保存先であるSPI NOR Flashには、無制限にErase/Writeできるわけではありません。

そのため、

void loop()
{
    preferences.putInt("temp", temperature);
}

のように、Loopを回るたびにFlashへ書き込む設計は避けます。

NVSにはFlashの特定領域だけへ書き込みが集中しないよう、内部にWear Levelingの仕組みがあります。EspressifのFAQでも、NVSには独自のErase/Write balancing mechanismが実装されていることが説明されています。

ただし、

Wear Levelingがある=何回でも書き込んでよい

ではありません。

FlashそのものにはErase Cycleの寿命があります。

したがってFirmware側でも、不要な書き込みを減らす設計が重要です。

「値が変わったときだけ保存する」

基本的な考え方はシンプルです。

たとえば温度設定値なら、

現在値 25℃
   ↓
ユーザーが28℃へ変更
   ↓
RAM上では28℃
   ↓
設定確定
   ↓
NVSへ28℃を保存

とします。

毎Loop、

25 → 保存
25 → 保存
25 → 保存
25 → 保存
25 → 保存

とする必要はありません。

設定値が変化したときや、ユーザーが設定を確定したタイミングなど、意味のあるタイミングだけFlashへ保存する方が合理的です。

NVSは電源断にも配慮されている

製品では、Flashへ書き込んでいる途中で突然電源が切れる可能性もあります。

NVSはこの点も考慮されており、Espressifは突然のPower Lossに対する保護とAtomic UpdateをNVSの特徴として挙げています。

これは設定値を扱ううえで大きなメリットです。

ただし、これも、

「いつ電源を切ってもApplication側では何も考えなくてよい」

という意味ではありません。

製品全体としては、電源断の可能性、保存するタイミング、設定値の整合性などを考える必要があります。

ESP-IDFでNVSへデータを保存する

Arduino-ESP32ではPreferencesを利用しましたが、ESP-IDFではNVS APIを直接使用できます。

基本的な流れは、

NVSを初期化
    ↓
Namespaceを開く
    ↓
値を読み書き
    ↓
Commit
    ↓
Handleを閉じる

です。

まずNVS Flashを初期化します。

#include "nvs_flash.h"
#include "nvs.h"

esp_err_t ret = nvs_flash_init();

if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
    ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {

    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
}

ESP_ERROR_CHECK(ret);

NVS Partitionに空きPageがない場合や、現在のNVS Libraryと互換性のない形式が検出された場合などを考慮した、ESP-IDFで一般的に使われる初期化方法です。EspressifのNVS API Documentationでも同様の初期化処理が示されています。

Namespaceを開く

NVSを初期化したら、nvs_open()でNamespaceを開きます。

nvs_handle_t handle;

ESP_ERROR_CHECK(
    nvs_open("settings", NVS_READWRITE, &handle)
);

ここでは、

Namespace = settings
Mode      = Read / Write

として開いています。

読み出しだけでよければ、

NVS_READONLY

を指定できます。

Preferencesの、

preferences.begin("settings", false);

と考え方はよく似ています。

ESP-IDFで整数を保存する

たとえば温度設定値25を保存する場合は、

int32_t temperature = 25;

ESP_ERROR_CHECK(
    nvs_set_i32(handle, "temp", temperature)
);

とします。

ただし、ESP-IDFではここで終わりではありません。

重要なのが、

ESP_ERROR_CHECK(
    nvs_commit(handle)
);

です。

NVS APIでは、nvs_set_*()を実行しただけでは変更内容が必ずしもNon-Volatile Storageへ確定しているとは限りません。nvs_commit()を呼び出して変更を確定します。

最後に、

nvs_close(handle);

でHandleを閉じます。

ESP-IDFで保存値を読み出す

保存した値はnvs_get_*()で取得できます。

int32_t temperature = 20;

esp_err_t err =
    nvs_get_i32(handle, "temp", &temperature);

if (err == ESP_ERR_NVS_NOT_FOUND) {
    temperature = 20;
}

この例では、"temp"が存在しなければDefault値として20を使用しています。

Preferencesの、

preferences.getInt("temp", 20);

ではDefault値まで一つのAPIで指定できましたが、ESP-IDFでは戻り値を確認して処理できます。

少しコード量は増えますが、

Keyが存在しない
データ型が違う
その他のErrorが発生した

といった状態をApplication側で明確に扱えます。

nvs_commit()を忘れない

ESP-IDFでNVSを初めて使うときに注意したいのがnvs_commit()です。

基本的には、

nvs_set_xxx()
     ↓
nvs_set_xxx()
     ↓
nvs_set_xxx()
     ↓
nvs_commit()

という流れで変更内容を確定できます。

たとえば、

ESP_ERROR_CHECK(
nvs_set_i32(handle, "temp", 25)
);

ESP_ERROR_CHECK(
nvs_set_u8(handle, "mode", 1)
);

ESP_ERROR_CHECK(
nvs_commit(handle)
);

のように複数項目を更新したあと、Commitすることもできます。

ここでも、

1回値を変更するたびに、何も考えずFlashへ書き込む

のではなく、Applicationとして設定が確定するタイミングを考えることが重要です。

PreferencesとESP-IDF NVS APIはどちらを使う?

どちらを使うかは、使用しているFrameworkによって考えると分かりやすいです。

開発環境選択肢
Arduino IDE + Arduino-ESP32Preferences
PlatformIO + ArduinoPreferences
ESP-IDFNVS API
ESP-IDF + Arduino Component用途に応じて選択

Arduino Frameworkで小さな設定値を保存するなら、Preferencesは非常に扱いやすい方法です。

一方ESP-IDFでは、NVS APIを直接使うことでError処理やPartitionなどを含めて、より細かく制御できます。

重要なのは、

PreferencesとNVSのどちらが高性能か

という比較ではありません。

Preferences自体がNVSを利用しているため、使用しているFrameworkに適したInterfaceを選ぶと考える方が自然です。

Arduino IDE・PlatformIO・ESP-IDFの違いや、どの開発環境を選べばよいかについては「ESP32の開発環境を比較|Arduino IDE・PlatformIO・ESP-IDFはどれを選ぶ?」で詳しく解説しています。

NVSとLittleFSはどう使い分ける?

ESP32ではFlashへデータを保存する方法として、NVS以外にLittleFSなどのFile Systemを利用する方法もあります。

大まかな使い分けは、

データ向いている方法
設定値NVS
動作モードNVS
Calibration値NVS
FlagNVS
少量の文字列NVS
大きな設定FileLittleFSなど
HTML / CSSLittleFSなど
Fileとして扱いたいDataLittleFSなど
大量のLog用途に適したFile System等を検討

と考えると分かりやすいでしょう。

NVSはKeyを指定して、

"temp" → 25

のように扱う用途が得意です。

一方、

/config/settings.json

のようにFileそのものとして管理したければ、File Systemの方が扱いやすい場合があります。

EspressifもNVSをConfiguration Dataなどの保存に適した方式として位置付け、大量データについてはFile System等の利用を案内しています。

Factory Resetはどう実装する?

製品では、保存した設定を工場出荷状態へ戻したい場合があります。

Preferencesでは、

preferences.clear();

を使用すると、現在開いているNamespace内のKeyを削除できます。

特定のKeyだけ削除するなら、

preferences.remove("temp");

も利用できます。

ESP-IDFでは、

nvs_erase_key(handle, "temp");

で特定Keyを削除できます。

Namespace内のすべてのKeyを削除する場合は、

nvs_erase_all(handle);
nvs_commit(handle);

とします。

これを利用して、

Factory Reset操作
       ↓
設定Namespaceを消去
       ↓
再起動
       ↓
Keyなしを検出
       ↓
Default設定を生成

という構成にできます。

NVS Partition全体を簡単に消さない

ここは製品Firmwareでは注意したいところです。

Factory Resetだからといって、

nvs_flash_erase();

を安易に呼ぶ設計にはしない方がよいでしょう。

NVS PartitionにはApplicationが想定している以外のデータが存在する可能性があります。

たとえば、

user_settings
network
calibration
production

のように用途をNamespaceで分けておけば、

ユーザー設定だけ初期化する
Calibration値は残す

といった制御ができます。

Factory Resetで「何を消して、何を残すか」は、製品仕様として決めておくことが重要です。

Firmwareを更新してもNVSの値は残る?

通常、Firmwareを書き換えただけでNVS Partitionが自動的に消去されるわけではありません。

そのためOTAなどでFirmwareを更新した後も、NVSに保存された設定値を引き継ぐ設計ができます。

これは便利ですが、別の問題もあります。

たとえばVersion 1のFirmwareで、

temp
mode

を保存していたとします。

Version 2では、

temp
mode
language
sensor_type

へ設定項目が増えるかもしれません。

さらに将来、

mode : uint8_t

だったものを別の形式へ変更する可能性もあります。

すると、

古いFirmwareが保存したデータを、新しいFirmwareがどう解釈するか

を考える必要があります。

設定データにもVersionを持たせる

長期間更新する製品では、設定データにVersion情報を持たせる方法があります。

たとえば、

config_version = 2
temp           = 25
mode           = 1

としておきます。

起動時に、

NVS読出し
   ↓
config_version確認
   ↓
現在Versionと同じ?
   ├─ YES → そのまま使用
   │
   └─ NO
       ↓
   Migration処理

とします。

これによってFirmware Update後に設定項目が変わっても、古い設定を新しい形式へ変換できます。

これはNVS固有の機能というより、Application側で設計するデータ管理方法です。

しかし製品を長期間Updateするなら非常に重要になります。

Calibration値とユーザー設定は分ける

もう一つ実務でおすすめしたいのが、保存データを種類ごとに分けることです。

たとえば、

Namespace : "user"
├─ temp
├─ volume
└─ mode

Namespace : "cal"
├─ offset
└─ gain

Namespace : "system"
├─ config_version
└─ initialized

という構成です。

こうしておけばFactory Resetするときに、

user → 消す
cal  → 残す

といった処理ができます。

工場で書き込んだCalibration値までユーザー操作で消えてしまう設計は避けたいところです。

データの用途と寿命を考えてNamespaceを設計すると、後々のFirmware変更にも対応しやすくなります。

保存直後に電源を切って大丈夫?

NVS自体には突然のPower Lossを考慮した仕組みがありますが、製品としてはもう一段考えます。

たとえばユーザーが設定を変更するたびに、

ボタン操作
 ↓
即Flash Write
 ↓
ボタン操作
 ↓
即Flash Write

とすると、操作回数だけ書き込みが発生します。

そこで、

設定変更
 ↓
RAM上で更新
 ↓
一定時間変更なし
 ↓
設定確定
 ↓
NVSへ保存

という方法も考えられます。

ただし保存を遅らせすぎれば、その間に電源が切れたとき変更内容が失われます。

つまり、

Flash寿命を優先して保存回数を減らす

ことと、

設定変更を確実に残す

ことにはTrade-offがあります。

製品仕様に応じて保存タイミングを決める必要があります。

毎秒保存するとどうなる?

仮に設定値を毎秒Flashへ書き込むとします。

1日では、

60 × 60 × 24
= 86,400回

です。

1年では約3,150万回の保存要求になります。

NVSにはWear Levelingがあるため、「同じFlash Cellを3,150万回直接Eraseする」という単純な話ではありません。

しかし、そもそも設定値を毎秒不揮発化する必要がないのであれば、Firmware側で不要な書き込みを発生させないことが第一です。

たとえば累積稼働時間のような値を保存したい場合にも、毎秒Flashへ保存するのではなく、

RAM上で積算
 ↓
一定間隔
または
正常Shutdown時
 ↓
NVSへ保存

など、要求仕様に応じた方法を検討します。

NVSへ保存する値・しない値を最初に決める

Firmware設計時には、変数ごとに、

電源OFF後も必要?

を考えると整理しやすくなります。

たとえば、

データNVS保存
ユーザー設定温度
Wi-Fi設定
Calibration値
初期設定完了Flag
現在の一時的なADC値×
通信中の一時Buffer×
毎周期更新する内部変数×
大量の連続Log△ 別方式を検討

「後で必要になるかもしれないから全部保存する」のではなく、電源断後に復元する必要があるものだけを選ぶのが基本です。

製品FirmwareでのNVS設計チェックリスト

最後に、実際にNVSを使うときの確認項目をまとめます。

確認項目内容
保存対象本当に電源OFF後も必要か
データ量NVSに適した小さなデータか
Namespace用途ごとに整理されているか
Default値初回起動時の値を定義したか
書き込み頻度不要なFlash Writeがないか
保存タイミング設定確定時など明確になっているか
Error処理読み書き失敗を考慮しているか
Factory Reset消すデータ・残すデータを決めたか
Calibrationユーザー設定と分離したか
Version管理Firmware更新後の互換性を考えたか
Migration設定形式変更時の処理を考えたか
電源断保存途中・保存前の電源断を考慮したか

APIを呼んで値を保存するだけなら簡単です。

しかし製品では、数年後のFirmware Updateまで考えて保存データを設計することが重要になります。

まとめ|ESP32の設定保存にはNVSを活用する

ESP32で電源OFF後も設定値を保持したい場合、NVSを利用できます。

Arduino-ESP32ではPreferences Libraryを使うことで、NVSを比較的簡単に扱えます。

ESP-IDFではNVS APIを直接使用し、

nvs_open()
nvs_get_xxx()
nvs_set_xxx()
nvs_commit()
nvs_close()

という流れでデータを管理できます。

重要なのは、

Flashに保存できるから、何でも保存する

という考え方にしないことです。

NVSは、

設定値
Calibration値
動作モード
初期化Flag

など、小さく、頻繁には変更されないデータの保存に適しています。

大量のデータや頻繁なLogには、用途に応じてLittleFSなど別の保存方法を検討します。

また製品Firmwareでは、

書き込み頻度
Factory Reset
設定Version
Firmware Update時のMigration

まで考えておくことで、後からFirmwareを変更したときにも対応しやすくなります。

NVSは単なる「電源を切っても値が残る機能」ではありません。

製品の設定データを長期間どう管理するか

という視点で使うと、より実用的な仕組みになります。

今回の記事からは、既存の**「ESP32の開発環境を比較|Arduino IDE・PlatformIO・ESP-IDFはどれを選ぶ?」**への内部リンクも自然に設定できます。PreferencesとESP-IDF NVS APIの違いを読んで「そもそもどの開発環境を選ぶ?」となった読者をつなげられます。

スポンサーリンク

スポンサーリンク

-ESP32, ソフトウェア, 回路設計

Copyright© 江藤良樹の仕事は物づくり , 2026 All Rights Reserved.