今天来简单说说MCAL中的FEE模块,因为S32K3的FEE模块在MCAL层应该是比较特殊的存在,因为它本身牵扯不到任何的硬件模块,而是一屁股坐在了Fls模块上,它负责发号施令,而Fls层负责累死累活。
在了解Fee模块之前得最好先了解一下Fls模块,关于Fls模块的介绍,大家请参照一位资深大佬的文章,文章链接如下:
《AUTOSAR MCAL详解:FLS》,请仔细参考。
首先来看一下Cluster group, Cluster, Block, Sector之间的关系:
从图中可以看出,Cluster Group可以理解成是一块大的存储块,然后它又包含若干个Cluster,一个Cluster Group最少有两个Cluster, 其中Cluster可以理解成是模拟EE的滚动大单元,而Block为最小滚动单元。众所周知,FEE就是损失空间来增大使用周期的应用,当我们存数据时,Block为最小存储单元,也为最小滚动单元,当在一个Cluster已经没有空间来创建新的Block时,就会触发Cluster的swap操作,将有效Block复制到下一个Cluster继续滚动,而当前Cluster则会无效。一个Cluster会由多个Flash物理扇区Sector组成,而组成一个Cluster的扇区必须是连续的。
下图显示了一个Cluster的数据结构:
在图中可以看到,一个Cluster由Cluster头,Block头和Block Data组成;其中Cluster头表明了其ID,状态,起始地址和大小;而Block头表明了其ID,状态,长度,起始地址偏移,而每个Block则是指向了其对应的data存储区。这里需要注意的是Invalid Flag,这一部分是没有被使用的,是保留的。至于checksum是用来检测头的数据正确性的,而Block Assignment是给下面的Fee Swap Foreign Blocks功能使用的。
还有一点很容易让人忽略,就是在Fee更新数据的时候,Block头的增长方向是由上而下的,而Data的更新方向其实是自下而上的,最终两者会在中间相遇。
最后总结一下FEE存储数据的原理,首先就是Fee_init会完成Cluster的初始化(如果读取到有效Cluster,则会扫描Block并记录最后一条有效Block;如果没有有效Cluster,则会新建);当需要写数据时,则会新建一条Block来完成数据的更新,此为Block的滚动。随着Block的累加,当Cluster没有空间创建Block时,则会进行Cluster swap操作,此为Cluster的滚动。
在使用Fee模块之前,Fee在初始化操作中会启动Cluster Scan操作,而这个操作就是为了找到有效Cluster,并且找到当前有效Block的地址指针,以及写操作的指针。
Block Update操作即为Fee_Write操作,它的流程如下:
Read Block Data操作即为Fee_Read操作,它的流程如下:
以下四种情况会触发Swap操作:
Swap操作的流程如下:
下表显示了在swap的5个阶段,新老Cluster的状态变化:
在配置MCAL的时候,会有一些选项让人很迷惑,接下来看看有哪些选项需要注意。
这个选项是什么意思呢?如果你在更新数据的时候,一些出乎意料的破坏性事件,比如掉电,异常等,导致最后一步的valid flag没有标记成功,那么当上电去扫描Blcok的时候,则会受到这个选项影响。
这个功能是为了两个应用会访问同一个Cluster的场景而设计的,比如Bootloader和APP都得访问一个Cluster,那么就可以通过这个来实现。
如果使能这个功能,就可以在每个Block的配置中配置其的归属权限,其中SHARD即为Bootloader和APP都可以访问。如下图:
这个功能会增加Cluster Swap的时间。
这个功能则是实现了Fee的坏块管理功能,而Fee Sector Erase Retries (0 -> 255) 则可指定尝试读取的次数,一当读取失败的次数超过了这个值,则Fee会标记这个Sector为坏块。下次读写Block时,则会自动跳过坏块。
这个功能是为了避免一种情况而设计的。就是在更新Block的时候,发现当前Cluster没有空间了,这个时候就会触发Cluster Swap功能,这就导致了整个更新Block的时间过长。而这个功能其实就是在Cluster中预留一块空间,而这块空间能够容纳一条所有的Immediate Block。 写block时,当发现当前Cluster的空间不够时,则会将新的Block写到这个预留空间里,随后调用一个检测函数(Fee_EraseImmediateBlock)去检测,如果一旦检测到预留空间被使用了,则触发Cluster Swap。这样做的意义就是将Fee Write操作与Fee Swap操作分离,让Fee Write操作快速完成。
这个选项非常重要,决定了Block data的最小操作单元,举个例子,如果将一个Block的大小设置为8个字节,那么在flash中,一个Block的data部分不只是会占用8个字节,而是会占32个字节。默认最小设置成32个字节,因为这跟ECC检测字长有关。
因为Fee操作的对象是FLASH, 所以如果Fee在写flash的时候被事件打断则很容易造成FLASH出现ECC错误。那么一旦Fee操作时被打断(如断电),Flash发生了ECC错误,那么在下次重启,会对系统造成什么样的影响呢?
ECC错误发生在Cluster头处
如果Fee在写Cluster头时,发生了ECC错误,则会导致整个Custer无效,当Fee扫描Cluster时,会直接擦除整个Custer区域。
ECC错误发生在Block头和Valid Flag处
如果Fee在写Block头或者Valid Flag时,发生了ECC错误,使得Block头或者Valid Flag数据错误,则会在上电初始化的时候,则会标记当前Cluster无效,在下次进行Block写操作的时候,直接触发Swap操作,直接抛弃当前的Cluster。不仅仅是ECC错误,如果Block不合法(Checksum有误,未知的Block ID,Blcok与配置的不匹配),同样会触发Swap。
ECC错误发生在Data区域处
如果ECC错误出现在data区,而其对应的Block头是有效的,这对整个Fee状态机没有任何影响。唯一的影响只是会丢失当前的这一个数据,而历史数据是可以保留的。
在Fee操作Data Flash的期间,如果发生了ECC错误,那么针对这种情况,MCU如何去救急呢?下面列出三种解决方案。
S32K3片上是有Code flash和Data flash两块flash的,而它们两者如果出现ECC错误的话,都可以导致S32K3进入Hardfault中断,但是这个中间过程有个区别,如下图:
Code flash发生ECC错误导致HardFault只需要经过一个开关,而Data flash发生ECC错误导致HardFault则需要经过两个开关。
这第一个解决方法就是断开其中一个开关Gate-PFCR4[DERR_SUP],让Data flash即使发生了ECC错误也不会导致K3进入HardFault,当Fee触发初始化或者swap操作时,就会触发擦操作,从而消除ECC错误。
而在MCAL的配置中实现这一点,只需要在Fls模块中使能Fls Data Error Suppression这个选项就可以了。
这种方法是建立在打开HardFault中断的基础上的,基本思路就是,当CPU执行操作Flash的指令时触发了HardFault中断,然后在中断中进行相关判断后,强行让CPU跳过这条会触发ECC错误的指令,让CPU从HardFault返回后能继续运行下去。
基本流程图如下:
而在MCAL的配置中实现这一个方法,则需要在Fls模块使能Fls ECC Handling HardfaultHandler这个选项。
另外还需要在HardFault中实现处理代码,除了实现CPU指令的强行跳转外,还需要调用调用函数**Fls_DsiHandler()**来更新Fls的状态机。
下面贴出HardFault处理代码:
(**注:**在代码中,需要获得data_pt ,syndrome_u32 这两个参数,这两个参数可通过K3的ERM模块去获取,我这里为了简单,就直接简单化了,后期通过ERM获取该参数待更新。)
void HardFault_Handler(void)
{
register uint32_t Reg;
uint32_t *LR_Pointer = NULL;
Fls_ExceptionDetailsType ExceptionDetails;
Fls_CompHandlerReturnType res;
__asm volatile ("mov %0, r14\n" : "=r" (Reg) );
if((Re2TM_from_FPS_PSP_to_use_PSP!=Reg)&&(Re2TM_from_nFPS_PSP_to_use_PSP!=Reg))
{
__asm volatile ("MRS %0, msp\n" : "=r" (Reg) );
}
else
{
__asm volatile ("MRS %0, psp\n" : "=r" (Reg) );
}
LR_Pointer =(uint32_t *) (Reg + 0x18 + 0x18);
//第一个0x18指的是在栈内PC与R1之间的偏移,这是固定的
//第二个0x18指的是Hardfault函数为保护现场压栈的大小,可通过观察汇编代码得到PUSH的大小
if(S32_SCB->HFSR)
{
S32_SCB->HFSR = S32_SCB->HFSR;
}
//data_pt 可由ERM得到,这里为了简单
ExceptionDetails.data_pt = S32_SCB->BFAR;
//syndrome 可由ERM得到,这里为了简单
ExceptionDetails.syndrome_u32 = C40_DSI_EXC_SYNDROME;
res = Fls_DsiHandler(&ExceptionDetails);
if(FLS_HANDLED_SKIP == res)
{
*LR_Pointer += 4;
return;
}
else
{
while(1);
}
}
这种方法的本质其实就是利用OS的多任务管理功能来处理。基本思路如下图所示:
基本流程为:
而在MCAL的配置中实现这一个方法,则需要在Fls模块使能Fls ECC Handling ProtectionHook这个选项。
然后需要定义Fls Read Function Callout函数,如下:
而这个函数是需要用户自己去实现的,其需要实现的功能主要是调用OS API创建一个新任务。而在这个新任务中需要去调用Fls模块的底层函数Fls_ReadEachBlock来完成与Flash相关的操作。
如果在执行Fls_ReadEachBlock发生ECC错误,则客户需要自己调用ProtectionHook()来处理,ProtectionHook()也是需要用户自己去实现的,其中必须要做的事情就是结束Small Task。而用户对于错误的处理,必须要调用函数Fls_DsiHandler()来更新Fls的状态机。