跳到主要内容

NVS存储

概述

NVS(非易失性存储)使设备在断电重启后仍能保留关键数据。演示通用的 JSON 文件存储实现,同时利用 RS232/RS485 串口输出日志,方便通过串口工具观察存储效果。

技术栈
  • 运行环境:.NET nanoFramework
  • 硬件平台:ESP32-S3 (YF3300)
  • 存储介质:Flash 文件系统 (SPIFFS)
  • 日志输出:RS232 (COM2) + RS485 (COM1) + Debug

所需 NuGet 包

包名用途
nanoFramework.CoreLibrary基础运行时
nanoFramework.JsonJSON 序列化/反序列化
nanoFramework.System.IO.FileSystem文件系统读写
nanoFramework.Hardware.Esp32ESP32 引脚功能配置
nanoFramework.System.IO.PortsRS232/RS485 串口通信
nanoFramework.System.IO.Streams流操作(文件系统依赖)
nanoFramework.System.TextUTF-8 编码
nanoFramework.System.Collections集合类型支持

硬件连接说明

例程通过串口输出日志,便于在终端观察 BootCount 递增:

串口端口名TXRX波特率
RS232COM2GPIO11GPIO129600
RS485COM1GPIO9GPIO109600

核心概念

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 次启动
========================================