一、部分 NvM API 解释
(1)Std_ReturnType NvM_ReadBlock(NvM_BlockIdType BlockId,void* NvM_DstPtr)
把Nv Block中的数据copy到NvM_DstPtr指向的RAM中,NvM_DstPtr可以是临时RAM,也可以是永久RAM(永久RAM即配置工具中配置的RAM Block)。如果NvM_DstPtr是NULL_PTR,且配置了永久RAM,那么数据会被copy到永久RAM中。
(2)Std_ReturnType NvM_WriteBlock(NvM_BlockIdType BlockId,const void* NvM_SrcPtr)
把NvM_SrcPtr指向的RAM中的数据Copy到NvBlock中,NvM_SrcPtr可以是临时RAM,也可以是永久RAM。如果NvM_SrcPtr是NULL_PTR,且配置了永久RAM,那么数据会从永久RAM中copy到NvBlock。
(3)Std_ReturnType NvM_ReadPRAMBlock(NvM_BlockIdType BlockId)
等同于(1)的NvM_DstPtr = NULL_PTR时的作用,即把NvBlock中的数据copy到配置的永久RAM中。
(4)Std_ReturnType NvM_WritePRAMBlock(NvM_BlockIdType BlockId)
等同于(2)的NvM_SrcPtr = NULL_PTR时的作用,即把数据从永久RAM中copy到NvBlock。
二、隐式同步
一个NvBlock(下图蓝色块)对应一个RAM Block(下图橙色块),可以是临时RAM,也可以是永久RAM,这个RAM Block建议只被一个SWC(APP)使用。
APP在写入数据时的流程如下:
- (1)SWC把新的数据写到RAM Block中
- (2)APP调用 NvM_WriteBlock 或 NvM_WritePRAMBlock(仅当RAM Block是永久RAM时),请求把RAM Block中的数据写入 NvBlock。
- (3)从发起请求开始,到NvM模块写入完成为止(不管成功还是失败)的这段时间内,APP不能再次更改RAM Block中的值,但可以读取。
- (4)APP可以用轮询的方式周期性检查NvM写入操作是否完成,NvM也可以用回调函数的方式来通知APP操作完成。
- (5)当NvM操作完成后,APP才可以重新更改RAM Block中的值。
APP在读取数据时的流程如下:
- (1)APP提供一个RAM Block,用来装从NvBlock中读取的数据。
- (2)APP调用NvM_ReadBlock,发起请求把数据从NvBlock读取到RAM Block。
- (3)从发起请求到NvM操作完成的这段时间内,APP不能读取和写入RAM Block中的值。
- (4)APP可以用轮询的方式周期性检查NvM写入操作是否完成,NvM也可以用回调函数的方式来通知APP操作完成。
- (5)当NvM操作完成后,RAM Block中就是读取到的新的数据了,这个时候APP才可有使用。
在这种机制下,NvM不对RAM Block的数据一致性(数据一致性简单理解就是数据的正确性,比如我想要写什么数据,最终写入的就是什么数据)负责,使用RAM Block的APP负责确保RAM Block的数据一致性,也就是说在NvM写入和读取NvBlock期间,不能再更改RAM Block的值,以及在NvM读取数据期间,不访问RAM Block。
不推荐多个SWC共享一个RAM Block。因为如果多个SWC共享一个RAM Block,那么不仅要确保这些SWC在访问RAM Block的时候互相之间不冲突,还要确保在NvM写入期间所有的SWC都不能访问这个RAM Block。这套机制会很复杂,而且容易出问题,比如:APP用NvM回调函数的方式来判断NvM写入是否完成,APP1发起了一个NvM写入请求,然后等待写入完成的回调函数被触发,之后回调函数被触发了,但其实触发这个回调函数的写入操作不是这个APP1请求的,而是另一个APP2在之前请求的,此时APP1认为RAM Block中的数据已经被写入NvBlock了,但实际还没有。诸如此类,总之就是机制很复杂,且容易出问题,不推荐。但你要非得用隐式同步且多个APP共享一个RAM Block行不行呢,我觉得应该也行,但需要详细的去设计整套逻辑,确保使用时不冲突。
三、显式同步
显式同步与隐式同步相比较,多了一个RAM块——RAM Mirror:
首先我们来看一看达芬奇中和这个有关的几个配置项:
其中Use Synchronization Mechanism用来配置是否使用显式同步,下面两个回调函数我们在后文将会介绍怎么使用。如果勾选了使用显式同步,那么将不允许配置RAM Block Data,NvM协议栈会自动为其分配一个RAM Mirror,也就是上图中的RAM Mirror。
我们来看看显式同步的数据写入和读取流程:
写入流程:
- APP把想要写入的数据存到RAM Block中。
- APP调用NvM_WriteBlock或NvM_WritePRAMBlock,发起一个写入数据请求。如果调用NvM_WriteBlock这个API的话,需要输入一个SrcPtr的地址,但我理解这个地址现在是没用的,所以可能输入啥都行,这个未验证,存疑。这一步的目的就是发起一个写入数据的请求。
- NvM模块之后会调用Write Ram Block To Nv Callback这里配置的回调函数,在调用这个回调函数之前APP都可以修改RAM Block中的值。
- 当NvM模块调用Write Ram Block To Nv Callback这个回调函数时,会有一个输入参数,如下面的代码所示,即RAM Mirror的地址,APP应该在这个回调函数中把RAM Block中的数据copy到RAM Mirror中,这个时候APP要确保copy的数据的一致性,也就是在复制数据的过程中不能允许其他APP访问。这个回调函数可以返回E_OK或E_NOT_OK,如果复制过程中出现问题返回了E_NOT_OK,NvM协议栈会自动重复调用这个回调函数,重复次数最多为NvMRepeatMirrorOperations配置的次数。
typedef P2FUNC(Std_ReturnType, NVM_APPL_CODE, NvM_WriteRamToNvMCbkPtrType)(P2VAR(void, AUTOMATIC, NVM_APPL_DATA) NvMBuffer);
- APP复制数据完成后,APP就可以再次读写RAM Block中的值了。
- APP可以用轮询的方式获取NvM的操作结果,NvM也可以用回调函数的方式通知APP操作结果。
读取流程(与写入类似):
- APP准备一个RAM Block用来存储从NvM Block读取的数据。
- APP调用API NvM_ReadBlock 或 NvM_ReadPRAMBlock发起一个NvM读取数据请求。
- NvM模块之后会调用Read Ram Block From Nv Callback这里配置的回调函数,在调用这个回调函数之前APP都可以读写RAM Block中的值。
- 当NvM模块调用Read Ram Block From Nv Callback这个回调函数时,会有一个输入参数,如下面的代码所示,即RAM Mirror的地址,与上面的区别是这次是个常量指针,因为是APP在这个回调函数中把RAM Mirror中的数据copy到RAM Block中,这个时候APP也要确保copy的数据的一致性,也就是在复制数据的过程中不能允许其他APP访问RAM Block。这个回调函数可以返回E_OK或E_NOT_OK,如果复制过程中出现问题返回了E_NOT_OK,NvM协议栈会自动重复调用这个回调函数,重复次数最多为NvMRepeatMirrorOperations配置的次数。
typedef P2FUNC(Std_ReturnType, NVM_APPL_CODE, NvM_ReadRamFromNvMCbkPtrType)(P2CONST(void, AUTOMATIC, NVM_APPL_DATA) NvMBuffer);
- APP复制数据完成后,APP就可以再次使用RAM Block中的值了。
- APP可以用轮询的方式获取NvM的操作结果,NvM也可以用回调函数的方式通知APP操作结果。
我们对比隐式同步可以看到,APP在大部分时间都可以读写RAM Block中的数据,只有在两个回调函数中复制数据的极短时间内不能使用RAM Block,所以显式同步使得APP在使用RAM Block中的数据的时候更加方便灵活。缺点就是多占用一块RAM 空间,且多了一步copy的操作。
显式同步与隐式同步最根本的区别就是,显式同步APP是知道NvM使用RAM Block的起始时间点和结束时间点的,而且这两个时间点是APP自己来控制的,即两个回调函数中的复制操作起始和结束的时间点。APP不需要通过读取NvM的操作状态来判断当前是否可以使用RAM Block。
显式同步是支持多个APP共享一个RAM Block的,机制也比较简单,设置一个互斥量或AUTOSAR OS中的Resource来保护一下,哪个APP或上面的两个回调函数使用RAM Block前申请一下互斥量,使用完了再释放就可以了。
注意一点:如果APP1先发起了一个写入或读取的请求,但NvM还没有处理,回调函数也还没有调,APP2有发起了一个相同类型(指写入或读取)的请求,那么这两个(或多个)请求会被合并,NvM只会处理一次,回调函数只会调一次。