flash实现镜头由远及近_SFUD | 一款串行 Flash 通用驱动库

4f9169b327743164d44c2a9ae40826ed.png
本文首发于公众号Mculover666

嵌入式开源项目精选专栏

本专栏由Mculover666创建,主要内容为寻找嵌入式领域内的优质开源项目,一是帮助开发者使用开源项目实现更多的功能,二是通过这些开源项目,学习大佬的代码及背后的实现思想,提升自己的代码水平,和其它专栏相比,本专栏的优势在于:

不会单纯的介绍分享项目,还会包含作者亲自实践的过程分享,甚至还会有对它背后的设计思想解读

目前本专栏包含的开源项目有:

  • cJSON | 一个轻量级C语言JSON解析器
  • paho | 支持10种语言编写mqtt客户端,总有一款适合你!
  • MultiButton | 一个小巧简单易用的事件驱动型按键驱动模块
  • letter-shell | 一个功能强大的嵌入式shell
  • EasyLogger | 一款轻量级且高性能的日志库

如果您自己编写或者发现的开源项目不错,欢迎留言或者私信投稿到本专栏,分享获得双倍的快乐!

1. SFUD

本期给大家带来的开源项目是 SFUD,一款串行 Flash 通用驱动库,作者armink,目前收获 407 个 star,遵循 MIT 开源许可协议。

SFUD全称Serial Flash Universal Driver,是一款开源的串行 SPI Flash 通用驱动库,由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计

SFUD的特点在于:

  • 支持 SPI/QSPI 接口
  • 面向对象设计(同时支持多个 Flash 对象)
  • 可灵活裁剪、扩展性强
  • 支持 4 字节地址
项目地址: https://github.com/armink/SFUD

2. 移植SFUD

2.1. 移植思路

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

对于这些开源项目,其实移植起来也就两步:

  • ① 添加源码到裸机工程中;
  • ② 实现需要的接口即可;

2.2. 准备裸机工程

本文中我使用的是小熊派IoT开发套件,主控芯片为STM32L431RCT6:

674fb43b3b64d6c0ea2e59227f9eb2bc.png

板载Flash型号为W25Q64JV,大小64Mbit,与STM32的QSPI接口相连:

f0d73a83c68657ace2f4a8db94084632.png

移植之前需要准备一份裸机工程,我使用STM32CubeMX生成,需要初始化以下配置:

  • 配置SPI Flash通信接口(SPI或QSPI)
  • 配置一个串口用于打印信息
  • printf重定向

具体过程请参考:

  • STM32CubeMX_06 | 使用USART发送和接收数据(查询模式)
  • STM32CubeMX_09 | 重定向printf函数到串口输出的多种方法
  • STM32CubeMX_18 | 使用硬件QSPI读写SPI Flash(W25Q64)
使用CubeMX配置好SPI或QSPI通信即可,不用编写W25Q64驱动。

2.3. 添加SFUD到工程中

① 复制源码到工程中:

67ec70d0829744cf2caec52d290f0e30.png

② 在keil中添加 SFUD 组件的源码文件:

  • srcsfud.c:SFUD核心功能源码;
  • srcsfud_sfdp.c:读取并分析SFDP功能源码;
  • portsfud_port.c:SFUD移植接口;

314b76a88199787a6dd327fa454172ed.png

③ 将sfud/inc头文件路径添加到keil中:

8fb511fc6cdce26a7ea1d11eabf1f732.png

2.4. 实现SFUD移植接口

SFUD的移植接口都已经写好了,在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);

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

/**
 * QSPI fast read data
 */
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底层使用的SPI/QSPI接口和SPI设备对象初始化接口:

sfud_err sfud_spi_port_init(sfud_flash *flash);

关于SFUD底层所抽象出来的SPI设备对象,在接下来的设计思想解读章节中会详细讲述。

本文中所使用的裸机工程是基于HAL库的,在SFUD源码的Demo中也有一份HAL库的工程,因为基于HAL库的移植接口实现都是一样的,所以我直接将Demo中的sfud_port.c文件复制过来替换:

39263f928e08976b7d62c4463084810f.png

复制过来之后,如果使用的不是STM32L4系列的芯片,则需要修改sfud_port.c中包含的头文件:

81fcd6b9966c0e56a87655a9bf7fe766.png

2.5. 配置SFUD

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

ba3a0d8f4f3d1da9d367ab11883c977b.png

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

215c6aff10f4b7d5973a380dfcd1bb91.png

至此,SFUD移植、配置完成,接下来就可以愉快的使用了!

3. 使用SFUD

使用时包含头文件:

#include <sfud.h>

3.1. 初始化SFUD

初始化SFUD的API如下,该函数会初始化 Flash 设备表中的全部设备:

sfud_err sfud_init(void);

在QSPI模式下,SFUD 对于 QSPI 模式的支持仅限于快速读命令,通过该函数可以配置 Flash 所使用的 QSPI 总线的实际支持的数据线最大宽度,例如:1 线(默认值,即传统的 SPI 模式)、2 线、4 线:

sfud_err sfud_qspi_fast_read_enable(sfud_flash *flash, uint8_t data_line_width);

所以,在main函数中编写如下初始化函数:

/* USER CODE BEGIN 2 */

/* SFUD初始化 */
if(sfud_init() != SFUD_SUCCESS)
{
 printf("SFUD init fail.rn");
}
/* 使能QSPI快读 */
sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25Q64_DEVICE_INDEX), 1);

/* USER CODE END 2 */

编译、下载之后,可以在串口终端中看到SFUD打印的日志:

5f72ed56a84a09cd6473c65f0ff12a96.png

SFUD初始化Flash设备成功后进行接下来的读写测试。

3.2. 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);

接下来使用作者编写的demo测试。

首先在main.c开头编写代码,开辟一块缓冲区用于存放测试数据:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* SFUD读写Flash数据测试的缓冲区 */
#define SFUD_DEMO_TEST_BUFFER_SIZE                     1024
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];

/* SFUD读写Flash数据测试函数 */
void sfud_demo(uint32_t addr, size_t size, uint8_t *data);

/* USER CODE END 0 */

然后在main.c最后添加测试函数:

/* USER CODE BEGIN 4 */
/**
 * SFUD demo for the first flash device test.
 *
 * @param addr flash start address
 * @param size test flash size
 * @param size test flash data buffer
 */
void sfud_demo(uint32_t addr, size_t size, uint8_t *data)
{
    sfud_err result = SFUD_SUCCESS;
    extern sfud_flash *sfud_dev;
    const sfud_flash *flash = sfud_get_device(SFUD_W25Q64_DEVICE_INDEX);
    size_t i;
    /* prepare write data */
    for (i = 0; i < size; i++)
    {
        data[i] = i;
    }
    /* erase test */
    result = sfud_erase(flash, addr, size);
    if (result == SFUD_SUCCESS)
    {
        printf("Erase the %s flash data finish. Start from 0x%08X, size is %zu.rn", flash->name, addr, size);
    }
    else
    {
        printf("Erase the %s flash data failed.rn", flash->name);
        return;
    }
    /* write test */
    result = sfud_write(flash, addr, size, data);
    if (result == SFUD_SUCCESS)
    {
        printf("Write the %s flash data finish. Start from 0x%08X, size is %zu.rn", flash->name, addr, size);
    }
    else
    {
        printf("Write the %s flash data failed.rn", flash->name);
        return;
    }
    /* read test */
    result = sfud_read(flash, addr, size, data);
    if (result == SFUD_SUCCESS)
    {
        printf("Read the %s flash data success. Start from 0x%08X, size is %zu. The data is:rn", flash->name, addr, size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0Frn");
        for (i = 0; i < size; i++)
        {
            if (i % 16 == 0)
            {
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", data[i]);
            if (((i + 1) % 16 == 0) || i == size - 1)
            {
                printf("rn");
            }
        }
        printf("rn");
    }
    else
    {
        printf("Read the %s flash data failed.rn", flash->name);
    }
    /* data check */
    for (i = 0; i < size; i++)
    {
    
        if (data[i] != i % 256)
        {
            printf("Read and check write data has an error. Write the %s flash data failed.rn", flash->name);
            break;
        }
    }
    if (i == size)
    {
        printf("The %s flash test is success.rn", flash->name);
    }
}

/* USER CODE END 4 */

main函数中,SFUD初始化代码之后,调用该函数进行Flash测试:

/* 测试Flash读写 */
sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);

编译、下载,在串口终端中查看结果:

780eb177eeb948cf354f1b769e987b7a.png

3.3. 移植前后内存占用情况

0b85980dc8289d6e6c8580d358a955bc.png

SFUD中获取Flash信息有两种方式:

  • 使用SFDP 参数方式:开关宏SFUD_USING_SFDP
  • 使用库自带的 Flash 参数信息表:开关宏SFUD_USING_FLASH_INFO_TABLE

本文中两种方式都开启,所以移植之后较大,实际使用中可以视情况关闭这两个功能。

SFDP功能关闭后,只会查询该库在 /sfud/inc/sfud_flash_def.h 中提供的 Flash 信息表,代码量会降低,但是软件适配性也随之降低。

查表功能关闭后,该库只驱动支持 SFDP 规范的 Flash,也会适当的降低部分代码量。

一般情况下上述二者必须要选择一个,在实际使用时视情况而定,但是也可以两者都不开启,直接指定好具体的某款 Flash 参数。

4. SFUD设计思想解读

4.1. Flash设备对象

SFUD中最重要的就是Flash设备对象,一切操作都是对这个Flash设备对象进行的,每个Flash设备对象独立,所以SFUD也支持系统中存在多个Flash设备对象。

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

/**
 * serial flash device
 */
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;

其中Flash设备的通信接口信息由 sfud_spi 对象管理,包括SPI读写数据函数,加锁解锁函数定义如下:

/**
 * SPI device
 */
typedef struct __sfud_spi {
    /* SPI device name */
    char *name;
    /* SPI bus write read data function */
    sfud_err (*wr)(const struct __sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
                   size_t read_size);
#ifdef SFUD_USING_QSPI
    /* QSPI fast read function */
    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);
#endif
    /* lock SPI bus */
    void (*lock)(const struct __sfud_spi *spi);
    /* unlock SPI bus */
    void (*unlock)(const struct __sfud_spi *spi);
    /* some user data */
    void *user_data;
} sfud_spi, *sfud_spi_t;

4.2. JESD216 SFDP标准

SFDP全称 Serial Flash Discoverable Parameter,它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准。

该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。

所以 SFUD 在初始化时会优先读取 SFDP 表参数,以达到SFUD在支持SFDP标准的Flash上全部适用的效果,更加通用。

那么SFDP标准的内容是什么呢?SFDP标准强制规范必须要有:

  • SFDP标题头
  • 1st参数头
  • JEDEC Flash基本参数表格

SFDP标题头一般为“S”“F”“U”“D”,如果能读取出这四个字符,则认为该款Flash支持SFDP标准,比如在sfud_sfdp源码中校验代码如下:

/* check SFDP header */
if (!(header[0] == 'S' &&
      header[1] == 'F' &&
      header[2] == 'D' &&
      header[3] == 'P')) {
    SFUD_DEBUG("Error: Check SFDP signature error. It's must be 50444653h('S' 'F' 'D' 'P').");
    return false;
}

接下来是一些预留空内容,属于厂商可选内容,Flash厂商可以在这些空白内容中添加自己的厂商ID识别号、SFDP版本号、参数长度以及存放参数表格的地址指针,比如读取W25Q64的结果中显示:

b15a01d22cd9777478d13f3aa023ddba.png

接下来的 JEDEC Flash基本参数表格里面规范和定义了该器件的一些最基本的读取方式、指令内容、扇区大小和芯片容量等信息:

ef6fd76024475cb4424f4f5b8df989a7.png

4.3. 添加库目前不支持的 Flash

如果你使用的Flash型号比较老或者不支持SFDP,SFUD库当然考虑到了这一点,所以提供了Flash设备参数表,在sfdu_flash_def.h文件的 SFUD_FLASH_CHIP_TABLE 就能看到当前所有支持的 Flash:

b4cebc72d60dbb6fc010b3fdacccb874.png

如果你使用的Flash型号既不支持SFDP,也不在此Flash设备参数表中,那么就需要手动添加到该设备参数表中才可以正常使用。

具体的添加方式请参考SFUD项目的README文档中2.5节,讲述的非常详细。

5. 项目工程源码获取和问题交流

目前我将SFUD源码、我移植到小熊派STM32L431RCT6开发板的工程源码上传到了QQ群里(包含好几份HAL库,QQ相对速度快点),可以在QQ群里(群号:1040480463)下载,有问题也可以在群里交流,当然也欢迎大家分享出来自己移植的工程到QQ群里

cf7e3518305666860c0e5219f44b8df4.png

接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值