文章目录
- 一、BSW的Memory功能
- 二、BSW的存储及Block
- 三、Memory Abstraction Interface模块
- 四、EEPROM Abstraction模块介绍
- 五、EEPROM Driver模块介绍
- 参考文献
一、BSW的Memory功能
1-1、Memory基本功能
1-1-1、Memory功能介绍
总所周知,用户能用的ROM存储方式现在一般有两种:EEPROM和FLASH仿EEPROM;而存储可以是片内的,也可以是片外的。因此,一般就有2*2=4种ROM存储方式:
- 主芯片片内FLASH仿EEPROM
- 主芯片片内EEPROM(前提是该芯片片内有的话,很多低端一点的芯片都不带片内EEPROM)
- 板载片外FLASH仿EEPROM
- 板载片外EEPROM
注意事项:
- EEPROM和FLASH都是ROM一种,相同大小容量下Flash相对便宜一些,但是只能按块擦写,不能按位擦写。想要让Flash做到按位擦写,就需要先将Flash原来的数据备份一份,再修改其中想要修改的位,再按块擦除,最后将改写好的数据再烧录回原块中。这个过程需要软件的处理,所以称为FLASH仿EEPROM。
- 片内存储,是用芯片内部的DFLASH或片内EEPROM进行数据存储;外部存储,是通过外部DFLASH或外部EEPROM进行存储,一般会调用SPI、IIC等通信方式外挂EEPROM芯片。
1-1-2、Memory术语解释
- NVRAM Manager:简称NVM,存储服务管理模块。它是应用层访问非易失性数据的唯一接口,在上电读取数据下电存储数据,也支持Immediately存储数,提供非易失数据的管理服务。这里是统一按块编写的,不关心是哪种存储类型;写完后有回调函数通知Memory Abstraction,然后再通知NVM。
- Memory Abstraction Interface:简称MemIf,存储抽象接口模块。主要作用是将需要读写的信息解耦,分别分派给外部EEPROM或内部DFLASH。
- EEPROM Abstraction:简称EA,EEPROM的抽象层。主要作用就是进一步封装片外或片内EEPROM驱动,给上层提供统一的API接口
- Flash EEPROM Emulation:简称FEE,Flash的抽象层。参考EEPROMAbstraction。
- Extermal E2 Drv:ECU抽象层中片外EEPROM的驱动,SPI驱动的上层,由于片外的EEPROM要通过SPI通信才能访问。因此,片外EEPROM的驱动要放到ECU抽象层中。
- Ext FIs Drv:ECU抽象层中片外Flash的驱动,SPI驱动的上层。参考ExtermalE2 Drv。
- SPI Handler Drv:MCAL中对片上SPI的驱动。
- EEPROM Drv:简称Eep,MCAL中对片上EEPROM的驱动。
- Flash Drv:简称Fls,MCAL中对片上FLASH的驱动。
- SPI EEPROM FLASH:片内的SPI、EEPROM和FLASH功能模块。
- Extermal E2 Memory:片外EEPROM,就是板载的EEPROM,需要通过SPI访问。
- External flash Memory:片外Flash,就是板载的Flash,需要通过SPI访问。
1-2、片内存储
- 黄色箭头:选择片内EEPROM时的数据流向。
- 蓝色箭头:选择片内FLASH时的数据流向。
- 特点:不需要外部存储设备,不需要特殊的SPI、IIC等通信。
1-3、片外存储
用过片外存储的应该相对了解一些,大部分都是用的SPI通信,加上一些其他的控制命令,如果是我们直接手写,就需要查阅存储芯片的手册,再配置SPI,再将定义好的命令通过SPI发送进去。而AutoSAR其实也是一样的,只不过是将这么些流程封装成了模块,使得这个流程看起来更加清楚了。
特点:通信Driver层做了SPI通信的驱动适配,利用主芯片上的SPI资源与外部存储设备通信。
二、BSW的存储及Block
2-1、NVM
NvM(Non Volatile Memory Manager)类似于ComM(总管)。ComM是负责总管通信,而NvM负责总管存储;同样的,ComM可以管理CAN、LIN、ETH等这些总线,NvM可以管理Flash、EEPROM这些存储器,都做到了对不同硬件的统一管理。NvM主要具有以下的功能:
- 排队管理机制:当有数据想要存入Memory的时候,由于Flash和EEPROM都需要先擦除再写入,速度会比RAM慢很多,所以会产生数据的排队。
- 而排队机制可以手动配置是FIFO或者是带有优先级的队列这两种方式。
- 可以配置队列的深度。
- 支持立即数据(immediate data,即要求立即写入的数据,比如crash data)的快速写入,即可不用排队。
- 上下电的存储处理:在上下电的时候,应该会有一些关于Memory的处理,就是通过NvM来做出处理
- 在上电的时候,要读取NV里的必要的数据。
- 在下电的时候,要将必要的数据写入NV里面。
- 支持数据校验:主要利用CRC校验检查是否数据出错
- 当校验出错时,触发使用默认数据(直接使用或者也可以CallBack)
- 数据的写保护(Write Protection):类似于写Flash要先解锁;还有一种方式是设置成单次写入,仅允许写入一次。
- 对数据的余存储:主要就是增加一些关键数据的可靠性。
2-2、Fee
Fee(Flash EEPROM Emulation)是对Falsh仿EEP的一个抽象的具体实现。Flash被抽象成了一个一个的Flash Sector,Flash Sector又被分为了一个一个的Flash Page。
- Flash Sector:就是Flash能擦除的最小单元,也可以配置为更大。
- Flash Page:Fee只能通过Flash Page去访问Flash,也就是Fee的最小单元,Flash Sector包含Flash Page,如下图所示:
下面有两点需要大家注意
- Fee会调用Flash驱动(FLS)去实现对硬件的真正的擦除和写入等功能;但是Flash由于寿命的原因,不能老是擦除同一个地址的Flash,所以Fee还会自动管理写入的地址,尽量平均的去写入数据;
- Fee对Flash写入的时候,是会先备份一遍的,再进行擦除。也就是说我们至少需要两个Flash Sector。过程是:首先将要擦除的Sector的数据都备份到另一个Sector中,然后擦除该Sector,之后一个page一个page的写入数据,直到将这个Sector写满。
2-3、Ea
Ea(EEPROM Abstraction)是对EEPROM的抽象,它是调用EEPROM的驱动(EEP)来实现最终的操作。同样,它也会考虑EEPROM的寿命问题来将存储的数据做一个均摊。但Ea与Flash不同是,EEPROM不用一块一块的擦除,它比较灵活,可以定点写入数据。所以它可以以交替的形式把一个Block写入几个Memroy Locations里面,如下图所示:
2-4、Nvm协议栈架构
2-5、Block
同通信的PDU一样,存储功能也需要一些特殊的数据结构来存放和管理我们的NV数据(NV data)。
2-5-1、NVRAM Block作用
NVRAMBlock的作用类似于IPDU,但它们仅仅只是作用上相似,其功能实现是完全不同的。首先用户是不可能直接操作NV Memory的,所以肯定需要开辟一块RAM区域用于暂存我们的NV data;然后数据需要校验,那么肯定需要有一个空间来存放我们的校验是否正确等必须信息;假如校验出错,我们还需要从某个地方获取该值的默认数据以作为错误处理。所以我们的NVRAM Block就被AutoSAR设计成下面这样:
- NVRAM Block分成四个子Block组成,其中NV Blcok、RAM Block和Administrative Block是必须存在的;ROMBlock是可选的;
- NV Block存储于NV Memory(EEPROM、Flash),可选择是否添加Header和CRC校验;
- RAM Block就是NV Block在RAM中的一个映射,因为应用层不能直接操作NV(那样太慢了),所以基本上是一个1:1的结构;
- ROM Block一般是当出错时读取其中的默认值来取代出错值(ROM只读不能写入);
- Administrative Block用来保存NVRAM Block的一些属性、错误和状态信息,也用来存放datasets的信息;
- NVRAM Block的这些可选项都由NVRAM block descriptor来配置,工具中可看到该配置。
- NVRAM block descriptor还能配置NVRAM Block的几乎所有选项,包括像优先级、类型等选项。
2-5-2、NVRAM Block类型
NVRAM Block里一般都只包含1个RAM Block、1个Administrative Block和0 / 1个ROM Block。但是NV Block可以包含多个,根据包含了几个NV Block,我们将NVRAM Block分为三种类型:Native(正常)、Redundant(冗余)和Dataset(数据集):
- Native:即一个RAM Block保存到对应的一个NV Block中;
- Redundant:将一个RAM Block保存到两个NV Block中,当一个数据失效后可以使用另一个(备份作用);
- Dataset:最多支持255个NV Block,类似于一个数组,可以用Data lndex来索引其中的数据。
2-5-3、FEE Block
Fee Block是在Fee中使用的Block,以一种叫做“Chunks”的方式来做管理。一个Chunk可以包含几个Blocks,而Chunk之间是用一种链表的形式连接起来的,Chunk就是其中的节点。
注意:假如NVRAM Block的Block Length假如是16,那么Fee Block的Block Size应该要加上CRC长度(CRC16加加2,CRC32加4)。所以假如是CRC32,那么FeeBlock的BlockSize就应该为20。下面的Ea Block也是一样。
2-5-4、EA Block
Ea相比于Fee就简单很多,因为其能够实现定点擦除和写入,但是价格也要贵很多。
2-6、本段总结
三、Memory Abstraction Interface模块
存储协议栈负责ECU中非易失性数据的存储管理。存储协议栈包括NVM、MemIf、Ea、Fee、Eep、Fls模块的详细介绍及代码分析,本段为Memory Abstraction Interface(MemIf)模块详细介绍篇。
3-1、功能介绍
MemIf模提供统一的接口让NVRAM管理器访问几个内存抽象模块(FEE或EA模块)。内存抽象接口(MemIf模块)应从底层FEE或EA模块的数量中抽象,并在统一的线性地址空间上提供虚拟分割。
3-2、关键概念
- Fast Mode:例如,在启动/关闭期间,底层驱动程序可以被切换到快速模式,以便在这些阶段允许快速读/写。
- Slow Mode:在正常操作期间,底层驱动程序可以凭借慢速模式使用,以减少在底层设备/通信媒体的运行时或阻塞时间方面的资源使用。
注意:Fast Mode和Slow Mode可能取决于驱动程序的实现和底层设备的功能。也可能取决于NVRAM Manage的配置,因此也取决于特定项目的需求。
3-3、模块相关文档
3-4、关键类型定义
3-4-1、MemIf_StatusType
MemIf_StatusType类型标示底层抽象模块和设备驱动器的当前状态。
typedef enum
{
MEMIF_UNINIT = 0,
MEMIF_IDLE,
MEMIF_BUSY,
MEMIF_BUSY_INTERNAL
} MemIf_StatusType; /*!< \trace CREQ-793 */
3-4-2、MemIf_JobResultType
MemIf_JobResultType表示上一个作业的结果。
typedef enum
{
MEMIF_JOB_OK = 0,
MEMIF_JOB_FAILED,
MEMIF_JOB_PENDING,
MEMIF_JOB_CANCELED,
MEMIF_BLOCK_INCONSISTENT,
MEMIF_BLOCK_INVALID
} MemIf_JobResultType; /*!< \trace CREQ-793 */
3-4-3、MemIf_ModeType
MemIf_ModeType表示底层抽象模块和设备驱动程序的操作模式。
typedef enum
{
MEMIF_MODE_SLOW = 0,
MEMIF_MODE_FAST
} MemIf_ModeType; /*!< \trace CREQ-793 */
3-5、关键API定义
MemIf模块没有自己内部的功能逻辑,所有的API接口都会直接映射到对应的底层抽象模块(Ea, Fee)的API。参数 Device Index 用于选择内存抽象模块(以及内存设备)。如果只配置了一个内存抽象模块,则忽略 Device Index 参数。如果只配置了一个内存抽象模块,则内存抽象接口应实现为一组宏,将内存抽象接口API映射到相应的内存抽象模块的API。具体如下所示:
#define MemIf_Write(DeviceIndex, BlockNumber, DataPtr) Fee_Write(BlockNumber, DataPtr) //DeviceIndex ignore
如果配置了多个内存抽象模块,则内存抽象接口应使用有效的机制将API调用映射到适当的内存抽象模块。
注意:一种解决方案是使用指向函数的指针表,其中参数开发索引DeviceIndex 被用作数组索引。
typedef struct
{
uint8 DeviceIndex;
Std_ReturnType (WriteFctPtr)(uint16 BlockNumber, uint8* DataBufferPtr);
}MemIf_Write;
MemIf_Write MemIf_WriteFctPtr[] =
{
{0, Fee_Write},
{1, Ea_Write},
}
#define MemIf_Write(DeviceIndex, BlockNumber, DataPtr) MemIf_WriteFctPtr[DeviceIndex](BlockNumber,DataPtr)
如果配置了多个内存抽象模块,并且为该模块启用了开发错误检测,则内存抽象接口API的功能应检查参数DeviceIndex是否为现有设备或模块服务中的广播标识符。内存抽象接口API的功能应向非法参数DeviceIndex跟踪器(DET,Default Error Tracer)报告检测到的错误,错误代码为MEMIF_E_PARAM_DEVICE,同时不得执行被调用的服务。如果内存抽象接口API的调用函数检测到属于非法参数DeviceIndex的错误并具有返回值,则应设置如下:
- MemIf_GetStatus: MEMIF_UNINIT
- MemIf_GetJobResult: MEMIF_JOB_FAILED
- All other functions: E_NOT_OKc
3-5-1、MemIf_SetMode(R21-11 NA)
MemIf_SetMode调用所有底层内存抽象模块的SetMode功能。注意:该功能中故意遗漏了DeviceIndex,即内存接口应将所有底层模块切换到请求的模式。在这种情况下,不需要一个额外的“广播”参数,因为设备不能单独切换到不同的模式。
FUNC(void, MEMIF_CODE) MemIf_SetMode(MemIf_ModeType Mode);
3-5-2、MemIf_Read
MemIf_Read调用由DeviceIndex所选择的底层内存抽象模块的“读取”功能。
如果内存抽象接口启用了开发错误检测,并且根据 SWS_MemIf_00022 检测到开发错误,则函数应返回 E_NOT_OK,否则应返回底层模块的被调用函数值。
FUNC(Std_ReturnType, MEMIF_CODE) MemIf_Read(uint16 DeviceIndex, uint16 BlockNumber, uint16 BlockOffset, MemIf_DataPtr_pu8 DataBufferPtr, uint16 Length);
3-5-3、MemIf_Write
MemIf_Write调用由DeviceIndex所选择的底层内存抽象模块的“写入”功能。
如果内存抽象接口启用了开发错误检测,并且根据 SWS_MemIf_00022 检测到开发错误,则函数应返回 E_NOT_OK,否则应返回底层模块的被调用函数值。
FUNC(Std_ReturnType, MEMIF_CODE) MemIf_Write(uint16 DeviceIndex, uint16 BlockNumber, MemIf_DataPtr_pu8 DataBufferPtr);
3-5-4、MemIf_Cancel
MemIf_Cancel调用由DeviceIndex所选择的底层内存抽象模块的“取消”功能。
FUNC(void, MEMIF_CODE) MemIf_Cancel(uint16 DeviceIndex);
3-5-5、MemIf_GetStatus
MemIf_GetStatus调用由DeviceIndex所选择的底层内存抽象模块的“GetStatus”功能。
FUNC(MemIf_StatusType, MEMIF_CODE) MemIf_GetStatus(uint8 DeviceIndex);
如果使用表示广播到所有已配置设备(MEMIF_BROADCAST_ID)的设备索引来调用函数MemIf_GetStatus,那么内存抽象接口模块将依次调用所有底层设备的“GetStatus”函数。它将返回以下值之一:
- MEMIF_IDLE - 如果所有底层设备都返回了此状态
- MEMIF_UNINIT - 如果至少有一个设备返回了此状态,所有其他返回的状态都将被忽略
- MEMIF_BUSY - 如果至少有一个已配置设备返回了此状态,并且没有其他设备返回MEMIF_UNINIT
- MEMIF_BUSY_INTERNAL - 如果至少有一个已配置设备返回了此状态,并且没有其他设备返回MEMIF_BUSY或MEMIF_UNINIT
注意:在对MemIf_GetStatus的调用中使用的特殊“broadcast”设备ID用于查询是否所有设备都处于空闲状态,以便关闭ECU(电子控制单元)。
3-5-6、MemIf_GetJobResult
MemIf_GetJobResult调用由DeviceIndex所选择的底层内存抽象模块的“GetJobResult”功能。
FUNC(MemIf_JobResultType, MEMIF_CODE) MemIf_GetJobResult(uint16 DeviceIndex);
3-5-7、MemIf_InvalidateBlock
MemIf_InvalidateBlock调用由DeviceIndex选择的底层内存抽象模块的“无效验证块”功能。
FUNC(Std_ReturnType, MEMIF_CODE) MemIf_InvalidateBlock(uint16 DeviceIndex, uint16 BlockNumber);
3-5-8、MemIf_EraseImmediateBlock
MemIf_EraseImmediateBlock调用由DeviceIndex选择的底层内存抽象模块的“立即删除块”功能。
FUNC(Std_ReturnType, MEMIF_CODE) MemIf_EraseImmediateBlock(uint16 DeviceIndex, uint16 BlockNumber);
3-5-9、MemIf_GetVersionInfo(NA)
void MemIf_GetVersionInfo(P2VAR(Std_VersionInfoType, AUTOMATIC, MEMIF_APPL_DATA) VersionInfoPtr);
3-6、Mandatory Interfaces
以下是实现模块核心功能所需的所有接口。如果不同时使用Fee和Ea,则MemIf模块的功能非常简单,就是一个底层Ea或者Fee的宏封装层。
API Function | Header File | Description |
---|---|---|
Ea_Cancel | Ea.h | Cancels the ongoing asynchronous operation. |
Ea_EraseImmediateBlock | Ea.h | Erases the block BlockNumber. |
Ea_GetJobResult | Ea.h | Service to return the JobResult. |
Ea_GetStatus | Ea.h | Service to return the Status. |
Ea_InvalidateBlock | Ea.h | Invalidates the block BlockNumber. |
Ea_Read | Ea.h | Reads Length bytes of block Blocknumber at offset BlockOffset into the buffer DataBufferPtr. |
Ea_SetMode | Ea.h | Function to switch the mode of the underlying EEPROM Driver. |
Ea_Write | Ea.h | Writes the contents of the DataBufferPtr to the bolck BlockNumber. |
Fee_Cancel | Fee.h | Service to call the cancel function of the underlying flash driver… |
Fee_EraseImmediateBlock | Fee.h | Service to erase a logical block. |
Fee_GetJobResult | Fee.h | Service to query the result of the last accepted job issued by the upper layer software. |
Fee_GetStatus | Fee.h | Service to return the Status. |
Fee_InvalidateBlock | Fee.h | Service to invalidate a logical block. |
Fee_Read | Fee.h | Service to initiate a read job. |
Fee_SetMode | Fee.h | Function to switch the mode of the underlying Flash Driver. |
Fee_Write | Fee.h | Service to initiate a write job. |
CONST(MemIf_MemHwAApi_Type, MEMIF_CONST) MemIf_MemHwaApis[MEMIF_NUMBER_OF_DEVICES] =
{
/* EEPROM */
{
Ea_Read,
Ea_Write,
Ea_EraseImmediateBlock,
Ea_InvalidateBlock,
Ea_Cancel,
Ea_GetStatus,
Ea_GetJobResult,
Ea_SetMode
},
// /* FLASH */
// {
// Fee_Read,
// Fee_Write,
// Fee_EraseImmediateBlock,
// Fee_InvalidateBlock,
// Fee_Cancel,
// Fee_GetStatus,
// Fee_GetJobResult,
// Fee_SetMode
// }
};
四、EEPROM Abstraction模块介绍
4-1、功能介绍
MemIf模提供统一的接口让NVRAM管理器访问几个内存抽象模块(FEE或EA模块)。EEPROM抽象(EA)从设备特定的寻址方案和分割中提取,并为上层提供了一个虚拟的寻址方案和分割,以及“虚拟”无限数量的擦除周期。
4-2、关键概念
- NVRAM:Non-volatile RAM (Random Access Memory),非易失性RAM(随机存取存储器)。
- NvM: NVRAM Manager。
- (Logical) block:逻辑块,由模块用户所看到的最小的可写/可擦除的单元。由一个或多个虚拟页面组成。
- Virtual page:虚拟页,可以包含一个或几个物理页面,以方便处理逻辑块和地址计算。
- Internal residue:如果配置的块大小不是虚拟页面大小的整数倍,则在最后一个虚拟页面末尾的未使用空间。
- Virtual address:虚拟地址,由逻辑块内的16 bit的块号和16 bit的偏移量组成。
- Physical address:物理地址,用于访问逻辑块的设备特定格式(取决于底层EEPROM驱动程序和设备)的地址信息。
- Dataset:NVRAM Manager中的概念:一个用户可寻址的相同大小的块数组。例如,Dataset可用于为CAN驱动程序提供不同的配置设置(CANid,过滤器设置,……),而ECU具有相同的应用软件(例如车门控制模块)。
- Redundant copy:NVRAM Manager中的概念:存储两次,以提高数据存储的可靠性。
4-3、模块相关文档
4-4、模块功能详解
EEPROM抽象(EA)一次只能接受一个作业,即该模块不能为挂起的作业提供一个队列(这是NVRAM Manager实现的功能。
注意:由于NvM是该模块的唯一调用者,为了保持该模块相当小,模块功能不能检查模块当前是否忙(Busy)。NvM有责任序列化挂起的作业,并且只有在前一个作业完成或取消后才启动一个新作业。
4-4-1、Addressing scheme and segmentation
EEPROM抽象(EA)模块为上层提供了一个32位的虚拟线性地址空间和均匀的分割方案。这个虚拟的32位地址是由以下两部分组成:
- 16位块号(block number)-允许(理论上)最大65536个逻辑块
- 16位块偏移(block offset)-允许(理论上)最大每个块64k字节的块大小
16位块号表示一种可配置的(虚拟)分页机制。这个地址对齐的值可以从底层的EEPROM驱动程序和设备的值中得到。这个虚拟分页可以通过参数EA_VIRTUAL_PAGE_SIZE进行配置。EA模块的配置应使虚拟页面大小(在EA_VIRTUAL_PAGE_SIZE中定义)为物理页面大小的整数倍,即:不允许配置比实际物理页面大小更小的虚拟页面。
#define EA_VIRTUAL_PAGE_SIZE 16U // 虚拟页大小
#define EA_MAX_PHYSICAL_ADDRESS 4528u // 物理内存大小
#define EA_MAX_BLOCK_NUM 283u // 最大Block数 /*EA_MAX_PHYSICAL_ADDRESS / EA_VIRTUAL_PAGE_SIZE*/
示例:虚拟页面的大小被配置为8个字节,因此地址对齐方式为8个字节。块号为0的逻辑块被放置在物理地址x处。块号为1的逻辑块将被放置在x+8,块号为2将被放置在x+16。
注意:要求允许计算逻辑块的物理开始地址,而不是为地址映射创建必要的查找表(LookUp Table)。
#define Ea_GetStartPhysicalAddress(blocknum,offset) (Eep_AddressType)(blocknum * EA_VIRTUAL_PAGE_SIZE + offset)
#define Ea_GetEndPhysicalAddress(startaddr,length) (Eep_AddressType)(startaddr + length)
每个已配置的逻辑块应占已配置的虚拟页面大小的整数倍(配置参数EA_VIRTUAL_PAGE_SIZE)。逻辑块不能相互重叠,也不能相互包含在一起。示例:通过相应地设置参数EA_VIRTUAL_PAGE_SIZE,将地址对齐/虚拟分页配置为8个字节。逻辑块号#1被配置为32字节大小(参见Figure32. Virtual vs. physical memory layout)。逻辑块号#1恰好使用4个虚拟页面。因此,下一个逻辑块将获得块号#5,因为块号2、3和4被第一个逻辑块“阻塞”。第二个块被配置为100字节大小,占用13个虚拟页面,最后一页的4个字节未使用(internal residue)。因此,下一个可用的逻辑块号将是#17。
逻辑块 EaBlockNumber 不可配置块编号 0x0000 和 0xFFFF。
4-4-2、Address calculation
根据EA模块的实现和所使用的确切地址格式,EA模块的功能应结合16位块数和16位块偏移量,以推导出底层EEPROM驱动程序所需的物理EEPROM地址。
注意:底层EEPROM驱动程序所需的确切地址格式取决于具体的EEPROM地址访问格式,因此如何从给定的16位块数和16位块偏移量获得物理EEPROM地址取决于EEPROM设备和EEPROM设备驱动程序的实现。
只有16位块号中不表示特定数据集或冗余副本的位才能用于地址计算。由于NVRAM Manager需要DataSet信息,所以可以使用参数NVM_DATASET_SELECTION_BITS为NVRAM Manager配置要编码DataSet的位数。
- 详细解释与示例
数据集信息被配置为编码在16位块号的4个LSB中(允许每个NVRAM块最多16个数据集和总共4094个NVRAM块)。实现者决定存储一个直接相邻的逻辑块的所有数据集,并使用该块的长度和一个指针来访问每个数据集。为了计算块的起始地址(第一个数据集的地址),她/他只使用12个MSB来访问一个特定的数据集,她/他将块的大小乘以数据集索引(四个MSB)添加到这个起始地址。
- 最低的4位(LSB):用来表示一个块内的数据集编号(从0到15),这意味着每个NVRAM块最多可以包含16个不同的数据集。
- 最高的12位(MSB):用来表示NVRAM块本身的编号,用于定位物理存储中的逻辑块起始地址。因为剩下的12位可以表示多达4096个不同的数值(实际上这里提到的是4094个NVRAM块,因为起始地址和终止地址(0x000和块0xFFF)不可配置Block编号)。
- 地址计算步骤
- 假设:
- 块号 = 0x1234(16进制,对应二进制 0001 0010 0011 0100)
- 块大小 = 1 KB(1024字节)
- 每个数据集大小 = 块大小(即每个数据集占满整个块,实际中可能更小,但此处简化)。
- 步骤1:分解块号
- 高12位(MSB):取前12位 0001 0010 0011,即 0x123(16进制)。
- 低4位(LSB):取后4位 0100,即 0x4(对应十进制4)。
- 步骤2:计算逻辑块起始地址
- 假设物理内存中每个逻辑块的起始地址按块大小对齐(1 KB对齐):起始地址 = 高12位的值 × 块大小 = 0x123 × 1024。
- 例如,若 0x123 = 291(十进制):起始地址 = 291 × 1024 = 298,944 字节(或 0x48F00 16进制)。
- 步骤3:访问特定数据集
- 块号中的低4位(0x4)表示要访问该逻辑块中的第4个数据集(索引从0开始)。
- 每个数据集占1 KB,因此目标数据集地址为:目标地址 = 起始地址 + (数据集索引 × 块大小) = 298,944 + (4 × 1024) = 303,040 字节(或 0x49F00)。
- 内存布局示意图
逻辑块起始地址 | 数据集0地址 |
---|---|
数据集0 (索引0) | 占用 0x48F00 ~ 0x48FFF |
数据集1 (索引1) | 占用 0x49000 ~ 0x490FF |
… | … |
数据集4(索引4) | 占用 0x49F00 ~ 0x49FFF |
… | … |
数据集15(索引15) | 占用 0x4AF00 ~ 0x4AFFF |
4-4-3、Limitation of erase/write cycles
Ea模块的配置,应该通过EaNumberOfWriteCycles配置参数配置每一个逻块的预期读写周期。如果底层EEPROM设备或设备驱动程序不提供每个物理存储单元至少配置的擦/写周期数(通过EepAllowedWriteCycles配置参数),EA模块应提供扩展的擦/写访问的机制,使物理设备不会超过物理读写寿命。这也应适用于EA模块内部使用的所有管理数据。
示例:逻辑块号1被配置为预期的500.000个写入周期,底层的EEPROM设备和设备驱动程序仅被指定为100.000个擦除周期。在这种情况下,EA模块必须提供(至少)5个独立的存储器区域,并在内部交替进行这些区域之间的访问,以便每个物理存储器位置只在指定100.000个周期内被擦除。
#define EA_MAX_WAIT_TIME 4000u //4ms Reference to M95640
4-4-4、Handling of “immediate” data
包含即时数据的块必须即时写入,即这些块应无需必要就可写入,以便首先擦除相应的存储器区域(例如,通过使用预擦除的存储器)。在写入即时数据之前,NVRAM管理应取消持续的较低优先级的读取/擦除/写入或比较作业。
注意:硬件上正在运行的操作(例如写入一个页面或擦除一个扇区)通常不能在启动后中止。因此,即使对于即时数据,最长的硬件操作的最大时间也必须被接受为延迟。
示例:三个10字节的块已经配置为即时数据。EA模块/配置工具保留这30个字节(如果需要,外加每块/页面的实现特定开销),仅供此即时数据使用。即该存储区不得用于存储其他数据块。现在,NVRAM管理者已经请求EA模块编写一个100字节的数据块。在写入此块时,会出现需要写入一个(或多个)直接数据块的情况。因此,NVRAM管理者取消了正在进行的写请求,并随后对包含即时数据的(第一)块发出写请求。正在进行的写请求的取消由EA模块同步执行,并且作为对即时数据的写请求的底层EEPROM驱动程序可以被启动,而没有任何进一步的延迟。然而,在可以写入即时数据的第一个字节之前,EA模块分别的底层EEPROM驱动程序必须等待来自前一个写请求的持续硬件访问的结束(例如,写入页面、擦除扇区、通过SPI传输、……)。
4-4-5、Managing block consistency information
Ea模块应管理每个模块的信息,无论该模块是否“正确”。此一致性信息应只涉及块的内部处理,而不涉及块的内容。当一个块写操作启动时,EA模块会将对应的块标记为不一致(这并不一定意味着在物理设备上进行写操作。如果有其他方法检测逻辑块的一致性,则应避免更改与该逻辑块存储的管理信息)。在块写操作成功结束后,EA模块将(再次)将块标记为一致。
注意:此内部管理信息不应与块的可通过使用Ea_InvalidateBlock服务进行操作的有效性信息混淆,即EA模块应能够区分不一致的块和被上层故意失效的块。
4-5、Error Classification
4-5-1、Development Errors
#define EA_E_UNINIT 0x01 /* API service called while module is not (yet) initialized Development */
#define EA_E_INVALID_BLOCK_NO 0x02 /* API service called with invalid block number Development */
#define EA_E_INVALID_BLOCK_OFS 0x03 /* API service called with invalid block offset Development */
#define EA_E_PARAM_POINTER 0x04 /* API service called with invalid pointer argument Development */
#define EA_E_INVALID_BLOCK_LEN 0x05 /* API service called with invalid block length information Development */
#define EA_E_INIT_FAILED 0x09 /* Ea_Init failed Development */
4-5-2、Runtime Errors
#define EA_E_BUSY 0x06 /* API service called while module is busy Development */
#define EA_E_INVALID_CANCEL 0x08 /* Ea_Cancel called while no job was pending Development */
4-6、关键API定义
4-6-1、Ea_Init
初始化EEPROM抽象模块。Ea_Init在初始化模块后将模块状态从MEMIF_UNINIT设置为MEMIF_BUSY_INTERNAL,如果初始化在Ea_Init内完成,则在初始化成功完成后,功能Ea_Init应将模块状态从MEMIF_BUSY_INTERNAL设置为MEMIF_IDLE。
void Ea_Init(const Ea_ConfigType* ConfigPtr)
{
Ea_InitParameter();
}
Note:The following figure shows the call sequence for the Ea_Init routine. It is different from that of all other services of this module as it is not called by the NVRAM manager and not called via the memory abstraction interface.
4-6-2、Ea_SetMode(NA)
通过此功能来切换底层EEPROM驱动程序的模式。功能Ea_SetMode应检查模块状态是否为MEMIF_BUSY。如果是这种情况,函数Ea_SetMode将提高运行时错误EA_E_BUSY并返回给调用者,而不执行模式开关。Ea_SetMode应检查模块状态是为MEMIF_IDLE还是MEMIF_BUSY_INTERNAL。如果是这种情况,模块应接受模式更改请求。当模块完成内部管理操作后,应在模块的主功能中异步执行模式变更。
void Ea_SetMode(MemIf_ModeType Mode)
{
(void)Eep_SetMode(Mode);
}
4-6-3、Ea_Read
从块号为BlockNumber且块内地址偏移为BlockOffset地址处读取数据大小为Length的内容并传递到DataBufferPtr缓冲处。
函数Ea_Read应取块号和偏移量,并计算相应的内存读取地址。注意:地址偏移量和长度参数可以取给定类型范围内的任何值,这允许从逻辑块内的任意地址读取任意数量的字节。EA模块应在EA模块的主函数范围内异步执行读取操作。
- 如果当前模块状态为 MEMIF_IDLE,或当前模块状态为 MEMIF_BUSY INTERNAL,函数 Ea_Read 应接受读取请求,将给定/计算的参数复制到模块内部变量,启动读取任务,将 EA 模块状态设置为 MEMIF_BUSY,将任务结果设置为 MEMIF_JOB_PENDING,并以 E_OK 返回。
- 如果当前模块状态为 MEMIF_UNINIT 或 MEMIF_BUSY,函数 Ea_Read 将拒绝作业请求并返回 E_NOT_OK。
extern Std_ReturnType Ea_Read(uint16 BlockNumber,uint16 BlockOffset,uint8* DataBufferPtr,uint16 Length);
4-6-4、Ea_Write
将DataBufferPtr中的数据写入BlockNumber块中。
函数Ea_Write应取块号,并计算相应的内存写地址。此地址计算的块偏移量应固定为零。函数Ea_Write应将写入作业的长度参数设置为此逻辑块配置的长度。EA 模块应在 EA 模块的主函数内异步执行函数 Ea_Write 的写任务操作。
- 如果当前模块状态为 MEMIF_IDLE,或当前模块状态为 MEMIF_BUSY INTERNAL,函数 Ea_Write 应接受写入请求,将给定/计算的参数复制到模块内部变量,启动写入任务,将 EA 模块状态设置为 MEMIF_BUSY,将任务结果设置为 MEMIF_JOB_PENDING,并以 E_OK 返回。
- 如果当前模块状态为 MEMIF_UNINIT 或 MEMIF_BUSY,函数 Ea_Write 将拒绝作业请求并返回 E_NOT_OK。
Std_ReturnType Ea_Write(uint16 BlockNumber, const uint8* DataBufferPtr);
4-6-5、Ea_Cancel
取消正在进行的异步操作。
如果当前模块状态为 MEMIF_BUSY(即函数 Ea_Cancel 接受了取消待处理任务的请求),则函数 Ea_Cancel 应调用底层 EEPROM 驱动程序的取消函数,且函数 Ea_Cancel 应重置 EA 模块的内部变量,使模块为新的作业请求做好准备。也就是说,函数 Ea_Cancel 应将作业结果设为 MEMIF_JOB_CANCELED,将模块状态设为 MEMIF_IDLE。
如果当前模块状态不是 MEMIF_BUSY(即取消待处理任务的请求被函数 Ea_Cancel 拒绝),则函数 Ea_Cancel 不应改变当前模块状态或任务结果。如果当前模块状态不是 MEMIF_BUSY(即没有作业要取消,因此函数 Ea_Cancel 拒绝了取消待处理作业的请求),函数 Ea_Cancel 将引发运行时错误 EA_E_INVALID_CANCEL。
void Ea_Cancel(void)
{
Ea_MemHwStatus.EaCancle = TRUE;
(void)Eep_Cancel();
}
注意:
- Ea_Cancel函数和底层EEPROM驱动的cancel函数是同步的,即它们的工作一旦返回到调用者就完成了。另一方面,在EEPROM内存中进行正在进行的读、擦除或写作业是异步的。Cancel函数只能重置其模块内部变量,以便模块能够接受新的作业。它们不会取消硬件中正在进行的作业,也不会等待硬件完成正在进行的作业。这可能会导致这样的情况:模块的状态报告为IDLE,而硬件仍然在执行一个正在进行的作业。因此,EEPROM驱动程序的主要功能应该在开始新的工作之前检查硬件是否空闲。
- 如果必须写入更高优先级的数据(即即时数据),则只能使用Ea_Cancel来中止NV块的读写请求。
4-6-6、Ea_GetStatus
返回Ea模块的状态。具体参考Figure17. MemIf_StatusType。
4-6-7、Ea_GetJobResult
返回作业的服务API。具体参考:Figure18. MemIf_JobResultType。
MemIf_JobResultType Ea_GetJobResult(void)
{
MemIf_JobResultType retVal = Ea_MemHwStatus.JobStatus;
return retVal;
}
函数Ea_GetJobResult应返回NVRAM Manger请求的最后一个作业的状态。只有那些由上层直接请求的Job才会对函数Ea_GetJobResult返回的作业结果产生影响。即EA模块本身在内部管理操作过程中发放的作业,不得改变作业结果。
注意:为了便于实现这一点,EA模块可能必须实现第二组本地变量来存储内部作业的数据。
注意:内部管理操作(例如,“垃圾收集”)将仅在从NvM请求的作业的上下文中被调用。它们是必须在请求的作业之前还是之后完成,这是模块实现者的决定。
4-6-8、Ea_InvalidateBlock
使BlockNumber标识的块无效。
extern Std_ReturnType Ea_InvalidateBlock(uint16 BlockNumber);
函数 Ea_InvalidateBlock 将获取块编号并计算相应的内存块地址。函数 Ea_InvalidateBlock 应调用底层设备驱动程序的擦除函数或相应更改某些模块内部管理信息,从而使 BlockNumber块失效,具体取决于实现方式。
注意:请求的块的失效方式取决于模块的具体实现。内部管理信息必须存储在NV存储器中,因为它必须在软件重启或冷启动后仍保存历史状态信息。这些信息是什么以及它是如何存储的,取决于模块具体实现。
- 函数 Ea_InvalidateBlock 应检查模块状态是否为 MEMIF_BUSY。如果是这种情况,函数 Ea_InvalidateBlock 应拒绝无效性请求,引发运行时错误 EA_E_BUSY,并以 E_NOT_OK 返回。
- 函数 Ea_InvalidateBlock 应检查模块状态是否为 MEMIF_IDLE 或 MEMIF_BUSY_INTERNAL。如果是这种情况,模块应接受无效请求,将 Ea 模块状态设置为 MEMIF_BUSY,将作业结果设置为 MEMIF_JOB_PENDING,并向调用者返回 E_OK。
- The Ea module shall execute the block invalidation request asynchronously within the Ea module’s main function.
4-6-9、Ea_GetVersionInfo(NA)
4-6-10、Ea_EraseImmediateBlock
擦除BlockNumber标识的块。
extern Std_ReturnType Ea_EraseImmediateBlock(uint16 BlockNumber);
函数Ea_EraseImmediateBlock取块号,计算相应的内存块地址。此地址计算的块偏移量应固定为零。Ea_EraseImmediateBlock函数应确保EA模块能够立即写入数据。这是否涉及物理擦除内存区域,并因此调用底层驱动程序的擦除函数,取决于实现。
4-7、回调函数
Ea模块提供CallBack函数给下层Eep模块调用。
注意:根据组成NV内存栈的模块的实现,EA模块提供的回调例程可能会在中断级被调用。因此,EA模块的实现必须确保这些例程的运行时间相当短。例如,因为回调可能会通过几个软件层向上传播。在中断级别上是否允许/可行回调例程取决于项目特定的需求(反应时间)和限制(在中断环境下运行)。因此,系统设计必须确保所涉及的模块的配置符合这些要求。
4-7-1、Ea_JobEndNotification
底层的EEPROM驱动程序应调用函数Ea_JobEndNotification来报告异步操作的成功结束。
void Eep_JobEndNotification(void)
{
Eep_JobStatus = MEMIF_JOB_OK;
Ea_JobEndNotification();
}
4-7-2、Ea_JobErrorNotification
底层的EEPROM驱动程序应调用函数Ea_JobEndNotification来报告异步操作的失败。
void Eep_JobErrorNotification(void)
{
Eep_JobStatus = MEMIF_JOB_FAILED;
Ea_JobErrorNotification();
}
4-8、可配置接口
4-8-1、NvM_JobEndNotification
底层内存抽象使用该函数表示作业结束,没有错误。
void NvM_JobEndNotification(void)
{
NvM_JobProc_MemFinishNotification(NVM_REQ_OK);
}
在执行所有必要的内部管理操作后,Ea模块应调用配置参数EaNvMJobEnd通知中定义的函数。成功结束异步读操作意味着读作业已完成,结果正常。在完成所有必要的内部管理操作后,当异步写操作成功结束时,Ea模块将调用配置参数EaNvMJobEndNotification中定义的函数。异步写操作成功结束意味着写作业结束,结果是OK的,并且块已经被标记为有效。
4-8-2、NvM_JobErrorNotification
底层内存抽象使用该函数表示作业结束,有错误。
void NvM_JobErrorNotification(void)
{
NvM_JobProc_MemFinishNotification(NVM_REQ_NOT_OK);
}
在执行所有必要的内部管理和错误处理操作后,Ea模块应调用配置参数EaNvMJobError通知中定义的函数。异步读操作失败意味着读作业已完成并已失败。
五、EEPROM Driver模块介绍
5-1、功能介绍
EEPROM驱动程序提供从(到)EEPROM中读取、写、擦除的服务。它还提供了用于比较EEPROM中的数据块和内存中的数据块的服务(例如RAM)的服务。EEPROM模块提供的服务都是异步的。内部EEPROM的驱动程序直接访问微控制器硬件,并且位于微控制器抽象层(MCAL)中。外部EEPROM使用和外部设备通信的驱动程序(在大多数情况下是SPI)来访问外部EEPROM设备,并且位于ECU抽象层(ECAL)。
5-2、关键概念
- Data block:一个数据块可能包含1…n字节,并在EEPROM驱动程序的API中使用。数据块通过以下信息传递给EEPROM驱动程序:
- EEPROM中的地址偏移
- 指向内存位置的指针
- 数据长度
- Data unit:EEPROM中最小的数据实体。这些实体的读/写/删除操作可能会有所不同。
- Example 1: Motorola STAR12
- Read: 1 byte
- Write: 2 bytes
- Erase: 4 bytes
- Example 2: external SPI EEPROM device
- Read/Write/Erase: 1 byte
- Example 1: Motorola STAR12
- 一些外部SPI EEPROM设备提供了不同的访问模式的可能性:
- Normal mode:通过SPI与EEPROM设备进行的数据交换是按字节顺序执行的。这允许SPI与其他SPI设备,如外部看门狗等一起协同使用。
- Burst mode:通过SPI与EEPROM设备进行的数据交换是按块顺序执行的。块的大小取决于EEPROM属性,例如是64字节。由于传输大块,SPI被突发模式的EEPROM访问阻塞。此模式在需要快速读写数据的ECU启动和关闭阶段使用。
Abbreviation | Description |
---|---|
EEPROM | Electrically Erasable and Programmable Read Only Memory |
NVRAM | Non Volatile Random Access Memory |
NvM | Module name of NVRAM Manager |
EcuM | Module name of ECU State Manager |
DEM | Module name of Diagnostic Event Manager |
5-3、模块相关文档
5-4、Dependencies to other modules
5-5、模块功能详解
5-5-1、General behavior
- 对于片上EEPROM和片外EEPROM,Eep SWS都是有效的。
- Eep SWS为EEPROM操作定义了异步服务(read/write/erase/compare)。
- Eep模块不得缓冲作业。Eep模块一次只能接受一个任务(Job)。在作业处理过程中,Eep模块不接受其他作业(Job)。
- Eep模块不得缓冲要读取或写入的数据。Eep模块应使用通过API传递的指针所引用的应用程序数据缓冲区。
- EEP驱动程序应内部处理数据缓冲区的对齐问题。它不应对接收到的RAM缓冲区的对齐方式(因为它们是指向uint8类型的指针)提出任何要求,而应将传入的指针视为仅字节对齐进行处理。
5-5-2、Processing of jobs - general requirements
- Eep模块应允许配置为中断或轮询控制的作业处理(如果EEPROM硬件支持它)。如果硬件支持并启用了中断控制的作业处理,则位于Eep_Irq.c中的外部中断服务例程应调用一个额外的作业处理函数。
- 没有硬件中断支持处理的作业(例如读取和比较作业)和超时监督仍然需要Eep_MainFunction函数。
- 如果底层EEPROM技术要求读地址或长度信息有一定的位置对齐,如果读或比较作业的地址和/或长度参数没有正确对齐,则Eep_MainFunction应在内部补偿这个缺失的对齐,即Eep_MainFunction应对闪存提供字节级读访问,无论硬件施加的任何对齐限制。
- 对于通过SPI驱动外部EEPROM的Eep模块:
- 在正常的EEPROM模式下,Eep模块应通过使用配置为正常访问SPI EEPROM的SPI通道来访问外部EEPROM。
- Eep的配置应使配置参数EepNormalReadBlockSize的值符合正常SPI模式下可读的字节数。
- 在快速EEPROM模式下,Eep模块应通过使用配置为突发性(Burst)访问SPI EEPROM的SPI通道来访问外部EEPROM。
- Eep的配置应使配置参数EepFastReadBlockSize的值符合在突发(Burst)SPI模式下可读的字节数。
5-5-3、Processing of read jobs
Eep模块应该提供两种读模式:
- Normal 模式
- Fast 模式
对于驱动外部EEPROM的Eep模块:如果外部EEPROM不支持突发(Brust)模式,Eep模块应接受快速读取模式的选择,但行为应与正常模式相同(不关心模式参数)。
-
在正常EEPROM模式下,Eep模块应在一个作业处理周期内周期性读取EepNormalReadBlockSize参数指定的字节数,直到把这个作业(Job)的数据读完。示例:
- EepNormalReadBlockSize = 4
- 要读取的字节数:21
- 所需的作业处理周期数:6个
- 结果读取模式:4-4-4-4-4-1
-
在快速(Fast)EEPROM模式下,Eep模块应在一个作业处理周期内周期性读取EepFastReadBlockSize参数指定的字节数,直到把把这个作业(Job)的数据读完。示例:
- EepFastReadBlockSize= 32
- 要读取的字节数:110
- 所需的作业处理周期数:4个
- 结果读取模式:32-32-32-4
当一个读取作业成功完成时,Eep模块应将EEPROM状态设置为MEMIF_IDLE,并将作业结果设置为MEMIF_JOB_OK。如果配置了Callback函数,Eep模块应调用配置参数EepJobEndNotification中定义的通知函数。当在读取作业处理过程中检测到错误时,Eep模块应中止作业,将EEPROM状态设置为MEMIF_IDLE,并将作业结果设置为MEMIF_JOB_FAILED。如果配置了Callback函数,Eep模块应调用配置参数EepJobErrorNotification中定义的通知函数。
5-5-4、Processing of write jobs
Eep模块只能在一个作业处理周期向内部EEPROM硬件写入(和擦除)多个字节。对于内部的eeprom,通常每次可以编写1个数据字。一些外部EEPROM提供了一个RAM缓冲区(例如页面Page缓冲区),允许在一个步骤中写入多个字节。
Eep模块应提供两种不同的写入模式:
- Normal模式
- Fast模式
对于Eep模块驱动外部EEPROM的情况:如果外部EEPROM不提供突发(Burst)模式,Eep模块应接受快速模式的选择,但行为应与正常模式相同(不注意模式参数)。
- 在正常的EEPROM模式下,Eep模块应在一个作业处理周期内写入(并擦除)参数EepNormalWriteBlockSize指定的字节数。示例:
- EepNormalWriteBlockSize = 1
- 要写入的字节数: 4
- 要求的作业处理周期数:4
- 生成的写入模式:1-1-1-1
- 在快速写入模式下,Eep模块应在一个作业处理周期内写入(和擦除)由参数EepFastWriteBlockSize指定的字节数。
- EepFastWriteBlockSize= 16
- 要写入的字节数: 55
- 要求的作业处理周期数:4
- 生成的写入模式:16-16-16-7
如果要写入EEPROM单元的值已经包含在EEPROM单元中,如果Eep模块通过配置参数EepWriteCycleReduction被配置为这样做,那么Eep模块应该跳过该单元的编程。如果EEPROM硬件没有自动完成这一工作,Eep模块应在写入EEPROM单元之前擦除该单元。如果要写入的字节数小于可擦除和/或可写数据单元,Eep模块应通过读-修改-写操作来保存受影响的EEPROM单元的数据。如果给定的参数(EepromAddress和Length)与可擦/可写数据单元不一致,Eep模块应通过读-修改-写操作来保存受影响的EEPROM单元的数据。
在写入数据块时,Eep模块应尽量减少读-修改-写操作的次数。当一个写作业成功完成时,Eep模块将EEPROM状态设置为MEMIF_IDLE,并将作业结果设置为MEMIF_JOB_OK。如果配置了,Eep模块将调用在配置参数EepJobEndNotification中定义的通知。当写作业处理过程中检测到错误时,Eep模块应中止作业,并将EEPROM状态设置为MEMIF_IDLE,将作业结果设置为MEMIF_JOB_FAILED。如果配置了,Eep模块将调用在配置参数EepJobErrorNotification中定义的通知。
注意:写入EEPROM的数据不会在写入作业处理函数中进行验证。如果一个数据块需要这样做,那么必须在写作业完成后调用compare函数。这优化了写入速度,因为数据验证(回读和写入后比较数据)只在需要的地方进行。
5-5-5、Processing of Erase jobs
在一个作业处理周期内,EEPROM模块只能擦除EEPROM硬件所支持的字节数。如果EEPROM硬件支持且给定参数(EepromAddress和Length)与可擦除块对齐,则Eep模块应使用块擦除命令。如果给定的擦除参数(EepromAddress和Length)与可擦除数据单元不一致,Eep模块应通过读-修改-写操作保存受影响的EEPROM单元的内容。
当一次擦除作业成功完成时,Eep模块将EEPROM状态设置为MEMIF_IDLE,并将作业结果设置为MEMIF_JOB_OK。如果配置了,Eep模块将调用在配置参数EepJobEndNotification中定义的通知。当erase作业处理过程中检测到错误时,Eep模块中止作业,设置EEPROM状态为MEMIF_IDLE,设置作业结果为MEMIF_JOB_FAILED。如果配置了,Eep模块将调用在配置参数EepJobErrorNotification中定义的通知。
5-5-6、Processing of Compare jobs
- 当比较作业成功完成时,Eep模块将EEPROM状态设置为MEMIF_IDLE,并将作业结果设置为MEMIF_JOB_OK。如果配置了,Eep模块将调用在配置参数EepJobEndNotification中定义的通知。
- 当比较作业处理过程中检测到错误时,Eep模块中止作业,设置EEPROM状态为MEMIF_IDLE,设置作业结果为MEMIF_JOB_FAILED。如果配置了,Eep模块将调用在配置参数EepJobErrorNotification中定义的通知。
- 当比较作业处理过程中发现比较的数据区域不相等时,EEPROM驱动会中止作业,将EEPROM状态设置为MEMIF_IDLE,作业结果设置为MEMIF_BLOCK_INCONSISTENT。如果配置了,回调函数Eep_JobErrorNotification将被调用。
5-7、关键类型定义
5-7-1、Eep_ConfigType
typedef struct Eep_ConfigTypeTag
{
uint8 eep_reserver;
}Eep_ConfigType;
5-7-2、Eep_AddressType
typedef VAR(uint16, AUTOMATIC) Eep_AddressType;
- 每个 EEPROM 设备的 Eep_AddressType 类型下限应为 0。
- 如有必要,EEPROM 模块应在地址类型 Eep_AddressType 中添加设备特定的基地址。
5-7-3、Eep_LengthType
typedef VAR(uint16, AUTOMATIC) Eep_LengthType;
5-8、关键API定义
5-8-1、Eep_Init
void Eep_Init(const Eep_ConfigType* ConfigPtr)
- The function Eep_Init shall initialize all EEPROM relevant registers with the values of the structure referenced by the parameter
ConfigPtr.- After having finished the module initialization, the function Eep_Init shall set the EEPROM state to MEMIF_IDLE and shall set
the job result to MEMIF_JOB_OK.- The function Eep_Init shall set the EEPROM mode to the configured default mode.
- The Eep’s user shall not call the function Eep_Init during a running operation.
5-8-2、Eep_SetMode
void Eep_SetMode( MemIf_ModeType Mode );
- The function Eep_SetMode shall set the EEPROM operation mode to the given Mode parameter.
- The Eep’s user shall not call the function Eep_SetMode during a running operation.
5-8-3、Eep_Read
Std_ReturnType Eep_Read(Eep_AddressType EepromAddress, uint8* DataBufferPtr, Eep_LengthType Length )
- Eep_Read函数复制给定参数,启动读作业,设置EEPROM状态为MEMIF_BUSY,设置作业结果为MEMIF_JOB_PENDING,然后返回。
- 在Eep模块的作业处理功能中,读取作业应异步执行。在作业处理过程中,Eep模块从EepromAddress + EEPROM基址读入一个长度为Length的数据块到*DataBufferPtr。
5-8-4、Eep_Write
Std_ReturnType Eep_Write(Eep_AddressType EepromAddress, const uint8* DataBufferPtr, Eep_LengthType Length);
- Eep_Write函数复制给定参数,启动写作业,设置EEPROM状态为MEMIF_BUSY,设置作业结果为MEMIF_JOB_PENDING,然后返回。
- Eep模块应在其作业处理功能内异步执行写作业。在作业处理过程中,Eep模块将一个长度为Length的数据块从*DataBufferPtr写入EepromAddress+ EEPROM基址。
5-8-5、Eep_Erase
Std_ReturnType Eep_Erase(Eep_AddressType EepromAddress, Eep_LengthType Length);
- Eep_Erase函数复制给定参数,启动erase作业,设置EEPROM状态为MEMIF_BUSY,设置作业结果为MEMIF_JOB_PENDING,然后返回。
- Eep模块应该在Eep模块的作业处理功能中异步执行擦除作业。eeep模块应该擦除一个EEPROM块,从EepromAddress +大小为Length的EEPROM基址开始。
5-8-6、Eep_Compare
Std_ReturnType Eep_Compare(Eep_AddressType EepromAddress, const uint8* DataBufferPtr, Eep_LengthType Length);
- Eep_Compare函数复制给定参数,启动比较作业,设置EEPROM状态为MEMIF_BUSY,设置作业结果为MEMIF_JOB_PENDING,然后返回。
- Eep模块应在Eep模块的作业处理功能中异步执行比较作业。在作业处理过程中,Eep模块将大小为Length的EepromAddress + EEPROM基址的EEPROM数据块与相同长度的*DataBufferPtr数据块进行比较。
5-8-6、Eep_Cancel
void Eep_Cancel(void)
{
Eep_JobStatus = MEMIF_JOB_CANCELED;
}
- Eep_Cancel函数将取消正在进行的EEPROM读、写、擦除或比较作业。Eep_Cancel函数会同步中止正在运行的作业,这样从该函数返回后,上级可以直接请求新的作业。
- 注意:Eep_Cancel函数在其行为上是同步的,但同时也是异步的。Eep_Cancel函数的任务(即让模块为新的任务请求做好准备)在返回调用者时完成(因此它是同步的),但另一方面,一个擦除任务可能仍然在硬件设备中进行(因此它是异步的)。
- Eep_Cancel函数将EEP模块状态设置为MEMIF_IDLE。如果配置了,Eep_Cancel将调用EepJobErrorNotification中定义的错误通知函数,以便通知调用方作业的取消。如果作业结果当前为MEMIF_JOB_PENDING值,则Eep_Cancel函数将作业结果设置为MEMIF_JOB_CANCELED。否则作业结果不变。
- Eep模块的用户不能在运行Eep_MainFunction()函数时调用Eep_Cancel()函数。可以通过以下调度配置之一实现:
- 可能性1:NVRAM管理器和EEPROM驱动程序的工作功能是同步的(例如,在一个任务中顺序调用)
- 可能性2:调用Eep_MainFunction函数的任务不能被其他任务抢占。
- 注意:当使用Eep_Cancel函数取消正在进行的写或擦除作业时,受影响的EEPROM单元的状态和数据将是未定义的。
- 只有NVRAM Manager有权使用Eep_Cancel功能。
- 取消外部EEPROM设备中正在使用Eep_Cancel服务的任何作业可能会使该服务处于阻塞状态。
5-8-7、Eep_GetStatus
MemIf_StatusType Eep_GetStatus(void);
The function Eep_GetStatus shall return the EEPROM status synchronously.
5-8-8、Eep_GetJobResult
MemIf_JobResultType Eep_GetJobResult(void);
- 函数 Eep_GetJobResult 应同步返回 Eep 模块接受的最后一项作业的结果。
- 读/写/比较/擦除服务共享相同的作业状态。只能查询上次接受的作业的结果。EEPROM 驱动程序接受的每个新作业都会用 MEMIF_JOB_PENDING 覆盖作业结果。
5-9、Callback notifications
- EEPROM驱动被指定为一个内部微控制器外围设备或一个SPI外部设备。
- 在第一种情况下,该模块属于AUTOSAR软件架构的最低层,因此该模块规范没有识别任何回调函数。
- 在第二种情况下,该模块属于AUTOSAR软件架构的ECU抽象层,因此该模块应该根据SPI处理器/驱动器规范要求提供回调通知,但是这些不能在此处指定,因为它们依赖于模块详细设计。
- 这意味着它们取决于将要使用的SPI作业和SPI序列的数量。如果Eep模块支持SPI外部设备,则Eep模块应按照SPI处理器/驱动器规范的要求提供额外的回调通知。
5-10、Scheduled functions
void Eep_MainFunction(void);
- Eep_MainFunction函数将执行EEPROM读、写、擦除和比较作业的处理。
- 当一个任务被启动时,Eep的用户应该循环调用Eep_MainFunction函数,直到任务完成。
- 如果实现和/或底层硬件需要,配置参数EepJobCallCycle应使用用于EEPROM驱动程序的内部计时(截止日期监视、写入和擦除计时等)。
- 如果没有待处理的任务,函数Eep_MainFunction将返回而不做任何操作。
5-11、Expected interfaces
5-11-1、End Job Notification
当作业完成并得到肯定结果时,Eep 模块应调用配置参数 EepJobEndNotification 中定义的回调函数:
- Read finished & OK
- Write finished & OK
- Erase finished & OK
- Compare finished & data blocks are equal
void Eep_JobEndNotification(void);
5-11-2、Error Job Notification
当作业被取消或中止且结果为负时,Eep 模块应调用配置参数 EepJobErrorNotification 中定义的回调函数:
- Read aborted
- Write aborted or failed
- Erase aborted or failed
- Compare aborted or data blocks are not equal.
void Eep_JobErrorNotification(void);
5-12、Configuration example – external SPI EEPROM device
以下章节将帮助您更好地理解配置参数是如何定义和使用的。针对以下用例,给出了详细的实现和配置示例:用例
- 实现并配置一个用于通过SPI访问外部EEPROM设备的驱动程序。
- 使用AUTOSAR SPI处理器/驱动程序,利用内部缓冲区(internal buffers,IB)进行命令通信,使用外部缓冲区(external buffers,EB)进行数据传输。
- 配置并执行一个SPI读取命令。
此处假设了从外部EEPROM设备读取时SPI命令的某种固定格式和顺序。所选择的SPI API函数是为了操作这个EEPROM设备,以展示SPI总线交互的基本原则。在为实际设备实现驱动程序时,操作顺序很可能不同。具体使用的SPI API函数及其参数的选择和配置需要结合设备的数据手册与SPI处理器/驱动程序规范来确定。请注意,这里使用的SPI API函数仅作为示例;它们的确切签名和配置可能会有所变化。有效的参考始终是当前的SPI SWS(Specification of Software)。
5-12-1、External SPI EEPROM device usage scenario
假设以下场景:外部EEPROM设备是一个SPI从设备,要实现的EEPROM驱动程序使用SPI处理器/驱动模块作为SPI主机。通过专用的片选(Chip Select)线来寻址外部设备,每当执行涉及该设备的操作时,SPI主机会激活这条线(CS被拉低)。外部EEPROM采用串行操作码处理:在设备被其片选线拉低选中后,第一个字节将通过设备的SI线传输。这个字节包含一个8位读操作操作码(0x03),紧接着是一个8位地址字节。完成后,SI线上的任何数据都将被忽略。指定地址的数据(D7-D0)然后被移出到SO线上。如果只需要读取一个字节,在数据出来之后CS线应被拉高;否则,读序列将继续进行,地址自动递增,并连续移出数据。无论何时EEPROM驱动程序的用户想要读取数据,EEPROM驱动程序都会通过多个选定的SPI API调用将读请求转发给SPI处理器/驱动。为了遵循上述请求/响应行为,SPI需要精确配置以匹配预期的通信协议。因此,开发中的一个重要任务就是为与外部EEPROM设备的通信正确配置SPI驱动程序。
基于此配置,EEPROM驱动程序的实际实现结合配置的句柄ID使用SPI API函数将作业分配给SPI处理器/驱动:EEPROM驱动程序的实现可能会结合使用外部和内部SPI缓冲区来与SPI处理器进行通信:在接收到Eep_Read()请求时,EEPROM驱动程序使用Spi_WriteIB()在一个SPI通道内部缓冲区中写入EEPROM源地址。接着,它使用Spi_SetupEB()设置一个SPI外部缓冲区,用于指定要读取的字节数。然后,它调用 Spi_AsyncTransmit() 以启动一个 SPI 序列 EepReadSequence,该序列被配置为完全匹配上述硬件访问协议。一旦 SPI 读取序列完成,SPI 处理器/驱动程序通过调用 Spi_SeqEndNotification 来通知 EEPROM 驱动程序。此时,EEPROM 驱动程序可以通过分配的外部缓冲区安全地访问 EEPROM 数据,并进而完成 EEPROM 读取作业。
5-12-2、Configuration of SPI parameters
为了用SPI处理器/驱动程序,EEPROM驱动程序的实现者需要创建一个SPI配置,该配置包含一整套SPI配置容器,以确保所需的功能得到配置。
从上到下的视角来看,一个名为EepReadSequence的SpiSequence配置容器处理一个完整的读取序列。EepReadSequence进而使用一个名为EepReadJob的SpiJob来处理读取作业的详细信息。这包括对代表EEPROM设备及其指定片选(Chip Select)线的SpiExternalDevice的引用,以及逻辑电平特性如波特率、极性或数据移位边沿等。
EepReadJob进一步细分为一个有序的SpiChannels列表,按照顺序执行这些通道将与外部设备进行所需的SPI总线通信:
- EepChCommand用于发送读命令字节,使用默认的数据常量作为读操作码。
- EepChAddress用于通过内部缓冲区发送设备读地址。
- EepChReadData用于将请求的EEPROM数据读入由外部(相对于SPI)提供的缓冲区。
大致上,为EEPROM读命令配置SPI模块的工作流程包含以下步骤:
- 在ECU配置中为SPI创建一个类型为SpiDriver的容器EepDriver,代表外部EEPROM驱动程序。它将包含下面步骤中要创建的SpiExternalDevice、SpiChannel、SpiJob和SpiSequence类型的子容器。
- 查阅外部设备数据手册中的SPI特性,并相应地设置一个类型为SpiExternalDevice的容器EepDevice。在EepDevice中指定要使用的片选线。
- 查阅设备数据手册中的SPI读命令序列的详细信息。
- 在EepDriver中,分别为传输读命令操作码、EEPROM源地址以及接收设备响应的数据定义一个SpiChannel,例如:
- EepChCommand
- EepChAddress
- EepChReadData
- 根据设备数据手册中描述的通信序列,为每个通道定义SPI通道属性。特别地,配置缓冲区,即EepChAddress使用内部缓冲区,EepChReadData使用外部缓冲区。对于固定的读命令操作码,可以使用SpiDefaultData。
- 定义SpiJob EepReadJob,并设置其工作于EepDevice上。指定执行读作业所需按顺序执行的SpiJobs列表。在这个例子中,作业包含通道列表EepChCommand、EepChAddress、EepChReadData。
- 定义包含执行所需功能所需的SpiJobs列表的SpiSequence EepReadSequence。在这个例子中,EepReadSequence仅包含一个作业,即EepReadJob。填写EEPROM驱动程序提供的回调函数符号,例如Eep_ReadSequenceEndNotification。
- 将所有定义的SPI使用属性在EEPROM驱动程序中发布为一个符合SPI SWS的XML描述文件。
5-12-3、Generation of SPI configuration data
作为上述SPI配置的一部分,每个SpiSequence、SpiJob和SpiChannel都被分配了一个句柄ID。基于XML文件,将生成一个SPI包含文件,该文件发布这些信息:
#define Spi_EepReadSequence 10
#define Spi_EepReadJob 20
#define Spi_EepChCommand 31
#define Spi_EepChAddress 32
#define Spi_EepChReadData 33
5-12-4、SPI API usage
当接收到Eep_Read()请求时,EEPROM驱动程序首先需要将执行读命令所需的信息传递给SPI处理器/驱动程序。它使用Spi_WriteIB()函数在分配给EepChAddress通道的内部缓冲区中设置设备读地址:
Spi_WriteIB(Spi_EepChAddress, &EepromAddress);
接下来,为读取EEPROM设备数据设置外部缓冲区:
Spi_SetupEB(Spi_EepChReadData, NULL, buf_data, length);
最后,通过调用Spi_AsyncTransmit启动读序列:
Spi_AsyncTransmit(Spi_EepReadSequence);
在启动传输后,Eep_Read()返回。传输的其余部分由SPI处理器/驱动程序自主处理。一旦SPI序列完成,SPI处理器将使用回调函数Spi_SeqEndNotification通知EEPROM驱动程序。EEPROM驱动程序的主函数应确保以下两种情况之一:
- 如果序列成功完成,则相应地结束Eep_Read()请求并通过信号EepJobEndNotification进行通知;
- 如果接收到错误,则应触发EepJobErrorNotification并向DEM报告一个EEP_E_READ_FAILED生产错误。
参考文献
- https://www.autosar.org/fileadmin/standards/R21-11/CP/AUTOSAR_SWS_NVRAMManager.pdf
- https://www.autosar.org/fileadmin/standards/R24-11/CP/AUTOSAR_CP_SWS_MemoryAbstractionInterface.pdf
- https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_EEPROMAbstraction.pdf
- https://www.autosar.org/fileadmin/standards/R24-11/CP/AUTOSAR_CP_SWS_EEPROMDriver.pdf
- https://www.autosar.org/fileadmin/standards/R24-11/CP/AUTOSAR_CP_SWS_FlashEEPROMEmulation.pdf
- https://www.autosar.org/fileadmin/standards/R24-11/CP/AUTOSAR_CP_SWS_FlashDriver.pdf