编辑推荐: |
本文主要介绍CP Autosarde NvM模块,如何调试NVM模块,希望对您的学习有所帮助。
本文来自于汽车与基础软件
,由火龙果软件Alice编辑,推荐。 |
|
00 先给读者们跳一段舞

01 前言
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)); |
02 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去读取内容的过程。
03 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的读任务。
04 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 → R ead_W a it→Finished →Init。
在读到数据之后,则调用 Fee_UpdateStatus() 修改状态机,结束当前block读取,进入下一个block或者全部读取完退出while循环。
这边还是把函数单列一个章节去讲吧,内容其实还是比较多的(调试了有点久 )。
05 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的读取流程如下:

06 NvM_Write概述
NvM的写入过程,即是上层通过调用NvM接口改变队列信息,之后
NvM_MainFunction() 再去逐级往下调用 Fee_MainFunction() ,最终将源Ram数据拷贝到目标Dflash地址中去。
流程图如下:

再附一个write过程的最底层接口调试图:

接下来就是具体的写入过程。
07 关键变量
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
08 写入过程
上层请求逻辑
其实应用层调用写入接口有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 () 之后 ,再看下属性:

这边会调用 F ee_Write() 设置 Fee_OrderFifo_st [ FEE_NVM_JOB ] ,


那么之后的 NvM_Prv_MainFunctionPollResult() , 只需要等待Fee的返回结果就可以了。
最后再执行 NvM_Prv_MainFunctionJobComplete() 、
NvM_Prv_MainFunctionResultEval() 清空状态,完成本次写操作。
rba_FeeFs1_MainFunction()
NvM 在改变 F ee_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_WRITED A TA_SEC_A_E


FEE_LL_WR_WAIT_WRITEHDRPG2_E

最后的最后,清空状态,结束“写”动作。

|