文章目录
前言
本文介绍CP AUTOSAR 架构下的Fls组件,基于S32K312芯片、NXP提供的MCAL包,使用EB Tresos工具进行配置的经验,不具体介绍芯片FLASH、存储外设等功能。
Fls组件实现MCU内部FLASH、外部FLASH的读、写、擦除、比较、块检查等功能。
Fls组件允许将FLASH驱动加载到RAM里执行操作。
Fls组件位于Memory Drivers层里,为上层Fee提供接口,Fls组件本身不关注FLASH的校验、冗余设计等,这些逻辑由Fee组件完成。
上图为CP AUTOSAR存储架构。
一、原理解析
Fls组件有以下几个功能和概念:
(一)、Flash page
FLASH可编程的单位,取决于硬件性能,通常一页为8BYTE,FLASH每次写数据的大小需要符合:数据长度%一个Flash page=0,也就是说一页为8BYTE的话,每次写入的数据长度需要能被8整除,不足的补FF。
FLASH每次写入的地址也需要能被一个Flash page整除。
(二)、Flash sector
FLASH可擦除的单位,取决于硬件性能,假如一个扇区是8192个字节的话,FLASH每次擦除数据的大小需要符合:擦除长度&8192=0,也就是说FLASH每次擦除的大小只能是8192 * X。软件里配置sector的大小时尽量跟实际的sector大小一致。
(三)、Flash access code
FLASH执行擦除、写数据时存储在RAM里的特殊代码。因为MCU的FLASH存在不同的partition,在相同的partition内代码不能同时RWW,即CPU在这分区不能取指令时又执行擦写操作,否则会出现硬件异常,有以下例子:
1、擦写代码存放在ram区,可以对codeflash、dataflash区进行擦写。
2、擦写代码存放在codeflash区,则不能对codeflash区进行擦写动作,但能对dataflash区进行擦写。
3、擦写代码存放在dataflash区,则不能对dataflash区进行擦写动作,但能对codeflash区进行擦写。
所以Fls组件允许在ram区运行Flash access code进行擦写动作,比如在Bootloader的场合、需要对FLASH ECC校验错误块擦除的场合。
(四)、虚拟地址和物理地址
MCU存在不同的分区,每个分区的物理地址都不同,但软件里操作时是按虚拟地址来操作,软件里配置虚拟地址映射到物理地址,例如:
Fls组件配置了两个Sector,第一个在codeflash区物理地址为0x00400000,大小为8192个字节,第二个在dataflash区物理地址为0x10000000,大小为8192个字节,但软件里操作的地址即虚拟地址基址为0,虚拟地址0-8191为第一个Sector,虚拟地址8192-16383为第二个Sector。
二、代码架构
NXP提供的本芯片Fls组件,内部FLASH驱动使用芯片的C40,外部FLASH驱动使用QSPI来操作,本章不涉及外部FLASH驱动。
(一)、多核访问
Fls允许多核访问,通过信号量来处理多核同步的问题,在Fls_MainFunction()判断当前的核是否处理完毕然后是否释放信号量。
(二)、擦写运行冲突
当FLASH要擦写的扇区与执行代码在同一个扇区时会有冲突,所以就需要把擦写代码放到RAM区里去执行。
C40_Ip_AccessCode()为执行FLASH擦写的代码,查看它的定义为acfls_code_rom
ld文件里acfls_code_rom地址为0x20406F00,
在执行擦写任务时如果使能了AC CODE
加载AC代码便会判断AC执行代码的扇区是否和要擦写的扇区一致,一致的话将C40_Ip_AccessCode()加载到RAM区,RAM区地址如果有定义的话,在执行擦写寄存器时变运行RAM里的代码。AC代码只能在擦写同步模式下使用。
(三)、数据同步
在执行擦写读任务时那要注意D-CACHE的影响,为预期FLASH里内容与实际内容一致,在执行读操作前需要禁用CACHE,执行擦写操作前打开CACHE执行擦写操作后禁用CACHE。
在将擦写执行代码复制到RAM区运行时也要注意CACHE的影响,不然执行完搬运代码后,数据可能还在CACHE里,然后CPU去执行目标RAM区里的代码就出错了,所以在搬运前要禁用CACHE,执行完要清理RAM区和CACHE。
(四)、FLASH加锁解锁
FLASH在每次擦写时需要先解锁,执行完再加锁,防止被异常篡改。
(五)、FLASH擦写程序异步同步
Fls组件的擦写任务都是异步的,也就是说调用了接口后,实际执行动作在Fls_MainFunction()里,但擦写代码可以选择同步还是异步,也就是说操作擦写寄存器后,FLASH擦写没那么快运行完,同步模式的话死等FLASH擦写完,期间可以用回调函数如添加喂狗操作,AC代码也只能用在同步模式,异步模式为查询式判断FLASH有没有擦写完。
/* On sync */
if ((STATUS_C40_IP_SUCCESS == ReturnCode) && (FALSE == Asynch))
{
Fls_IPW_CallAccessCodeWrite();
/* Check status of write hardware */
#if (STD_ON == C40_MAIN_INTERFACE_ENABLED)
ReturnCode = C40_Ip_MainInterfaceWriteStatus();
#endif
/* Assume that everything was ok */
LldRetVal = FLASH_E_OK;
/* The job has finished. Time to lock the sector */
if ( (Fls_u32JobAddrIt > (*(Fls_pConfigPtr->paSectorEndAddr))[Fls_u32JobSectorIt]) ||
(Fls_u32JobAddrIt > Fls_u32JobAddrEnd) )
{
if (STATUS_C40_IP_SUCCESS != C40_Ip_SetLock(Fls_Ipw_xVirtualSectorInUse, (uint8)FLS_DOMAIN_ID))
{
/* Sector locking failed */
Fls_eLLDJobResult = MEMIF_JOB_FAILED;
LldRetVal = FLASH_E_FAILED;
}
}
if (STATUS_C40_IP_SUCCESS != ReturnCode)
{
/* IP operation failed */
Fls_eLLDJobResult = MEMIF_JOB_FAILED;
LldRetVal = Fls_IPW_TranslateReturnCode(ReturnCode);
}
}
/* If failed or Async */
else
{
if (STATUS_C40_IP_SUCCESS != ReturnCode)
{
/* IP operation failed */
Fls_eLLDJobResult = MEMIF_JOB_FAILED;
LldRetVal = Fls_IPW_TranslateReturnCode(ReturnCode);
}
else
{
LldRetVal = FLASH_E_PENDING;
Fls_eLLDJob = FLASH_JOB_WRITE;
Fls_eLLDJobResult = MEMIF_JOB_PENDING;
}
}
(六)、FLASH ECC
FLASH在擦写时如果遇到突然掉电情况也就是低压擦写或者向同一个地址没擦除多次写不同数据会容易造成ECC错误,再下次读这块区域时便会触发硬件异常,尤其是DATA FLASH区,因为通常我们把数据存放在DATA FLASH区,NXP提供的参考文档有以下处理办法:
1、
通过使能Suppression开关,让FLASH发生ECC错误时不产生HardFault,让上层Fee识别到异常时触发擦除操作来消除ECC错误。
如上图在使能ECC CHECK后底层会先判断ECC有没有抑制掉,有抑制掉的话因为不会进入异常便检测对应地址ECC对不对,不对的话当前操作返回错误,上层就知道了当前读取操作失败。
2、
进入异常后让CPU跳过引起ECC异常的指令然后更新Fls状态机,退出异常中断返回当前程序让程序继续运行,上层Fee发现异常后执行擦除任务消除ECC或者直接在异常中断里执行擦除动作然后再返回让程序继续运行。
EB里使能Fls ECC Handling HardfaultHandler后,在进入异常中断里就可以调用Fls_DsiHandler()判断当前是否FLASH ECC故障,Fls_DsiHandler()有返回FLS_HANDLED_SKIP就说明是FLASH ECC引起的异常,这时可以在中断里来执行擦除动作并且更新Fls状态机或者退出让Fee来更新,之后退出中断。
Fls_DsiHandler()入参pExceptionDetailsPtr->data_pt是产生ECC的数据地址可由BFAR寄存器获得,pExceptionDetailsPtr->syndrome_u32是异常类型,固定写C40_DSI_EXC_SYNDROME。
跳过异常地址方法可以参考AN12201.pdf,里面有描述怎样改LR指针跳过异常地址。
3、
因为通过进入异常中断后通过跳过有ECC错误区域这种修改指针的方式不属于安全设计,所以有一个思路是在读取FLASH时创建一个小任务,在这个小任务里处理FLASH的操作,这样就与其它任务做了隔离,这个任务需要自己设计,当这个任务里发现有ECC错误后,删除本任务,之后其它任务来处理有ECC坏的区域。
通过使能Fls ECC Handling ProtectionHook来触发该策略。
Callout用户自定义来创建任务。
然后在任务里调用Fls_ReadEachBlock()读取FLASH区域,如果触发了ECC错误那么删除任务并且调用Fls_DsiHandler()来更新状态机,之后在其他主任务里处理坏的区域。
在将有ECC问题的区擦除后,弥补方法是将FLASH备份区填补到对应的擦除区或者将数据分为重要数据区和不重要数据区,重要数据区跟代码一样是永远不动的,擦写动作永远发生在不重要数据区,那么即使发生ECC错误后不重要数据区就没顾虑的擦除掉。
当然在遇到FLASH ECC异常时添加处理措施是一种安全模式的设计,产品上设计上还是要有一些防护,比如产品有网络管理功能,在每次进入BUS SLEEP时,要等FLASH操作执行完再进入休眠断电。如果产品电源接的是ACC电,那么电路上MCU的电源可能要添加大电容防止MCU断电太快,并且在MCU前级电路上增加低压监测电路,也使能MCU自己本身的低压监测功能,当低压时,关掉一切外设,然后运行Fls_MainFunction()。产品软件里每次触发复位时要等FLASH执行完再触发复位。
三、主要变量和类型描述
各种Callback、Callout用于的场合
FlsAcCallback:底层在同步模式下也就是执行擦写代码时等待擦写完成期间运行的回调函数,用户可在里面添加喂狗等操作。
FlsJobEndNotification:Fls的任务执行结束并且成功后通知Fee模块,和Fee集成时必须要添加。
FlsJobErrorNotification:Fls的任务执行结束并且错误后通知Fee模块,和Fee集成时必须要添加。
FlsStartFlashAccessNotif、FlsFinishedFlashAccessNotif:用户可以让Fls在执行擦写代码前和结束后可以添加操作比如关闭打开全局中断,操作中断不应在Fls里操作,应该在OS或RTE里操作,所以这两个回调供用户添加操作。
FlsReadFunctionCallout:此CALLOUT给用户添加任务,在任务里处理FLASH ECC的问题。
四、主要代码描述
(一)、void Fls_Init(const Fls_ConfigType * ConfigPtr)
Fls组件的初始化,包括涉及到FLASH的硬件外设初始化,在操作FLASH前必须先执行Fls_Init()。
(二)、Std_ReturnType Fls_Write(Fls_AddressType TargetAddress,const uint8 * SourceAddressPtr,Fls_LengthType Length)
Fls组件的写操作。TargetAddress为在配置范围内的虚拟地址并且能被页大小整除。SourceAddressPtr为写数据的地址,注意这写数据的变量要定义为全局变量。Length为写数据大小即Length=Flash page * X。
(三)、Std_ReturnType Fls_Erase(Fls_AddressType TargetAddress,Fls_LengthType Length)
Fls组件的擦除操作。TargetAddress为配置范围内的虚拟地址,因为MCU擦除单位是sector即最小擦除大小一个sector,每次Fls_Erase()会检查TargetAddress映射在哪个物理扇区内,擦除对应的物理扇区。Length为擦除数据大小,如果Length不足sector大小,则Length变成一个sector大小。
(四)、Std_ReturnType Fls_Read(Fls_AddressType SourceAddress, uint8 * TargetAddressPtr,Fls_LengthType Length )
Fls组件的读操作。SourceAddress为在配置范围内的虚拟地址,和写操作不同,该地址可以不需要被页大小整除。TargetAddressPtr为存放读出来的地址。Length为数据大小。
(五)、Std_ReturnType Fls_Compare(Fls_AddressType SourceAddress,const uint8 * TargetAddressPtr,Fls_LengthType Length)
Fls组件的比较操作,比较操作能比较目标地址的数据和预期的数据是否一致,基于芯片特性也能同时检查目标地址是否有ecc错误。SourceAddress为在配置范围内的虚拟地址,和写操作不同,该地址可以不需要被页大小整除。TargetAddressPtr为要比较数据的地址,注意这比较数据的变量要定义为全局变量。Length为数据大小。
(六)、Std_ReturnType Fls_BlankCheck(Fls_AddressType TargetAddress,Fls_LengthType Length)
Fls组件的块检查操作,即检查FLASH区域是不是等于FF。TargetAddress为任意配置范围内的虚拟地址。Length为要检查的数据大小。
(七)、void Fls_SetMode(MemIf_ModeType Mode)
设置Fls组件读写操作的快慢,具体实现依据每个芯片功能来,比如在MEMIF_MODE_SLOW模式下允许数据读写大小范围大,MEMIF_MODE_FAST模式下允许数据读写大小范围小。
(八)、void Fls_MainFunction(void)
Fls组件执行读、写、擦除、比较、块检查操作的运行函数,因为Fls组件的操作是异步的,即调用Fls_Write()、Fls_Erase()、Fls_Read()、Fls_Compare()、Fls_BlankCheck()后FLASH不会马上动作需要在Fls_MainFunction()里执行完成。
(九)、MemIf_StatusType Fls_GetStatus( void )
获取Fls组件的运行状态,为MEMIF_IDLE时表示Fls组件可以执行读写擦除比较块检查操作。
(十)、MemIf_JobResultType Fls_GetJobResult( void )
获取Fls组件的任务状态,即每次执行完操作后是否成功,为MEMIF_JOB_PENDING时表示Fls组件当前在执行读写擦除比较块检查操作。
(十一)、void Fls_Cancel(void)
中止当前的操作。
从以上可知有写任务、读任务、擦除任务、比较任务、块校验任务。
Fls_Write()、Fls_Erase()、Fls_Read()、Fls_Compare()、Fls_BlankCheck()都为异步操作,每次执行时都将在Fls_MainFunction()里运行完成,同时只能执行一个任务,比如写任务要执行完后才能成功调用下一个任务。
每个任务让用户只不需要关注物理地址,任务在执行时会将虚拟地址映射到物理地址并且会检查地址的合法性,例如以下:
软件配置了sector1物理地址为code flash的0x00400000软件设置大小为0x2000,sector2物理地址为data flash的0x10000000软件设置大小为0x1FF8,sector3物理地址为data flash的0x10002000软件设置大小为0x2000,所以软件里虚拟地址为0x0-0x5FF8。已知该芯片FLASH的page大小为8个字节,sector大小为0x2000个字节。
1、假如调用Fls_Write()时,TargetAddress设置为0x07,那写任务将不会执行,因为TargetAddress没有8个字节对齐。
2、假如调用Fls_Write()时,Length设置为0x07,那写任务将不会执行,因为Length没有8个字节对齐。
3、假如调用Fls_Write()时,TargetAddress设置为0x1FF8,Length设置为16,那实际上写入芯片FLASH的地址为code flash区的0x401FF8-0x401FFF,data flash区的0x10000000-0x1000000F,而不是code flash区的0x401FF8-0x402007。
4、假如调用Fls_Write()时,TargetAddress设置为0x3FF8,Length设置为8,那实际上写入芯片FLASH的地址为data flash区的0x10001FF8-0x10001FFF,而不是0x10002000-0x10002007。
5、假如调用Fls_Erase(),TargetAddress设置为0x2000,Length设置为0x1FF8,那实际上芯片FLASH擦除的地址是0x10000000-0x10001FFF,因为FLASH实际sector大小为0x2000。
五、EBTresos配置
本次用的是NXP的S32K3的MCAL,EB配置,主要配置如下:
Fls Access Code Erase、Fls Access Code Write:代码里存放AC的RAM地址,如果使能AC了情况下,软件里会把存放AC的FLASH的地址里的数据搬到设置好的RAM区去执行AC代码。
MEMIF_MODE_SLOW和MEMIF_MODE_FAST模式下最大和最小允许读写的长度。
Fls ECC Handling HardfaultHandler:开启ECC检测功能的API
Fls ECC Handling ProtectionHook:开启供AUTOSAROS ECC检测功能的API
Fls Sector Index:当前Fls Sector Index的索引
Fls Physical Sector:当前Sector选择的物理Sector区
Fls Page Size :页的大小,这里固定为8
Fls Sector Size:当前Sector的大小,一般要跟选择的物理Sector大小一样
Fls Sector Start Address:当前Sector开始的地址,起始地址为上一个Sector的结束地址
Fls Data Error Suppression:使能后抑制由于FLASH ECC错误引起的硬件异常。
六、使用范例
(一)、使用写任务后再使用比较任务
/**忽略其他配置**/
Std_ReturnType ret;
uint8 data[8] = {1,2,3,4,5,6,7,8};
Fls_Init(&Fls_Config_VS_0);
ret = Fls_Write(0x00, data, 8);
if(STD_ON == ret)
{
while (MEMIF_IDLE != Fls_GetStatus())
{
Fls_MainFunction();
}
ret = Fls_Compare(0x00, data, 8);
if(STD_ON == ret)
{
while (MEMIF_IDLE != Fls_GetStatus())
{
Fls_MainFunction();
}
if (!(MEMIF_JOB_OK == Fls_GetJobResult()))
{
/* 比较失败,说明目标地址的数据与data数组里的数据不一致 */
}
}
}
(二)、使用擦除任务后再使用块检测任务
/**忽略其他配置**/
Std_ReturnType ret;
Fls_Init(&Fls_Config_VS_0);
ret = Fls_Erase(0x00, 8092);
if(STD_ON == ret)
{
while (MEMIF_IDLE != Fls_GetStatus())
{
Fls_MainFunction();
}
ret = Fls_BlankCheck(0x00, 8092);
if(STD_ON == ret)
{
while (MEMIF_IDLE != Fls_GetStatus())
{
Fls_MainFunction();
}
if (!(MEMIF_JOB_OK == Fls_GetJobResult()))
{
/* 块检测失败,目标地址不是FF或者有ECC故障,说明前面擦除失败 */
}
}
}
七、参考资料
AUTOSAR_CP_SWS_FlashDriver.pdf
AUTOSAR_CP_SRS_FlashDriver.pdf
AUTOSAR_EXP_LayeredSoftwareArchitecture.pdf
RTD_FLS_IM.pdf
RTD_FLS_UM.pdf
AN12201.pdf
总结
NXP的MCAL外部FLASH使用QSPI来驱动,本文没讲述外部FLASH驱动怎么使用,另外本文文字描述多点,更像是本人的使用笔记,仅供参考,如有不对地方欢迎指教。