CP Autosar - 万字长文调试NvM
先给读者们跳一段
前言
NvM模块我们知道,位于AutoSar的Memory Stack的服务层,Stack的结构图如下:
那么,AutoSar(ETAS)的代码是如何实现EEPROM的读写呢?以如下代码为引,调试下NvM读取时关键变量、状态机的跳转过程。
NvM_ReadAll();
do
{
NvM_MainFunction();
MemIf_Rb_MainFunction();
NvM_Rb_GetStatus(&NvM_Sts);
MemIF_Sts = MemIf_Rb_GetStatus();
} while ((NVM_RB_STATUS_BUSY == NvM_Sts) || (MEMIF_BUSY == MemIF_Sts));
NvM_ReadAll()
根据isolar中配置的NvM block属性 NvM_Prv_BlockDescriptors_acst,
设置NvM block的请求状态 NvM_Prv_stRequests_rAMwAM_au16 为
NvM_Prv_ServiceBit_ReadAll_e ,
设置请求结果状态 NvM_Prv_stRequestResult_rAwAM_au8 为
NVM_REQ_PENDING 。
(方便后面调试复制,蓝色:变量,绿色:枚举,咖色:函数)
如图所示:
如开头代码的while循环,接下来是挨个block去读取内容的过程。
NvM_MainFunction()
还算是个比较清爽的、以 NvM_Prv_Main_st.Activity_rAMwM_en 变化做状态机跳转的函数。
接下来一个个状态机分解调试一下看看。
首先可以看下Init之后,NvM_Prv_Main_st.Activity_rAMwM_en:
NVM_PRV_ACTIVITY_ARBITRATE
NvM活动仲裁:
在执行完函数 NvM_Prv_MainFunctionArbitrate() 后,可以看到
NvM_Prv_Main_St 结构体的变化如下:
选取了第一个有效的地址进行Read操作。
NVM_PRV_ACTIVITY_JOB_START
- 如果我们配置了SingleBlockStartCallback,则会首先调用回调函数;
2.之后会根据当前NvM的状态,去调用 NvM_Prv_JobStart_Read 函数,而从这边,会去区分是使用FEE(flash模拟EEPROM)接口还是EA(eeprom抽象)接口。
目前项目使用的是FEE,自然就执行到了 FEE_Read 函数了。
这里面也不算复杂,是对 Fee_OrderFifo_st 结构体的赋值。
3.最后,改变状态机进入下一个步骤。
NVM_PRV_ACTIVITY_POLL_RESULT
调用 Fee_GetJobResult 获取下全局变量 Fee_GlobModuleState_st 的值,即Fee的当前状态。当状态不为 MEMIF_JOB_PENDING ,可以去下一个步骤
NVM_PRV_ACTIVITY_JOB_COMPLETE 。
那么其实NvM就是通过 job_start 和 poll_result 这2个状态机,对Fee模块做请求和等待结果的处理。
NVM_PRV_ACTIVITY_JOB_COMPLETE
调用的 NvM_Prv_JobComplete_Read,其实是一个保护函数,如果NvM状态为未完成状态,重新回到 NvM_Prv_MainFunctionJobStart 。
”
之后 进入NVM_PRV_ACTIVITY_RESULT_EVAL 。
NVM_PRV_ACTIVITY_RESULT_EVAL
如果读的流程没有问题,修改 NvM_Prv_stRequestResult_rAwAM_au8 的状态为 NVM_REQ_OK ,则会调用
NvM_Prv_MainFunctionResultEval_ResetMainStates,这边又是个内联函数,需要打特殊的断点才能看到。
清除NvM_Prv_stRequests_rAMwAM_au16请求状态,初始化
NvM_Prv_Main_st.JobData_st。再回到 NVM_PRV_ACTIVITY_ARBITRATE, 准备执行下一个block的读任务。
MemIf_Rb_MainFunction()
其实就是封装了Fee的mainfuction。而 Fee_MainFunction() ,是以
Fee_Rb_WorkingState_en 变化做状态机跳转。暂时调试一下读。
FEE_RB_IDLE_E
承接NvM_Mainction()中的NVM_PRV_ACTIVITY_JOB_START,可以看到是在那边修改了Fee_OrderFifo_st 中 FEE_NVM_JOB 的 Mode_en 为 Fee_Read_Order。
那么FEE_Mainfunction(),在执行查找 Fee_SearchNextOrder() 之后,就执行 Fee_LoadNextOrder(),
去修改 Fee_Rb_WorkingState_en 为 Read(FEE_RB_READ_MODE_E)。
FEE_RB_READ_MODE_E
执行函数:
备注有误哦。
这个函数里面每个调用的子函数都有自己单独的状态机,调试的相当头疼,不过结束再回来看,发现其实状态机的跳转过程全部是一样的,大致如下:
Init→Search→Read→Read_Wait→Finished→Init。
在读到数据之后,则调用 Fee_UpdateStatus() 修改状态机,结束当前block读取,进入下一个block或者全部读取完退出while循环。
这边还是把函数单列一个章节去讲吧,内容其实还是比较多的(调试了有点久)。
Fee_HLReadBlock()
基于Fee_RdWrOrder_st.Fee_HLRdBlock的状态机跳转。
FEE_HL_RDWR_BLK_INIT_E
获取Fee_OrderFifo_st中当前需要读的block的PersistentId、lenth和Flags,赋值给Fee_GlobInfoLastRdHeader_st。然后设置Fee_RdWrOrder_st.Fee_HLRdBlock状态为
FEE_HL_SEARCH_BLK_HDR_E 去查找block的头。
FEE_HL_SEARCH_BLK_HDR_E
循环执行 函数 Fee_LLSearchSpecifiedBlkHeader(),它也有自己的状态机跳转
Fee_RdWrOrder_st.Fee_LLSearchBlkHdr_en,断点打上调就完了!
1.Fee_RdWrOrder_st.Fee_LLSearchBlkHdr_en
执行Fee_GetMostCurrentSectorIdx()获取当前的使用物理块,
然后调用 Fee_LLGetAddressFromCache,获取block的地址给到结构体Fee_RdWrOrder_st。
其中,Fee_Cache_au32就是 fee block的存储位置,在Fee_Init()时就已经设置好了。
如果我们曾经写过某个block,那么cache中存储的即为具体位置,如果没有写过,那么会有一个无效的地址
FEE_CACHE_TMP_INVALID_VALUE (0xCAFEAFFE)。
2.FEE_LL_SEARCHBLK_BLK_HEADER_E
执行Fee_LLSearchNextBlkHeader(),它的状态机是
Fee_RdWrOrder_st.Fee_LLRdState_en。
简单总结就是确定读取位置,polling模式读取,读取完成后校验内容。
如果是无效地址,则返回Fee_ERROR_E,初始化
Fee_RdWrOrder_st.Fee_LLRdState_en;
如果是有效地址,则在 Fee_LLSearchNextBlkHeader函数中会进一步调用
Fls_Read()。
不过这个函数也不是读函数,仅是在dflash not busy时,将地址偏移0xAF00 0000(使用的tc3**)后给到Fls_CinfigPtr。
之后调用 Fls_17_Dmu_MainFunction,到最底层的Fls_lMainRead,才是读函数。
首先确认 寄存器状态 HF_ERRSR、HF_ECCC,然后将ReadAddress的内容读到ReadBufferPtr的位置。
读完之后置位FlsJobResult为 MEMIF_JOB_OK,
那么Fee_CheckFlsJobResult 函数就会去设置
Fee_RdWrOrder_st.Fee_LLRdState_en 为 FEE_LL_READ_FINISHED_E。
3.FEE_LL_READ_FINISHED_E
然后校验数据的头(0xA53C96)和 CRC16,之后
函数Fee_LLSearchSpecifiedBlkHeader返回FEE_ORDER_FINISHED_E,结束读命令。
设置Fee_RdWrOrder_st.Fee_HLRdBlock = FEE_HL_CHECK_BLK_CS_E。
block header结构体如下:
FEE_HL_CHECK_BLK_CS_E
执行 函数 Fee_LLCalcBlkCrcInFlash,它也有状态机
Fee_RdWrOrder_st.Fee_LLCalcCrcBlk_en,其中又是去Dflash中读取数据,不过这次读的是Crc32部分的内容并校验。
FEE_HL_RD_DATA_FROM_BLK_E
执行 Fee_LLCopyData2Buffer(),终于到了给咱们设置的Ram buffer赋值的地方了,最终将函数读取完毕。
整个流程Fee的读取流程如下:
NvM_Write概述
NvM的写入过程,即是上层通过调用NvM接口改变队列信息,之后
NvM_MainFunction()再去逐级往下调用Fee_MainFunction(),最终将源Ram数据拷贝到目标Dflash地址中去。
流程图如下:
再附一个write过程的最底层接口调试图:
接下来就是具体的写入过程。
关键变量
UDS中某个2E服务DID:
NvM的请求队列:
NvM_Prv_Queue_ast[]
Fee的请求队列:
Fee_OrderFifo_st[]
Fee存储 读、写的状态机和其他信息:
Fee_RdWrOrder_st
Fee的工作状态机:
Fee_Rb_WorkingState_en
Fee上次读指令存储的信息:
Fee_GlobInfoLastRdHeader_st
写入的存储临时buffer:
Fee_llPageBuf_au32
写入过程
上层请求逻辑
其实应用层调用写入接口有2种,一种是仅请求状态,之后统一时间段存储(可以是休眠前):
NvM_SetRamBlockStatus(NvM_BlockIdType BlockId, boolean BlockChanged),
另一种则是目前项目使用的立即触发存储:
NvM_WriteBlock(NvM_BlockIdType BlockId, const void*NvM_SrcPtr)
从函数内容来看,基本上只是请求的类型不同。
函数会首先判断下请求的NvM block的lenth和buffer地址的有效性,之后调用NvM_Prv_Queue_EnqueueRequest(idQueue_uo,&BlockData_pcst>QueueEntry_st);
对队列进行赋值操作,如下图:
这边还能看到一个细节信息,队列的最大值size_cu16为50,其实是与isolar中的配置是一致的。
NvM_MainFunction()
和Read一样,以 NvM_Prv_Main_st.Activity_rAMwM_en为状态机跳转。
首先,NvM_Prv_MainFunctionArbitrate() 函数将 队列
NvM_Prv_Queues_ast的内容拷贝到NvM_Prv_Main_st 中,之后设置
JobData_st 。
经过函数 NvM_Prv_JobStart_Write()之后,再看下属性:
这边会调用 Fee_Write()设置 Fee_OrderFifo_st[FEE_NVM_JOB],
那么之后的NvM_Prv_MainFunctionPollResult(), 只需要等待Fee的返回结果就可以了。
最后再执行 NvM_Prv_MainFunctionJobComplete() 、
NvM_Prv_MainFunctionResultEval()清空状态,完成本次写操作。
rba_FeeFs1_MainFunction()
NvM在改变Fee_orderFifo_st之后 ,FEE_mainfunction()就要出场工作了。
主函数以 为状态机工作。
Fee通过执行 Fee_SearchNextOrder ()和Fee_LoadNextOrder()来查找order和Fee block的属性。
之后,调用函数 Fee_HLWriteBlock(),
(状态机Fee_RdWrOrder_st.Fee_HLWrBlock_en)。
写之前先要读,把当前dflash中最新的一块数据读出来,通过
Fee_LLSearchSpecifiedBlkHeader() ,流程跟readall中一致。
然后计算当前待存储数据的Crc,
如果crc和取读的内容一致,那么其实就不需要写入,直接返回上一级即可,如果需要写入那么就开始填充。这里可以看到除了CRC,数据段也填充了2位,这就是整个头了。
再附一下header的结构体:
再往底层去,调用
Fee_LLWriteBlock(&Fee_GlobInfoLastRdHeader_st,
Fee_OrderFifo_st[Fee_idxActQueue_u8].DataBufferPtr_pu8);
(状态机:Fee_RdWrOrder_st.Fee_LLWrBlock_en)
计算存储所用空间,空间不够会触发erase的逻辑,下一篇做压力测试的时候再来调试。
如果空间是够用的,则调用 做真正的写动作,这里是先写了8个字节(1个page)。
整个Fls写过程的调试过程如下,
FEE_LL_WR_WRITEHEADER_E
FEE_LL_WR_WRITEDATA_SEC_A_E
FEE_LL_WR_WAIT_WRITEHDRPG2_E
最后的最后,清空状态,结束“写”动作。
文章转载自公众号:汽车与基础软件