您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
万字长文实战解析AUTOSAR模块NvM与Fee
 
作者: 业余正规军
   次浏览      
 2023-8-28
 
编辑推荐:

本文主要介绍了AUTOSAR模块NvM与Fee方面的内容。希望对你的学习有帮助。
本文来自于微信公众号焉知汽车,由火龙果软件Linda编辑,推荐。

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.最后,改变状态机进入下一个步骤。

 

调用 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→Read_Wait→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_BlockId
Type 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

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

 
   
次浏览       
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试

最新活动计划
C++高级编程 12-25 [线上]
白盒测试技术与工具实践 12-24[线上]
LLM大模型应用与项目构建 12-26[特惠]
需求分析最佳实践与沙盘演练 1-6[线上]
SysML建模专家 1-16[北京]
UAF架构体系与实践 1-22[北京]
 
 
最新文章
简述Matplotlib
Python三维绘图--Matplotlib
Python数据清洗实践
PyTorch实战指南
Python爬虫与数据可视化
最新课程
Python应用开发最佳实践
Python+数据分析+tensorflow
Python 编程方法和应用开发
人工智能+Python+大数据
Python及数据分析
更多...   
成功案例
某通信设备企业 Python数据分析与挖掘
某银行 人工智能+Python+大数据
某领先数字地图提供商 Python数据分析与机器学习
北京 Python及数据分析
某金融公司 Python编程方法与实践培训
更多...