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,请参考以下步骤:

  1. 打开SDK根目录。

  2. 拷贝 ftl_v1\ftl.hbsp\sdk_lib\inc\,替换目标路径下已有 ftl.h 文件。

  3. 如果使用 GCC 编译,将 ftl_v1\librtl87x2g_sdk.a 拷贝到 bsp\sdk_lib\lib\rtl87x2g\gcc\,替换目标路径下已有 librtl87x2g_sdk.a 文件。

  4. 如果使用 MDK 编译,将 ftl_v1\rtl87x2g_sdk.lib 拷贝到 bsp\sdk_lib\lib\rtl87x2g\mdk\,替换目标路径下已有 rtl87x2g_sdk.lib 文件。

  5. 重新编译APP工程。

FTL空间的功能划分

FTL空间按照其功能划分为以下两块存储空间,以默认设置的FTL物理空间大小为16K为例。

  1. BT存储空间

    1. 逻辑地址范围 [0x0000, 0x0D00),但是这部分空间的大小可通过otp_config.h参数调整。(当前SDK暂时未支持)

    2. 用于存储BT信息,例如设备地址,link key等。

    3. 详细请参考 LE Host

  2. APP存储空间

    1. 逻辑地址范围 [0x0D00, 0x17F0)。

    2. 用于存储APP的信息。

    3. 可用以下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文件的配置参数来调整。操作步骤如下:

  1. 首先利用Flash Map Generate Tool(随MP Tool一起发布)来生成 flash map.iniflash_map.h 文件。

  2. flash_map.h 文件拷贝到APP工程目录下,例如: sdk\samples\bluetooth\ble_peripheral,这样APP编译的时候可以获取正确的加载地址。

  3. 将第一步生成的 flash map.ini 加载到MP Tool中生成config文件用于烧录,如 修改 flash map 配置文件 示例,调整FTL的物理空间至32K。

../../../../_images/Modify_flash_map.png

修改 flash map 配置文件

备注

  1. 当调整FTL物理空间大小时,APP可使用的最大逻辑空间也会对应改变。假设实际FTL的物理空间大小设置的mK(m是4的整数倍),对应APP可使用的最大逻辑空间等于 \(((511*(m-4) - 4) - 3328)\) bytes。

  2. 为了提升FTL读取的效率,底层做了一套物理地址和逻辑地址的映射机制,这个映射表会占用一定的RAM空间。在默认设置FTL物理空间大小为16K情况下,这个映射表占用2298byte的RAM HEAP空间。假设实际FTL的物理空间大小设置的mK(m是4的整数倍),其映射表占用的RAM空间等于 \(((511 * (m-4) - 4) * 0.375)\) bytes。

  3. 用户在调整FTL空间大小时,需要依具体的应用场景来合理调整,如果调至过大将会浪费一部分RAM资源。另一方面,如果对于选用的比较小的flash并且想压缩FTL的占用的空间,必须保证FTL的物理空间不小于12K。由于映射表中每个逻辑地址对应的物理地址默认使用12位bit来表示,FTL的物理空间最大可以调整至32K。

FTL v2

SDK v1.3.0及以后版本默认使用FTL v2。FTL v2延续了v1的基本功能,并且在以下方面进行优化升级:

  1. 扩大映射表单元,每个逻辑地址对应的物理地址使用16个bit来表示(v1使用12个bit),可以支持更大的存储空间。

  2. 新增通过“module”划分逻辑空间的功能。

    1. 每个FTL module在创建时都可以独立设置block_len。合理选择“module”的block_len参数,能够减少辅助信息的空间占用而获得更高的flash利用率。

    2. “module”之间逻辑空间相互独立,每个“module”的逻辑地址都从0地址开始,有助于上层应用组织和管理数据。

FTL空间的功能划分

FTL初始化时自动创建“系统module”,用于保证FTL基础功能和存储BT信息,用户可创建新的module以存储用户数据。

  1. 系统module

    1. 逻辑地址范围 [0x0000, 0x0C00),占用物理空间6144 bytes,但这部分空间的大小可通过otp_config.h参数调整。(当前SDK暂时未支持)

    2. [0x0000, 0x0A20) 用于存储BT信息,例如设备地址,link key等,详细请参考 LE Host

    3. [0xA20, 0x0C00) 用于存储FTL module info等。

  2. 用户module

    1. 支持用户创建最多6个module,在创建新的module前,需要确保FTL物理空间充足,请参考 调整FTL空间的大小

    2. 用于存储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将创建失败,计算方法如下:

../../../../_images/FTLv2_flash_calculation_cn.png

FTLv2物理空间计算

此外,为提升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)

  1. 擦除Sector(4K-Byte) (FLASH_NOR_ERASE_SECTOR)

  2. 擦除Block(64K-byte) (FLASH_NOR_ERASE_BLOCK)

  3. 擦除chip(flash整片擦除) (FLASH_NOR_ERASE_CHIP)

备注

需要注意的是FLASH_NOR_ERASE_CHIP在一般应用中没有使用场景,因此整片擦除功能很少使用。

位模式

除了标准串行外围接口(SPI)之外,大多数flash还支持高性能的双位/四位模式I/O SPI,由以下六个引脚控制。

  1. 串行时钟 (CLK)

  2. 片选 (CS#)

  3. 串行数据IO0 (DI)

  4. 串行数据IO1 (DO)

  5. 串行数据IO2 (WP#)

  6. 串行数据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以防止写入和擦除操作,但有以下两个缺点。

  1. 如果使用管脚#WP用作保护功能,就无法使用flash的四位模式。

  2. 硬件保护只能选择保护整颗flash或者全部不保护,不能保护部分flash空间。

RTL87x2G通过一个更为灵活的机制——软件块保护(Software Block Protect,BP)来防止意外的flash的擦写操作。其原理是利用flash状态寄存器中的一些BP位来选择要保护的范围。

Flash使用状态寄存器中的BP(X)位来识别要锁定的块的数量,以及TB位来决定锁定的方向。RTL87x2G只支持从flash的低地址端开始上锁。

../../../../_images/Flash_Block_Protect.png

软件块保护

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软件块保护

../../../../_images/Flash_Block_Protect_For_GD25WD80C.png

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的相关配置总结如下。

  1. 宏FEATURE_RAM_CODE(mem_config.h中配置)
    a. 配置为1时,默认不加任何section修饰的代码都将在RAM上执行。
    b. 配置为0时,默认不加任何section修饰的代码都将在flash上执行。
  2. Section修饰(参考app_section.h)
    a. APP_FLASH_TEXT_SECTION:指定代码在flash上执行。
    b. APP_RAM_TEXT_SECTION:指定代码在RAM上执行。

    备注

    如果RAM空间不足,要优先将时间敏感的代码放在RAM上执行以保证效率。

  3. 提高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。