3. NvM 核心对象与配置模型
NvM 的配置模型本质上是一套“逻辑块—镜像—物理存储”的三层映射。理解这套映射,是后续配置 NvM 通道、选择 CRC 策略、处理写保护与冗余的基础。很多初学者在配置工具里看到 Native、Redundant、Dataset 这些选项时容易混淆,原因往往是没有先建立 Block 与各类 RAM/ROM/NV 存储区之间的对应关系。
一、Block:NvM 管理的最小逻辑单元
NvM Block 是应用层与 NvM 模块交互的逻辑句柄。每个 Block 由 NvMBlockDescriptor 描述,包含块 ID、长度、管理类型、CRC 类型、RAM 块引用、ROM 块引用、NV 块引用以及写保护、写验证等属性。应用通过 NvM_ReadBlock、NvM_WriteBlock、NvM_RestoreBlockDefaults 等 API 操作 Block,而不直接触碰底层 Flash 或 Ea/Fee。
Block 的管理类型(NvMBlockManagementType)决定了它的存储结构:
- NVM_BLOCK_NATIVE:一个 Block 对应一份 NV 数据,结构最简单,适合标定量、配置参数等单实例数据。
- NVM_BLOCK_REDUNDANT:一个 Block 对应两份 NV 数据(NV 块副本),写入时交替更新,读取时通过 CRC 选择有效副本,用于提高掉电场景下的可靠性。
- NVM_BLOCK_DATASET:一个 Block 对应多个数据集(Dataset),每个数据集有独立的 NV 存储位置,应用通过
NvM_ReadBlock的 DatasetIndex 参数选择访问哪一个。
二、RAM Block、ROM Block 与 NV Block 的角色
RAM Block 是 Block 在运行时的镜像,位于 RAM 中,应用读写的是它。NvM 在 NvM_ReadBlock 时把 NV 数据搬入 RAM Block,在 NvM_WriteBlock 时把 RAM Block 内容写入 NV 存储。RAM Block 的长度必须与 Block 长度一致,且通常要求按对齐方式放置,避免非对齐访问带来的性能损失。
ROM Block 是默认值来源。当 NV 数据首次上电为空、CRC 校验失败或调用 NvM_RestoreBlockDefaults 时,NvM 从 ROM Block 拷贝默认值到 RAM Block。ROM Block 通常放在 Flash 的常量区,由链接脚本分配,内容在编译期确定。对于 Dataset 类型,每个数据集可以各自拥有一个 ROM Block 默认值。
NV Block 是数据在非易失存储中的实际载体。NvM 不直接操作 Flash,而是通过 Ea(EEPROM Abstraction)或 Fee(Flash EEPROM Emulation)访问。NV Block 的地址、长度、CRC 类型由配置决定。对于 Redundant 类型,NV Block 有两个副本;对于 Dataset 类型,每个数据集对应一个 NV Block 实例。
三、四类对象的映射关系
把上述对象串起来看:应用持有 Block ID,NvM 通过 Block 描述符找到对应的 RAM Block 和 NV Block,读操作是 NV→RAM,写操作是 RAM→NV,默认值恢复是 ROM→RAM。Dataset 类型在此基础上增加了一个“数据集索引”维度,Redundant 类型则在 NV 侧增加了一个副本维度。
四、配置时的关键参数与判断依据
在配置工具中,每个 Block 需要明确以下参数,它们直接决定上述映射是否成立:
| 参数 | 含义 | 典型取值 / 判断依据 |
|---|---|---|
NvMBlockLength |
Block 数据长度(字节) | 与 RAM Block、NV Block 长度一致;需考虑 CRC 附加字节 |
NvMBlockManagementType |
管理类型 | 单实例选 Native;高可靠选 Redundant;多组参数选 Dataset |
NvMBlockCrcType |
CRC 校验类型 | CRC8 / CRC16 / CRC32;与 Ea/Fee 的 CRC 配置保持一致 |
NvMRamBlockDataAddress |
RAM Block 地址 | 由链接脚本分配,需满足对齐要求 |
NvMRomBlockDataAddress |
ROM Block 地址 | 常量区地址;无默认值时可置空 |
NvMNvBlockLength |
NV Block 长度 | 通常等于 Block 长度加 CRC 长度 |
NvMDatasetSelectionBits |
数据集索引位数 | Dataset 类型必填;决定最大数据集数量,如 3 位支持 8 个 |
五、一个可参考的配置示例
假设某 ECU 需要保存一组标定参数(单实例,长度 32 字节)和一组工况自适应数据(4 个数据集,每个 16 字节)。配置可以这样组织:
/* 标定参数:Native 类型 */
NvMBlockDescriptor CalibBlock = {
.BlockId = 1,
.BlockLength = 32,
.ManagementType = NVM_BLOCK_NATIVE,
.CrcType = NVM_CRC16,
.RamBlockAddress = &CalibRam[0],
.RomBlockAddress = &CalibRom[0],
.NvBlockLength = 34, /* 32 + 2 字节 CRC */
.WriteProtection = false
};
/* 自适应数据:Dataset 类型,4 个数据集 */
NvMBlockDescriptor AdaptBlock = {
.BlockId = 2,
.BlockLength = 16,
.ManagementType = NVM_BLOCK_DATASET,
.CrcType = NVM_CRC8,
.RamBlockAddress = &AdaptRam[0],
.RomBlockAddress = &AdaptRom[0],
.NvBlockLength = 17, /* 16 + 1 字节 CRC */
.DatasetSelectionBits = 2, /* 2^2 = 4 个数据集 */
.WriteProtection = false
};
这里 DatasetSelectionBits = 2 表示索引范围为 0~3,共 4 个数据集。应用调用 NvM_ReadBlock(2, 0, ...) 读取第 0 个数据集,NvM_ReadBlock(2, 3, ...) 读取第 3 个。每个数据集在 NV 侧对应独立的 NV Block 实例,在 ROM 侧对应独立的默认值数组。
掌握 Block、RAM Block、ROM Block、NV Block 四者的映射关系后,后续配置 NvM 通道、选择 Ea/Fee 底层、处理写保护与冗余时,就能清楚每一步操作作用在哪一层对象上,避免“改了配置却不知道数据最终落在哪里”的问题。