专栏首页AIoT开源项目分享STM32通用FLASH管理软件包——SFUD/FAL

STM32通用FLASH管理软件包——SFUD/FAL

本次介绍的两个软件包SFUD/FAL都与FLASH有关,并且都可以独立使用或者结合在一起使用,两个软件包都对操作系统无依赖,可以使用裸机移植,也很方便移植到各种系统。

这两个软件包的作者都是armink,armink的开源仓库地址:https://github.com/armink,更多好玩的软件,请到作者仓库查询。

这两个软件包也是OTA的底层,如果对OTA感兴趣的也可以参考此文章。

以下将结合rtthread系统,分别对这两个软件包做下演示。

1.SFUD

SFUD 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险

1.1 主要特点:

  • 支持 SPI/QSPI 接口、
  • 面向对象(同时支持多个 Flash 对象)
  • 可灵活裁剪、扩展性强
  • 支持 4 字节地址

1.2 资源占用:

  • 标准占用:RAM:0.2KB ROM:5.5KB
  • 最小占用:RAM:0.1KB ROM:3.6KB

1.3 设计原理:

  • 什么是 SFDP :它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准,最新版 V1.6B (点击这里查看)。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
  • 不支持 SFDP 怎么办 :如果该 Flash 不支持 SFDP 标准,SFUD 会查询配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息(添加方法详细见 2.5 添加库目前不支持的 Flash)。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。

1.4 如何移植:

项目地址:https://github.com/armink/SFUD

在移植过程中一定要参考两个资料:项目的readme文档和demo工程。

对于使用rtthread完整版来说,作者已经把SFUD制作成了rtthread的内置组件了,对于使用者只需要勾选就可以了:

勾选后,就已经移植完成了!

对于rtthread完整版的来说移植太简单了,不利于切换到其他平台,所以本次移植教程以rtthread nano为例,裸机移植可以参考作者的demo工程

  1. 通过cubmx打开SPI
  1. 下载SFUD项目源码,并添加到工程目录中;
  1. 完善sfud_port.c接口文件

① 实现底层SPI/QSPI读写接口:

/**
 * SPI write data then read data
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
        size_t read_size) {
    sfud_err result = SFUD_SUCCESS;
    spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
    /**
     * add your spi write and read code
     */
    RT_ASSERT(spi);

    HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_RESET);
    if(write_size && read_size)
{
        if(HAL_SPI_Transmit(spi_dev->spix, (uint8_t *)write_buf, write_size, 1000)!=HAL_OK) 
        {
            result = SFUD_ERR_WRITE;
        }   
       /* For simplicity reasons, this example is just waiting till the end of the
       transfer, but application may perform other tasks while transfer operation
       is ongoing. */
        while (HAL_SPI_GetState(spi_dev->spix) != HAL_SPI_STATE_READY);
        if(HAL_SPI_Receive(spi_dev->spix, (uint8_t *)read_buf, read_size, 1000)!=HAL_OK)
        {
            result = SFUD_ERR_READ;
        }
    }else if(write_size)
{
        if(HAL_SPI_Transmit(spi_dev->spix, (uint8_t *)write_buf, write_size, 1000)!=HAL_OK) 
        {
            result = SFUD_ERR_WRITE;
        }
    }else
{
        if(HAL_SPI_Receive(spi_dev->spix, (uint8_t *)read_buf, read_size, 1000)!=HAL_OK)
        {
            result = SFUD_ERR_READ;
        }
}
       /* For simplicity reasons, this example is just waiting till the end of the
    transfer, but application may perform other tasks while transfer operation
    is ongoing. */
    while (HAL_SPI_GetState(spi_dev->spix) != HAL_SPI_STATE_READY);
    HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_SET);
    return result;
}

如果使用的是QSPI通信方式,还需要实现快速读取数据的接口:

#ifdef SFUD_USING_QSPI
/**
 * read flash data by QSPI
 */
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
        uint8_t *read_buf, size_t read_size) {
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your qspi read flash data code
     */
    RT_ASSERT(spi);
    RT_ASSERT(sfud_dev);
    RT_ASSERT(rtt_dev);


    return result;
}
#endif /* SFUD_USING_QSPI */

本次演示使用的是SPI,所以没有定义SFUD_USING_QSPI这个宏。

② 实现SPI设备对象初始化接口:

static spi_user_data user_spi = { .spix = &hspi2, .cs_gpiox = BSP_DATAFALSH_CS_GPIOX, .cs_gpio_pin = BSP_DATAFALSH_CS_GPIO_PIN };
sfud_err sfud_spi_port_init(sfud_flash *flash) {
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your port spi bus and device object initialize code like this:
     * 1. rcc initialize
     * 2. gpio initialize
     * 3. spi device initialize
     * 4. flash->spi and flash->retry item initialize
     *    flash->spi.wr = spi_write_read; //Required
     *    flash->spi.qspi_read = qspi_read; //Required when QSPI mode enable
     *    flash->spi.lock = spi_lock;
     *    flash->spi.unlock = spi_unlock;
     *    flash->spi.user_data = &spix;
     *    flash->retry.delay = null;
     *    flash->retry.times = 10000; //Required
     */
    rt_mutex_init(&lock, "sfud_lock", RT_IPC_FLAG_FIFO);    
    MX_SPI_Init();
    #if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F0) \
        || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32G0)

    SET_BIT(hspi2.Instance->CR2, SPI_RXFIFO_THRESHOLD_HF);
    #endif
    switch (flash->index) {
    case SFUD_W25QXX_DEVICE_INDEX: {

            /* 同步 Flash 移植所需的接口及数据 */
            flash->spi.wr = spi_write_read;
            flash->spi.lock = spi_lock;
            flash->spi.unlock = spi_unlock;
            flash->spi.user_data = &user_spi;
            /* about 100 microsecond delay */
            flash->retry.delay = retry_delay_100us;
            /* adout 60 seconds timeout */
            flash->retry.times = 60 * 10000;

            break;
        }
    }
    return result;
}

③ 实现其他接口:

static struct rt_mutex                 lock;
static char log_buf[256];

static void spi_lock(const sfud_spi *spi) {
    sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data);
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data);

    RT_ASSERT(spi);
    RT_ASSERT(sfud_dev);
    RT_ASSERT(rtt_dev);

    rt_mutex_take(&lock, RT_WAITING_FOREVER);
}

static void spi_unlock(const sfud_spi *spi) {
    sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data);
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data);

    RT_ASSERT(spi);
    RT_ASSERT(sfud_dev);
    RT_ASSERT(rtt_dev);

    rt_mutex_release(&lock);
}
static void retry_delay_100us(void) {
    /* 100 microsecond delay */
    rt_thread_delay((RT_TICK_PER_SECOND * 1 + 9999) / 10000);
}
void sfud_log_debug(const char *file, const long line, const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    rt_kprintf("[SFUD](%s:%ld) ", file, line);
    /* must use vprintf to print */
    rt_vsnprintf(log_buf, sizeof(log_buf), format, args);
    rt_kprintf("%s\n", log_buf);
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    rt_kprintf("[SFUD]");
    /* must use vprintf to print */
    rt_vsnprintf(log_buf, sizeof(log_buf), format, args);
    rt_kprintf("%s\n", log_buf);
    va_end(args);
}

1.5 如何使用:

先说明下本库主要使用的一个结构体 sfud_flash 。SFUD中最重要的就是Flash设备对象,一切操作都是对这个Flash设备对象进行的,每个Flash设备对象独立,所以SFUD也支持系统中存在多个Flash设备对象。

Flash设备对象管理着Flash存储器的所有信息,原型在sfud_def.h中,定义如下:

typedef struct {
    char *name;                                  /**< serial flash name */
    size_t index;                                /**< index of flash device information table  @see flash_table */
    sfud_flash_chip chip;                        /**< flash chip information */
    sfud_spi spi;                                /**< SPI device */
    bool init_ok;                                /**< initialize OK flag */
    bool addr_in_4_byte;                         /**< flash is in 4-Byte addressing */
    struct {
        void (*delay)(void);                     /**< every retry's delay */
        size_t times;                            /**< default times for error retry */
    } retry;
    void *user_data;                             /**< some user data */

#ifdef SFUD_USING_QSPI
    sfud_qspi_read_cmd_format read_cmd_format;   /**< fast read cmd format */
#endif

#ifdef SFUD_USING_SFDP
    sfud_sfdp sfdp;                              /**< serial flash discoverable parameters by JEDEC standard */
#endif

} sfud_flash, *sfud_flash_t;

1.5.1 配置SFUD:

SFUD的核心功能配置文件在sfud_cfg.h,修改说明如下:

修改完了之后,还需要去修改刚刚复制替换的sfud_port.c文件,与刚刚填写的配置信息相对应:

至此,SFUD移植、配置完成,接下来介绍如何使用API接口!

1.5.2 API 说明:

  • 初始化 SFUD 库:将会调用 sfud_device_init ,初始化 Flash 设备表中的全部设备。如果只有一个 Flash 也可以只使用 sfud_device_init 进行单一初始化。
sfud_err sfud_init(void)
  • 初始化指定的 Flash 设备
sfud_err sfud_device_init(sfud_flash *flash)

参数

描述

flash

待初始化的 Flash 设备

  • 获取 Flash 设备对象在 SFUD 配置文件中会定义 Flash 设备表,负责存放所有将要使用的 Flash 设备对象,所以 SFUD 支持多个 Flash 设备同时驱动。本方法通过 Flash 设备位于设备表中索引值来返回 Flash 设备对象,超出设备表范围返回 NULL 。
sfud_flash *sfud_get_device(size_t index)

参数

描述

index

Flash 设备位于 FLash 设备表中的索引值

  • Flash擦除/读写操作① 读取Flash数据:
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data)

② 擦除 Flash 数据:

sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size);

③ 往Flash写数据:

sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data);
sfud_err sfud_erase_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data)

1.6 应用示例:

通过调用sfud_init()对sfud初始化后,可以使用rtthread的sfud命令行对flash进行性能测试:

2.FAL

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

从上图可以看出FAL抽象层位于SFUD框架的上层,可以将多个Flash硬件(包括片内Flash和片外Flash)统一进行管理,并向上层比如OTA层提供对底层多个Flash硬件的统一访问接口,方便上层应用对底层硬件的访问操作。我上篇文章介绍的FOTA就是在FAL层之上做的,理解了这篇文章,FOTA的底层就理解了。参考文章:STM32通用Bootloader——FOTA

2.1 主要特点:

  • 支持静态可配置的分区表,并可关联多个 Flash 设备;
  • 分区表支持 自动装载 。避免在多固件项目,分区表被多次定义的问题;
  • 代码精简,对操作系统 无依赖 ,可运行于裸机平台,比如对资源有一定要求的 Bootloader;
  • 统一的操作接口。保证了文件系统、OTA、NVM(例如:EasyFlash) 等对 Flash 有一定依赖的组件,底层 Flash 驱动的可重用性;
  • 自带基于 Finsh/MSH 的测试命令,可以通过 Shell 按字节寻址的方式操作(读写擦) Flash 或分区,方便开发者进行调试、测试;

2.2 如何移植:

项目地址:https://github.com/RT-Thread-packages/fal

同样在移植过程中一定要参考两个资料:项目的readme文档和samples的移植说明。

对于使用rtthread完整版来说,使用者只需要勾选就可以了:

对于rtthread完整版的来说移植很简单,所以本次移植教程还是以rtthread nano为例,在上个移植完SFUD工程的基础上,继续移植FAL。

2.2.1 下载FAL项目源码,并添加到工程目录中;

2.2.2 定义 flash 设备

在定义 Flash 设备表前,需要先定义 Flash 设备。可以是片内 flash, 也可以是片外基于 SFUD 的 spi flash:

  • 定义片内 flash 设备可以参考 fal_flash_stm32f2_port.c
  • 定义片外 spi flash 设备可以参考 fal_flash_sfud_port.c
  1. FAL SFUD(W25Q64 Flash)移植拷贝FAL项目samples\porting的fal_flash_sfud_port.c到工程中。因为这个工程是在SFUD的基础上移植的,所以可以直接使用sfud的API接口:
  1. FAL MCU Flash移植STM32片内Flash驱动,RT-Thread已经在libraries\HAL_Drivers \drv_flash\目录下提供了,可以根据芯片自行拷贝到工程,本次演示项目使用的是STM32L431单片机,所以拷贝drv_flash_l4.c到工程中:

RT-Thread提供的内部flash驱动通过宏#define PKG_USING_FAL向FAL提供的fal_flash_dev设备对象onchip_flash,包含了STM32L431片内Flash的参数及其访问接口函数:

在board.h中或者rtconfig.h中定义flash的参数:

#define STM32_FLASH_START_ADRESS     ((uint32_t)0x08000000)
#define STM32_FLASH_SIZE             (256 * 1024)
#define STM32_FLASH_END_ADDRESS      ((uint32_t)(STM32_FLASH_START_ADRESS + STM32_FLASH_SIZE))

#define STM32_SRAM1_START              (0x20000000)      
#define STM32_SRAM1_END                (STM32_SRAM1_START + 64 * 1024)   // 结束地址 = 0x20000000(基址) + 64K(RAM大小)

2.2.3 定义 flash 设备表

Flash 设备表定义在 fal_cfg.h 头文件中,定义分区表前需 新建 fal_cfg.h 文件 ,请将该文件统一放在对应 BSP 或工程目录的 port 文件夹下,并将该头文件路径加入到工程。fal_cfg.h 可以参考 示例文件 fal/samples/porting/fal_cfg.h 完成。

设备表示例:

extern struct fal_flash_dev nor_flash0;
extern const struct fal_flash_dev stm32_onchip_flash;
/* flash device table */
#define FAL_FLASH_DEV_TABLE                                          \
{                                                                   \
                                                                  \
    &stm32_onchip_flash,                                           \
    &nor_flash0,                                                     \
}

Flash 设备表中,有两个 Flash 对象,一个为 STM32F2 的片内 Flash ,一个为片外的 Nor Flash。

2.2.4 定义 flash 分区表

分区表也定义在 fal_cfg.h 头文件中。Flash 分区基于 Flash 设备,每个 Flash 设备又可以有 N 个分区,这些分区的集合就是分区表。在配置分区表前,务必保证已定义好 Flash 设备设备表。fal_cfg.h 可以参考 示例文件 fal/samples/porting/fal_cfg.h 完成。

分区表示例:

#define FAL_PART_TABLE                                                               \
{                                                                               \
    {FAL_PART_MAGIC_WROD, "app",    "onchip_flash", 64*1024,       192*1024, 0}, \
    {FAL_PART_MAGIC_WROD, "ef", FAL_USING_NOR_FLASH_DEV_NAME, 0 , 1024 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD, "download", FAL_USING_NOR_FLASH_DEV_NAME, 1024 * 1024 , 512 * 1024, 0}, \
    {FAL_PART_MAGIC_WROD, "factory", FAL_USING_NOR_FLASH_DEV_NAME, (1024 + 512) * 1024 , 512 * 1024, 0}, \
}

用户需要修改的分区参数包括:分区名称、关联的 Flash 设备名、偏移地址(相对 Flash 设备内部)、大小,需要注意以下几点:

  • 分区名保证 不能重复
  • 关联的 Flash 设备 务必已经在 Flash 设备表中定义好 ,并且 名称一致 ,否则会出现无法找到 Flash 设备的错误;
  • 分区的起始地址和大小 不能超过 Flash 设备的地址范围 ,否则会导致包初始化错误;

至此,FAL移植完成,接下来介绍如何使用API接口!

2.3 如何使用:

API 说明:

  • 查找 Flash 设备
const struct fal_flash_dev *fal_flash_device_find(const char *name)

参数

描述

name

Flash 设备名称

return

如果查找成功,将返回 Flash 设备对象,查找失败返回 NULL

  • 查找 Flash 分区
const struct fal_partition *fal_partition_find(const char *name)

参数

描述

name

Flash 分区名称

return

如果查找成功,将返回 Flash 分区对象,查找失败返回 NULL

  • 获取分区表
const struct fal_partition *fal_get_partition_table(size_t *len)

参数

描述

len

分区表的长度

return

分区表

  • 临时设置分区表

FAL 初始化时会自动装载默认分区表。使用该设置将临时修改分区表,重启后会 丢失 该设置

void fal_set_partition_table_temp(struct fal_partition *table, size_t len)

参数

描述

table

分区表

len

分区表的长度

  • 从分区读取数据
int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size)

参数

描述

part

分区对象

addr

相对分区的偏移地址

buf

存放待读取数据的缓冲区

size

待读取数据的大小

return

返回实际读取的数据大小

  • 往分区写入数据
int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size)

参数

描述

part

分区对象

addr

相对分区的偏移地址

buf

存放待写入数据的缓冲区

size

待写入数据的大小

return

返回实际写入的数据大小

  • 擦除分区数据
int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size)

参数

描述

part

分区对象

addr

相对分区的偏移地址

size

擦除区域的大小

return

返回实际擦除的区域大小

  • 擦除整个分区数据
int fal_partition_erase_all(const struct fal_partition *part)

参数

描述

part

分区对象

return

返回实际擦除的区域大小

  • 打印分区表
void fal_show_part_table(void)
  • 创建块设备

该函数可以根据指定的分区名称,创建对应的块设备,以便于在指定的分区上挂载文件系统

struct rt_device *fal_blk_device_create(const char *parition_name)

参数

描述

parition_name

分区名称

return

创建成功,则返回对应的块设备,失败返回空

  • 创建 MTD Nor Flash 设备

该函数可以根据指定的分区名称,创建对应的 MTD Nor Flash 设备,以便于在指定的分区上挂载文件系统

struct rt_device *fal_mtd_nor_device_create(const char *parition_name)

参数

描述

parition_name

分区名称

return

创建成功,则返回对应的 MTD Nor Flash 设备,失败返回空

  • 创建字符设备

该函数可以根据指定的分区名称,创建对应的字符设备,以便于通过 deivice 接口或 devfs 接口操作分区,开启了 POSIX 后,还可以通过 oepn/read/write 函数操作分区。

struct rt_device *fal_char_device_create(const char *parition_name)

参数

描述

parition_name

分区名称

return

创建成功,则返回对应的字符设备,失败返回空

2.4 应用示例:

通过调用fal_init()对fal初始化后,可以使用rtthread的fal命令行进行性能测试:

FAL对分区进行读写测试:

此演示项目的工程代码:https://gitee.com/Aladdin-Wang/RT-FOTA-STM32L431

本文分享自微信公众号 - AIoT开源项目分享(Aladdin-Wang),作者:kk

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 制作STM32F429的SPI FLASH下载算法

    AIoT-KK
  • OOPC精要——撩开“对象”的神秘面纱

    由于前文《C/C++面向对象编程之封装》存在一些小错误或者难以理解的地方,Gorgon Meducer(傻孩子,PLOOC开源项目的作者)对此进行了修改和必要的...

    AIoT-KK
  • C/C++面向对象编程之封装

    面向过程开发,就像是总有人问你要后续的计划一样,下一步做什么,再下一步做什么,意外、事物中断、突发事件怎么做。理论上来说,任何一个过程都可以通过“顺序,循环,分...

    AIoT-KK
  • 腾讯精选50题算法【二叉搜索树的最近公共祖先】

    最近几周掺杂着需求、以及一些琐碎的事情,算法的学习一直都是默默的在搞,没有形成文章。

    程序员小跃
  • 一句话形容乐享,我们送腾讯月饼!

    腾讯乐享,一站式企业社区,凝聚腾讯10年企业管理经验与精华,通过知识库、问答、课堂、考试、活动、投票和论坛等核心应用,满足企业知识管理、文化建设和培训学习等多元...

    腾讯乐享
  • 3301: [USACO2011 Feb] Cow Line

    3301: [USACO2011 Feb] Cow Line Time Limit: 10 Sec  Memory Limit: 128 MB Submit: ...

    HansBug
  • 层次网络形成游戏(Social and Information Networks)

    我们提出了一个新的网络形成游戏,解释了在自利或功利最大化的个人决定在他们之间建立或严重的权威或合作关系的群体中出现的各种等级结构。我们考虑两种情况:首先考虑在建...

    李欣颖6837176
  • 聊聊flink的SourceFunction

    flink-streaming-java_2.11-1.6.2-sources.jar!/org/apache/flink/streaming/api/func...

    codecraft
  • J2ME and MIDP devices and emulators

    How vital it is to know the available MIDP devices and the emulators for mobile ...

    田春峰-JCJC错别字检测
  • 图表美化教程|图案与形状填充

    今天跟大家分享简单的信息图制作方法——形状/图案填充法。 ▽▼▽ 今天教大家简单的图表形状填充。 ●●●●● 首先是一个已经做好的柱形图。 ? 每天都看着这样的...

    数据小磨坊

扫码关注云+社区

领取腾讯云代金券