这一节开始来介绍一下在AUTOSAR架构中一个非常重要的部分,那就是存储系统,即AUTOSAR的Memory Stack。
Memory Stack结构图如下:
再细致一点如下图:
Memory Stack也是非常完整的一个AUTOSAR结构,有服务层的NvM模块,有ECU抽象层的Fee模块和Ea模块,还有MCAL层的驱动。其中NVRAM Manager模块是整个Memory Stack的控制器,它更多的关注于数据本身的,对数据进行校验,读写操作的发起,而根据最终存储器类型的不同,则最终负责数据存储和读写操作的则是Fee模块(FLASH)和Ea模块(EEPROM)。而在MCAL层则根据是内部FLASH,外部FLASH,外部EEPROM的各种情况而灵活变化。
NvM 模块位于 Memory Stack 的服务层,实现了对ECU中Non-Volatile数据的统一管理。NvM 模块主要功能如下:
1. 将不同类型的存储器(Flash、EEPROM 等)进行统一管理,提供AUTOSAR 标准服务接口
2. 提供以 Block 为单位的读、写等服务
3. 基于优先级的数据块访问机制,提高用户访问的灵活性和实时性
4. 提供访问任务的队列
5. 工作请求异步处理
6. 提供 CRC 机制,保障数据的一致性
7. 提供数据故障恢复机制
8. 提供任务完成通知机制
这里的Block可不是FLASH里的那个硬件Block,这里的Block是一种软件层面上的玩意,它是NvM操作的数据基本单元,也就是说,NvM模块里面操作的对象就是Block,所以在AUTOSAR里想要存储数据,那么就要提前给数据分配Block。
NvM 支持三种类型的 Block,分别为:
1. Native Block:最常用的 Block,用于一般数据的管理;
2. Redundant Block:该 Block 支持数据冗余,可用于对一些安全性要求较高的数据进行管理,其实就是可以对其进行备份的Block。
3. Dataset Block:该 Block 支持同一类型的多套数据。用户可以根据当前的运行状态,选择使用多套数据中的一套。
每个 Block,又由以下几个部分组成:
1. NV Block:位于掉电不丢失的设备,如 Flash,用于正常的存储数据;
2. RAM Block:程序运行时,用户的应用程序直接访问的 Block,有点类似于缓存,当条件满足时会将其更新至NV Block。临时 RAM Block:在运行时,用户通过接口将 RAM 的地址传递给 NvM;永久 RAM Block:用户配置给该 Block 的固定的 RAM 地址,运行时不可改变。
3. ROM Block:存储该 Block 的默认值,位于掉电不丢失的设备,当 NV Block 的数据异常时,可恢复为默认值;
4. Administrative Block:管理 Block,一般位于 RAM 中,用于记录和存储程序运行时 Block的状态,如地址、CRC 结果、Block 状态等。Administrative Block 由 NvM 内部实现,不用关心。
不同类型Block的组成情况如下表所示:
类型 | NV | RAM | Administrative | ROM |
---|---|---|---|---|
NATIVE | 1 | 1 | 1 | 0 ~ 1 |
REDUNDANT | 2 | 1 | 1 | 0 ~ 1 |
DATASET | 1 ~ 256 | 1 | 1 | 0 ~ 256 |
此外NvM 内部还会预留 2 个特殊 Block,Block0 和Block_CfgID。其中Block0 不存储任何实际数据信息,仅用于记录 WriteAll 和 ReadAll 的工作结果,Block_CfgID 则用于存储当前内存的配置信息。用户实际使用的 Block 从第 3 个 Block 开始。
在NvM模块的下层模块Fee和Ea模块中,它们的基本数据存储单元也叫做Block,NvM模块的Block数据来源于Fee和Ea的Block,前面提到了NvM的三种Block由若干个NV Block组成,所以这两种Block是一对多的情况。NvM的Block编号叫做Block Handle,Fee和Ea的Block编号叫做Block ID,也就是说APP通过Block Handle来索引NvM的数据,而NvM则是通过Block ID来索引Fee和Block的Block数据。
Block ID 的计算公式:Block ID = (Block Base Num << DataSelectionBits) + DataIndex。其中Block Base Num根据Blocl类型而定(Native,Redundant ,Dataset ),DataSelectionBits和DataIndex根据具体应用情况进行设置。其中当 NvM 调用 MemIf 的接口,访问 Fee 或 Ea 时,传递的 Block 参数为 Block ID。
Block三种组成成分Block的存储数据格式如下:
其中 RAM 和 NV Block,都有可选的 Header 和 CRC 区域。Header 可用于存储 Block 的Block ID 信息,固定占用两个字节的信息,CRC 区域用于存储 Block 的 CRC,根据 CRC 的类型,占用 1、2 或 4 个字节。ROM Block 仅存储默认数据,不存储 Header 和 CRC 内容。用户在读取和写入数据的时候,不需要关心 Header 和 CRC,NVM 模块将自动对 Header 和 CRC进行填充、计算、校验等工作。
前面提到了Redundant Block 有 2 个 NV Block,其主要特点包括:
Dataset Block 可以具备多个 NV Block 和 ROM Block,具体的数量限制与用户配置的NVM_DATASET_SELECTION_BITS 有关系。
假设 NVM_DATASET_SELECTION_BITS 为 n,n 小于等于 8。则 NV Block 的个数范围为0 ~ 2^n,而 ROM Block 的个数范围为 0 ~(256-2^n),NV Block 的个数与 ROM Block 的个数总和不能超过 2^n。
在访问 Dataset 类型的 Block 之前,必须先调用 NvM_SetDataIndex 设置目标的 DataIndex,DataIndex 的范围必须注意下述条件:1. DataIndex 不超过 NV 和 ROM Block 的总和;2. 假设配置了 m 个 NV Blocks,n 个 ROM Blocks,则 0 ~ (m-1)是 NV Block 的 Index 范围,m ~ (m+n-1)是 ROM Block 的 Index 范围。
在 Write 和 WriteAll 请求执行过程中,若对某个 Block 的写入失败,且该 Block 配置的写重试次数不为 0,则 NVM 会再次尝试对该 Block 进行写入,直到尝试次数超过用户配置的Retry次数阈值。
&emsp在 Read 和 ReadAll 请求执行过程中,若对某个 Block 的读取失败,且该 Block 配置的读重试次数不为 0,则 NVM 会再次尝试对该 Block 进行读取,直到尝试次数超过用户配置的Retry次数阈值。
NvM_GetErrorStatus是一个经常被使用到的函数,通过调用这个函数可以获取当去NvM模块的当前状态,具体如下:
请求结果 | 描述 |
---|---|
NVM_REQ_OK | 默认状态,上一次工作成功完成。 |
NVM_REQ_NOT_OK | 1. 任意请求中,调用下层 MemIf 接口返回 E_NOT_OK,或下层请求结果为 MEMIF_JOB_FAILED。 2. 在 WriteAll 和 ReadAll 中,如果有任意一个 Block 的执行结果不是非错误结 果 之 一 , 则 WriteAll 和 ReadAll 的 结 果 为NVM_REQ_NOT_OK。 3. 对于使能了同步镜像机制的 Block,如果调用同步回调函数返回E_NOT_OK , 且 错 误 次 数 超 过 限 制 , 则 也 会 上 报 该NVM_REQ_NOT_OK。 |
NVM_REQ_PENDING | 工作处于队列中,或正在执行。 |
NVM_REQ_INTEGRITY_FAILED | Read 或 ReadAll 中,检测到下述情况,且无隐性恢复默认数据:1. CRC 校验失败;2. 下层请求结果为 MEMIF_BLOCK_INCONSISTENT |
NVM_REQ_BLOCK_SKIPPED | 在 ReadAll 或 WriteAll 或 ValidateAll 中,该 Block 被跳过。 |
NVM_REQ_NV_INVALIDATED | Read 或 ReadAll 中,下层请求结果为 MEMIF_BLOCK_INVALID。如果读取冗余 Block,该结果表明两个 NV Block 读取的结果均为MEMIF_BLOCK_INVALID。 |
NVM_REQ_CANCELLED | 1. 调用 NvM_CancelJobs 被取消的 Block;2. 调用 NvM_CancelWriteAll 被取消的 Block;3. 任意请求执行过程中 , 下层请求结果为NVM_REQ_CANCELLED. |
NVM_REQ_REDUNDANCY_FAILED | 未使用。 |
NVM_REQ_RESTORED_FROM_ROM | Block 恢复了默认数据,包括隐性和显性恢复。 |
NVM_REQ_BUSYING | 未使用。 |
NVM_REQ_WRONGID | Read 或 ReadAll 中,BlockID 检查失败,且无隐性恢复默认数据。 |
NVM_REQ_MIRROR_OP_FAILED | 未使用。 |
NVM_REQ_VERIFY_FAILED | Write 或 WriteAll 中,数据回读校验失败。 |
NVM_REQ_CFGID_MISMATCH | 未使用。 |
在前面介绍BswM模块中提到了,BSW层模块可以给BswM模块发送Mode Request和Mode Indication,而NvM模块则可以在下面情况下去给BswM模块发送Mode Indication:
1. 当 SingleJob 进入队列时,调用 BswM_NvM_CurrentBlockMode(Pending);
2. 当 SingleJob 工作结束时,调用 BswM_NvM_CurrentBlockMode,状态由本次工作的结果决定;
3. 当 SingleJob 工作被取消时,调用 BswM_NvM_CurrentBlockMode(Cancel);
4. 当 MultiJob 进入队列时,调用 BswM_NvM_CurrentJobMode(Pending);
5. 当 MultiJob 工作结束时,调用 BswM_NvM_CurrentJobMode,状态由本次工作的结果决定;
6. 当 WriteAll 工作被取消时,调用 BswM_NvM_CurrentJobMode(Cancel)。
上面提到了一个NvM模块Block会对应一个或者多个Fee和Ea模块Block,而MemIf模块就是将这两种位于不同层的Block对应起来,即实现Block Handle和Block ID的对应关系。在这里就不多做介绍了,非常简单。
补充1:除了以上简单介绍的几大点,NvM模块还有一大堆的细节,大家如果想更深入了解NvM模块,请参考AUTOSAR官方文档《AUTOSAR_SWS_NVRAMManager.pdf》。
补充2:除了以上简单介绍的几大点,MemIf模块还有一大堆的细节,大家如果想更深入了解MemIf模块,请参考AUTOSAR官方文档《AUTOSAR_SWS_MemoryAbstractionInterface.pdf》。