作者:Stephen Du
免责声明: 本文为个人学习笔记及总结,仅代表个人观点,尽可能保证内容准确性。
所有文字均是自己码出来的,所有图片均为自己勾画(除部分来源于原始标准)。
复制/转发请注明来源/作者。
欢迎添加微信交流学习。
主要提供对Flash的读,写,擦相关操作服务;如果底层硬件支持的话,也提供一些配置接口来设置擦/写保护功能。
如上图所示,Fls属于MCAL层,主要给FEE(Flash EEPROM Emulation)模块使用。
FLS驱动不但负责片上Flash,同时也负责片外Flash。但是这两种情况在AUTOSAR架构里面负责模块所对应的分层是不一样的,这里可能容易误解,前面不是刚说FLS模块负责两种情况么。是的,FLS模块要同时支持这两种场景。但是针对片外FLASH,通常是通过处理器数据/地址总线连接的(内存映射)。所以FLS驱动需要通过这些总线驱动来访问片外FLASH,而这些驱动是属于ECU抽象层的。当然也可能是通信接口,比如常见的QSPI,也可能是I/O驱动等,总之和片内的是不一样的。片内FLASH的驱动是直接可以访问控制器硬件的,属于微控制器抽象层(MCAL)。但在功能需求以及功能范围上,不管片内还是片外FLASH,两者都是一样的,拥有相同的API。
这里有必要说明一下一些关键的术语定义,因为有些术语与硬件相关,而不同厂商可能称呼不一样,而AUTOSAR以这里定义的为准。而对于那些常见的缩写、术语这里不做过多介绍。
术语 | 定义描述 |
---|---|
Flash Sector/闪存区 | 一个最小可擦除单元定义为一个区,与闪存硬件相关。 |
Flash Page/闪存页 | 一个最小可编程/写单元定义为一个页,与闪存硬件相关。 |
Flash Access Code(AC) /闪存访问代码 | 被片内FLASH驱动的主函数(用于处理任务/Job的服务)调用的一段特殊代码,以用于擦/写FLASH硬件。简称AC。 |
FLASH驱动只能擦除完整的区,编写完整的页。不提供任何类型的重写机制,因为其内部没有任何缓存,所有API都是通过指针的方式直接使用应用层的缓存数据。
FLASH驱动也不提供数据完整性检查机制(比如:Checksum/校验和,冗余存储)。读/写过程中的数据完整性通常由上层模块负责实现。
在ECU应用模式下,Fls驱动的设设计意图主要是给FEE(Flash EEPROM Emulation)模块使用以写入用户数据,而不是为了写程序代码到Flash里面。程序代码的写入应该是在 boot 模式下完成的,boot模式不在AUTOSAR的范围之内。如下图所示:
FLS模块会合并所有可用空间以组成一个线性空间。由FlsBaseAddress
和FlsTotalSize
两个参数负责实现。什么意思呢?假如你有两片物理地址不连续的空间需要FLS驱动管理,他们的大小分别为4K和8K。那么应用层看来这两片空间是连续的,一共12K,从基础地址开始。FLS驱动内部会自动计算/映射到实际的物理地址里面。应用层操作的那个地址参数是一个虚拟地址/virtual address。
FLS驱动内部会处理缓存对齐问题,对外面传进来的Buff没有地址对齐要求,参数都是uint8 *
类型的。
一个ECU里面允许存在多个FLASH驱动实例,每个实例有唯一的编号(FlsDriverIndex),编号以0开始。
前面提到闪存访问代码(AC),这些代码比较特殊,用户可根据不同使用场景选择是否拷贝到RAM上运行,这些代码按规定是放到独立的文件(Fls_ac.c)里面的。
至于为什么要拷贝到RAM上运行,那是因为针对片内FLASH,如果代码放到FLASH上面,假如你想擦除/编程某片区域,并且这片区域刚好和代码所在区域属于同一个Partition,这时候就会出错。因为在同一个partition是不能同时编程和执行代码的(受硬件限制)。如果不在同一个partition,那就无所谓了,可用直接在FLASH上面运行。
加载到RAM上面这种工作是由擦除/写函数负责实现的,用户只需要配置是否需要加载到RAM上,如果使能了,则需要告诉加载的RAM地址(通过配置实现),这个区域需要在链接文件里面配置好。
虽然加载是由擦/写函数实现的,但是因为这两个函数都是异步的,所以真正负责调用执行AC的还是在主函数里面。
当擦/写任务完成或者被取消,如果AC被加载到RAM上,则主函数会负责将AC卸载掉(比如重写那片RAM空间)。
AC会临时关掉中断直到擦/写完成。所以这段代码越少越好,执行时间越短越好,通常只包含最核心的一些命令。
Fls Sector Size配置强烈建议与实际物理大小匹配,否则你使用的地址可能与实际物理地址不是一一对应的。比如实际物理地址是4K(4096Bytes),但是你实际配置的Sector为2K(2048Bytes),并且你配置了两个这样的Sector。然后你调用Fls_Write函数写入时,传输起始地址参数为2048,可能你的原始用意就是写到物理地址以2048开始的地方,但是实际写入的物理地址会写到4096这个物理地址里面去。这就是因为你配置的2K小于实际的4K导致的。这种配置Fls驱动会将2K~4K这块空间保护起来不操作,也就是Fls驱动管理的地址空间实际上是不连续的。他会将你传入的参数拿来计算,这里的计算会以你配置的大小来计算。这里的2048这个地址计算后应该属于第二个Sector的起始地址。但是物理单元的第二个起始地址是4096,这就是为何会写到4096里面去,而不是我们想的2048这个地址(这个地址根本写不进去数据,前面提到的被保护了)。
Fls_Write函数的起始地址及长度参数要求以page大小对齐。否则会出错。建议将DET功能打开。假如page大小为8字节。但是传入的参数为写入100字节用户数据。这种情况只有前面96字节(8的整数倍)可以正常写入,而最后4字节(100-96)无法写入。这是因为多余的4字节不满足对齐要求,如果贸然写入势必会将另外的4字节(8-4)给误覆盖了。因为每次写入都是page大小或page大小的整数倍写入的。
Fls_Erase函数可以擦除一个或多个Sectors。它的两个参数分别为起始地址和长度,这里没有强制要求必须对齐之类的。但是需要注意的是他是根据你的起始地址以及长度信息来计算这个地址范围属于哪(几)个Sectors,然后会根据计算结果自动一次性擦除所有归属的Sectors(这就是前面提到擦除多个Sectors的原因)。这里就需要注意,还是以上面提到的参数为例,假如你现在调用这个函数,参数分别为1,4096。这样调用会实际擦除两个Sectors。因为起始地址1归属于Sector 0,结束地址4096(4096+1-1),这个地址已经是下一个Sector的地址空间了。所有需要特别注意,防止误擦。
但是如果你打开了开发错误检查开关,起始地址和长度参数有对齐要求,需要和sector边界对齐。比如上述的例子就会报错。
所有API的地址参数都是以逻辑地址0开始的,FLS驱动会自己加上基础偏移地址(FlsBaseAddress)。
Fls_Cancel函数只针对后面四种任务:读、写、擦、比较。它可以取消前述4种正在运行的任务。这个函数是同步类型的。什么意思呢?也就是说这个函数返回退出后就可以进行新的任务请求了,因为是同步函数,返回后即代表取消这个动作执行完了。这个函数会将内部的一些状态复位以准备进行新的任务请求。比如读/写函数开始时会设置状态为BUSY/PENDING。执行Cancel函数后会设置状态为IDLE。取消后,用户可选择是否通知应用层(通过配置实现)。被取消的任务的缓存行为是未定义的。
前面说到取消正在运行的任务。这里其实不太严格。由于很多函数(比如:擦、写)都是异步类型的。并且像擦这种任务是比较费时的,假如我已经发起擦的请求给硬件,但是还没完成,这个过程中又发起了Cancel请求,这种情况其实是取消不了的的。所有这得具体看代码怎么实现。
这也是一个同步函数。它用于获取最近一次任务的结果状态。读、写、擦、比较这四种任务是共享一个任务结果的。什么意思呢?假如你连续发起多个任务请求,比如:写和读,然后调用获取任务结果的函数,它只能获取最后任务请求读的结果,而不能获取之前的写任务的结果。其实很好理解,你会发现这个函数都没有参数的。
前文提到,那些异步请求任务(读、写、擦、比较),真正执行是由主函数来实现的,而那些接受用户请求的函数只是将任务从用户那里获取然后放那里等待主函数处理。主函数需要被程序周期调用。如果有任务正等待被处理,则去处理相应的任务。如果没有任务需要处理,则主函数啥都不干,这时候也可以不用周期调用主函数。
对于读和比较任务,如果底层硬件对地址有对齐要求,则主函数需要在内部处理掉。因为读、比较函数对用户是没有对齐要求的。
由于Fls这种操作通常比较费时,所以并不建议程序频繁操作,而是在系统上下电的时候去读写。这就会导致问题,如果异常掉电或者做上下电实验的时候会丢数据。这种问题属于系统性问题,要解决这种问题,通常需要硬件,软件协同处理才能解决。
首先硬件层面需要有低压检测电路,有些人会说,不是很多芯片都有低压监测功能么,那还需要硬件加低压检测电路干什么,不是增加成本么,真的有必要么?是的,即使芯片有低压监控功能,也是需要硬件加低压检测电路的,因为芯片那个电压监控太晚了,电压太低了。所以需要硬件单独加电路,比如乘用车12V系统(一般电池电压会在14V左右)。通常硬件低压监控电路在10V左右就要启用(有些做得好的,甚至有两级硬件低压监控电路),当系统电压低于这个门限值后产生中断。硬件除了需要低压监控电路外,通常还需要一个大的电容。这个电容的作用就是个备用电源,以备不时之需,在紧急情况下为系统提供能量。硬件有了这些后,是不是就可以了呢?当然是不行的,这么重要的场合怎么能缺了软件呢?电容的电是有限的,根本支撑不了多久,你不可能背个充电宝吧。所以还需要软件配合才行,要不然也形同虚设。软件的主要工作就是在检测到硬件的低压中断信号后立马将耗电的外设全部关掉,通常情况下只需要保留Fls相关的电路,其余的全部关掉,这样系统的功耗就下去了,这样电容的电就可以多撑一会儿。然后写数据进行保存。
说了这么多,其实这里就两个字:快,低。快是指检测到低压要快,要及时。低指功耗要低,电流要低,这样才能持久。所以这就是为什么需要硬件协助,因为如果靠软件来监控,这个时间是来不及的,等软件监控到可能早就没电了。
这里强调一下,硬件监控电路通常是监控12V供电,也就是你整个板子的最前端。电容的大小通常需要结合你的低功耗的功耗值进行计算。
如果上述流程处理不好,轻则会导致关键数据丢失。这个不难理解,如果你的系统处理得好,影响还不算很大,某些情况下甚至可以恢复。但是,但是严重得情况可能会导致你整个系统崩溃,再也起不来了。比如现在很多片子都有ECC功能。如果你在掉电过程中写了一半数据,这就会导致ECC错误。当下次上电系统起来后检测到ECC错误,如果你正好又没有处理ECC相关的那些错误中断,这就会导致系统复位。
实际上,存储是一个非常复杂,非常重要的一个组件。在Fls的上层模块中也考虑很多容错机制,如感兴趣,可阅读相关文章。