目录
[1. 准备工作](#1. 准备工作)
[2. 移植](#2. 移植)
[2.1 debug接口](#2.1 debug接口)
[2.2 SPI读写函数](#2.2 SPI读写函数)
[2.3 初始化](#2.3 初始化)
[3. 配置](#3. 配置)
[3.1 SFUD_USING_SFDP](#3.1 SFUD_USING_SFDP)
[3.2 SFUD_USING_FLASH_INFO_TABLE](#3.2 SFUD_USING_FLASH_INFO_TABLE)
[3.3. SFUD_FLASH_DEVICE_TABLE](#3.3. SFUD_FLASH_DEVICE_TABLE)
[3.4 SFUD_USING_QSPI](#3.4 SFUD_USING_QSPI)
[4. 实例](#4. 实例)
[4.1 获取flash设备对象](#4.1 获取flash设备对象)
[4.2 擦除](#4.2 擦除)
[4.3 写](#4.3 写)
[4.4 读](#4.4 读)
[4.5 擦写](#4.5 擦写)
[4.6 读写状态](#4.6 读写状态)
[5. FT4222H QSPI验证](#5. FT4222H QSPI验证)
[5.1 移植](#5.1 移植)
[5.1.1 debug接口](#5.1.1 debug接口)
[5.1.2 SPI读写函数](#5.1.2 SPI读写函数)
[5.1.2.1 spi_write_read](#5.1.2.1 spi_write_read)
[5.1.2.2 qspi_read](#5.1.2.2 qspi_read)
[5.1.3 初始化](#5.1.3 初始化)
[5.2 配置](#5.2 配置)
[5.3 初始化SFUD](#5.3 初始化SFUD)
[5.4 验证](#5.4 验证)
[5.4.1 使能QE](#5.4.1 使能QE)
[5.4.2 QSPI读](#5.4.2 QSPI读)
SFUD(Serial Flash Universal Driver)是开源(可免费商用)的SPI Flash通用驱动库。硬件平台采用STM32F103,SPI接口的Nor Flash。可以参考SFUD里面的README.md里面的移植教程。
1. 准备工作
下载SFUD的源代码,在SFUD源代码目录sfud内有3个目录

inc是头文件,src是源代码,将src和inc内的文件拷贝到自己的工程。其中src里面的代码不需要改动,属于SFUD的核心部分,而inc中有4个h文件,其中sfud_cfg.h是sfud的配置文件,这是用户主要需要改动的地方。
而文件夹port是移植部分的参考代码。
首先SPI接口需要先准备好,这里定义spi的读写函数如下:
cpp
void spim_transfer(uint8_t port, uint8_t* wr_buf, uint8_t* rd_buf, uint16_t len);
#define spim_write(port, buf, len) spim_transfer(port, buf, NULL, len)
#define spim_read(port, buf, len) spim_transfer(port, NULL, buf, len)
2. 移植
这步参考文件夹port中的sfud_port.c文件。
2.1 debug接口
sfud_cfg.h中有定义SFUD_DEBUG_MODE,表示打开调试模式,需要实现对应的2个log函数
cpp
void sfud_log_debug(const char *file, const long line, const char *format, ...)
void sfud_log_info(const char *format, ...)
为了简化这部分,将sfud_def.h中的SFUD_DEBUG和SFUD_INFO改成直接调用Printf以节约代码。
cpp
#ifndef SFUD_DEBUG
#define SFUD_DEBUG Printf //(...) sfud_log_debug(__FILE__, __LINE__, __VA_ARGS__)
#endif /* SFUD_DEBUG */
#else
#define SFUD_DEBUG(...)
#endif /* SFUD_DEBUG_MODE */
#ifndef SFUD_INFO
#define SFUD_INFO Printf //(...) sfud_log_info(__VA_ARGS__)
#endif
2.2 SPI读写函数
函数原型:
cpp
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)
这个函数类似spim_transfer,表示SPI的双工通信。特别的是参数spi,用户需要先得到该变量的成员变量user_data,这个变量是void *,是用户自己定义的。
对于SPI的读写,需要知道flash的CS接的哪个gpio, 使用MCU的哪个SPI,可以用结构体表示这个变量:
cpp
typedef struct {
uint8_t spi;
uint8_t cs;
}sfud_user_data_s;
函数实现如下:
cpp
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_user_data_s *spi_dev = (sfud_user_data_s *)spi->user_data;
gpio_out_low(spi_dev->cs);
spim_transfer(spi_dev->spi, write_buf, write_size, read_buf, read_size);
gpio_out_high(spi_dev->cs);
return SFUD_SUCCESS;
}
2.3 初始化
函数原型:
cpp
sfud_err sfud_spi_port_init(sfud_flash *flash)
主要就是配置这个参数flash。
SFUD是可以支持多个flash的,所以这里可以定义个结构体数组:
cpp
const sfud_user_data_s sflash[] =
{
{SFLASH_SPIx, SFLASH_PIN_CSx},
};
这里只有一个flash的情况,如果是多个就需要放多个配置,这个由用户决定。
cpp
sfud_err sfud_spi_port_init(sfud_flash *flash)
{
flash->spi.wr = spi_write_read;
flash->spi.lock = NULL;
flash->spi.unlock = NULL;
flash->spi.user_data = (void *)&sflash[flash->index];
flash->retry.delay = sfu_delay;
flash->retry.times = 60 * 10000;
return SFUD_SUCCESS;
}
成员变量retry表示出错后重试的参数,delay是延时函数,times是重试次数。
cpp
static void sfu_delay(void)
{
delayus(100);
}
3. 配置
3.1 SFUD_USING_SFDP
是否使用SFDP参数功能,关闭后只会查询该库在 /sfud_flash_def.h中提供的 Flash 信息表。
SFDP(Serial Flash Discoverable Parameters)是JEDEC组织定义的一种标准规范(JEDEC标准JESD216系列),用于自动发现和识别串行Flash存储器的特性和功能参数。
3.2 SFUD_USING_FLASH_INFO_TABLE
是否使用该库自带的 Flash 参数信息表(从sfud_flash_def.h中的SFUD_FLASH_CHIP_TABLE中列出来的flash),关闭后该库只驱动支持 SFDP 规范的 Flash。
3.3. SFUD_FLASH_DEVICE_TABLE
这个是flash设备列表,这个表是用户配置添加。例如flash型号是GD25Q32
cpp
enum {
SFUD_GD25Q32_DEVICE_INDEX = 0,
};
#define SFUD_FLASH_DEVICE_TABLE \
{ \
[SFUD_GD25Q32_DEVICE_INDEX] = {.name = "GD25Q32", .spi.name = "SPI1"}, \
}
这里的.name并不需要按照芯片名字写,是可以顺便定义的。
3.4 SFUD_USING_QSPI
开启后,SFUD 也将支持使用 QSPI 总线连接的 Flash,一般关掉。
4. 实例
在初始化SPI后,调用sfud_init就会遍历SFUD_FLASH_DEVICE_TABLE这个表里面的flash进行初始化
cpp
sfud_init();
如果flash读写有问题,就只会打印下面2行:
cpp
Start initialize Serial Flash Universal Driver(SFUD) V1.1.0.
You can get the latest version on https://github.com/armink/SFUD .
如果读写成功,则打印flash的基本信息:
cpp
The flash device manufacturer ID is 0xC8, memory type ID is 0x40, capacity ID is 0x16.
Check SFDP header is OK. The reversion is V1.0, NPN is 1.
Check JEDEC basic flash parameter header is OK. The table id is 0, reversion is V1.0, length is 9, parameter table pointer is 0x000030.
JEDEC basic flash parameter table info:
MSB-LSB 3 2 1 0
[0001] 0xFF 0xF1 0x20 0xE5
[0002] 0x01 0xFF 0xFF 0xFF
[0003] 0x6B 0x08 0xEB 0x44
[0004] 0xBB 0x42 0x3B 0x08
[0005] 0xFF 0xFF 0xFF 0xEE
[0006] 0xFF 0x00 0xFF 0xFF
[0007] 0xFF 0x00 0xFF 0xFF
[0008] 0x52 0x0F 0x20 0x0C
[0009] 0xFF 0x00 0xD8 0x10
4 KB Erase is supported throughout the device. Command is 0x20.
Write granularity is 64 bytes or larger.
Target flash status register is non-volatile.
3-Byte only addressing.
Capacity is 4194304 Bytes.
Flash device supports 4KB block erase. Command is 0x20.
Flash device supports 32KB block erase. Command is 0x52.
Flash device supports 64KB block erase. Command is 0xD8.
Find a GigaDevice flash chip. Size is 4194304 bytes.
Flash device reset success.
GD25Q32 flash device is initialize success.
只关闭SFUD_USING_FLASH_INFO_TABLE资源使用情况:
cpp
Ram Size: 0x5000(20KB), used: 1960(9.570313%)
Rom Size: 0x10000(64KB), used: 10739(16.38641%)
只关闭SFUD_USING_SFDP资源使用情况:
cpp
Ram Size: 0x5000(20KB), used: 1912(9.335938%)
Rom Size: 0x10000(64KB), used: 6933(10.57892%)
同时关闭SFUD_USING_FLASH_INFO_TABLE和SFUD_USING_SFDP的情况:
cpp
Ram Size: 0x5000(20KB), used: 1912(9.335938%)
Rom Size: 0x10000(64KB), used: 5881(8.973694%)
这种情况不能通过调用sfud_init初始化,会告知不支持设备
cpp
Start initialize Serial Flash Universal Driver(SFUD) V1.1.0.
You can get the latest version on https://github.com/armink/SFUD .
The flash device manufacturer ID is 0xC8, memory type ID is 0x40, capacity ID is 0x16.
Warning: This flash device is not found or not support.
Error: GD25Q32 flash device is initialize fail.
这种情况则需要通过sfud_device_init初始化设备,先设定好flash的参数:
cpp
sfud_flash sfud_norflash0 = {
.name = "norflash0",
.spi.name = "SPI1",
.chip = { "GD25Q32", SFUD_MF_ID_GIGADEVICE, 0x40, 0x16, 4L * 1024L * 1024L, SFUD_WM_PAGE_256B, 4096, 0x20 } };
关键是配置chip变量
cpp
typedef struct {
char *name; /**< flash chip name */
uint8_t mf_id; /**< manufacturer ID */
uint8_t type_id; /**< memory type ID */
uint8_t capacity_id; /**< capacity ID */
uint32_t capacity; /**< flash capacity (bytes) */
uint16_t write_mode; /**< write mode @see sfud_write_mode */
uint32_t erase_gran; /**< erase granularity (bytes) */
uint8_t erase_gran_cmd; /**< erase granularity size block command */
} sfud_flash_chip;
这里mf_id, type_id, capacity_id来源于flash的ID:

capacity是flash的容量大小,write_mode可以看datasheet里面芯片特性:

erase_gran是设置擦除单位,同样看datasheet,最小的擦除单位是sector,一个sector是4KByte,也可以用block擦除,看客户自己定义,一般会选择sector擦除。

erase_gran_cmd是擦除命令,和erase_gran设置对应的命令有关,例如sector擦除的话就是0x20

将sfud_init替换为sfud_device_init
cpp
//sfud_init();
sfud_device_init(&sfud_norflash0);
打印信息如下:
bash
Find a GigaDevice GD25Q32 flash chip. Size is 4194304 bytes.
Flash device reset success.
norflash0 flash device is initialize success.
4.1 获取flash设备对象
所有的flash操作都需要flash设备对象作为参数,所以一般需要先获取这个对象指针(如果是使用sfud_device_init初始化则直接用sfud_norflash0,注意这点是必须的,sfud_get_device获取的没有对应sfud_norflash0)
cpp
sfud_flash *flash;
flash = &sfud_norflash0;//sfud_get_device(SFUD_GD25Q32_DEVICE_INDEX);
if(flash == NULL)
{
Printf("flash is found\r\n");
}
else
{
}
4.2 擦除
擦除有2个函数sfud_erase和sfud_chip_erase,sfud_chip_erase是将整个flash擦除,而sfud_erase的原型是:
cpp
sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size)
注意起始地址和擦除数据大小按照 Flash 芯片的擦除粒度erase_gran对齐,否则执行擦除操作后,将会导致其他数据丢失。
cpp
if(sfud_erase(flash, 0, flash->chip.erase_gran) != SFUD_SUCCESS)
Printf("Erase flash fail\r\n");
4.3 写
写函数的原型:
cpp
sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data)
测试从地址0写入16个字节数据
cpp
const uint8_t wr_buf[16] = {
0xAA, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x55,
};
if(sfud_write(flash, 0, 16, wr_buf) != SFUD_SUCCESS)
Printf("Write flash fail\r\n");
4.4 读
读函数的原型:
cpp
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data)
从之前写的地址将16字节读回来,并打印
cpp
uint8_t rd_buf[16];
uint8_t i;
if(sfud_read(flash, 0, 16, rd_buf) != SFUD_SUCCESS)
Printf("Read flash fail\r\n");
for(i = 0; i < 16; i++)
{
Printf("0x%02x ", rd_buf[i]);
if(((i + 1) % 8) == 0)
Printf("\n");
}
Printf("\n");
打印结果如下:
cpp
Find a GigaDevice GD25Q32 flash chip. Size is 4194304 bytes.
Flash device reset success.
norflash0 flash device is initialize success.
0xaa 0x01 0x02 0x03 0x04 0x05 0x06 0x07
0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x55
4.5 擦写
函数原型:
cpp
sfud_err sfud_erase_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data)
这个函数功能类似写,区别是这个会先擦除,类似先调用sfud_erase再调用sfud_write,所以参数要求也必须符合sfud_erase的要求。
cpp
for(i = 0; i < 16; i++)
rd_buf[i] = 0;
if(sfud_erase_write(flash, 0, 16, wr_buf) != SFUD_SUCCESS)
Printf("Erase/write flash fail\r\n");
if(sfud_read(flash, 0, 16, rd_buf) != SFUD_SUCCESS)
Printf("Read flash fail\r\n");
for(i = 0; i < 16; i++)
{
Printf("0x%02x ", rd_buf[i]);
if(((i + 1) % 8) == 0)
Printf("\n");
}
Printf("\n");
4.6 读写状态
读状态对应命令0x05,写状态对应命令0x01
cpp
#define SFUD_CMD_READ_STATUS_REGISTER 0x05
#define SFUD_CMD_WRITE_STATUS_REGISTER 0x01



对应的函数原型:
cpp
sfud_err sfud_read_status(const sfud_flash *flash, uint8_t *status)
sfud_err sfud_write_status(const sfud_flash *flash, bool is_volatile, uint8_t status)
对于写状态函数多了一个is_volatile的参数,这个参数表示写寄存器的使能,如果为true,则表示状态寄存器的使能命令是50H,例如GD25Q32的这个变量需要设置为true

否则就是06H

5. FT4222H QSPI验证
实用FT4222H的QSPI接口验证SFUD的QSPI功能。官方软件下载
https://ftdichip.com/wp-content/uploads/2025/06/LibFT4222-v1.4.8.zip
https://ftdichip.com/wp-content/uploads/2025/06/LibFT4222-v1.4.8.zip对应sfud工程下载地址:https://gitee.com/pq113_6/ft4222h_sfud.git
解压后进入examples\flash_example文件夹内,复制文件夹spi_flash_quad_test_mxic为spi_flash_sfud,将这个文件夹内的所有文件名改为spi_flash_sfud.*,如下图:

用记事本编辑spi_flash_sfud.pro,将spi_flash_quad_test_mxic改为spi_flash_sfud,同样的方式修改文件spi_flash_sfud_static.pro。
Visual Studio打开samples文件夹下的LibFT4222_examples.sln,右键选择添加->现有项目

将spi_flash_sfud.vcxproj添加到工程中,选中这个工程,右键点击"设为启动项目"

将工程中的spi_flash_quad_test_mxic.cpp文件移除,再添加现有项目spi_flash_sfud.cpp,右键项目属性,将目标文件名改为sfi_flash_sfud


将sfud的inc/src/port三个文件夹拷贝到当前项目文件夹内,然后配置工程的头文件目录,增加inc目录。

此时编译工程应该可以通过。
5.1 移植
5.1.1 debug接口
在VS中可以不用改,因为不存在节约ROM的问题,sfud_port.c里面的接口函数会直接调用printf函数。
5.1.2 SPI读写函数
SFUD的SPI和QSPI接口函数不同,STM32平台实现的是SPI接口,这里也添加好。先在sfud_port.c添加FT4222H相关的头文件。
cpp
#include "ftd2xx.h"
#include "LibFT4222.h"
5.1.2.1 spi_write_read
同样先定义用户变量
cpp
typedef struct {
FT_HANDLE handle;
}sfud_user_data_s;
handle是对应FT4222H的句柄。
cpp
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_user_data_s* spi_dev = (sfud_user_data_s*)spi->user_data;
FT4222_STATUS ftStatus = FT4222_OK;
uint16_t sizeTransferred = 0;
bool endTransfer = true;
if ((read_buf != NULL) && (read_size > 0))
endTransfer = false;
if ((write_buf != NULL) && (write_size > 0))
{
ftStatus = FT4222_SPIMaster_SingleWrite(
spi_dev->handle,
(uint8_t*)write_buf,
write_size,
&sizeTransferred,
endTransfer
);
if (ftStatus != FT4222_OK)
return SFUD_ERR_NOT_FOUND;
if (sizeTransferred != write_size)
return SFUD_ERR_WRITE;
}
if ((read_buf != NULL) && (read_size > 0))
{
ftStatus = FT4222_SPIMaster_SingleRead(
spi_dev->handle,
read_buf,
read_size,
&sizeTransferred,
true
);
if (ftStatus != FT4222_OK)
return SFUD_ERR_NOT_FOUND;
if (sizeTransferred != read_size)
return SFUD_ERR_READ;
}
return SFUD_SUCCESS;
}
程序没有处理write_size不等于read_size(非零)的情况,默认认为write_size一般情况等于read_size的情况。
5.1.2.2 qspi_read
对于QSPI接口,只定义了读操作。这个函数的原型如下:
cpp
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)
参数说明:
spi - 指向SPI设备配置结构的指针
addr - 要读取的Flash存储器起始地址
qspi_read_cmd_format - 核心配置结构体,含义如下
| 字段 | 含义 | 取值范围 | 典型值 |
|---|---|---|---|
| instruction | QSPI命令码 | 0x00-0xFF | 0x03(标准读), 0x0B/0x0E(快速读) |
| instruction_lines | 指令阶段线数 | 1/2/4 | 1(单线), 4(四线) |
| address_size | 地址字节数 | 3-4字节 | 3(24位地址), 4(32位地址) |
| address_lines | 地址阶段线数 | 1/2/4 | 4(四线传输) |
| alternate_bytes_lines | 备用字节线数 | 0-4 | 0(未使用) |
| dummy_cycles | 空操作周期数 | 0-255 | 8(典型快速读) |
| data_lines | 数据阶段线数 | 1/2/4 | 4(四线高速传输) |
read_buf - 存储读取数据的缓冲区指针
read_size - 要读取的字节数
首先参数检查和定义一些变量
cpp
// 参数有效性检查
if (!read_buf || read_size == 0)
return SFUD_ERR_NOT_FOUND;
if (!qspi_read_cmd_format)
return SFUD_ERR_NOT_FOUND;
sfud_err result = SFUD_SUCCESS;
sfud_user_data_s* spi_dev = (sfud_user_data_s*)spi->user_data;
FT4222_STATUS ftStatus = FT4222_OK;
uint16_t sizeTransferred = 0;
接下来就是构建读命令,命令格式分3部分,第一部分是1个字节的指令,由qspi_read_cmd_format->instruction指定;第二部分是3或4个字节长度的地址,该长度由qspi_read_cmd_format->address_size指定;第三部分是dummy cycles数,即地址发送结束后设备需要多少个空闲时钟才能发送数据,由qspi_read_cmd_format->dummy_cycles指定,这个dummy一般不会超过3个字节,这里分配16个字节。dummy部分的数据位数是和地址部分一样的,比如addr是QSPI格式,那么dummy也是QSPI。(8 / qspi_read_cmd_format->address_lines)计算的是一个clock是多少位数据,所以qspi_read_cmd_format->dummy_cycles / (8 / qspi_read_cmd_format->address_lines)就是计算需要多少个dummy字节,比如dummy clock要求6个clock,采用QSPI方式,那么dummy byte为6/(8 / 4)=3字节。
cpp
// 构建QSPI命令帧
uint8_t cmd_frame[5 + 16] = { 0 }; // 最大支持4字节地址+1字节指令+16字节dummy
size_t cmd_len = 0;
uint8_t dummy_byte = 0;
// 添加指令
cmd_frame[cmd_len++] = qspi_read_cmd_format->instruction;
// 添加地址(按指定字节数)
for (uint8_t i = 0; i < qspi_read_cmd_format->address_size / 8; i++)
{
cmd_frame[cmd_len++] = (addr >> (8 * (qspi_read_cmd_format->address_size / 8 - 1 - i))) & 0xFF;
}
// 添加dummy cycles(如果有)
if (qspi_read_cmd_format->dummy_cycles > 0)
{
for (uint16_t i = 0; i < qspi_read_cmd_format->dummy_cycles / (8 / qspi_read_cmd_format->address_lines); i++)
{
cmd_frame[cmd_len++] = 0xff; // dummy byte
dummy_byte++;
}
}
API函数FT4222_SPIMaster_MultiReadWrite是将所有命令(SPI和DSPI/QSPI命令)发送出去,所以需要先计算出SPI发送数据数。
cpp
size_t single_len = 0;
if (qspi_read_cmd_format->instruction_lines < 2)
{
single_len++;
}
if (qspi_read_cmd_format->address_lines < 2)
{
single_len += qspi_read_cmd_format->address_size / 8;
single_len += dummy_byte;
}
将FT4222H设置为DSPI或QSPI模式
cpp
if(qspi_read_cmd_format->data_lines == 2)
ftStatus = FT4222_SPIMaster_SetLines(spi_dev->handle, SPI_IO_DUAL);
else
ftStatus = FT4222_SPIMaster_SetLines(spi_dev->handle, SPI_IO_QUAD);
if (FT4222_OK != ftStatus)
return SFUD_ERR_NOT_FOUND;
执行DSPI或QSPI读取
cpp
ftStatus = FT4222_SPIMaster_MultiReadWrite(
spi_dev->handle,
read_buf,
cmd_frame,
single_len,
cmd_len - single_len,
read_size,
&sizeTransferred
);
if (ftStatus != FT4222_OK)
{
SFUD_DEBUG("qspi read fail=%d\n", ftStatus);
return SFUD_ERR_NOT_FOUND;
}
将FT4222H恢复为SPI模式
cpp
ftStatus = FT4222_SPIMaster_SetLines(spi_dev->handle, SPI_IO_SINGLE);
if (FT4222_OK != ftStatus)
return SFUD_ERR_NOT_FOUND;
5.1.3 初始化
在spi_flash_sfud.cpp中删除所有关于flash部分的代码,保留FT4222H的部分。
main函数中默认是打开的第一个FT4222H设备,这里也保留这样的配置,
cpp
static sfud_user_data_s sflash[1];
这里只定义一个sflash,打开的设备句柄保存到这个sflash变量中。
cpp
ftStatus = FT_OpenEx((PVOID)g_FT4222DevList[0].SerialNumber, FT_OPEN_BY_SERIAL_NUMBER, &ftHandle);
if (FT_OK != ftStatus)
{
printf("Open a FT4222 device failed!\n");
return 0;
}
sflash[0].handle = ftHandle;
同样,实现sfud_spi_port_init函数,其中需要初始化qspi_read这个变量
cpp
static void sfu_delay(void)
{
Sleep(1);
}
sfud_err sfud_spi_port_init(sfud_flash* flash)
{
flash->spi.wr = spi_write_read;
flash->spi.lock = NULL;
flash->spi.unlock = NULL;
flash->spi.user_data = (void*)&sflash[flash->index];
#ifdef SFUD_USING_QSPI
flash->spi.qspi_read = qspi_read;
#endif
flash->retry.delay = sfu_delay;
flash->retry.times = 60 * 1000;
return SFUD_SUCCESS;
}
5.2 配置
主要需要打开SFUD_USING_QSPI。
5.3 初始化SFUD
在初始化FT4222H后,初始化SFUD
cpp
printf("****************** SFUD **********************\n");
sfud_init();
sfud_flash* sfud_flash = sfud_get_device(SFUD_XXXX_DEVICE_INDEX);
sfud_device_init(sfud_flash);
这里验证的是SPI的通信,可以看到flash的信息就说明SPI通信已经OK。
cpp
[SFUD]Find a GigaDevice flash chip. Size is 4194304 bytes.
对于QSPI接口,还需将自己板子上的NOR FLASH型号添加到sfud_flash_def.h的SFUD_FLASH_EXT_INFO_TABLE中,例如这里使用的是GD25Q32
cpp
/* GD25Q32 */ \
{SFUD_MF_ID_GIGADEVICE, 0x40, 0x16, NORMAL_SPI_READ|QUAD_OUTPUT}, \
对于GD25Q32,可以查看datasheet看命令的支持情况,

芯片可以支持DUAL_OUPUT, DUAL_IO, QUAD_OUTPUT和QUAD_IO四种方式。
从程序代码来看,程序是优先选择IO方式的,如下代码是QSPI接口的配置:
cpp
if (read_mode & QUAD_IO) {
qspi_set_read_cmd_format(flash, SFUD_CMD_QUAD_IO_READ_DATA, 1, 4, 6, 4);
} else if (read_mode & QUAD_OUTPUT) {
qspi_set_read_cmd_format(flash, SFUD_CMD_QUAD_OUTPUT_READ_DATA, 1, 1, 8, 4);
} else {
qspi_set_read_cmd_format(flash, SFUD_CMD_READ_DATA, 1, 1, 0, 1);
}
OUTPUT和IO的区别是对应的命令不同,注意这里dummy的区别,IO是6位dummy,而OUTPU是8位dummy。
由于SFUD对于QUAD_IO默认支持6位dummy clock(即支持的是3字节dummy,因为一个dummy clock是4位数据,6位dummy clock就是3个字节),这里选择只支持QUAD_OUTPUT。可以查一下6BH命令的时序图

所以qspi_read部分需要配合这部分时序实现
5.4 验证
和STM32平台一样的测试代码。
cpp
if (sfud_erase(sfud_flash, 0, sfud_flash->chip.erase_gran) != SFUD_SUCCESS)
printf("Erase flash fail\r\n");
const uint8_t wr_buf[16] = {
0xAA, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x55,
};
if (sfud_write(sfud_flash, 0, 16, wr_buf) != SFUD_SUCCESS)
printf("Write flash fail\r\n");
uint8_t rd_buf[16];
uint8_t i;
if (sfud_read(sfud_flash, 0, 16, rd_buf) != SFUD_SUCCESS)
printf("Read flash fail\r\n");
for (i = 0; i < 16; i++)
{
printf("0x%02x ", rd_buf[i]);
if (((i + 1) % 8) == 0)
printf("\n");
}
printf("\n");
这段代码只测试了SPI接口,一般不会有问题。接下来验证QSPI部分。
5.4.1 使能QE
SFDU没有写QE部分的代码,所以这部分需要根据自己的平台写这部分代码。GD25Q32的设置方式是0x31写状态寄存器2的bit 2为1。
cpp
// GD25Q32E状态寄存器定义
#define GD25Q32E_CMD_WR_EN 0x06 // 写使能
#define GD25Q32E_CMD_WR_DIS 0x04 // 写使能禁用
#define GD25Q32E_CMD_RD_SR 0x35 // 读取状态寄存器2
#define GD25Q32E_CMD_WR_SR 0x01 // 写状态寄存器
#define GD25Q32E_ADDR_SR2 0x31 // 状态寄存器2地址
/**
* GD25Q32E QE使能函数
* @param flash SFUD闪存设备对象
* @return SFUD_SUCCESS 或错误码
*/
sfud_err gd25q32e_enable_qe(sfud_flash* flash) {
uint8_t cmd[4];
sfud_err result;
cmd[0] = GD25Q32E_CMD_WR_EN;
result = flash->spi.wr(&flash->spi, cmd, 1, NULL, 0);
if (result != SFUD_SUCCESS)
return result;
cmd[0] = GD25Q32E_ADDR_SR2;
cmd[1] = 0x02; // 设置QE位
result = flash->spi.wr(&flash->spi, cmd, 2, NULL, 0);
if (result != SFUD_SUCCESS)
return result;
uint8_t status;
cmd[0] = GD25Q32E_CMD_RD_SR;
result = flash->spi.wr(&flash->spi, cmd, 1, &status, 1);
if ((result != SFUD_SUCCESS) || !(status & 0x02)) {
printf("Set QE fail, status=0x%02x\n", status);
return SFUD_ERR_WRITE;
}
return SFUD_SUCCESS;
}
5.4.2 QSPI读
在使能QE后,需调用sfud_qspi_fast_read_enable设置DSPI(参数为2)或QSPI(参数为4)
cpp
#ifdef SFUD_USING_QSPI
printf("******QSPI Read Test******\n");
if (gd25q32e_enable_qe(sfud_flash) == SFUD_SUCCESS)
{
sfud_qspi_fast_read_enable(sfud_flash, 4);
if (sfud_read(sfud_flash, 0, 16, rd_buf) != SFUD_SUCCESS)
printf("Read flash fail\r\n");
for (i = 0; i < 16; i++)
{
printf("0x%02x ", rd_buf[i]);
if (((i + 1) % 8) == 0)
printf("\n");
}
}
else
{
printf("Set QE fail\n");
}
#endif