一、FAL管理与示例

FAL (Flash Abstraction Layer) Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及 分区操作的 API ,FAL 框架图如下:

从上图可以看出FAL抽象层位于SFUD框架的上层,可以将多个Flash硬件(包括片内Flash和片外Flash)统一进行管理,并向上层比如DFS文件系统层提供对底层多个Flash硬件的统一访问接口,方便上层应用对底层硬件的访问操作。

1、FAL软件包获取

说明:(1)“fal_cfg.h” :存储分区配置文件需要自己添加,可拷贝官方stm32现有的代码;(2)“SFUD”:针对片外所有类型的Flash进行统一管理的组件,可用可不用,FAL接口可直接调用SPI Flash驱动接口;(3)“W25Q64”:SPI Flash设备名称,默认为 norflash0,根据自己需要修改。

目录结构如下:

说明:(1)红框标记的 “ports” 目录是仿照官方自写的,其余为官方目录;(2)“fal_flash_sfud_port.c”为外挂 Flash 接口文件,“fal_flash_stm32_port.c”为内置flash接口文件。

2、FAL管理

FAL既然是对多个Flash设备进行分区管理的,自然会对Flash设备和分区有相应的数据结构描述。

(1)FAL设备与分区控制块

FAL设备与FAL分区的描述结构体如下:

/** File      : fal_def.h*/
#define FAL_SW_VERSION                 "0.5.0"
...
/* FAL flash and partition device name max length */
#ifndef FAL_DEV_NAME_MAX
#define FAL_DEV_NAME_MAX 24
#endifstruct fal_flash_dev
{char name[FAL_DEV_NAME_MAX];/* flash device start address and len  */uint32_t addr;size_t len;/* the block size in the flash for erase minimum granularity */size_t blk_size;struct{int (*init)(void);int (*read)(long offset, uint8_t *buf, size_t size);int (*write)(long offset, const uint8_t *buf, size_t size);int (*erase)(long offset, size_t size);} ops;/* write minimum granularity, unit: bit. 1(nor flash)/ 8(stm32f4)/ 32(stm32f1)/ 64(stm32l4)0 will not take effect. */size_t write_gran;
};
typedef struct fal_flash_dev *fal_flash_dev_t;/*** FAL partition*/
struct fal_partition
{uint32_t magic_word;/* partition name */char name[FAL_DEV_NAME_MAX];/* flash device name for partition */char flash_name[FAL_DEV_NAME_MAX];/* partition offset address on flash device */long offset;size_t len;uint32_t reserved;
};
typedef struct fal_partition *fal_partition_t;
说明:(1)fal_flash_dev结构体除了包含设备名、起始地址、长度、块大小等对flash设备的描述参数,还包括对flash设备的操作函数指针,这些操作函数需要在移植FAL时由下层的驱动实现;(2)fal_partition结构体则包含分区名、设备名、分区在设备上的偏移地址和长度等,从该结构体定义也可以看出一个fal_partition分区不能跨flash设备分配。

(2)FAL初始化过程

了解FAL原理先从FAL组件初始化过程开始:

/** File      : fal.c*/
/*** FAL (Flash Abstraction Layer) initialization.* It will initialize all flash device and all flash partition.** @return >= 0: partitions total number*/
int fal_init(void)
{extern int fal_flash_init(void);extern int fal_partition_init(void);int result;/* initialize all flash device on FAL flash table */result = fal_flash_init();if (result < 0) {goto __exit;}/* initialize all flash partition on FAL partition table */result = fal_partition_init();
__exit:if ((result > 0) && (!init_ok)){init_ok = 1;log_i("RT-Thread Flash Abstraction Layer (V%s) initialize success.", FAL_SW_VERSION);}else if(result <= 0){init_ok = 0;log_e("RT-Thread Flash Abstraction Layer (V%s) initialize failed.", FAL_SW_VERSION);}return result;
}
----------------------------------------------------------------------------------
/** File      : fal_flash.c*/static const struct fal_flash_dev * const device_table[] = FAL_FLASH_DEV_TABLE;
static const size_t device_table_len = sizeof(device_table) / sizeof(device_table[0]);
static uint8_t init_ok = 0;/*** Initialize all flash device on FAL flash table** @return result*/
int fal_flash_init(void)
{size_t i;if (init_ok){return 0;}for (i = 0; i < device_table_len; i++){assert(device_table[i]->ops.read);assert(device_table[i]->ops.write);assert(device_table[i]->ops.erase);/* init flash device on flash table */if (device_table[i]->ops.init){device_table[i]->ops.init();}log_d("Flash device | %*.*s | addr: 0x%08lx | len: 0x%08x | blk_size: 0x%08x |initialized finish.",FAL_DEV_NAME_MAX, FAL_DEV_NAME_MAX, device_table[i]->name, device_table[i]->addr, device_table[i]->len,device_table[i]->blk_size);}init_ok = 1;return 0;
}
-----------------------------------------------------------------------------------
/** File      : fal_partition.c*/
/* partition magic word */
#define FAL_PART_MAGIC_WORD         0x45503130
#define FAL_PART_MAGIC_WORD_H       0x4550L
#define FAL_PART_MAGIC_WORD_L       0x3130L
#define FAL_PART_MAGIC_WROD         0x45503130//USED static const struct fal_partition partition_table_def[] SECTION("FalPartTable") = FAL_PART_TABLE; // SECTION("FalPartTable") CDK编译不过;
static const struct fal_partition partition_table_def[] = FAL_PART_TABLE;/*** Initialize all flash partition on FAL partition table** @return partitions total number*/
int fal_partition_init(void)
{size_t i;const struct fal_flash_dev *flash_dev = NULL;if (init_ok){return partition_table_len;}#ifdef FAL_PART_HAS_TABLE_CFGpartition_table = &partition_table_def[0];partition_table_len = sizeof(partition_table_def) / sizeof(partition_table_def[0]);
#else/* load partition table from the end address FAL_PART_TABLE_END_OFFSET, error return 0 */long part_table_offset = FAL_PART_TABLE_END_OFFSET;size_t table_num = 0, table_item_size = 0;uint8_t part_table_find_ok = 0;uint32_t read_magic_word;fal_partition_t new_part = NULL;flash_dev = fal_flash_device_find(FAL_PART_TABLE_FLASH_DEV_NAME);if (flash_dev == NULL){log_e("Initialize failed! Flash device (%s) NOT found.", FAL_PART_TABLE_FLASH_DEV_NAME);goto _exit;}/* check partition table offset address */if (part_table_offset < 0 || part_table_offset >= (long) flash_dev->len){log_e("Setting partition table end offset address(%ld) out of flash bound(<%d).", part_table_offset, flash_dev->len);goto _exit;}table_item_size = sizeof(struct fal_partition);new_part = (fal_partition_t)FAL_MALLOC(table_item_size);if (new_part == NULL){log_e("Initialize failed! No memory for table buffer.");goto _exit;}/* find partition table location */{uint8_t read_buf[64];part_table_offset -= sizeof(read_buf);while (part_table_offset >= 0){if (flash_dev->ops.read(part_table_offset, read_buf, sizeof(read_buf)) > 0){/* find magic word in read buf */for (i = 0; i < sizeof(read_buf) - sizeof(read_magic_word) + 1; i++){read_magic_word = read_buf[0 + i] + (read_buf[1 + i] << 8) + (read_buf[2 + i] << 16) + (read_buf[3 + i] << 24);if (read_magic_word == ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L)){part_table_find_ok = 1;part_table_offset += i;log_d("Find the partition table on '%s' offset @0x%08lx.", FAL_PART_TABLE_FLASH_DEV_NAME,part_table_offset);break;}}}else{/* read failed */break;}if (part_table_find_ok){break;}else{/* calculate next read buf position */if (part_table_offset >= (long)sizeof(read_buf)){part_table_offset -= sizeof(read_buf);part_table_offset += (sizeof(read_magic_word) - 1);}else if (part_table_offset != 0){part_table_offset = 0;}else{/* find failed */break;}}}}/* load partition table */while (part_table_find_ok){memset(new_part, 0x00, table_num);if (flash_dev->ops.read(part_table_offset - table_item_size * (table_num), (uint8_t *) new_part,table_item_size) < 0){log_e("Initialize failed! Flash device (%s) read error!", flash_dev->name);table_num = 0;break;}if (new_part->magic_word != ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L)){break;}partition_table = (fal_partition_t) FAL_REALLOC(partition_table, table_item_size * (table_num + 1));if (partition_table == NULL){log_e("Initialize failed! No memory for partition table");table_num = 0;break;}memcpy(partition_table + table_num, new_part, table_item_size);table_num++;};if (table_num == 0){log_e("Partition table NOT found on flash: %s (len: %d) from offset: 0x%08x.", FAL_PART_TABLE_FLASH_DEV_NAME,FAL_DEV_NAME_MAX, FAL_PART_TABLE_END_OFFSET);goto _exit;}else{partition_table_len = table_num;}
#endif /* FAL_PART_HAS_TABLE_CFG *//* check the partition table device exists */for (i = 0; i < partition_table_len; i++){flash_dev = fal_flash_device_find(partition_table[i].flash_name);if (flash_dev == NULL){log_d("Warning: Do NOT found the flash device(%s).", partition_table[i].flash_name);continue;}if (partition_table[i].offset >= (long)flash_dev->len){log_e("Initialize failed! Partition(%s) offset address(%ld) out of flash bound(<%d).",partition_table[i].name, partition_table[i].offset, flash_dev->len);partition_table_len = 0;goto _exit;}}init_ok = 1;_exit:#if FAL_DEBUGfal_show_part_table();
#endif#ifndef FAL_PART_HAS_TABLE_CFGif (new_part){FAL_FREE(new_part);}
#endif /* !FAL_PART_HAS_TABLE_CFG */return partition_table_len;
}
说明:(1)FAL组件初始化最重要的是维护两个表:一个是flash设备表;另一个是FAL分区表;两个表的元素分别是fal_flash_dev结构体地址和fal_partition结构体对象;(2)fal_flash_dev设备表主要由底层的Flash驱动——包括MCU片内Flash和SFUD驱动的片外Flash——提供,也即FAL移植的重点就是在Flash驱动层向FAL提供fal_flash_dev设备表,每个flash设备提供设备表中的一个元素;(3)fal_partition分区表由用户事先配置在fal_cfg.h头文件中,FAL向上面的用户层提供的分区访问接口函数操作的内存区间就是从fal_partition分区表获取的,最后对分区的访问还是通过Flash驱动提供的接口函数(fal_flash_dev.ops)实现的。

(3)FAL分区管理接口

FAL主要是进行分区管理的,所以向应用层提供的接口函数主要也是对分区的访问,Flash分区访问接口函数要想访问到Flash硬件设备最终需要调用Flash驱动向FAL提供的接口函数指针实现。

FAL分区访问接口函数声明如下:

/** File      : fal_flash.c*//*** find flash device by name** @param name flash device name** @return != NULL: flash device*            NULL: not found*/
const struct fal_flash_dev *fal_flash_device_find(const char *name);
-----------------------------------------------------------------------------------
/** File      : fal_partition.c*//*** find the partition by name** @param name partition name** @return != NULL: partition*            NULL: not found*/
const struct fal_partition *fal_partition_find(const char *name);/*** get the partition table** @param len return the partition table length** @return partition table*/
const struct fal_partition *fal_get_partition_table(size_t *len);/*** read data from partition** @param part partition* @param addr relative address for partition* @param buf read buffer* @param size read size** @return >= 0: successful read data size*           -1: error*/
int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size);/*** write data to partition** @param part partition* @param addr relative address for partition* @param buf write buffer* @param size write size** @return >= 0: successful write data size*           -1: error*/
int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size);/*** erase partition data** @param part partition* @param addr relative address for partition* @param size erase size** @return >= 0: successful erased data size*           -1: error*/
int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size);/*** erase partition all data** @param part partition** @return >= 0: successful erased data size*           -1: error*/
int fal_partition_erase_all(const struct fal_partition *part);

(4)FAL分区转设备接口

DFS elmfat文件系统只能挂载到块设备上,而FAL管理的分区只是一段连续的flash存储空间并不是一个块设备。因此需要SFUD将SPI Flash注册为一个块设备后可以顺利挂载。

有时想在一个Flash设备上分出多个空间分别用于不同的用途,比如FAL框架图中展示的,一部分空间用于挂载文件系统,一部分空间用于存储非易失配置参数,另一部分空间用于存储OTA文件。这就需要把一个flash物理设备转换为多个逻辑设备,FAL便提供了将flash分区转换为BLK/MTD/Char设备的功能。

FAL分区转换BLK/MTD/Char设备的过程有很大的类似性,这里以FAL分区转BLK块设备为例,说明其工作原理。首先看FAL块设备的数据结构描述:

/** File      : fal_rtt.c*//* ========================== block device ======================== */
struct fal_blk_device
{struct rt_device                parent;struct rt_device_blk_geometry   geometry;const struct fal_partition     *fal_part;
};// 接着看FAL块设备的创建与注册过程:
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops blk_dev_ops =
{RT_NULL,RT_NULL,RT_NULL,blk_dev_read,blk_dev_write,blk_dev_control
};
#endif/*** create RT-Thread block device by specified partition** @param parition_name partition name** @return != NULL: created block device*            NULL: created failed*/
struct rt_device *fal_blk_device_create(const char *parition_name)
{struct fal_blk_device *blk_dev;const struct fal_partition *fal_part = fal_partition_find(parition_name);const struct fal_flash_dev *fal_flash = NULL;if (!fal_part){log_e("Error: the partition name (%s) is not found.", parition_name);return NULL;}if ((fal_flash = fal_flash_device_find(fal_part->flash_name)) == NULL){log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name);return NULL;}blk_dev = (struct fal_blk_device*) rt_malloc(sizeof(struct fal_blk_device));if (blk_dev){blk_dev->fal_part = fal_part;blk_dev->geometry.bytes_per_sector = fal_flash->blk_size;blk_dev->geometry.block_size = fal_flash->blk_size;blk_dev->geometry.sector_count = fal_part->len / fal_flash->blk_size;/* register device */blk_dev->parent.type = RT_Device_Class_Block;#ifdef RT_USING_DEVICE_OPSblk_dev->parent.ops  = &blk_dev_ops;
#elseblk_dev->parent.init = NULL;blk_dev->parent.open = NULL;blk_dev->parent.close = NULL;blk_dev->parent.read = blk_dev_read;blk_dev->parent.write = blk_dev_write;blk_dev->parent.control = blk_dev_control;
#endif/* no private */blk_dev->parent.user_data = RT_NULL;log_i("The FAL block device (%s) created successfully", fal_part->name);rt_device_register(RT_DEVICE(blk_dev), fal_part->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);}else{log_e("Error: no memory for create FAL block device");}return RT_DEVICE(blk_dev);
}

FAL块设备的创建跟 I / O设备模型框架中设备的创建注册过程类似,主要是还是向 I / O设备模型框架注册一个块设备及其接口函数,使该设备可以通过 I / O设备管理接口访问。

FAL创建块设备向I / O设备管理层注册的访问接口函数最终调用的是FAL分区访问接口函数,下面以块设备写入接口函数实现过程为例进行说明:

/** File      : fal_rtt.c*/static rt_size_t blk_dev_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)
{int ret = 0;struct fal_blk_device *part;rt_off_t phy_pos;rt_size_t phy_size;part = (struct fal_blk_device*) dev;assert(part != RT_NULL);/* change the block device's logic address to physical address */phy_pos = pos * part->geometry.bytes_per_sector;phy_size = size * part->geometry.bytes_per_sector;ret = fal_partition_erase(part->fal_part, phy_pos, phy_size);if (ret == (int) phy_size){ret = fal_partition_write(part->fal_part, phy_pos, buffer, phy_size);}if (ret != (int) phy_size){ret = 0;}else{ret = size;}return ret;
}
-----------------------------------------------------------------------------------/** File      : fal_partition.c*/
/*** write data to partition** @param part partition* @param addr relative address for partition* @param buf write buffer* @param size write size** @return >= 0: successful write data size*           -1: error*/
int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size)
{int ret = 0;const struct fal_flash_dev *flash_dev = NULL;assert(part);assert(buf);if (addr + size > part->len){log_e("Partition write error! Partition address out of bound.");return -1;}flash_dev = fal_flash_device_find(part->flash_name);if (flash_dev == NULL){log_e("Partition write error!  Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name);return -1;}ret = flash_dev->ops.write(part->offset + addr, buf, size);if (ret < 0){log_e("Partition write error! Flash device(%s) write error!", part->flash_name);}return ret;
}

3、FAL移植

FAL移植的关键是向其提供fal_flash_dev设备表,也相当于flash驱动层向FAL抽象层提供该flash设备的参数及访问接口函数。

在上述第一小部分“FAL目录结构”中,为了避免后期软件更新覆盖的问题,因此单独创建了“ports”目录,并实现相应部分功能。同时修改 SConscript 脚本文件:

from building import *
import rtconfigcwd     = GetCurrentDir()
src     = Glob('src/*.c')
CPPPATH = [
cwd + '/inc',
cwd + '/ports',
]LOCAL_CCFLAGS = ''if GetDepend(['FAL_USING_SFUD_PORT']):src += Glob('ports/fal_flash_sfud_port.c')group = DefineGroup('fal', src, depend = ['RT_USING_FAL'], CPPPATH = CPPPATH, LOCAL_CCFLAGS = LOCAL_CCFLAGS)Return('group')

(1)FAL SFUD(W25Q64 Flash)移植

/** File      : fal_flash_sfud_port.c*/#ifndef FAL_USING_NOR_FLASH_DEV_NAME
#define FAL_USING_NOR_FLASH_DEV_NAME             "W25Q64"
#endifstatic int init(void);
static int read(long offset, uint8_t *buf, size_t size);
static int write(long offset, const uint8_t *buf, size_t size);
static int erase(long offset, size_t size);//static sfud_flash_t sfud_dev = NULL;struct fal_flash_dev nor_flash0 =
{.name       = FAL_USING_NOR_FLASH_DEV_NAME,.addr       = 0,.len        = 8 * 1024 * 1024,.blk_size   = 4096,.ops        = {init, read, write, erase},.write_gran = 1
};
说明:(1)此文件需依次实现定义接口 {init, read, write, erase},官方已经实现,只需将其调用的SFUD相关的接口与 SPI Flash驱动接口对应即可;(2)本次移植是将SFUD层去掉了,此接口直接调用的SPI  Flash的逻辑驱动,但需要注意分区擦除的逻辑,可借鉴stm32官方已经实现的逻辑,需要考虑是否为全片擦除,若不是则考虑剩余的空间分别与64KB、32KB和sector(4KB)的大小关系。

SFUD向FAL注册的接口函数实际调用的是SFUD框架层的接口函数,调用过程如下:

/** File      : fal_flash_sfud_port.c*/static int init(void)
{#ifdef RT_USING_SFUD/* RT-Thread RTOS platform */sfud_dev = rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME);
#else/* bare metal platform */extern sfud_flash sfud_norflash0;sfud_dev = &sfud_norflash0;
#endifif (NULL == sfud_dev){return -1;}/* update the flash chip information */nor_flash0.blk_size = sfud_dev->chip.erase_gran;nor_flash0.len = sfud_dev->chip.capacity;return 0;
}static int read(long offset, uint8_t *buf, size_t size)
{assert(sfud_dev);assert(sfud_dev->init_ok);sfud_read(sfud_dev, nor_flash0.addr + offset, size, buf);return size;
}static int write(long offset, const uint8_t *buf, size_t size)
{assert(sfud_dev);assert(sfud_dev->init_ok);if (sfud_write(sfud_dev, nor_flash0.addr + offset, size, buf) != SFUD_SUCCESS){return -1;}return size;
}static int erase(long offset, size_t size)
{assert(sfud_dev);assert(sfud_dev->init_ok);if (sfud_erase(sfud_dev, nor_flash0.addr + offset, size) != SFUD_SUCCESS){return -1;}return size;
}

(2)FAL MCU Flash移植

STM32L475片内Flash驱动,RT-Thread已经在libraries\HAL_Drivers \drv_flash\drv_flash_l4.c目录下提供了,同时还通过条件宏提供了向FAL注册fal_flash_dev设备表项的代码:

// projects\stm32l475_dfs_sample\board\board.h#define STM32_FLASH_START_ADRESS       ((uint32_t)0x08000000)
#define STM32_FLASH_SIZE               (512 * 1024)
#define STM32_FLASH_END_ADDRESS        ((uint32_t)(STM32_FLASH_START_ADRESS + STM32_FLASH_S// libraries\HAL_Drivers\drv_flash\drv_flash_l4.cconst struct fal_flash_dev stm32_onchip_flash = { "onchip_flash", STM32_FLASH_START_ADRESS, STM32_FLASH_SIZE, 2048, {NULL, fal_flash_read, fal_flash_write, fal_flash_erase} };static int fal_flash_read(long offset, rt_uint8_t *buf, size_t size)
{return stm32_flash_read(stm32_onchip_flash.addr + offset, buf, size);
}static int fal_flash_write(long offset, const rt_uint8_t *buf, size_t size)
{return stm32_flash_write(stm32_onchip_flash.addr + offset, buf, size);
}static int fal_flash_erase(long offset, size_t size)
{return stm32_flash_erase(stm32_onchip_flash.addr + offset, size);
}

STM32L475向FAL提供的fal_flash_dev设备对象stm32_onchip_flash包含了STM32L475片内Flash的参数及其访问接口函数,Flash参数在工程目录的board.h头文件中定义,Flash访问接口函数则在驱动文件drv_flash_l4.c中提供,接口函数最终调用的是STM32L4 HAL库函数,这里就不展开介绍其过程了。

如果想使用STM32L475片内Flash驱动,需要定义相应的宏,这里通过在工程目录Kconfig文件中增加menuconfig配置来实现,新增的配置如下:

// projects\stm32l475_dfs_sample\board\Kconfig
menu "Hardware Drivers Config"config SOC_STM32L475VEboolselect SOC_SERIES_STM32L4default y
......
menu "On-chip Peripheral Drivers"......config BSP_USING_ON_CHIP_FLASHbool "Enable on-chip FLASH"default n......

为何增加宏BSP_USING_ON_CHIP_FLASH的配置项呢?主要是从工程编译管理文件libraries\HAL_Drivers\SConscript中查得的,看驱动文件drv_flash/drv_flash_l4.c的编译依赖宏是BSP_USING_ON_CHIP_FLASH与SOC_SERIES_STM32L4,后者在RT-Thread CPU架构与BSP移植过程时已经定义,所以这里只需要在Kconfig文件中增加宏BSP_USING_ON_CHIP_FLASH的配置选项即可。

在Kconfig文件中新增宏BSP_USING_ON_CHIP_FLASH配置后保存,在工程目录env环境输入menuconfig开启刚才新增的配置选项,如下图所示:

(3)FAL分区表配置

在分区表配置文件ports\fal\fal_cfg.h中主要修改fal_flash_dev设备对象名,Flash设备名NOR_FLASH_DEV_NAME,FAL分区表FAL_PART_TABLE的定义等内容。

我们将onchip_flash分为两个分区,将W25Q128 Flash分为五个分区,同时修改我们在底层flash驱动中提供的fal_flash_dev设备对象名和Flash设备名,修改后的FAL分区配置文件代码如下:

// projects\stm32l475_dfs_sample\ports\fal\fal_cfg.h
......
#ifndef FAL_USING_NOR_FLASH_DEV_NAME
#define NOR_FLASH_DEV_NAME             "norflash0"
#else
#define NOR_FLASH_DEV_NAME              FAL_USING_NOR_FLASH_DEV_NAME
#endif/* ===================== Flash device Configuration ========================= */
extern const struct fal_flash_dev stm32_onchip_flash;
extern struct fal_flash_dev nor_flash0;/* flash device table */
#define FAL_FLASH_DEV_TABLE                                          \
{                                                                    \&stm32_onchip_flash,                                             \&nor_flash0,                                                     \
}
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE                                                                                                  \
{                                                                                                                       \{FAL_PART_MAGIC_WROD,        "app",     "onchip_flash",                                    0,       384 * 1024, 0}, \{FAL_PART_MAGIC_WROD,      "param",     "onchip_flash",                           384 * 1024,       128 * 1024, 0}, \{FAL_PART_MAGIC_WROD,  "easyflash", NOR_FLASH_DEV_NAME,                                    0,       512 * 1024, 0}, \{FAL_PART_MAGIC_WROD,   "download", NOR_FLASH_DEV_NAME,                           512 * 1024,      1024 * 1024, 0}, \{FAL_PART_MAGIC_WROD, "wifi_image", NOR_FLASH_DEV_NAME,                  (512 + 1024) * 1024,       512 * 1024, 0}, \{FAL_PART_MAGIC_WROD,       "font", NOR_FLASH_DEV_NAME,            (512 + 1024 + 512) * 1024,  7 * 1024 * 1024, 0}, \{FAL_PART_MAGIC_WROD, "filesystem", NOR_FLASH_DEV_NAME, (512 + 1024 + 512 + 7 * 1024) * 1024,  7 * 1024 * 1024, 0}, \
}
#endif /* FAL_PART_HAS_TABLE_CFG */

到这里FAL移植就完成了,在使用FAL前需要对其进行初始化,也即调用调用函数fal_init,下面用一个示例程序验证FAL软件包移植是否成功。

4、FAL使用示例

我们主要想通过示例验证FAL移植是否有问题,并熟悉FAL向上层提供的接口函数的使用,所以本示例先初始化FAL组件,然后对特定分区进行擦除、读取、写入等访问操作,同时根据分区名获取fal_partition分区参数及fal_flash_dev设备参数等信息。

// fal_sample.c#include "rtthread.h"
#include "rtdevice.h"
#include "board.h"
#include "fal.h"#define BUF_SIZE 1024static int fal_test(const char *partiton_name)
{int ret;int i, j, len;uint8_t buf[BUF_SIZE];const struct fal_flash_dev *flash_dev = RT_NULL;const struct fal_partition *partition = RT_NULL;if (!partiton_name){rt_kprintf("Input param partition name is null!\n");return -1;}partition = fal_partition_find(partiton_name);if (partition == RT_NULL){rt_kprintf("Find partition (%s) failed!\n", partiton_name);ret = -1;return ret;}flash_dev = fal_flash_device_find(partition->flash_name);if (flash_dev == RT_NULL){rt_kprintf("Find flash device (%s) failed!\n", partition->flash_name);ret = -1;return ret;}rt_kprintf("Flash device : %s   ""Flash size : %dK   \n""Partition : %s   ""Partition size: %dK\n", partition->flash_name, flash_dev->len/1024,partition->name,partition->len/1024);/* erase all partition */ret = fal_partition_erase_all(partition);if (ret < 0){rt_kprintf("Partition (%s) erase failed!\n", partition->name);ret = -1;return ret;}rt_kprintf("Erase (%s) partition finish!\n", partiton_name);/* read the specified partition and check data */for (i = 0; i < partition->len;){rt_memset(buf, 0x00, BUF_SIZE);len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);ret = fal_partition_read(partition, i, buf, len);if (ret < 0){rt_kprintf("Partition (%s) read failed!\n", partition->name);ret = -1;return ret;}for(j = 0; j < len; j++){if (buf[j] != 0xFF){rt_kprintf("The erase operation did not really succeed!\n");ret = -1;return ret;}}i += len;}/* write 0x00 to the specified partition */for (i = 0; i < partition->len;){rt_memset(buf, 0x00, BUF_SIZE);len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);ret = fal_partition_write(partition, i, buf, len);if (ret < 0){rt_kprintf("Partition (%s) write failed!\n", partition->name);ret = -1;return ret;}i += len;}rt_kprintf("Write (%s) partition finish! Write size %d(%dK).\n", partiton_name, i, i/1024);/* read the specified partition and check data */for (i = 0; i < partition->len;){rt_memset(buf, 0xFF, BUF_SIZE);len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);ret = fal_partition_read(partition, i, buf, len);if (ret < 0){rt_kprintf("Partition (%s) read failed!\n", partition->name);ret = -1;return ret;}for(j = 0; j < len; j++){if (buf[j] != 0x00){rt_kprintf("The write operation did not really succeed!\n");ret = -1;return ret;}}i += len;}ret = 0;return ret;
}static void fal_sample(void)
{/* 1- init */fal_init();if (fal_test("param") == 0){rt_kprintf("Fal partition (%s) test success!\n", "param");}else{rt_kprintf("Fal partition (%s) test failed!\n", "param");}if (fal_test("download") == 0){rt_kprintf("Fal partition (%s) test success!\n", "download");}else{rt_kprintf("Fal partition (%s) test failed!\n", "download");}
}MSH_CMD_EXPORT(fal_sample, fal sample);


二、DFS挂载到FAL分区示例

在前篇博客DFS文件系统管理与devfs/elmfat示例中我们将DFS框架中的elmfat文件系统挂载到了SFUD驱动的W25Q128块设备上,这里增加FAL flash抽象层,我们将elmfat文件系统挂载到W25Q128 flash设备的filesystem分区上,由于FAL管理的filesystem分区不是块设备,需要先使用FAL分区转BLK设备接口函数将filesystem分区转换为块设备,然后再将DFS elmfat文件系统挂载到filesystem块设备上。

挂载DFS elmfat文件系统的示例程序主要还是使用前篇博客elmfat_sample函数中的代码,只是在前面增加了fal初始化和将分区filesystem创建为块设备的代码。按照该目标在fal_sample.c文件中新增实现代码如下:

/** File:fal_sample.c*/
#include "dfs_posix.h"#define FS_PARTITION_NAME  "filesystem"static void fal_elmfat_sample(void)
{int fd, size;struct statfs elm_stat;struct fal_blk_device *blk_dev;char str[] = "elmfat mount to W25Q flash.", buf[80];/* fal init */fal_init();/* create block device */blk_dev = (struct fal_blk_device *)fal_blk_device_create(FS_PARTITION_NAME);if(blk_dev == RT_NULL)rt_kprintf("Can't create a block device on '%s' partition.\n", FS_PARTITION_NAME);elsert_kprintf("Create a block device on the %s partition of flash successful.\n", FS_PARTITION_NAME);/* make a elmfat format filesystem */if(dfs_mkfs("elm", FS_PARTITION_NAME) == 0)rt_kprintf("make elmfat filesystem success.\n");/* mount elmfat file system to FS_PARTITION_NAME */if(dfs_mount(FS_PARTITION_NAME, "/", "elm", 0, 0) == 0)rt_kprintf("elmfat filesystem mount success.\n");/* Get elmfat file system statistics */if(statfs("/", &elm_stat) == 0)rt_kprintf("elmfat filesystem block size: %d, total blocks: %d, free blocks: %d.\n", elm_stat.f_bsize, elm_stat.f_blocks, elm_stat.f_bfree);if(mkdir("/user", 0x777) == 0)rt_kprintf("make a directory: '/user'.\n");rt_kprintf("Write string '%s' to /user/test.txt.\n", str);/* Open the file in create and read-write mode, create the file if it does not exist*/fd = open("/user/test.txt", O_WRONLY | O_CREAT);if (fd >= 0){if(write(fd, str, sizeof(str)) == sizeof(str))rt_kprintf("Write data done.\n");close(fd);   }/* Open file in read-only mode */fd = open("/user/test.txt", O_RDONLY);if (fd >= 0){size = read(fd, buf, sizeof(buf));close(fd);if(size == sizeof(str))rt_kprintf("Read data from file test.txt(size: %d): %s \n", size, buf);}
}
MSH_CMD_EXPORT_ALIAS(fal_elmfat_sample, fal_elmfat,fal elmfat sample);

三、Easyflash移植到FAL分区示例

我们在使用linux或者windows系统时都有专门配置环境变量的地方,在使用RT-Thread时也有要保存类似环境变量这种键值关系的空间,特别是对于蓝牙/WIFI等射频类通信需要保存的配置项还不少。有些配置是在系统运行过程中产生的,自然不能保存到代码区,由于这些配置在下次开机时仍需调用,也不能保存到SRAM内存区,只能保存到非易失性存储区NVM(non-volatile memory)。

在系统运行过程中产生的配置项需要保存到NVM非易失性存储区,也即ROM / Flash中,当然也可以使用DFS文件系统保存到某个配置文件中,但在文件中保存/获取配置项的值并没有那么便利,RT-Thread提供了一个easyflash软件包提供了专门的键值对(Key-Value)管理接口,可以让用户很方便的在NVM中通过接口函数保存/获取配置项,而不需要关心该配置项的存储位置。

1、Easyflash软件包获取

easyflash支持ENV环境变量、IAP在线升级、LOG日志保存等功能,这里我们只使用ENV环境变量功能,所以另外两个保持默认的未选中状态, 下载的easyflash软件包的目录结构如下

说明:(1)移植信息和接口信息和 FAL 组件一样,都在 md 文件中详细说明;(2)ports目录下文件在使用时二选一,具体可参考 README.md 文件。

2、easyflash环境变量管理

easyflash管理环境变量ENV的数据结构描述:

// \inc\easyflash.htypedef struct _ef_env {char *key;void *value;size_t value_len;
} ef_env, *ef_env_t;// \ports\ef_fal_port.c
/* default ENV set for user */
static const ef_env default_env_set[] = {{"iap_need_copy_app", "0"},{"iap_need_crc32_check", "0"},{"iap_copy_app_size", "0"},{"stop_in_bootloader", "0"},
};

用户设置的环境变量都保存在default_env_set[]表中,用户可以在编写代码时事先在该表中配置一部分环境变量,也可以在使用过程中通过接口函数往里面新增、删除、修改、获取环境变量,easyflash移植有SFUD和FAL两种方式,SFUD是直接在某个Flash上使用easyflash,FAL则是在某个分区上使用easyflash,我们只需要将环境变量保存在一段较小的flash分区中,因此使用FAL移植接口文件ef_fal_port.c。

easyflash软件包初始化过程:

// src\easyflash.c/*** EasyFlash system initialize.** @return result*/
EfErrCode easyflash_init(void) {extern EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size);extern EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size);extern EfErrCode ef_iap_init(void);extern EfErrCode ef_log_init(void);size_t default_env_set_size = 0;const ef_env *default_env_set;EfErrCode result = EF_NO_ERR;result = ef_port_init(&default_env_set, &default_env_set_size);#ifdef EF_USING_ENVif (result == EF_NO_ERR) {result = ef_env_init(default_env_set, default_env_set_size);}
#endif#ifdef EF_USING_IAPif (result == EF_NO_ERR) {result = ef_iap_init();}
#endif#ifdef EF_USING_LOGif (result == EF_NO_ERR) {result = ef_log_init();}
#endifif (result == EF_NO_ERR) {EF_INFO("EasyFlash V%s is initialize success.\n", EF_SW_VERSION);} else {EF_INFO("EasyFlash V%s is initialize fail.\n", EF_SW_VERSION);}EF_INFO("You can get the latest version on https://github.com/armink/EasyFlash .\n");return result;
}
-----------------------------------------------------------------------------------// ports\ef_fal_port.c/*** Flash port for hardware initialize.** @param default_env default ENV set for user* @param default_env_size default ENV size** @return result*/
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {EfErrCode result = EF_NO_ERR;*default_env = default_env_set;*default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);rt_sem_init(&env_cache_lock, "env lock", 1, RT_IPC_FLAG_PRIO);part = fal_partition_find(FAL_EF_PART_NAME);EF_ASSERT(part);return result;
}
-----------------------------------------------------------------------------------// src\ef_env.c
/*** Flash ENV initialize.** @param default_env default ENV set for user* @param default_env_size default ENV set size** @return result*/
EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size) {EfErrCode result = EF_NO_ERR;#ifdef EF_ENV_USING_CACHEsize_t i;
#endifEF_ASSERT(default_env);EF_ASSERT(ENV_AREA_SIZE);/* must be aligned with erase_min_size */EF_ASSERT(ENV_AREA_SIZE % EF_ERASE_MIN_SIZE == 0);/* sector number must be greater than or equal to 2 */EF_ASSERT(SECTOR_NUM >= 2);/* must be aligned with write granularity */EF_ASSERT((EF_STR_ENV_VALUE_MAX_SIZE * 8) % EF_WRITE_GRAN == 0);if (init_ok) {return EF_NO_ERR;}#ifdef EF_ENV_USING_CACHEfor (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) {sector_cache_table[i].addr = FAILED_ADDR;}for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) {env_cache_table[i].addr = FAILED_ADDR;}
#endif /* EF_ENV_USING_CACHE */env_start_addr = EF_START_ADDR;default_env_set = default_env;default_env_set_size = default_env_size;EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE);result = ef_load_env();#ifdef EF_ENV_AUTO_UPDATEif (result == EF_NO_ERR) {env_auto_update();}
#endifif (result == EF_NO_ERR) {init_ok = true;}return result;
}

easyflash初始化实际上主要是对环境变量表 default_env_set的相关处理,先是从移植文件ef_fal_port.c中获取default_env_set的首地址与元素个数,包括获取easyflash所使用FAL分区的结构体对象指针;然后配置ENV管理中需要使用的全局变量,最后将default_env_set表中配置的环境变量加载到SRAM内存中去,方便用户程序对环境变量的使用与配置。

easyflash环境变量管理的常用接口函数声明:

// ports\ef_fal_port.c#ifdef EF_USING_ENV
/* only supported on ef_env.c */
size_t ef_get_env_blob(const char *key, void *value_buf, size_t buf_len, size_t *saved_value_len);
EfErrCode ef_set_env_blob(const char *key, const void *value_buf, size_t buf_len);/* ef_env.c, ef_env_legacy_wl.c and ef_env_legacy.c */
EfErrCode ef_load_env(void);
void ef_print_env(void);
char *ef_get_env(const char *key);
EfErrCode ef_set_env(const char *key, const char *value);
EfErrCode ef_del_env(const char *key);
EfErrCode ef_save_env(void);
EfErrCode ef_env_set_default(void);
size_t ef_get_env_write_bytes(void);
EfErrCode ef_set_and_save_env(const char *key, const char *value);
EfErrCode ef_del_and_save_env(const char *key);
#endif

3.3 easyflash移植

移植文件ports\EasyFlash\ef_fal_port.c的修改,前面介绍的ef_port_init文件读取环境变量配置表default_env_set的信息,并通过FAL分区名获取分区对象指针,所以在ef_fal_port.c文件中需要修改环境变量配置表default_env_set和要存储的FAL分区名FAL_EF_PART_NAME。

// ef_fal_port.c/* EasyFlash partition name on FAL partition table */
#define FAL_EF_PART_NAME               "easyflash"/* default ENV set for user */
static const ef_env default_env_set[] = {{"boot_times", "0"}
};

easyflash要在FAL分区上保存/读取环境变量,需要实现访问FAL分区的函数,从与下面FAL抽象层的交互接口可以更熟悉移植过程,easyflash访问FAL分区的函数实现如下:

// ef_fal_port.cstatic const struct fal_partition *part = NULL;/*** Flash port for hardware initialize.** @param default_env default ENV set for user* @param default_env_size default ENV size** @return result*/
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {EfErrCode result = EF_NO_ERR;*default_env = default_env_set;*default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);rt_sem_init(&env_cache_lock, "env lock", 1, RT_IPC_FLAG_PRIO);part = fal_partition_find(FAL_EF_PART_NAME);EF_ASSERT(part);return result;
}/*** Read data from flash.* @note This operation's units is word.** @param addr flash address* @param buf buffer to store read data* @param size read bytes size** @return result*/
EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size) {EfErrCode result = EF_NO_ERR;fal_partition_read(part, addr, (uint8_t *)buf, size);return result;
}/*** Erase data on flash.* @note This operation is irreversible.* @note This operation's units is different which on many chips.** @param addr flash address* @param size erase bytes size** @return result*/
EfErrCode ef_port_erase(uint32_t addr, size_t size) {EfErrCode result = EF_NO_ERR;/* make sure the start address is a multiple of FLASH_ERASE_MIN_SIZE */EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);if (fal_partition_erase(part, addr, size) < 0){result = EF_ERASE_ERR;}return result;
}
/*** Write data to flash.* @note This operation's units is word.* @note This operation must after erase. @see flash_erase.** @param addr flash address* @param buf the write data buffer* @param size write bytes size** @return result*/
EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size) {EfErrCode result = EF_NO_ERR;if (fal_partition_write(part, addr, (uint8_t *)buf, size) < 0){result = EF_WRITE_ERR;}return result;
}

4、easyflash使用示例

在使用easyflash组件前需要先调用easyflash_init进行初始化,在示例程序中我们获取环境变量boot_times的值,然后对其执行加一操作,串口打印当前boot_times的值后,再把新的环境变量值设置给变量名boot_times,最后将该环境变量的新值保存到FAL指定分区中。

需要注意的是获取的环境变量值为字符串格式,如果要对其进行数字运算,需要先将该值转换为数字类型,同样保存该环境变量时也是以字符串形式设置的,通过easyflash提供的接口函数参数格式也可以看出来。

// easyflash_sample.c#include "easyflash.h"
#include <stdlib.h>static void easyflash_sample(void)
{/* fal init */fal_init();/* easyflash init */if(easyflash_init() == EF_NO_ERR){uint32_t i_boot_times = NULL;char *c_old_boot_times, c_new_boot_times[11] = {0};/* get the boot count number from Env */c_old_boot_times = ef_get_env("boot_times");/* get the boot count number failed */if (c_old_boot_times == RT_NULL)c_old_boot_times[0] = '0';i_boot_times = atol(c_old_boot_times);/* boot count +1 */i_boot_times ++;rt_kprintf("===============================================\n");rt_kprintf("The system now boot %d times\n", i_boot_times);rt_kprintf("===============================================\n");/* interger to string */sprintf(c_new_boot_times, "%d", i_boot_times);/* set and store the boot count number to Env */ef_set_env("boot_times", c_new_boot_times);ef_save_env();}
}
MSH_CMD_EXPORT(easyflash_sample, easyflash sample);

RT-Thread—FAL与EasyFlash组件移植相关推荐

  1. rt thread 使用FAL遇到fal_init() undefined reference

    rt thread FAL 0.5版,之前有没有不知道,遇到一个坑. 在main.cpp里面已经 #include <fal.h> fal_init() 编译报错,说 fal_init() ...

  2. 基于RT-Thread Studio的FAL,EasyFlash,文件系统移植

    一.新建工程 ​ 1.2.配置时钟 1.3.修改main文件 #include <rtthread.h> #include <rtdevice.h> #include < ...

  3. RT-Thread完整版fal及easyflash移植

    最近深圳开始隔离了,居家办公对于我们这种需要做验证仿真的,而家伙都没在手上的打工人太难了.因为最近项目需要用到fal加easyflash软件包,而之前搞过,但是忘记具体的了.还好留有文档,现在也转为文 ...

  4. GD32上FAL Flash分区驱动移植及Easyflash与FlashDB移植说明

    GD32上FAL Flash分区驱动移植及Easyflash与FlashDB移植说明 效果 移植前提 下载源码 移植过程 加入以下文件及文件夹到工程目录和工程 将demo目录下的接口文件做下修改 修改 ...

  5. RT Thread Free Modbus移植问题整理

    RT Thread Free Modbus移植问题整理 问题描述: 在读写寄存器中,写数据正常,只能读1个寄存器的值,多个值会异常. 在移植过程中发现串口(或RS485)数据接收长度异常. 一.环境描 ...

  6. 正点原子delay函数移植到rt thread操作系统(HAL库)

    正点原子教程中涉及到的操作系统只涉及了UCOS的教程,其中例程的system文件夹中的delay.c函数只是适配了UCOS. 下面将delay.c函数移植到rt thread中,使用的bsp是rt t ...

  7. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

  8. Yeelink平台使用——远程控制 RT Thread + LwIP+ STM32

    1.前言     [2014年4月重写该博文]     经过若干时间的努力终于搞定了STM32+LwIP和yeelink平台的数据互通,在学习的过程中大部分时间花在以太网协议栈学习上,但是在RT Th ...

  9. stm32f407单片机rt thread 片外spi flash OTA升级配置示例

    参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...

最新文章

  1. ssh“权限太开放”错误
  2. Mysql:is not allowed to connect to this MySQL server
  3. Tengine HTTPS原理解析、实践与调试
  4. 计算机七年级书籍段落,七年级下册片段.doc
  5. Servlet的入门
  6. UOJ351 新年的叶子
  7. Maven多个mudule只编译、打包指定module
  8. svn 存储方式BDB与FSFS比较
  9. markdown与latex:矩阵的书写
  10. PHP 编写接口并在header中进行简单的校验
  11. lammps建模_LAMMPS之Moltemplate建模教程
  12. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java软件学院门户网站u09k9
  13. Google谷歌新手SEO优化教程篇【1】
  14. matlab 数据分割,科学网—MATLAB把一个包含多个站点数据的文件分割到各个站点单独的文件夹 - 张乐乐的博文...
  15. PHP正则表达式笔记与实例详解
  16. 直角坐标积分化为极坐标积分_将直角坐标系下的二次积分转换成极坐标系下的二次积分...
  17. 机器学习之线性回归_通过线性回归开始机器学习之旅
  18. 因子分析python代码_关于「因」的诗词(649首)_诗词名句网
  19. 白苹果了怎么办_苹果7无限黑屏白苹果,苹果7开不了机怎么办
  20. 影响GPS定位精度的基本概念

热门文章

  1. 2021年T电梯修理考试及T电梯修理考试总结
  2. MIGO 行项目屏幕增强创建实例
  3. 云网融合助力大型企业网络底座布局
  4. 帝国cmssitemap.php,帝国cms百度sitemap插件
  5. 如何在云服务器上部署web项目(CVM)
  6. 如何判断一笔交易是否属于欺诈 数据挖掘算法与现实生活中的应用案例
  7. Unity3D 游戏开发之内存优化
  8. C++ RPG创建游戏角色
  9. migo初始化库存 s4_货物移动_初始化库存(MvT561)
  10. 宝洁公司收购德国达姆施塔特默克集团的消费者健康业务