ESP32 Arduino 框架中 Preferences 类的使用指导:函数讲解与实战示例

ESP32 Arduino 框架中 Preferences 类的使用指导:函数讲解与实战示例

1. 引言:为什么需要 Preferences?

在 ESP32 开发中,我们经常需要存储一些配置参数、设备状态或用户数据,这些数据需要在设备断电重启后依然能够保留。虽然可以使用文件系统(如 SPIFFS、LittleFS)或 EEPROM 模拟库,但 ESP32 Arduino 核心库内置的Preferences类提供了一种更简单、高效且可靠的非易失性存储(NVS)解决方案。

Preferences 库封装了 ESP32 的非易失性存储(NVS)功能,具有以下优势:

  • 键值对存储:使用简单的键(字符串)来存取数据,无需管理复杂的文件路径。
  • 数据类型丰富:支持整型、浮点型、字符串、二进制数据等多种类型。
  • 命名空间隔离:不同功能模块的数据可以存放在不同的命名空间下,避免键名冲突。
  • 原子操作与磨损均衡:底层 NVS 机制保证了数据写入的原子性,并具有磨损均衡特性,延长 Flash 寿命。
  • 使用简便:无需手动初始化文件系统,API 直观易用。

2. 基本使用流程与头文件

使用 Preferences 前,需要包含相应的头文件并创建对象。

#include <Preferences.h> Preferences preferences;

基本操作遵循“打开 -> 读写 -> 关闭”的流程,关闭操作会将数据真正提交到 Flash。

3. 核心函数详解

3.1 初始化与命名空间管理

  • begin(const char* name, bool readOnly=false, const char* partition_label=NULL)
    • 功能:打开一个命名空间。如果命名空间不存在,则创建它。
    • 参数
      • name:命名空间名称,用于数据隔离。
      • readOnly:是否为只读模式打开。默认为false(读写模式)。
      • partition_label:指定使用的 NVS 分区标签,通常为NULL使用默认的 “nvs” 分区。
    • 返回值:成功打开返回true,失败返回false
  • end()
    • 功能:关闭当前命名空间,确保所有更改写入 Flash。这是一个重要的步骤,不应省略。

3.2 数据写入函数(Put)

用于存储数据。函数名通常以put开头。

  • putChar(const char* key, int8_t value):存储 8 位有符号整数。
  • putUChar(const char* key, uint8_t value):存储 8 位无符号整数。
  • putShort(const char* key, int16_t value):存储 16 位有符号整数。
  • putUShort(const char* key, uint16_t value):存储 16 位无符号整数。
  • putInt(const char* key, int32_t value):存储 32 位有符号整数。
  • putUInt(const char* key, uint32_t value):存储 32 位无符号整数。
  • putLong(const char* key, int32_t value):存储 32 位长整型(与putInt相同)。
  • putULong(const char* key, uint32_t value):存储 32 位无符号长整型(与putUInt相同)。
  • putLong64(const char* key, int64_t value):存储 64 位有符号整数。
  • putULong64(const char* key, uint64_t value):存储 64 位无符号整数。
  • putFloat(const char* key, float_t value):存储单精度浮点数。
  • putDouble(const char* key, double_t value):存储双精度浮点数。
  • putBool(const char* key, bool value):存储布尔值。
  • putString(const char* key, const String& value):存储字符串(String 对象)。
  • putString(const char* key, const char* value):存储字符串(C 风格字符串)。
  • putBytes(const char* key, const void* value, size_t len):存储任意二进制数据。

3.3 数据读取函数(Get)

用于读取数据。如果键不存在,则返回指定的默认值。

  • getChar(const char* key, int8_t defaultValue=0)
  • getUChar(const char* key, uint8_t defaultValue=0)
  • getShort(const char* key, int16_t defaultValue=0)
  • getUShort(const char* key, uint16_t defaultValue=0)
  • getInt(const char* key, int32_t defaultValue=0)
  • getUInt(const char* key, uint32_t defaultValue=0)
  • getLong(const char* key, int32_t defaultValue=0)
  • getULong(const char* key, uint32_t defaultValue=0)
  • getLong64(const char* key, int64_t defaultValue=0)
  • getULong64(const char* key, uint64_t defaultValue=0)
  • getFloat(const char* key, float_t defaultValue=NAN)
  • getDouble(const char* key, double_t defaultValue=NAN)
  • getBool(const char* key, bool defaultValue=false)
  • String getString(const char* key, const String& defaultValue=String(""))
  • size_t getBytes(const char* key, void* buf, size_t maxLen):读取二进制数据到缓冲区,返回实际读取的字节数。

3.4 其他实用函数

  • remove(const char* key):删除指定键及其值。
  • clear():清除当前命名空间下的所有键值对。
  • freeEntries():获取当前命名空间下剩余的可用条目数(键值对数量)。
  • isKey(const char* key):检查指定键是否存在。

4. 综合使用示例

下面是一个完整的示例,演示如何存储 WiFi 配置、设备启动次数和一段自定义二进制数据。

#include <Preferences.h> Preferences prefs; void setup() { Serial.begin(115200); delay(1000); // 1. 打开(或创建)名为 "my_app" 的命名空间 if (!prefs.begin("my_app")) { Serial.println("Failed to open preferences namespace"); return; } // 2. 读写数据 // 读取启动次数,如果不存在则默认为0,然后加1并写回 uint32_t bootCount = prefs.getUInt("boot_count", 0); bootCount++; prefs.putUInt("boot_count", bootCount); Serial.printf("Device boot count: %u\n", bootCount); // 存储 WiFi SSID 和密码 prefs.putString("wifi_ssid", "MyHomeWiFi"); prefs.putString("wifi_pass", "SecurePassword123"); // 读取 WiFi 配置(如果之前存储过) String ssid = prefs.getString("wifi_ssid", ""); String pass = prefs.getString("wifi_pass", ""); if (ssid.length() > 0) { Serial.printf("Stored WiFi SSID: %s\n", ssid.c_str()); // 注意:实际项目中不应在日志中打印密码 } // 存储和读取浮点数(例如传感器校准值) float calibrationFactor = 1.025; prefs.putFloat("cal_factor", calibrationFactor); float readCal = prefs.getFloat("cal_factor", 1.0); Serial.printf("Calibration factor: %.3f\n", readCal); // 存储和读取二进制数据(例如一个简单的结构体) struct MyData { uint8_t id; uint16_t value; } dataToStore = {0xAB, 1234}; prefs.putBytes("my_struct", &dataToStore, sizeof(dataToStore)); MyData dataRead; size_t len = prefs.getBytes("my_struct", &dataRead, sizeof(dataRead)); if (len == sizeof(MyData)) { Serial.printf("Read binary data: ID=0x%02X, Value=%u\n", dataRead.id, dataRead.value); } // 3. 关闭命名空间,提交更改 prefs.end(); Serial.println("Preferences saved successfully."); } void loop() { // 主循环无需操作 Preferences delay(10000); }

5. 高级技巧与注意事项

5.1 命名空间规划

为不同的功能模块使用不同的命名空间,例如"wifi_config""device_settings""user_data"。这可以提高代码的可维护性,并允许单独清除某个模块的数据。

5.2 错误处理

始终检查begin()的返回值。写入失败可能由于 NVS 分区已满或 Flash 损坏。

5.3 数据更新策略

频繁更新同一个键可能会加速 Flash 磨损。对于频繁变化的数据(如传感器实时值),应考虑在 RAM 中缓存,定期或仅在必要时写入 Preferences。

5.4 字符串长度限制

单个键值对的总大小(键名长度 + 数据长度)存在限制(通常约为 1984 字节)。过长的字符串应分段存储或考虑使用文件系统。

5.5 与文件系统的选择

使用 Preferences 当:存储键值对形式的配置、状态标志、计数器等小型结构化数据。
使用 SPIFFS/LittleFS 当:需要存储大文件、日志、网页资源或非结构化的长文本。

6. 常见问题排查(FAQ)

  • Q:数据写入后,重启读取不到?
    A:确保每次修改后都调用了end()close()Preferences类中end()即关闭)。
  • Q:begin()失败返回 false?
    A:检查 NVS 分区是否在分区表中被正确配置,或 Flash 存储空间是否已满。
  • Q:可以存储数组吗?
    A:可以,使用putBytesgetBytes来存储和读取整个数组。
  • Q:如何清空所有数据?
    A:在 Arduino IDE 的“工具”菜单中,选择“擦除 Flash”选项。在代码中,可以对特定命名空间使用clear()

7. 总结

Preferences 库是 ESP32 Arduino 开发中管理非易失性数据的利器。它通过简单的键值对 API 和内置的磨损均衡机制,让数据持久化变得安全便捷。掌握其核心函数和最佳实践,可以有效地存储设备配置、运行状态和用户设置,提升项目的可靠性。

建议在实际项目中,结合具体需求规划命名空间和键名,并养成良好的“打开-关闭”习惯,确保数据完整性。