前言
NVM这块还只停留在按需求配置阶段,遇到问题不能深入分析。本系列文章将从遇到的问题或者提出的疑问着手,一起来更深入学习AUTOSAR架构下的存储协议栈。目前遇到的问题和疑问如下:
1. 通过RTE接口连续读写NVM Block会影响其他Block的读写吗?
2. 没有配置NVM Rom Block的default value且没有写过NVM Block,通过RTE接口去读NVM Block读到的是什么值?
3. Fee模块中的Block的长度更改会造成NVM Block在Flash中重排吗?中间插入NVM Block了?
本文使用的AUTOSAR配置工具为:Vector公司的Davinci工具
参考文章:
AUTOSAR存储协议栈– NVRAM Manager 模块介绍(三)
AUTOSAR存储协议栈– NVRAM Manager 模块介绍(二)
AUTOSAR存储协议栈– NVRAM Manager 模块介绍(一)
AUTOSAR存储协议栈– EEPROM Driver模块介绍
AUTOSAR存储协议栈– EEPROM Abstraction模块介绍
AUTOSAR存储协议栈– Memory Abstraction Interface模块介绍
正文
1.应用SWC读写NVM Block的两种方式
入下图所示,Vector的AUTOAR配置工具Davinci Developer提供两种方式配置SWC使用NVM的服务。
方式一:SWC和NVM直连。NVM提供5个标准接口供SWC使用,SWC可以直接通过C-S接口直接读写NVM Block。这种方式适用于NVM Block仅被一个SWC访问的场景。
图1:SWC和NVM直连
如下图所示,在Developer中直接配置SWC中的Service Need的NvM_Block_NeeDs即可配置NVM服务。
图2:配置NvMBlockNeed
最后在Rte_SWC.h生成的读写NvM_Block的接口如下所示,Rte接口直接封装了NvM的标准接口(NvM_ReadBlock, NvM_WriteBlock, NvM_GetErrorStates)。
方式二:SWC通过NonvolatileMemoryBlock访问NVM。Davinci提供一个类似NvM User的NonvolatileMemoryBlock的中间模块,SWC配置普通的S-R接口和NonvolatileMemoryBlock连接,NonvolatileMemoryBlock模块通过标准的S-R接口和NvM连接。 SWC通过NonvolatileMemoryBloc来读写NvM模块。这种方式适用于多个SWC访问同一个NvM Block的场景。
图3:SWC通过NonvolatileMemoryBlock访问NVM
如下图所示,使用方式二需要配置中间NonvolatileMemoryBlock模块,然后在SWC和NonvolatileMemoryBlock分别配置S-R的读写接口,然后进行连接即可。
图4:配置NoVolatileMemoryBlock
如下所示,生成的Rte_SWC.h中SWC读写NvM Block的接口仅仅是读写一个名为Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName全局变量。
FUNC(Std_ReturnType, RTE_CODE) Rte_Read_SWC_Pp_NvData_PortName_Rx_Element_NvData_PortName(P2VAR(uint8, AUTOMATIC, RTE_SWC_APPL_VAR) data) /* PRQA S 1505, 3206 */ /* MD_MSR_Rule8.7, MD_Rte_3206 */
{
Std_ReturnType ret = RTE_E_OK; /* PRQA S 2981 */ /* MD_MSR_RetVal */
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_MemCpy(data, Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName, sizeof(NvData_PortName)); /* PRQA S 0314, 0315, 0316 */ /* MD_Rte_0314, MD_Rte_0315, MD_Rte_0316 */
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
return ret;
}
FUNC(Std_ReturnType, RTE_CODE) Rte_Write_SWC_Pp_NvData_PortName_Tx_Element_NvData_PortName(P2CONST(uint8, AUTOMATIC, RTE_SWC_APPL_DATA) data) /* PRQA S 1505, 2982 */ /* MD_MSR_Rule8.7, MD_Rte_2982 */
{
Std_ReturnType ret = RTE_E_OK; /* PRQA S 2981 */ /* MD_MSR_RetVal */
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_MemCpy(Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName, data, sizeof(NvData_PortName)); /* PRQA S 0314, 0315, 0316 */ /* MD_Rte_0314, MD_Rte_0315, MD_Rte_0316 */
Rte_OsApplication_QM_Core0_DirtyFlags.Rte_DirtyFlag_NonVolatileMemoryBlock_NVBlockDescriptor_PortName = 1U;
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
/* scheduled trigger for runnables: NonVolatileMemoryBlockTriggerRunnable */
(void)SetEvent(AplTask_10ms_Core0, Rte_Ev_Run_NonVolatileMemoryBlock_NonVolatileMemoryBlockTriggerRunnable); /* PRQA S 3417 */ /* MD_Rte_Os */
return ret;
}
然后NvM也通过Rte_SetMirror_xxx/Rte_GetMirror_xxx接口来更新这个Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName全局变量。
FUNC(Std_ReturnType, RTE_CODE) Rte_SetMirror_NonVolatileMemoryBlock_NVBlockDescriptor_PortName(P2CONST(void, AUTOMATIC, RTE_APPL_DATA) NVMBuffer) /* PRQA S 3112 */ /* MD_Rte_3112 */
{
Std_ReturnType ret = E_NOT_OK; /* PRQA S 2981 */ /* MD_MSR_RetVal */
CONST(uint16_least, RTE_CONST) size = sizeof(NvData_PortName);
if (size <= 100U) /* PRQA S 2991, 2995 */ /* MD_Rte_2991, MD_Rte_2995 */ /* COV_RTE_NVMBUFFER_SIZE */
{
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_MemCpy32(Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName, NVMBuffer, size); /* PRQA S 0314, 0315, 0316 */ /* MD_Rte_0314, MD_Rte_0315, MD_Rte_0316 */
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
ret = E_OK;
}
return ret;
}
FUNC(Std_ReturnType, RTE_CODE) Rte_GetMirror_NonVolatileMemoryBlock_NVBlockDescriptor_PortName(P2VAR(void, AUTOMATIC, RTE_APPL_VAR) NVMBuffer) /* PRQA S 3112 */ /* MD_Rte_3112 */
{
Std_ReturnType ret = E_NOT_OK; /* PRQA S 2981 */ /* MD_MSR_RetVal */
CONST(uint16_least, RTE_CONST) size = sizeof(NvData_PortName);
if (size <= 100U) /* PRQA S 2991, 2995 */ /* MD_Rte_2991, MD_Rte_2995 */ /* COV_RTE_NVMBUFFER_SIZE */
{
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_MemCpy32(NVMBuffer, Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName, size); /* PRQA S 0314, 0315, 0316 */ /* MD_Rte_0314, MD_Rte_0315, MD_Rte_0316 */
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
ret = E_OK;
}
return ret;
}
2.RAM Block和ROM Block
RAM Block最后在代码中体现就是一个全局变量(存放在栈Ram中),ROM Block在代码中体现就是一个const 全局变量(存放在Data Flash中)。Ram Block是一定会有的,Rom Block是可配置的。其中最主要的就是default value的配置。
方式1配置RAM Block和ROM Block:
方式1是SWC通过NvM Block Needs直接访问NvM,如果配置了Default Value就配置了ROM Block。
图5:NvM Block Needs配置RAM和ROM Block
方式2配置RAM Block和ROM Block:
方式2是SWC通过NonvolatileMemoryBlock间接访问NvM,Rom Block通过勾选来配置,Init Value也就是Default value也可配置。
图5:NonvolatileMemoryBlock配置RAM和ROM Block
RAM Block对应的代码:
VAR(Nvdata_PortName, RTE_VAR_INIT) Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName;
ROM Block对应的代码:
CONST(Nvdata_PortName, RTE_CONST_DEFAULT_RTE_CDATA_GROUP) Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName_ROM_NVBlockDescriptor_PortName = {
0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U
};
关于Default Value:
如SWS_NvM_00388所诉,在ECU启动阶段,如果配置了ROM Block,NvM_ReadAll会把ROM block中的default value拷贝到RAM Block。
如SWS_NvM_00085所述,如果没有配置ROM Block,则RAM Block的值需要应用来保证。在Davinci Developer中可以配置RAM Block的Init Value,也可以让RAM Block有一个初始值。
如果,我们既没有配置RAM Block的Init value,也没有配置ROM Block,也从没有写过NvBlock,那么我们第一次去读的RAM Block是.BSS段的一个全局变量(未初始化的全局变量在.BSS段),而MCU上电后会对.BSS段作清零的操作,也就是说我们会读到0。
3. 应用SWC读写NVM过程
SWC通过NonvolatileMemoryBlock读NvM Ram Block:SWC就是读一个名为Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName的全局变量,也就是RAMBlock。
FUNC(Std_ReturnType, RTE_CODE) Rte_Read_SWC_Pp_NvData_PortName_Rx_Element_NvData_PortName(P2VAR(uint8, AUTOMATIC, RTE_SWC_APPL_VAR) data) /* PRQA S 1505, 3206 */ /* MD_MSR_Rule8.7, MD_Rte_3206 */
{
Std_ReturnType ret = RTE_E_OK; /* PRQA S 2981 */ /* MD_MSR_RetVal */
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_MemCpy(data, Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName, sizeof(NvData_PortName)); /* PRQA S 0314, 0315, 0316 */ /* MD_Rte_0314, MD_Rte_0315, MD_Rte_0316 */
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
return ret;
}
SWC通过NonvolatileMemoryBlock写NvM Ram Block:SWC就是写一个名为Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName的全局变量,也就是RAMBlock。同时会设置一个更新Flag,SetEvent触发一个OS Task事件。
FUNC(Std_ReturnType, RTE_CODE) Rte_Write_SWC_Pp_NvData_PortName_Tx_Element_NvData_PortName(P2CONST(uint8, AUTOMATIC, RTE_SWC_APPL_DATA) data) /* PRQA S 1505, 2982 */ /* MD_MSR_Rule8.7, MD_Rte_2982 */
{
Std_ReturnType ret = RTE_E_OK; /* PRQA S 2981 */ /* MD_MSR_RetVal */
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_MemCpy(Rte_NonVolatileMemoryBlock_NVBlockDescriptor_PortName, data, sizeof(NvData_PortName)); /* PRQA S 0314, 0315, 0316 */ /* MD_Rte_0314, MD_Rte_0315, MD_Rte_0316 */
Rte_OsApplication_QM_Core0_DirtyFlags.Rte_DirtyFlag_NonVolatileMemoryBlock_NVBlockDescriptor_PortName = 1U;
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
/* scheduled trigger for runnables: NonVolatileMemoryBlockTriggerRunnable */
(void)SetEvent(AplTask_10ms_Core0, Rte_Ev_Run_NonVolatileMemoryBlock_NonVolatileMemoryBlockTriggerRunnable); /* PRQA S 3417 */ /* MD_Rte_Os */
return ret;
}
NonvolatileMemoryBlock的Runnable被Event事件激活调用NvM_WriteBlock:
RTE_LOCAL FUNC(void, RTE_CODE) NonVolatileMemoryBlockTriggerRunnable(void)
{
if (Rte_OsApplication_QM_Core0_DirtyFlags.Rte_DirtyFlag_NonVolatileMemoryBlock_NVBlockDescriptor_PortName == 1U)
{
NvM_RequestResultType NvM_ErrorStatus = 0;
(void)NvM_GetErrorStatus(NvMConf_NvMBlockDescriptor_NonVolatileMemoryBlockNVBlockDescriptor_PortName, &NvM_ErrorStatus);
if (NvM_ErrorStatus != NVM_REQ_PENDING)
{
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_OsApplication_QM_Core0_DirtyFlags.Rte_DirtyFlag_NonVolatileMemoryBlock_NVBlockDescriptor_PortName = 0;
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
(void)NvM_WriteBlock(NvMConf_NvMBlockDescriptor_NonVolatileMemoryBlockNVBlockDescriptor_PortName, NULL_PTR);
}
else
{
Rte_DisableOSInterrupts(); /* PRQA S 1881, 4558 */ /* MD_Rte_Os, MD_Rte_Os */
Rte_NvBlockPendingFlags.Rte_NvBlockPendingFlag_NonVolatileMemoryBlock_NVBlockDescriptor_PortName = 1U;
Rte_EnableOSInterrupts(); /* PRQA S 1881, 4558, 2983 */ /* MD_Rte_Os, MD_Rte_Os, MD_Rte_2983 */
}
}
}
NvM_WriteBlock调用NvM_QueueJob将Job排队:
FUNC(Std_ReturnType, NVM_PUBLIC_CODE) NvM_WriteBlock(NvM_BlockIdType BlockId, P2CONST(void, AUTOMATIC, NVM_APPL_DATA) NvM_SrcPtr)
{
Std_ReturnType returnValue = E_NOT_OK;
uint8 detErrorId = NVM_E_NO_ERROR;
const NvM_RamMngmtPtrType NvM_RamMngmt_ptloc = NvM_GetMngmtAreaPtr(BlockId);
if(NvM_WriteProtectionChecks(NvM_RamMngmt_ptloc) == TRUE) /* SBSW_NvM_FuncCall_PtrParam_BlockMngmtArea */
{
/* PRQA S 0311, 0316 1 */ /* MD_NvM_11.5_CastLossOfConst, MD_NvM_11.5_JobQueue_CastVoidPtrToObjPtr */
if (NvM_QueueJob(BlockId, NVM_INT_FID_WRITE_BLOCK, (NvM_RamAddressType)NvM_SrcPtr)) /* SBSW_NvM_FuncCall_PtrParam_QueueJob */
{
if(NvM_SrcPtr == NULL_PTR)
{
NvM_EnterCriticalSection();
NvM_RamMngmt_ptloc->NvRamAttributes_u8 |= (NVM_STATE_VALID_SET | NVM_STATE_CHANGED_SET); /* SBSW_NvM_AccessBlockManagementArea */
NvM_ExitCriticalSection();
}
returnValue = E_OK;
}
}
return returnValue;
}
NvM_QueueJob会判断队列是否已经Full,如果Full则Job被丢弃,如果没有Full,则Job入队列:
FUNC(boolean, NVM_PRIVATE_CODE) NvM_QueueJob(NvM_BlockIdType BlockId,
NvM_InternalServiceIdType ServiceId,
NvM_RamAddressType RamAddress
)
{
boolean retVal = FALSE;
boolean queueFull;
boolean blockAlreadyPending;
/* get block management area */
const NvM_RamMngmtPtrType ramMngmtPtr =
((BlockId & NVM_DCM_BLOCK_OFFSET) != 0u) ? (&NvM_DcmBlockMngmt_t) : (&NvM_BlockMngmtArea_at[BlockId]);
#if(NVM_JOB_PRIORISATION == STD_ON)
const uint8 priority = (uint8)NvM_BlockDescriptorTable_at[NVM_BLOCK_FROM_DCM_ID(BlockId)].BlockPrio_u8;
/* NvM_HighPrioQueue is only the right queue if block has an immediate priority, current job is a
write-job and requested block is not a DCM-Block. Otherwise NvM_NormalPrioQueue is right queue. */
P2VAR(NvM_JobQueueType, AUTOMATIC, NVM_PRIVATE_DATA) usedQueue =
((priority == 0u) && (ServiceId == NVM_INT_FID_WRITE_BLOCK) && ((BlockId & NVM_DCM_BLOCK_OFFSET) == 0u)) ?
(&NvM_HighPrioQueue) : (&NvM_NormalPrioQueue);
#else
P2VAR(NvM_JobQueueType, AUTOMATIC, NVM_PRIVATE_DATA) usedQueue = &NvM_NormalPrioQueue;
#endif
/* #200 critical section (Reason: During accessing the job queue, it shall not be possible to access it from another task) */
NvM_EnterCriticalSection();
/* check queue fill status before queuing the block! */
queueFull = (usedQueue->EmptyList == NVM_LIST_END);
blockAlreadyPending = (ramMngmtPtr->NvRamErrorStatus_u8 == NVM_REQ_PENDING);
/* #210 queue is not full and the requested block isn't already pending */
if((queueFull == FALSE) && (blockAlreadyPending == FALSE))
{
/* #211 find next free element in queue */
const NvM_QueueEntryRefType elem = NvM_QueuePop(&usedQueue->EmptyList); /* SBSW_NvM_FuncCall_PtrParam_Queue */
CONSTP2VAR(NvM_QueueEntryType, AUTOMATIC, NVM_PRIVATE_DATA) elemPtr = &NvM_JobQueue_at[elem];
/* #212 setup and queue NvM job */
elemPtr->BlockId = BlockId; /* SBSW_NvM_AccessJobQueue */
elemPtr->RamAddr_t = RamAddress; /* SBSW_NvM_AccessJobQueue */
elemPtr->ServiceId = ServiceId; /* SBSW_NvM_AccessJobQueue */
#if(NVM_JOB_PRIORISATION == STD_ON)
elemPtr->JobPrio = priority; /* SBSW_NvM_AccessJobQueue */
#endif
NvM_QueuePush(&usedQueue->SrvList, elem); /* SBSW_NvM_FuncCall_PtrParam_Queue */
/* #213 set the block status to NVM_REQ_PENDING */
ramMngmtPtr->NvRamErrorStatus_u8 = NVM_REQ_PENDING; /* SBSW_NvM_AccessBlockManagementArea */
/* block queued and pending, return successfully */
retVal = TRUE;
}
return retVal;
}
综上所述,SWC读写NvM接口的时候,其实读写的是一个Ram Block的全局变量。调用写NvM的接口时会触发NonvolatileMemoryBlock的NonVolatileMemoryBlockTriggerRunnable被Os调用,NonVolatileMemoryBlockTriggerRunnable根据Block的写Flag调用NvM_WriteBlock,NvM_WriteBlock中调用NvM_QueueJob,NvM_QueueJob根据队列是否Full来将Job入队列。
也就是说,如果有SWC一直在频繁的调用写NvM的接口,就会导致NvM的队列Full,导致其他的SWC的写NvM Block的请求被丢弃,最终对外表现是NvM存不进去。
Ps: 本人就遇到过队列大小配的太小,导致SWC的NvM写请求被丢弃的问题。。。
4. 问题回答
问题1:通过RTE接口连续读写NVM Block会影响其他Block的读写吗?
答:会。如果SWC连续写NvM Block,且NvM模块的队列大小配置太小,就会导致NvM模块的队列Full后SWC的写请求被丢弃。
问题2:没有配置NVM Rom Block的default value且没有写过NVM Block,通过RTE接口去读NVM Block读到的是什么值?
答:要看有没有配置NvM Ram Block的Init Value。如果配置NvM Ram Block的Init Value,那么读到的就是NvM Ram Block的Init Value,如果没有配置,读到的就是0。
问题3:Fee模块中的Block的长度更改会是的NVM Block在Flash中重排吗?中间插入NVM Block了?
答:还没研究透彻,请关注本公众号的后续文章。
5. 总结
搞清楚了NVM的基本概念后,配置都比较容易。难的是出现问题后怎么查问题,因为NvM和Fee/EA,Fls/EEProm模块的实现还是很复杂的,常见的问题如下:
nSWC开发者抱怨NvM数据存不进去
nSWC开发者抱怨NvM数据在ECU复位变成了默认值
nSWC开发者抱怨NvM数据在ECU复位后改成了异常值
如果,存储协议栈配置本身没有问题,常见的原因如下:
nNvM队列配置太小,以及SWC在频繁的请求写NvM
nSWC在下电前把自己的Block写成了默认值,这个时候,我们可以通过修改NvM Block的Default Value为另一个值来证明不是NvM本身的问题,是SWC自己写了个Default value。
n用来接收NvM读数据的SWC的全局变量复位后并不是直接从NvM读取数据,而是从Back Ram(复位后内容不清除)中读取数据,Back Ram异常后导致接收NvM数据的全局变量内容错乱。
如果不是以上的问题,那就可能是NvM本身或者ECU系统出问题了,这就需要深入分析NvM源码加调试了。
推荐阅读
汽车电子嵌入式精彩文章汇总第一期:20210530-20230703
End
欢迎点赞,关注,转发,在看,您的每一次鼓励,都是我最大的动力!
汽车电子嵌入式
微信扫描二维码,关注我的公众号
原文始发于微信公众号(汽车电子嵌入式):AUTOSAR架构下NVM Block连续写及Default Value问题分析