Flash
Flash作为一种非易失性存储器,与RAM不同。RAM可以直接读写,直接执行代码或存储数据。而flash的写入操作只能在空的或已擦除的单元内进行。因此,在执行flash写访问之前常常需要先执行擦除访问。
备注
Flash具有最高100,000次的编程次数限制,如果频繁对某个区块擦写,将容易产生坏块。当用户需要利用flash存储数据时,flash驱动程序的接口并不首推给APP使用,更建议使用 FTL 接口。
Flash布局
RTL87x2G集成flexible memory controller( FMC ),支持外挂SPI Flash。FMC支持最大的地址映射空间为64M bytes,对应地址空间0x04000000 ~ 0x07FFFFFF。Flash详细布局参考: flash布局 。
FTL
FTL(Flash Translation Layer)是基于flash driver实现的软件抽象层,用于用户读写flash数据。FTL物理空间的位置可以查看 flash布局 。FTL的目的是简化对flash中数据的修改过程。
如果不使用FTL,在用户需要修改flash上的数据时,需要先擦除flash。频繁修改flash时,会在擦除上浪费大量时间。而FTL用逻辑地址代替物理地址来访问flash,在用户进行数据修改时,原先的数据不会被直接修改,而是将新的数据会写入新的物理地址,并且逻辑地址会指向该物理地址。在这种方式下,可以避免频繁擦除flash,节约修改数据所需时间。
当FTL检测到没有足够的空间写入新记录时,会触发垃圾回收(GC,Garbage Collection)机制,对过期或不再被使用的记录进行擦除和回收。FTL由于循环使用flash空间、均匀分布擦写操作,可以避免某些块过早失效,达到磨损均衡的效果。
目前,FTL支持v1和v2版本。v1版本包含上述功能的完整实现。在v1的基础上,v2版本在最大存储空间和flash利用率方面进行了改进,并且支持创建“module”划分逻辑空间。
备注
FTL适用于需要频繁改写的数据,一方面API接口更为简单,另一方面有助于延长flash使用寿命(磨损均衡)。
如果数据量较大、只读(或偶尔改写)、不需要支持OTA升级,推荐使用flash API将数据存储在“APP Defined Section”区域。
如果数据量较大、只读(或偶尔改写)、需要支持OTA升级,推荐将数据存储在OTA bank内的App Data1、App Data2、App Data3、App Data4、App Data5、App Data6和secure app data区域。
FTL v1
SDK v1.2.0及以前版本默认支持FTL v1,在后续SDK版本如需使用FTL v1,请参考以下步骤:
打开SDK根目录。
拷贝
ftl_v1\ftl.h
到bsp\sdk_lib\inc\
,替换目标路径下已有ftl.h
文件。如果使用 GCC 编译,将
ftl_v1\librtl87x2g_sdk.a
拷贝到bsp\sdk_lib\lib\rtl87x2g\gcc\
,替换目标路径下已有librtl87x2g_sdk.a
文件。如果使用 MDK 编译,将
ftl_v1\rtl87x2g_sdk.lib
拷贝到bsp\sdk_lib\lib\rtl87x2g\mdk\
,替换目标路径下已有rtl87x2g_sdk.lib
文件。重新编译APP工程。
FTL空间的功能划分
FTL空间按照其功能划分为以下两块存储空间,以默认设置的FTL物理空间大小为16K为例。
BT存储空间
逻辑地址范围 [0x0000, 0x0D00),但是这部分空间的大小可通过otp_config.h参数调整。(当前SDK暂时未支持)
用于存储BT信息,例如设备地址,link key等。
详细请参考 LE Host。
APP存储空间
逻辑地址范围 [0x0D00, 0x17F0)。
用于存储APP的信息。
可用以下API读/写APP存储的数据
uint32_t ftl_save(void *pdata, uint16_t offset, uint16_t size); uint32_t ftl_load(void *pdata, uint16_t offset, uint16_t size);
备注
ftl_save API和ftl_load API在实现上已对传入的offset参数加上APP存储空间的偏移0x0D00,因此,APP使用这两个API时offset参数从0开始规划即可。
调整FTL空间的大小
FTL的物理空间大小是可配置的,通过修改config文件的配置参数来调整。操作步骤如下:
首先利用Flash Map Generate Tool(随MP Tool一起发布)来生成
flash map.ini
和flash_map.h
文件。将
flash_map.h
文件拷贝到APP工程目录下,例如:sdk\samples\bluetooth\ble_peripheral
,这样APP编译的时候可以获取正确的加载地址。将第一步生成的
flash map.ini
加载到MP Tool中生成config文件用于烧录,如 修改 flash map 配置文件 示例,调整FTL的物理空间至32K。
备注
当调整FTL物理空间大小时,APP可使用的最大逻辑空间也会对应改变。假设实际FTL的物理空间大小设置的mK(m是4的整数倍),对应APP可使用的最大逻辑空间等于 \(((511*(m-4) - 4) - 3328)\) bytes。
为了提升FTL读取的效率,底层做了一套物理地址和逻辑地址的映射机制,这个映射表会占用一定的RAM空间。在默认设置FTL物理空间大小为16K情况下,这个映射表占用2298byte的RAM HEAP空间。假设实际FTL的物理空间大小设置的mK(m是4的整数倍),其映射表占用的RAM空间等于 \(((511 * (m-4) - 4) * 0.375)\) bytes。
用户在调整FTL空间大小时,需要依具体的应用场景来合理调整,如果调至过大将会浪费一部分RAM资源。另一方面,如果对于选用的比较小的flash并且想压缩FTL的占用的空间,必须保证FTL的物理空间不小于12K。由于映射表中每个逻辑地址对应的物理地址默认使用12位bit来表示,FTL的物理空间最大可以调整至32K。
FTL v2
SDK v1.3.0及以后版本默认使用FTL v2。FTL v2延续了v1的基本功能,并且在以下方面进行优化升级:
扩大映射表单元,每个逻辑地址对应的物理地址使用16个bit来表示(v1使用12个bit),可以支持更大的存储空间。
新增通过“module”划分逻辑空间的功能。
每个FTL module在创建时都可以独立设置block_len。合理选择“module”的block_len参数,能够减少辅助信息的空间占用而获得更高的flash利用率。
“module”之间逻辑空间相互独立,每个“module”的逻辑地址都从0地址开始,有助于上层应用组织和管理数据。
FTL空间的功能划分
FTL初始化时自动创建“系统module”,用于保证FTL基础功能和存储BT信息,用户可创建新的module以存储用户数据。
系统module
逻辑地址范围 [0x0000, 0x0C00),占用物理空间6144 bytes,但这部分空间的大小可通过otp_config.h参数调整。(当前SDK暂时未支持)
[0x0000, 0x0A20) 用于存储BT信息,例如设备地址,link key等,详细请参考 LE Host。
[0xA20, 0x0C00) 用于存储FTL module info等。
用户module
支持用户创建最多6个module,在创建新的module前,需要确保FTL物理空间充足,请参考 调整FTL空间的大小。
用于存储APP的信息。
FTL APIs
int32_t ftl_init_module(char *module_name, uint16_t malloc_size, uint8_t block_len);
int32_t ftl_save_to_module(char *module_name, void *pdata, uint16_t offset, uint16_t size);
int32_t ftl_load_from_module(char *module_name, void *pdata, uint16_t offset, uint16_t size);
备注
ftl_init_module的block_len参数决定module的最小可访问逻辑空间,需要对齐4bytes。根据实际存储数据单元的大小,尽可能选用较大的block_len能够减少RAM空间占用并获得更高的flash利用率(减少了辅助信息占用的空间)。
ftl_save_to_module/ftl_load_from_module的offset参数为存储/读取数据的逻辑地址,需要与block_len对齐。
ftl_init_module()
用于创建FTL module,ftl_save_to_module()
/ ftl_load_from_module()
API用于向module写入数据/从module读取数据。在创建新的FTL module之前,请确保FTL物理空间充足,详见 调整FTL空间的大小。
基础示例如下:
#define EXT_FTL_NAME "TEST_FTL"
#define EXT_FTL_LOGIC_SIZE (0x1000)
#define EXT_FTL_BLOCK_SIZE (64)
void ftl_ext_module_demo(void)
{
// Init an ext FTL module
ftl_init_module(EXT_FTL_NAME, EXT_FTL_LOGIC_SIZE, EXT_FTL_BLOCK_SIZE);
// Save data
uint8_t data_buf[EXT_FTL_BLOCK_SIZE];
memset(data_buf, 0x5A, EXT_FTL_BLOCK_SIZE);
uint16_t test_offset = 0x800;
uint32_t ret = ftl_save_to_module(EXT_FTL_NAME, data_buf, test_offset, EXT_FTL_BLOCK_SIZE);
if (ret != ESUCCESS)
{
//save data error
return;
}
//Load data
uint8_t read_buf[EXT_FTL_BLOCK_SIZE];
ret = ftl_load_from_module(EXT_FTL_NAME, read_buf, test_offset, EXT_FTL_BLOCK_SIZE);
if (ret != ESUCCESS)
{
//load data error
return;
}
}
调整FTL空间的大小
调整FTL物理空间方法与FTL v1一致,请参考 调整FTL空间的大小。
进行调整时,需要保证FTL module可用物理空间满足需求,否则module将创建失败,计算方法如下:
此外,为提升FTL读取效率,在创建module时会为其建立映射表,用于实现逻辑地址和物理地址的转换。FTL module的映射表占用RAM空间: (malloc_size / block_len * 2Bytes) 。
备注
由于FTL需要预留空间用于“系统module”和GC机制,并且FTL物理空间大小与4KB对齐,所以如果选用较小的flash并且需要压缩FTL占用的空间,必须保证FTL物理空间不小于16KB。(FTL预留空间可通过otp_config.h参数调整,当前SDK暂时未支持)
Flash API
如果FTL无法满足需求而必须使用flash driver,请调用下文中介绍的flash 读、写、擦 操作。
读操作
函数名 |
flash_nor_read_locked |
---|---|
函数原型 |
FLASH_NOR_RET_TYPE flash_nor_read_locked(uint32_t addr, uint8_t *data, uint32_t len) |
功能描述 |
读取flash数据 |
输入参数 |
addr:flash读取地址 data:读出数据的存储buffer,注意buffer的地址不能在flash上 len: 读取的数据长度 |
返回值 |
若读取成功,通过十进制查看返回值为24(对应FLASH_NOR_RET_SUCCESS) |
先决条件 |
无 |
写操作
函数名 |
flash_nor_write_locked |
---|---|
函数原型 |
FLASH_NOR_RET_TYPE flash_nor_write_locked(uint32_t addr, uint8_t *data, uint32_t len) |
功能描述 |
写数据到flash |
输入参数 |
addr:flash写入地址 data:写入数据的存储buffer,注意buffer的地址不能在flash上 len: 写入的数据长度 |
返回值 |
若写入成功,通过十进制查看返回值为24(对应FLASH_NOR_RET_SUCCESS) |
先决条件 |
无 |
擦除操作
函数名 |
flash_nor_erase_locked |
---|---|
函数原型 |
FLASH_NOR_RET_TYPE flash_nor_erase_locked(uint32_t addr, FLASH_NOR_ERASE_MODE mode) |
功能描述 |
擦除flash |
输入参数 |
addr:flash擦除地址 FLASH_NOR_ERASE_MODE:flash擦除类型 |
返回值 |
若擦除成功,通过十进制查看返回值为24(对应FLASH_NOR_RET_SUCCESS) |
先决条件 |
无 |
flash_nor_erase_locked()
支持三种擦除类型(FLASH_NOR_ERASE_MODE)
擦除Sector(4K-Byte) (FLASH_NOR_ERASE_SECTOR)
擦除Block(64K-byte) (FLASH_NOR_ERASE_BLOCK)
擦除chip(flash整片擦除) (FLASH_NOR_ERASE_CHIP)
备注
需要注意的是FLASH_NOR_ERASE_CHIP在一般应用中没有使用场景,因此整片擦除功能很少使用。
位模式
除了标准串行外围接口(SPI)之外,大多数flash还支持高性能的双位/四位模式I/O SPI,由以下六个引脚控制。
串行时钟 (CLK)
片选 (CS#)
串行数据IO0 (DI)
串行数据IO1 (DO)
串行数据IO2 (WP#)
串行数据IO3 (HOLD#)
三种位模式
- 单位模式
标准的SPI模式,也称为1位模式,只使用CLK,CS#,DI和DO。WP#作为写保护的输入,而HOLD#作为保持功能的输入。
- 双位模式
使用CLK,CS#,并将DI、DO分别用作IO0、IO1。WP#和HOLD#的功能和和单位模式中一致。
- 四位模式
使用CLK,CS#,并将DI、DO、WP#、HOLD#分别用作IO0、IO1、IO2、IO3。由于6根管脚全部被使用,所以四位模式下写保护和保持功能不可用。
备注
目前RTL87x2G支持SDR和DTR两种四位读取模式,用户如果需要使用相应的四位读取模式,应预先确认flash型号与特性。
位模式切换
RTL87x2G为支持更多flash型号,在启动时默认flash都是在1位模式。如果用户需要切换至高速位模式(2或4位模式),可调用SDK中提供的接口:flash_nor_try_high_speed_mode()
切换至高速位模式,并由参数“mode”配置flash尝试切换至的高位模式,如果切换失败将会切回1位模式。
SDK中提供的进行位模式切换的接口函数原型如下。
函数名 |
flash_nor_try_high_speed_mode |
---|---|
函数原型 |
FLASH_NOR_RET_TYPE flash_nor_try_high_speed_mode(FLASH_NOR_IDX_TYPE idx, FLASH_NOR_BIT_MODE mode) |
功能描述 |
切换flash到不同的位模式 |
输入参数 |
idx:默认设置为FLASH_NOR_IDX_SPIC0 mode:切换的位模式(FLASH_NOR_1_BIT_MODE, FLASH_NOR_2_BIT_MODE, FLASH_NOR_4_BIT_MODE, FLASH_NOR_DTR_4_BIT_MODE) |
返回值 |
若切换成功,通过十进制查看返回值为24(对应FLASH_NOR_RET_SUCCESS) |
先决条件 |
无 |
软件块保护
Flash支持通过硬件保护管脚(#WP)来锁定整个flash以防止写入和擦除操作,但有以下两个缺点。
如果使用管脚#WP用作保护功能,就无法使用flash的四位模式。
硬件保护只能选择保护整颗flash或者全部不保护,不能保护部分flash空间。
RTL87x2G通过一个更为灵活的机制——软件块保护(Software Block Protect,BP)来防止意外的flash的擦写操作。其原理是利用flash状态寄存器中的一些BP位来选择要保护的范围。
Flash使用状态寄存器中的BP(X)位来识别要锁定的块的数量,以及TB位来决定锁定的方向。RTL87x2G只支持从flash的低地址端开始上锁。
RTL87x2G默认是利用BP功能来保护一些重要的数据(如配置的参数)和代码段,而不是整个flash空间。
备注
需要注意的是,由于不同的flash供应商和型号有不同的规则和限制,而且flash状态寄存器默认使用NVRAM类型来保持数据,BP函数需要改变BP位来切换不同的保护级别,但是NVRAM有100,000次编程限制。虽然大多数厂商支持使用0x50命令将flash状态寄存器切换至SRAM类型,但也不是所有的flash厂商都支持,例如MXIC。那么,频繁访问状态寄存器将会损伤flash。
RTL87x2G所使用的flash不一定支持上图所示有比例的上锁方式(如0~1/2~1/4~…)。例如 GD25WD80C软件块保护 。
提供的进行BP设定的接口函数如下。
函数名 |
flash_nor_set_bp_lv_locked |
---|---|
函数原型 |
FLASH_NOR_RET_TYPE flash_nor_set_bp_lv_locked(FLASH_NOR_IDX_TYPE idx, uint8_t bp_lv) |
功能描述 |
设定flash bp等级 |
输入参数 |
idx:默认设定为FLASH_NOR_IDX_SPIC0 bp_lv:设定的bp等级 |
返回值 |
若设定成功,通过十进制查看返回值为24(对应FLASH_NOR_RET_SUCCESS) |
先决条件 |
无 |
函数名 |
flash_nor_get_bp_lv_locked |
---|---|
函数原型 |
FLASH_NOR_RET_TYPE flash_nor_get_bp_lv_locked(FLASH_NOR_IDX_TYPE idx, uint8_t *bp_lv) |
功能描述 |
获取flash当前的bp等级 |
输入参数 |
idx:默认设置为FLASH_NOR_IDX_SPIC0 bp_lv:读出数据的存储buffer |
返回值 |
若获取成功,通过十进制查看返回值为24(对应FLASH_NOR_RET_SUCCESS) |
先决条件 |
无 |
为了用户使用方便,RTL87x2G提供了一种自动上锁机制,由宏 DEFAULT_FLASH_BP_LEVEL_ENABLE 控制。当在头文件 otp_config.h
中定义 #define DEFAULT_FLASH_BP_LEVEL_ENABLE 1 后,
RTL87x2G将会依据所配置的flash layout和所选flash的型号锁到一个最大可以上锁的范围,即从flash起始地址到OTA bank1结束地址(flash 布局参考:flash布局 )。大多数flash都支持按级别保护flash,如包含前64KB、128KB、256KB、512KB等不同大小的区域。
当划分flash布局时,需尽可能地将OTA bank1结束地址偏移对齐到flash支持的某个保护级别范围内。这样做的目的是,将重要数据区和代码区完全上锁,且不影响紧随其后的存放需要改写的数据区,
如“FTL”、“OTA Tmp”区。一旦flash布局确定之后,RTL87x2G将自动解析flash布局配置参数并查询所选Flash信息来设置BP保护级别。当使用的flash 型号只支持如 GD25WD80C软件块保护 所示的块保护方式,RTL87x2G会选择不上锁。
综上所述,RTL87x2G 共支持两种上锁方式:
使用接口
flash_nor_set_bp_lv_locked()
进行上锁,这种方式更加直接,不受限制。 不过bp_lv 需要用户根据对应flash的datasheet 来进行推测。 以W25Q16DV为例, 当需要设定上锁区域为开始的256KB时, 对应到状态寄存器中BP2为0, BP1为1, BP0为1,即flash_nor_set_bp_lv_locked()
中参数bp_lv 为3。打开宏 DEFAULT_FLASH_BP_LEVEL_ENABLE 使用自动上锁机制。使用这种方式用户无需计算bp_lv, 但是当使用的flash 型号只支持如 GD25WD80C软件块保护 所示的块保护方式,会上锁失败。
备注
由于当前版本中 OTA demo中还未支持在flash 上锁的情况下进行升级, 所以目前暂不推荐使用此功能。
省电
Flash的功耗模式主要分以下三种场景。
工作模式:耗电一般在10 mA数量级
待机模式:耗电一般在几十uA左右
Power Down模式:耗电更低,甚至低于1 uA
Flash在没有访问时会自动进入待机模式,当需要再访问时会自动进入工作状态。而要进入Power Down模式,需要使用特定的命令:大多数flash使用命令0xB9控制其进入Power Down模式,使用命令0xAB退出Power Down模式,而MXIC是通过切换#CS引脚退出Power Down模式。
需要注意的是,flash在进入Power Down模式之后,除了退出Power Down命令(0xAB)之外,接收其他的命令都是危险的。因为flash在进入Power Down模式之后只接受唤醒命令退出Power Down模式,其他命令都将被忽略,但flash控制器可能会进入无限循环等待flash的响应。
为了防止滥用Flash Power Down模式带来的风险,系统的DLPS机制中已经加入Flash Power Down模式的控制:进入DLPS时自动下指令使flash进入Power Down模式,在退出DLPS时唤醒flash。
XIP
XIP (eXecute In Place)指的是允许代码直接在flash中执行,而不必将其复制到RAM中。这节省了RAM空间,如果RAM剩余空间足够,APP代码可以直接在RAM上执行,有助于提高性能和降低功耗。如果所剩的RAM空间不够,需要将部分或者全部APP代码放在flash上执行。RTL87x2G上执行XIP的相关配置总结如下。
- 宏FEATURE_RAM_CODE(mem_config.h中配置)a. 配置为1时,默认不加任何section修饰的代码都将在RAM上执行。b. 配置为0时,默认不加任何section修饰的代码都将在flash上执行。
- Section修饰(参考app_section.h)a. APP_FLASH_TEXT_SECTION:指定代码在flash上执行。b. APP_RAM_TEXT_SECTION:指定代码在RAM上执行。
备注
如果RAM空间不足,要优先将时间敏感的代码放在RAM上执行以保证效率。
- 提高XIP执行代码的效率的方式将flash切换至双位或者四位模式:调用
flash_nor_try_high_speed_mode()
。
RTL87x2G通过SPIC的支持可以使用自动模式去读取flash并且直接在SPI Flash上执行代码。但是用户有可能通过调用提供的接口在用户模式下对flash执行读写擦操作,而用户模式访问flash的操作不是原子性的,如果被另一个自动模式访问操作打断,就会造成flash访问异常。为保证用户模式访问flash操作的原子性,XIP时需要遵循以下的使用限制和注意事项。
备注
RTL87x2G中提供的用户模式访问flash的接口都带“_locked”后缀,默认加临界区保护,会关闭0和1优先级以下的中断,也就是中断优先级数值设置为2到7的中断(中断优先级数值越大,中断优先级越低)。如果flash操作的时间过久,比如一次写大量的数据,建议将一次写的动作拆分成多次少量数据写的动作,以避免关闭中断时间过长而丢中断。
如果有时间非常关键的中断(中断优先级0和1的中断)需要处理,需要保证中断服务例程本身不能XIP,中断服务例程中也禁止访问flash。