FAL (Flash Abstraction Layer) Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及 分区操作的 API ,FAL 框架图如下:
从上图可以看出FAL抽象层位于SFUD框架的上层,可以将多个Flash硬件(包括片内Flash和片外Flash)统一进行管理,并向上层比如DFS文件系统层提供对底层多个Flash硬件的统一访问接口,方便上层应用对底层硬件的访问操作。
从Github下载的RT-Thread源码中并没有FAL软件包的源码,我们可以通过git工具获取,menuconfig中也配置了FAL软件包的配置项,在配置项内有FAL软件包的下载地址等信息,我们配置启用FAL软件包后即可通过git工具(前提是需要安装并配置后Git工具)自动下载FAL软件包源码到工程目录。
在前篇博客的工程目录stm32l475_dfs_sample中打开env环境,运行menuconfig命令,启用FAL配置界面如下:
启用FAL软件包后出现更多选项,我们想使用FAL管理STM32L475片内Flash和W25Q128 Flash,其中W25Q128使用SFUD框架驱动,在启用FAL软件包后,我们可以选择使用SFUD驱动,SFUD设备名默认为norflash0,可以更改设备名为W25Q128,使用SFUD驱动的配置界面如下:
保存配置退出后,在env环境执行pkgs --update命令,会自动从FAL的github仓库获取FAL软件包源码到本地工程目录,如下图所示:
获取到的FAL软件包目录结构如下:
FAL既然是对多个Flash设备进行分区管理的,自然会对Flash设备和分区有相应的数据结构描述。
FAL设备与FAL分区的描述结构体如下:
// projects\stm32l475_dfs_sample\packages\fal-latest\inc\fal_def.h
/* FAL flash and partition device name max length */
#ifndef FAL_DEV_NAME_MAX
#define FAL_DEV_NAME_MAX 24
#endif
struct 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;
};
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;
fal_flash_dev结构体除了包含设备名、起始地址、长度、块大小等对flash设备的描述参数,还包括对flash设备的操作函数指针,这些操作函数需要在移植FAL时由下层的驱动实现。
fal_partition结构体则包含分区名、设备名、分区在设备上的偏移地址和长度等,从该结构体定义也可以看出,一个fal_partition分区不能跨flash设备分配。
了解FAL原理,先从FAL组件初始化过程开始:
// projects\stm32l475_dfs_sample\packages\fal-latest\src\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;
}
// projects\stm32l475_dfs_sample\packages\fal-latest\src\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();
}
......
}
init_ok = 1;
return 0;
}
// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c
USED static const struct fal_partition partition_table_def[] SECTION("FalPartTable") = FAL_PART_TABLE;
static const struct fal_partition *partition_table = NULL;
/**
* 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_CFG
partition_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_DEBUG
fal_show_part_table();
#endif
#ifndef FAL_PART_HAS_TABLE_CFG
if (new_part)
{
FAL_FREE(new_part);
}
#endif /* !FAL_PART_HAS_TABLE_CFG */
return partition_table_len;
}
FAL组件初始化最重要的是维护两个表:一个是flash设备表;另一个是FAL分区表,两个表的元素分别是前面介绍过的fal_flash_dev结构体地址和fal_partition结构体对象。
fal_flash_dev设备表主要由底层的Flash驱动(包括MCU片内Flash和SFUD驱动的片外Flash)提供,也即FAL移植的重点就是在Flash驱动层向FAL提供fal_flash_dev设备表,每个flash设备提供设备表中的一个元素。
fal_partition分区表由用户事先配置在fal_cfg.h头文件中,FAL向上面的用户层提供的分区访问接口函数操作的内存区间就是从fal_partition分区表获取的,最后对分区的访问还是通过Flash驱动提供的接口函数(fal_flash_dev.ops)实现的。
FAL主要是进行分区管理的,所以向应用层提供的接口函数主要也是对分区的访问,Flash分区访问接口函数要想访问到Flash硬件设备,最终需要调用Flash驱动向FAL提供的接口函数指针实现,FAL分区访问接口函数声明如下:
// projects\stm32l475_dfs_sample\packages\fal-latest\src\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);
// projects\stm32l475_dfs_sample\packages\fal-latest\src\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);
/**
* set partition table temporarily
* This setting will modify the partition table temporarily, the setting will be lost after restart.
*
* @param table partition table
* @param len partition table length
*/
void fal_set_partition_table_temp(struct fal_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);
前篇博客介绍DFS elmfat文件系统时谈到,elmfat文件系统只能挂载到块设备上,FAL管理的分区只是一段连续的flash存储空间,并不是一个块设备。前篇博客将DFS elmfat文件系统挂载到W25Q128 Flash上,实际就是挂载到一个块设备上,SFUD将W25Q128 Flash注册为一个块设备,所以可以顺利挂载。
有时候我们想在一个Flash设备上分出多个空间分别用于不同的用途,比如FAL框架图中展示的,一部分空间用于挂载文件系统,一部分空间用于存储非易失配置参数,另一部分空间用于存储OTA文件。这就需要把一个flash物理设备转换为多个逻辑设备,FAL便提供了将flash分区转换为BLK/MTD/Char设备的功能。
FAL分区转换BLK/MTD/Char设备的过程有很大的类似性,这里以FAL分区转BLK块设备为例,说明其工作原理。首先看FAL块设备的数据结构描述:
// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c
struct fal_blk_device
{
struct rt_device parent;
struct rt_device_blk_geometry geometry;
const struct fal_partition *fal_part;
};
接着看FAL块设备的创建与注册过程:
// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c
/**
* 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_OPS
blk_dev->parent.ops = &blk_dev_ops;
#else
blk_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);
}
#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
FAL块设备的创建跟 I / O设备模型框架中设备的创建注册过程类似,主要是还是向 I / O设备模型框架注册一个块设备及其接口函数,使该设备可以通过 I / O设备管理接口访问。
FAL创建块设备向I / O设备管理层注册的访问接口函数最终调用的是FAL分区访问接口函数,下面以块设备写入接口函数实现过程为例进行说明:
// projects\stm32l475_dfs_sample\packages\fal-latest\src\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;
}
// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c
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;
}
前面介绍了FAL移植的关键是向其提供fal_flash_dev设备表,也相当于flash驱动层向FAL抽象层提供该flash设备的参数及访问接口函数。
考虑到packages下面的软件版本后续可能会升级覆盖,我们不在\packages\fal-latest目录下直接进行移植修改,而是在packages目录外新建一个文件夹ports专门保存软件包的移植文件信息。
新建与packages软件包同级的移植文件目录ports,将packages\fal-latest\samples\porting目录下的fal_cfg.h与fal_flash_sfud_port.c文件复制一份到ports\fal目录下,将packages\fal-latest\SConscript复制一份到ports\fal目录下,将packages\SConscript复制一份到ports目录下,复制文件后的目录结构如下图所示:
由于不再使用packages\fal-latest\samples\porting目录下的移植文件,可以将packages\fal-latest\SConscript文件中如下的代码删除:
// projects\stm32l475_dfs_sample\packages\fal-latest\SConscript
/* Delete the code below */
if GetDepend(['FAL_USING_SFUD_PORT']):
src += Glob('samples\porting\fal_flash_sfud_port.c')
由于ports\fal目录及下面的文件名有变化,所以需要修改编译脚本ports\fal\SConscript,主要是修改文件目录及文件名,修改后的编译脚本如下:
// projects\stm32l475_dfs_sample\ports\fal\SConscript
from building import *
import rtconfig
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd]
LOCAL_CCFLAGS = ''
if GetDepend(['FAL_USING_SFUD_PORT']):
src += Glob('fal_flash_sfud_port.c')
if rtconfig.CROSS_TOOL == 'gcc':
LOCAL_CCFLAGS += ' -std=c99'
elif rtconfig.CROSS_TOOL == 'keil':
LOCAL_CCFLAGS += ' --c99'
group = DefineGroup('fal', src, depend = ['PKG_USING_FAL'], CPPPATH = CPPPATH, LOCAL_CCFLAGS = LOCAL_CCFLAGS)
Return('group')
完成上面的修改后,我们新建的移植文件目录ports下的移植文件就可以通过scons命令编译进工程内了,下面开始修改ports\fal目录下的移植文件。
我们想使用FAL管理STM32L475片内Flash和W25Q128片外Flash,W25Q128 Flash的驱动由SFUD框架提供。FAL提供了SFUD的移植示例文件\fal-latest\samples\porting\fal_flash_sfud_port.c,该文件已经被复制到ports\fal目录下,我们可以直接使用该文件。
FAL提供的SFUD移植示例中,SFUD框架向FAL提供的fal_flash_dev设备表项如下:
// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c
#ifndef FAL_USING_NOR_FLASH_DEV_NAME
#define FAL_USING_NOR_FLASH_DEV_NAME "norflash0"
#endif
static sfud_flash_t sfud_dev = NULL;
struct fal_flash_dev nor_flash0 = {FAL_USING_NOR_FLASH_DEV_NAME, 0, 8 * 1024 * 1024, 4096, {init, read, write, erase}};
// projects\stm32l475_dfs_sample\rtconfig.h
/* system packages */
#define PKG_USING_FAL
#define FAL_DEBUG_CONFIG
#define FAL_DEBUG 1
#define FAL_PART_HAS_TABLE_CFG
#define FAL_USING_SFUD_PORT
#define FAL_USING_NOR_FLASH_DEV_NAME "W25Q128"
#define PKG_USING_FAL_LATEST_VERSION
#define PKG_FAL_VER_NUM 0x99999
在menuconfig FAL配置项中我们使用了SFUD并且将设备名修改为了W25Q128,保存配置后在rtconfig.h中定义宏FAL_USING_NOR_FLASH_DEV_NAME的值为"W25Q128",在fal_flash_sfud_port.c文件中是通过条件宏定义的,也即优先使用外界定义的FAL_USING_NOR_FLASH_DEV_NAME,这里我们不需要修改。
SFUD提供的fal_flash_dev对象nor_flash0参数中,flash大小只有8M字节,我们可以修改为W25Q128的16M字节,也可以不修改,因为在调用初始化接口函数init后,会从flash设备读取正确的参数更新到nor_flash0表项中,我们在使用FAL组件前都需要调用FAL初始化函数fal_init,其内调用flash设备初始化函数fal_flash_init,最后会调用注册到fal_flash_dev设备表项中的初始化函数device_table[i]->ops.init,所以nor_flash0表项参数会在FAL初始化时被更新。
这里我们既然已经知道W25Q128 Flash的参数,便顺手把参数修改如下:
// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c
struct fal_flash_dev nor_flash0 = {FAL_USING_NOR_FLASH_DEV_NAME, 0, 16 * 1024 * 1024, 4096, {init, read, write, erase}};
SFUD向FAL注册的接口函数实际调用的是SFUD框架层的接口函数,调用过程如下:
// projects\stm32l475_dfs_sample\ports\fal\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;
#endif
if (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;
}
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_SIZE))
// libraries\HAL_Drivers\drv_flash\drv_flash_l4.c
const 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_STM32L475VE
bool
select SOC_SERIES_STM32L4
default y
......
menu "On-chip Peripheral Drivers"
......
config BSP_USING_ON_CHIP_FLASH
bool "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开启刚才新增的配置选项,如下图所示:
STM32L475片内Flash到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软件包移植是否成功。
我们主要想通过示例验证FAL移植是否有问题,并熟悉FAL向上层提供的接口函数的使用,所以本示例先初始化FAL组件,然后对特定分区进行擦除、读取、写入等访问操作,同时根据分区名获取fal_partition分区参数及fal_flash_dev设备参数等信息。
在projects\stm32l475_dfs_sample\applications目录下新建fal_sample.c文件,按照上面的实现目标在fal_sample.c文件中编写实现代码如下:
// projects\stm32l475_dfs_sample\applications\fal_sample.c
#include "rtthread.h"
#include "rtdevice.h"
#include "board.h"
#include "fal.h"
#define BUF_SIZE 1024
static 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文件系统管理与devfs/elmfat示例工程的基础上新增的,由于之前已经编写的源文件applications\dfs_sample.c中包含了QSPI设备绑定和SFUD Flash探测函数的自动调用,所以在本示例工程applications\fal_sample.c中并没有进行QSPI与SFUD flash初始化和注册工作,如果applications/dfs_sample.c源文件不存在,则需要在applications\fal_sample.c中完成QSPI与SFUD flash初始化及注册工作。
在工程目录启动env环境并执行scons --target=mdk5命令,编译生成MDK5工程文件,打开project.uvprojx编译无错误(由于部分RT-Thread组件不支持Clang编译器,把template.uvprojx工程默认编译器设置为ARM Compiler V5了),将程序烧录到STM32L475潘多拉开发板中,运行结果如下:
在finsh环境下运行fal_sample结果正常,输出到串口的不仅有FAL分区表信息,还有在特定分区中擦除、写入、读取数据的测试结果信息。
FAL为便于用户调试,也提供了finsh命令fal,包括fal probe / read / write / erase / bench等命令,命令使用示例如下:
本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample
在前篇博客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文件中新增实现代码如下:
// projects\stm32l475_dfs_sample\applications\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);
else
rt_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);
使用MDK5编译无报错,将程序烧录到STM32L475开发板中,程序运行结果如下:
本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample
我们在使用linux或者windows系统时都有专门配置环境变量的地方,在使用RT-Thread时也有要保存类似环境变量这种键值关系的空间,特别是对于蓝牙/WIFI等射频类通信需要保存的配置项还不少。有些配置是在系统运行过程中产生的,自然不能保存到代码区,由于这些配置在下次开机时仍需调用,也不能保存到SRAM内存区,只能保存到非易失性存储区NVM(non-volatile memory)。
在系统运行过程中产生的配置项需要保存到NVM非易失性存储区,也即ROM / Flash中,当然也可以使用DFS文件系统保存到某个配置文件中,但在文件中保存/获取配置项的值并没有那么便利,RT-Thread提供了一个easyflash软件包提供了专门的键值对(Key-Value)管理接口,可以让用户很方便的在NVM中通过接口函数保存/获取配置项,而不需要关心该配置项的存储位置。
在stm32l475_dfs_sample工程目录打开env执行menuconfig命令,在RT-Thread online packages --> tools packages --> easyflash中启用该软件包,选择lastest最新版本。
easyflash支持ENV环境变量、IAP在线升级、LOG日志保存等功能,这里我们只使用ENV环境变量功能,所以另外两个保持默认的未选中状态,easyflash配置界面如下:
保存配置,在env环境中执行pkgs --update命令,自动从github获取easyflash软件包到本地,获取结果如下:
下载的easyflash软件包的目录结构如下:
在packages\EasyFlash-latest\docs\zh目录下有详细的说明文档,包括api接口介绍、easyflash设计与实现原理、移植过程说明等,说明文档比较详细,这里就不过多赘述了,只简单介绍其初始化过程与移植过程。
先看下easyflash管理环境变量ENV的数据结构描述:
// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\inc\easyflash.h
typedef struct _ef_env {
char *key;
void *value;
size_t value_len;
} ef_env, *ef_env_t;
// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\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软件包初始化过程:
// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\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_ENV
if (result == EF_NO_ERR) {
result = ef_env_init(default_env_set, default_env_set_size);
}
#endif
#ifdef EF_USING_IAP
if (result == EF_NO_ERR) {
result = ef_iap_init();
}
#endif
#ifdef EF_USING_LOG
if (result == EF_NO_ERR) {
result = ef_log_init();
}
#endif
if (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;
}
// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\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;
}
// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\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_CACHE
size_t i;
#endif
EF_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_CACHE
for (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_UPDATE
if (result == EF_NO_ERR) {
env_auto_update();
}
#endif
if (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环境变量管理的常用接口函数声明:
// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\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
更多接口函数说明可参阅EasyFlash-latest\docs\zh\api.md文档或源码。
为了方便后面easyflash软件包的升级,以免我们的配置信息被覆盖,依然采用FAL移植时的处理方式,在新建的ports文件夹下再新建EasyFlash文件夹用于保存其移植文件,同时把packages\EasyFlash-latest\ports\ef_fal_port.c复制一份到ports\EasyFlash\ef_fal_port.c中,把packages\EasyFlash-latest\SConscript复制一份到ports\EasyFlash\SConscript中,移植文件配置目录如下:
在packages\EasyFlash-latest\SConscript文件中并没有添加packages\EasyFlash-latest\ports目录下的源码文件,我们也不使用该目录下的移植文件,所以该编译配置文件不需要修改。
打开ports\EasyFlash\SConscript并修改源文件与头文件的目录配置,修改后的编译配置脚本代码如下:
// projects\stm32l475_dfs_sample\ports\EasyFlash\SConscript
from building import *
# get current directory
cwd = GetCurrentDir()
# The set of source files associated with this SConscript file.
src = []
src += Glob('*.c')
path = [cwd]
group = DefineGroup('EasyFlash', src, depend = ['PKG_USING_EASYFLASH'], CPPPATH = path)
Return('group')
接下来看移植文件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。
在前面介绍FAL时,专门在W25Q128 flash上配置了一个名为easyflash的分区用于存储easyflash软件包管理的数据。环境变量我们先只设置一个开机次数,修改后的配置表default_env_set与分区名FAL_EF_PART_NAME如下:
// projects\stm32l475_dfs_sample\ports\EasyFlash\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分区的函数实现如下:
// projects\stm32l475_dfs_sample\ports\EasyFlash\ef_fal_port.c
static 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;
}
到这里easyflash组件的移植完成了,下面用一个示例程序验证移植是否成功。
在使用easyflash组件前需要先调用easyflash_init进行初始化,在示例程序中我们获取环境变量boot_times的值,然后对其执行加一操作,串口打印当前boot_times的值后,再把新的环境变量值设置给变量名boot_times,最后将该环境变量的新值保存到FAL指定分区中。
需要注意的是获取的环境变量值为字符串格式,如果要对其进行数字运算,需要先将该值转换为数字类型,同样保存该环境变量时也是以字符串形式设置的,通过easyflash提供的接口函数参数格式也可以看出来。
按照上面的任务目标,在fal_sample.c文件中新增easyflash示例代码如下:
// projects\stm32l475_dfs_sample\applications\fal_sample.c
#include "easyflash.h"
#include
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);
在工程目录打开env执行scons --target=mdk5命令,自动编译生成MDK5工程文件,打开project.uvprojx,编译无报错,将程序烧录到STM32L475开发板中,运行结果如下:
easyflash为方便调试,也提供了finsh命令printenv / resetenv / setenv / saveenv / getvalue等,这些命令的使用示例如下:
本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample