Flash
Flash is not the same as RAM since it is non-volatile storage. RAM enables direct access to read and write data. However, if flash is not empty, an erase operation is typically required before performing a flash write operation.
Note
Generally, flash has a maximum of 100,000 programming cycle limit, and if a block is frequently erased and written, it is prone to becoming a bad block. Flash driver is not preferred to be used by application layer, while FTL is suggested.
Flash Layout
The RTL87x2G chip integrates a flexible memory controller ( FMC ) that supports external SPI Flash. The FMC supports a maximum address mapping space of 64M bytes, corresponding to the address space 0x04000000 ~ 0x07FFFFFF. For a detailed layout of flash, refer to flash layout .
FTL
FTL (Flash Translation Layer) is a software abstraction layer built on the flash Driver, used for user read and write operations on flash data. The physical space location of FTL can be viewed at flash layout . The purpose of FTL is to simplify the process of modifying data in the flash.
Without FTL, modifying data on the flash requires the user to erase the flash first. When data is frequently modified, a significant amount of time is wasted on erasing. FTL, however, uses logical addresses instead of physical addresses to access the flash. When modifying data, the original data is not directly altered; instead, the new data is written to a new physical address, and the logical address points to this new physical address. This method avoids frequent flash erasures, saving time required for data modification.
When FTL detects that there is not enough space to write new records, it triggers the Garbage Collection (GC) mechanism to erase and reclaim outdated or unused records. By cyclically utilizing flash space and evenly distributing erase/write operations, FTL prevents certain blocks from failing prematurely, achieving wear leveling.
Currently, FTL supports both v1 and v2 versions. The v1 version includes a full implementation of the aforementioned features. Building upon v1, the v2 version improves maximum storage capacity and flash utilization, and supports creating logical space through “module” partitioning.
Note
FTL is suitable for data that needs frequent rewriting. On one hand, the API interface is simpler, and on the other hand, it helps to extend the lifespan of the flash (wear leveling).
If the data volume is large, read-only (or occasionally rewritten), and does not require OTA upgrade support, it is recommended to use the flash API to store data in the “APP Defined Section” area.
If the data volume is large, read-only (or occasionally rewritten), and requires OTA upgrade support, it is recommended to store data in the App Data1, App Data2, App Data3, App Data4, App Data5, App Data6, and secure app data areas within the OTA bank.
FTL v1
SDK V1.2.0 and previous versions support FTL v1 by default. If FTL v1 is needed in subsequent SDK versions, please follow these steps:
Open the SDK root directory.
Copy
ftl_v1\ftl.h
tobsp\sdk_lib\inc\
, replacing the existingftl.h
in the target path.If using GCC for compilation, copy
ftl_v1\librtl87x2g_sdk.a
tobsp\sdk_lib\lib\rtl87x2g\gcc\
, replacing the existinglibrtl87x2g_sdk.a
in the target path.If using MDK for compilation, copy
ftl_v1\rtl87x2g_sdk.lib
tobsp\sdk_lib\lib\rtl87x2g\mdk\
, replacing the existingrtl87x2g_sdk.lib
in the target path.Recompile the APP project.
Functional Division of FTL Space
Currently, the FTL space is divided into the following two storage spaces according to its functions. Take the default FTL physical space size of 16K as an example.
BT storage space
Logic address range: [0x0000, 0x0D00). But this space size can be changed by otp parameter. (Currently not supported by SDK)
This region is used to store BT information such as device address, link key, etc.
Refer to LE Host for more details.
APP storage space
Logic address range: [0x0D00, 0x17F0).
APP can use this region to store user-defined information.
The following APIs can be used to read/write APP storage data.
uint32_t ftl_save(void *pdata, uint16_t offset, uint16_t size); uint32_t ftl_load(void *pdata, uint16_t offset, uint16_t size);
Note
When calling ftl_save or ftl_load, the bottom layer has encapsulated an offset, which is 0x0D00 by default. Therefore, the offset parameter can be planned from 0.
Adjust the Size of the FTL Space
The physical space size of FTL is configurable, adjusted by modifying the configuration parameters in the config file. The steps are as follows:
First, use Flash Map Generate Tool (released with MP Tool) to generate
flash map.ini
andflash_map.h
file.Copy the
flash_map.h
file to the APP project directory, for example:sdk\samples\bluetooth\ble_peripheral
, so that the APP can obtain the correct loading address when compiling.Load
flash map.ini
into MP Tool to generate config file for download. As shown in Modify the flash map configuration file, the physical space of FTL will be adjusted to 32K.

Modify the flash map configuration file
Note
When adjusting the size of the FTL physical space, the logical space available for the APP will also change accordingly. Assuming that the physical space size of the actual FTL is set to mK (m is an integer multiple of 4), the logical space available for the corresponding APP is equal to \(((511*(m-4)-4)-3328)\) bytes.
In addition, in order to improve the efficiency of FTL reading, the bottom layer has made a mapping mechanism of physical address and logical address. This mapping table will occupy a certain amount of RAM space. When the FTL physical space size is set to 16K by default, this mapping table occupies 2298 bytes of buffer RAM heap space. Suppose the physical space size of the actual FTL is set to mK (m is an integer multiple of 4), and the RAM space occupied by its mapping table is equal to \(((511*(m-4)-4)*0.375)\) bytes. Therefore, users need to adjust the size of FTL space reasonably according to specific application scenarios. If it is too large, it will waste some RAM resources.
On the other hand, if choosing a smaller flash and wanting to compress the space occupied by FTL, ensure that the physical space of FTL is not smaller than 12K. At the same time, since the physical address corresponding to each logical address in the mapping table is represented by 12 bits by default, the maximum physical space of FTL can be adjusted to 32K.
FTL v2
SDK v1.3.0 and later versions default to using FTL v2. FTL v2 extends the basic functionalities of v1 and includes optimizations and upgrades in the following areas:
Expanded mapping table units: Each logical address is represented by 16 bits of a physical address (v1 used 12 bits), supporting larger storage spaces.
Added functionality to divide logical space through “modules”.
Each FTL module can independently set the block_len when created. Choosing the block_len parameter for a “module” reasonably can reduce the space occupied by auxiliary information and achieve higher flash utilization.
Logical spaces between “modules” are independent of each other, with each “module” starting its logical address from 0, which aids upper-level applications in organizing and managing data.
Functional Division of FTL Space
When FTL is initialized, the “system module” is automatically created to ensure the basic functions of FTL and store BT information. Users can create new modules to store user data.
System module
Logical address range: [0x0000, 0x0C00). It takes up 6144 bytes of physical space. However, this space size can be changed by otp parameter. (Currently not supported by SDK)
[0x0000, 0x0A20) is used to store BT information such as device address, link key, etc. Refer to LE Host for more details.
[0xA20, 0x0C00) is used to store FTL module information.
User module
Users are allowed to create up to 6 modules. Before creating a new module, it is necessary to ensure that the physical space of FTL is sufficient. Refer to Adjust the Size of the FTL Space for the calculation formula.
APP can use this region to store user-defined information.
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);
Note
The block_len (the third parameter of ftl_init_module) determines the minimum access logical space of the module, which needs to be aligned with 4 bytes. Based on the actual size of the storage data units, choosing a larger block_len whenever possible can reduce RAM usage and achieve higher flash memory efficiency (as it minimizes the space occupied by auxiliary information).
The offset parameter of ftl_save_to_module/ftl_load_from_module is the logical address for saving/loading data and needs to be aligned with block_len.
ftl_init_module()
is used to create an FTL module, and ftl_save_to_module()
/ ftl_load_from_module()
are used to write data to or read data from the module. Before creating a new FTL module, please ensure there is sufficient FTL physical space. For more details, see Adjust the Size of the FTL Space.
The basic example is as follows:
#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;
}
}
Adjust the Size of the FTL Space
The method of adjusting the FTL physical space is similar to FTL v1. For details, please refer to Adjust the size of the FTL space.
When adjusting the physical space of FTL, it is necessary to ensure that the available physical space of the FTL module meets the requirements; otherwise, the module will fail to be created. The calculation method is shown in the figure below:
The calculation formula is as follows:

FTLv2 Physical Space Calculation
Additionally, to enhance FTL read efficiency, a mapping table is created for each module to facilitate the conversion between logical and physical addresses. The mapping table for the FTL module occupies RAM space: (malloc_size / block_len * 2 Bytes).
Note
Since FTL requires reserved space for the “system module” and GC mechanism, and the FTL physical space is aligned with 4KB, if a smaller flash is chosen and the FTL space needs to be compressed, it must be ensured that the FTL physical space is no less than 16KB. (The FTL reserved space can be adjusted via the otp_config.h parameter, currently unsupported by the SDK).
Flash API
If the FTL size does not meet the requirements, please refer to the flash interfaces described in the section below. There are three basic operations for flash: Read, Write, and Erase . User should invoke the interfaces described in this section to perform these operations.
Read
Function Name |
flash_nor_read_locked |
---|---|
Function Prototype |
FLASH_NOR_RET_TYPE flash_nor_read_locked(uint32_t addr, uint8_t *data, uint32_t len) |
Function Description |
read flash data |
Input Parameter |
addr: read address of flash data: storage buffer for reading data, note that the storage buffer cannot be located on flash len: read data length |
Return Value |
If the reading is successful, the returned value is 24 in decimal notation (Corresponding to FLASH_NOR_RET_SUCCESS) |
Prerequisite |
None |
Write
Function Name |
flash_nor_write_locked |
---|---|
Function Prototype |
FLASH_NOR_RET_TYPE flash_nor_write_locked(uint32_t addr, uint8_t *data, uint32_t len) |
Function Description |
write data to flash |
Input Parameter |
addr: write address of flash data: storage buffer for writing data, note that the storage buffer cannot be located on flash len: write data length |
Return Value |
If the writing is successful, the returned value is 24 in decimal notation (Corresponding to FLASH_NOR_RET_SUCCESS) |
Prerequisite |
None |
Erase
Function Name |
flash_nor_erase_locked |
---|---|
Function Prototype |
FLASH_NOR_RET_TYPE flash_nor_erase_locked(uint32_t addr, FLASH_NOR_ERASE_MODE mode) |
Function Description |
erase flash |
Input Parameter |
addr: erase address of flash FLASH_NOR_ERASE_MODE: erase type of flash |
Return Value |
If the erasing is successful, the returned value is 24 in decimal notation (corresponding to FLASH_NOR_RET_SUCCESS) |
Prerequisite |
None |
flash_nor_erase_locked()
supports 3 erase types (FLASH_NOR_ERASE_MODE)
FLASH_NOR_ERASE_SECTOR (4K-Byte)
FLASH_NOR_ERASE_BLOCK (64K-Byte)
FLASH_NOR_ERASE_CHIP
Note
It should be noted that there is no usage scenario of chip erase in a normal application, so chip erase is rarely used.
Bit Modes
Except for the standard Serial Peripheral Interface (SPI), most flash models also support high-performance Dual/Quad modes I/O SPI controlled by six pins.
Serial Clock (CLK)
Chip Select (CS#)
Serial Data I/O0 (DI)
Serial Data I/O1 (DO)
Serial Data I/O2 (WP#)
Serial Data I/O3 (HOLD#)
Three Bit Modes
- Single Mode
Standard SPI mode, also called 1-bit mode, only uses CLK, CS#, DI, and DO pins. WP# is still available for write protect input, and HOLD# is also available for hold input.
- Dual Mode
In 2-bit mode, apart from using CLK and CS#, DI and DO pins become bidirectional I/O pins: I/O0 and I/O1. In comparison to single mode, the functionalities of WP# and HOLD# remain unchanged.
- Quad Mode
In 4-bit mode, in addition to using CLK and CS#, DI, DO, WP# and HOLD# are respectively used as I/O0, I/O1, I/O2, and I/O3. As all pins are used in this mode, write protect and hold functions are not available.
Note
RTL87x2G supports two quad modes: STR and DTR. If users need to use the corresponding quad read mode, it is necessary to check the flash datasheet or AVL for flash features.
Bit Mode Switch
In order to support as many flash models as possible, single mode (1-bit mode) is used at IC chip boot time.
If users need to switch to high-speed bit mode (2 or 4-bit mode), please call interface flash_nor_try_high_speed_mode()
provided in the SDK.
The parameter “mode” is used to configure bit mode.
If dual mode (2-bit mode) or quad mode (4-bit mode) is selected, the flash is configured and calibrated.
If calibration passes, the bit mode is switched to the selected one.
Otherwise, it is automatically switched back to single mode (1-bit mode).
The prototype of the interface function provided by SDK for bit mode switching is as follows.
Function Name |
flash_nor_try_high_speed_mode |
---|---|
Function Prototype |
FLASH_NOR_RET_TYPE flash_nor_try_high_speed_mode( FLASH_NOR_IDX_TYPE idx, FLASH_NOR_BIT_MODE mode ) |
Function Description |
switch flash to high-speed bit mode |
Input Parameter |
idx: the default value is FLASH_NOR_IDX_SPIC0 mode: the bit-mode to switch to (FLASH_NOR_1_BIT_MODE, FLASH_NOR_2_BIT_MODE, FLASH_NOR_4_BIT_MODE, FLASH_NOR_DTR_4_BIT_MODE) |
Return Value |
If the switching is successful, the returned value is 24 in decimal notation (Corresponding to FLASH_NOR_RET_SUCCESS) |
Prerequisite |
None |
Software Block Protect
Although flash supports HW protect pin (#WP) to lock all flash to prevent writing and erasing operations, there are still two disadvantages.
If #WP is used for flash protection, Quad mode (4-bit mode) is not allowed.
Hardware protection can just choose to protect all or protect none, and can’t protect partially.
A mechanism to solve these problems is flash software Blocks Protection (BP). It uses some BP bits in flash status register to select the level (range) to protect as shown in Figure Flash Block Protect.
Flash uses BP(x) bits in status registers to identify number of blocks to lock, and TB bit to determine the locking direction. RTL87x2G only supports locking starting from the low address end of flash.

Flash Block Protect
By default, RTL87x2G uses BP to protect some important data such as configuration, security, and code sections. Not all portions will be protected.
Note
It should be noted that flash status register by default uses NVRAM type to store data. BP function needs to change (write) BP bits to switch to different protect level, but NVRAM has 100,000 times programming limitation; although most vendors support 0x50 command to switch using SRAM type, not all of them support it, such as MXIC.
Not all flash used by RTL87x2G supports proportional block protect (0~1/2~1/4…). GD25WD80C, for instance Flash Block Protect for GD25WD80C .

Flash Block Protect for GD25WD80C
APIs are as follows.
Function Name |
flash_nor_set_bp_lv_locked |
---|---|
Function Prototype |
FLASH_NOR_RET_TYPE flash_nor_set_bp_lv_locked ( FLASH_NOR_IDX_TYPE idx, uint8_t bp_lv ) |
Function Description |
set flash bp level |
Input Parameter |
idx: the default value is FLASH_NOR_IDX_SPIC0 bp_lv: the bp level to set |
Return Value |
If the setting is successful, the returned value is 24 in decimal notation (Corresponding to FLASH_NOR_RET_SUCCESS) |
Prerequisite |
None |
Function Name |
flash_nor_get_bp_lv_locked |
---|---|
Function Prototype |
FLASH_NOR_RET_TYPE flash_nor_get_bp_lv_locked(FLASH_NOR_IDX_TYPE idx, uint8_t *bp_lv) |
Function Description |
get flash bp level |
Input Parameter |
idx: the default value is FLASH_NOR_IDX_SPIC0 bp_lv: storage buffer for getting bp level |
Return Value |
If the retrieval is successful, the returned value is 24 in decimal notation (Corresponding to FLASH_NOR_RET_SUCCESS) |
Prerequisite |
None |
For user convenience, the RTL87x2G provides an automatic locking mechanism controlled by the macro DEFAULT_FLASH_BP_LEVEL_ENABLE . When #define DEFAULT_FLASH_BP_LEVEL_ENABLE 1 is defined in the header file otp_config.h
, the maximum lockable range will be locked according to the configured flash layout and the chosen flash model, that is,
from the starting address of the flash to the end address of OTA bank1 (refer to flash layout ).
Most flash devices support level-based protection, covering different sizes of areas including the initial 64KB, 128KB, 256KB, 512KB, etc. When partitioning the flash layout, it’s crucial to align the OTA bank1 end address
offset with one of the protection levels supported by the flash, as much as possible.
This ensures that important data and code areas are fully locked, without impacting the data area filled with information that needs to be overwritten,
like the ‘FTL’ or ‘OTA Tmp’ area. Once the flash layout is set, RTL87x2G will automatically parse the flash layout configuration parameters and fetch the selected flash information to determine the BP protection level.
If the flash model in use solely supports the block protection mode as shown in Flash Block Protect for GD25WD80C , RTL87x2G will choose not to enable the lock.
Summarizing the information provided, the RTL87x2G supports two methods for protecting flash:
Using the
flash_nor_set_bp_lv_locked()
interface: This method is more straightforward and unrestricted. However, the bp_lv parameter needs to be determined by the user based on the corresponding flash’s datasheet. For instance, with the W25Q16DV, when setting the lock region to the first 256KB, the status register would have BP2 as 0, BP1 as 1, and BP0 as 1. Hence, the bp_lv parameter for theflash_nor_set_bp_lv_locked()
would be 3.Enabling the macro DEFAULT_FLASH_BP_LEVEL_ENABLE to use the automatic locking mechanism: With this method, users do not need to calculate the bp_lv parameter. However, If the flash in use solely supports the block protection mode as shown in Flash Block Protect for GD25WD80C , the locking process may fail.
Note
Since the current version of the OTA demo does not yet support upgrades when flash is locked, it is not recommended to use this feature at this time.
Power Saving
The power mode of flash is mainly divided into three scenarios.
Working: the power consumption is generally around 10 mA.
Standby: the power consumption is typically around the order of 10uA.
Power Down: the power consumption is lower, where it is even less than 1 uA.
Flash automatically enters standby mode without any access, and automatically enters working state when it needs to be accessed again. In order to enter Power Down mode, use a specific command. Most flash use the command 0xB9 to enter Power Down mode and use the command 0xAB to exit Power Down mode. But MXIC toggles the #CS pin to wake up flash.
After flash enters low power mode, it is dangerous to receive commands except for the exit DP command (0xAB). Because flash only accepts wakeup command to exit lower power mode, other commands will be ignored, while flash controller may step into an infinite loop waiting for a response from flash.
In order to avoid the risk of calling flash Power Down mode too frequently, the DLPS mechanism in RTL87x2G is equipped with flash Power Down mode control. When entering DLPS, the control flow automatically makes flash enter Power Down mode and wakes up when out of DLPS.
XIP
XIP (eXecute In Place) is a technique that allows code to be executed directly in flash memory without having to copy it into RAM. This saves RAM space and improves the efficiency of code execution. If the remaining RAM space is sufficient, APP code can be directly executed on RAM, which is conducive to improving performance and reducing power consumption. However, if the APP code is so large that the remaining RAM space is not enough, some or all of the APP codes need to be executed on flash (XIP). Configuration of XIP on RTL87x2G is as follows.
- Macro FEATURE_RAM_CODE (configuration in mem_config.h)a. when configured to 1, any code that does not contain any section modification will be executed on RAM.b. when configured to 0, any code that does not include any section modifiers by default will be executed on flash.
- Section modification (reference app_section.h)a. APP_FLASH_TEXT_SECTION: The specified code is executed on flash.b. APP_RAM_TEXT_SECTION: The specified code is executed on RAM.
Note
If RAM space is insufficient, give higher priority to implementing time-sensitive code on RAM to ensure efficiency.
- The way to improve the efficiency of XIP execution codeFlash is switched to 2-bit mode or 4-bit mode by calling
flash_nor_try_high_speed_mode()
.
RTL87x2G can access flash with auto read (directly accessing flash addresses) through SPIC, and execute code directly on SPI flash. However, the operation of accessing flash in user mode is not atomic. Once the accessing is interrupted by higher priority tasks or interrupts, it is possible to cause flash access error. To ensure atomicity of flash operations in user mode, XIP should follow the following restrictions and precautions.
Note
Accessing flash operations in user mode requires calling APIs with the “_locked” suffix to ensure critical area protection. If the time of the flash operation is too long, such as writing a large amount of data at one time, it is not suitable for critical zone protection, and can be split into a smaller amount of data written a few times.
If the time-critical interrupt needs to be processed, it is also necessary to ensure that the ISR (interrupt service routine) itself cannot be XIP, and the ISR also prohibits accessing the flash.