NVS存储
概述
NVS(非易失性存储)使设备在断电重启后仍能保留关键数据。演示通用的 JSON 文件存储实现,同时利用 RS232/RS485 串口输出日志,方便通过串口工具观察存储效果。
技术栈
- 运行环境:.NET nanoFramework
- 硬件平台:ESP32-S3 (YF3300)
- 存储介质:Flash 文件系统 (SPIFFS)
- 日志输出:RS232 (COM2) + RS485 (COM1) + Debug
所需 NuGet 包
| 包名 | 用途 |
|---|---|
nanoFramework.CoreLibrary | 基础运行时 |
nanoFramework.Json | JSON 序列化/反序列化 |
nanoFramework.System.IO.FileSystem | 文件系统读写 |
nanoFramework.Hardware.Esp32 | ESP32 引脚功能配置 |
nanoFramework.System.IO.Ports | RS232/RS485 串口通信 |
nanoFramework.System.IO.Streams | 流操作(文件系统依赖) |
nanoFramework.System.Text | UTF-8 编码 |
nanoFramework.System.Collections | 集合类型支持 |
硬件连接说明
例程通过串口输出日志,便于在终端观察 BootCount 递增:
| 串口 | 端口名 | TX | RX | 波特率 |
|---|---|---|---|---|
| RS232 | COM2 | GPIO11 | GPIO12 | 9600 |
| RS485 | COM1 | GPIO9 | GPIO10 | 9600 |
核心概念
ConfigStorage 类
封装了完整的文件存储生命周期:
| 方法 | 功能 |
|---|---|
ConfigStorage(path) | 构造函数,指定文件路径 |
Load() | 加载 JSON → 反序列化;文件不存在/损坏返回默认值 |
Save(config) | 序列化为 JSON → 写入文件 |
Exists() | 检查文件是否存在(判断首次启动) |
Delete() | 删除文件(恢复出厂设置) |
Log 方法
同时输出到 Debug 和两个串口,确保在各种调试方案下都能看到日志:
private static void Log(string message)
{
Debug.WriteLine(message); // VS 输出窗口
_rs232Port.Write(data, ...); // RS232 串口终端
_rs485Port.Write(data, ...); // RS485 串口终端
}
程序流程
Main()
│
├─ InitRS232() → 配置 GPIO11/12,打开 COM2
├─ InitRS485() → 配置 GPIO9/10,打开 COM1
│
├─ 1. 创建 ConfigStorage(@"I:\device_config.json")
│
├─ 2. storage.Exists() → 判断是否首次启动
│
├─ 3. storage.Load() → 加载 JSON → 反序列化为 DeviceConfig
│ └─ 文件不存在/损坏 → 返回默认配置
│
├─ 4. 首次启动:设置初始化参数
│
├─ 5. BootCount++ → storage.Save() → JSON 序列化 → 写入文件
│
├─ 6. storage.Load() → 验证持久化成功
│
└─ Thread.Sleep(Timeout.Infinite) → 保持运行
验证方法
断电重启后通过串口终端观察 BootCount 是否递增,验证 NVS 持久化是否生效。
项目结构
NvsStorage/
├── Program.cs # 主入口,启动计数 + 串口日志
├── ConfigStorage.cs # 通用配置存储类
├── NvsStorage.nfproj # 项目文件
├── packages.config # NuGet 依赖
├── NvsStorage.slnx # 解决方案
└── Properties/
└── AssemblyInfo.cs
全部代码
Program.cs
Program.cs
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.IO.Ports;
using YFSoft.Hardware.YF3300_ESP32S3;
using nanoFramework.Hardware.Esp32;
namespace NvsStorage
{
public class Program
{
private static SerialPort _rs232Port;
private static SerialPort _rs485Port;
public static void Main()
{
InitRS232();
InitRS485();
Log("\r\n========================================");
Log("=== NVS 存储测试开始 (COM1+COM2输出) ===");
Log("========================================");
var storage = new ConfigStorage(@"I:\device_config.json");
bool isFirstBoot = !storage.Exists();
if (isFirstBoot)
Log("[首次启动] 检测到首次启动,将创建默认配置");
var deviceConfig = storage.Load();
Log($"设备名称 : {deviceConfig.DeviceName}");
Log($"启动次数 : {deviceConfig.BootCount}");
if (isFirstBoot)
{
deviceConfig.DeviceName = "YF3300_ESP32S3";
deviceConfig.BootCount = 0;
}
deviceConfig.BootCount++;
bool saved = storage.Save(deviceConfig);
var verifyConfig = storage.Load();
if (saved && verifyConfig.BootCount == deviceConfig.BootCount)
{
Log($"\r\nNVS 持久化成功! 本次是第 {deviceConfig.BootCount} 次启动 ");
}
else
{
Log("!!! NVS 持久化失败 !!!");
}
Log("========================================");
Log("提示: 断电重启后查看串口,BootCount 应递增");
Log("========================================\r\n");
Thread.Sleep(Timeout.Infinite);
}
private static void InitRS232()
{
Configuration.SetPinFunction(Mainboard.RS232.TxPin, DeviceFunction.COM2_TX);
Configuration.SetPinFunction(Mainboard.RS232.RxPin, DeviceFunction.COM2_RX);
_rs232Port = new SerialPort(
Mainboard.RS232.PortName,
Mainboard.RS232.DefaultBaudRate,
Parity.None, 8, StopBits.One);
_rs232Port.Open();
}
private static void InitRS485()
{
Configuration.SetPinFunction(Mainboard.RS485.TxPin, DeviceFunction.COM1_TX);
Configuration.SetPinFunction(Mainboard.RS485.RxPin, DeviceFunction.COM1_RX);
_rs485Port = new SerialPort(
Mainboard.RS485.PortName,
Mainboard.RS485.DefaultBaudRate,
Parity.None, 8, StopBits.One);
_rs485Port.Open();
Thread.Sleep(500);
}
private static void Log(string message)
{
Debug.WriteLine(message);
byte[] data = Encoding.UTF8.GetBytes(message + "\r\n");
if (_rs232Port != null && _rs232Port.IsOpen)
_rs232Port.Write(data, 0, data.Length);
if (_rs485Port != null && _rs485Port.IsOpen)
_rs485Port.Write(data, 0, data.Length);
}
}
// 设备配置数据类
public class DeviceConfig
{
public int BootCount { get; set; }
public string DeviceName { get; set; } = "YF3300_ESP32S3";
}
}
ConfigStorage.cs
ConfigStorage.cs
using System;
using System.Diagnostics;
using System.IO;
using nanoFramework.Json;
namespace NvsStorage
{
public class ConfigStorage
{
private readonly string _filePath;
public ConfigStorage(string filePath)
{
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("文件路径不能为空");
_filePath = filePath;
}
public DeviceConfig Load()
{
if (!File.Exists(_filePath))
{
Debug.WriteLine("[ConfigStorage] File not found, using default: " + _filePath);
return new DeviceConfig();
}
try
{
string json = File.ReadAllText(_filePath);
if (string.IsNullOrEmpty(json))
{
Debug.WriteLine("[ConfigStorage] File is empty, using default: " + _filePath);
return new DeviceConfig();
}
object obj = JsonConvert.DeserializeObject(json, typeof(DeviceConfig));
DeviceConfig config = obj as DeviceConfig;
if (config != null)
{
Debug.WriteLine("[ConfigStorage] Loaded: BootCount=" + config.BootCount
+ " DeviceName=" + config.DeviceName);
return config;
}
}
catch (Exception ex)
{
Debug.WriteLine("[ConfigStorage] Load failed: " + ex.Message);
try { File.Delete(_filePath); } catch { }
}
return new DeviceConfig();
}
public bool Save(DeviceConfig config)
{
if (config == null)
{
Debug.WriteLine("[ConfigStorage] Cannot save null config");
return false;
}
try
{
string json = JsonConvert.SerializeObject(config);
File.WriteAllText(_filePath, json);
Debug.WriteLine("[ConfigStorage] Saved: BootCount=" + config.BootCount
+ " DeviceName=" + config.DeviceName);
return true;
}
catch (Exception ex)
{
Debug.WriteLine("[ConfigStorage] Save failed: " + ex.Message);
return false;
}
}
public bool Exists()
{
try { return File.Exists(_filePath); }
catch { return false; }
}
public void Delete()
{
try
{
if (File.Exists(_filePath))
{
File.Delete(_filePath);
Debug.WriteLine("[ConfigStorage] Deleted: " + _filePath);
}
}
catch (Exception ex)
{
Debug.WriteLine("[ConfigStorage] Delete failed: " + ex.Message);
}
}
}
}
验证效果
烧录后通过串口终端(波特率 9600)连接 RS232 或 RS485,断电重启可看到:
=== NVS 存储测试开始 (COM1+COM2输出) ===
设备名称 : YF3300_ESP32S3
启动次数 : 1
NVS 持久化成功! 本次是第 1 次启动
========================================
--- 断电重启后 ---
=== NVS 存储测试开始 (COM1+COM2输出) ===
设备名称 : YF3300_ESP32S3
启动次数 : 2 ← BootCount 递增,持久化成功
NVS 持久化成功! 本次是第 2 次启动
========================================