SFUD简介
SFUD是一个通用SPI Flash驱动库,通过SFUD可以库轻松完成对SPI Flash的读/擦/写的基本操作,而不用自己去看手册,写代码造轮子。但是SFUD的功能不仅仅于此:①通过SFUD库可以实现在一个项目中对多个Flash的同时驱动,只要把他们注册到SFUD中即可 ②嵌入式产品因为供货问题替换芯片型号是常有的事,如果我们的驱动代码是针对某一个具体的Flash而写的,那么每换一个型号可能就要修改一下代码。SFUD库通兼容市面上绝大多数的Flash型号,还可以在运行时感知Flash芯片的基本属性,因此替换Flash型号几乎不用修改代码。
本文先介绍SFUD的设计思路,最后再演示了SFUD库在STM32上的移植。
Flash设备上下文结构体
在SFUD中每一个Flash设备用一个设备上下文结构体 sfud_flash 类型来表示,这个结构体变量存储的信息包括:Flash设备的固有属性(如容量大小,厂商ID,擦除粒度,擦除操作指令码);Flash设备使用的SPI接口;Flash设备在 flash_table 中的索引等信息。
通过这个 sfud_flash 上下文结构体,SFUD就可以知道每个Flash设备的操作方法和状态。这个结构体有许多成员,有些通过用户初始化,有些则通过SFUD库自动初始化。
typedef struct {char *name; /* 设备名称 */size_t index; /* 设备在flash_table中的索引 */sfud_flash_chip chip; /* 设备信息 */sfud_spi spi; /* 设备所使用的SPI接口 */bool init_ok; /* 初始化状态标志 */bool addr_in_4_byte; /* 是否使用4字节地址寻址模式 */struct { /* 向设备发送指令时等待响应过程的延时和尝试次数 */void (*delay)(void); /* 设备操作延时等待函数 */size_t times; /* 等待设备操作完成的重试次数 */} retry;void *user_data; /* 用户自定义数据 */#ifdef SFUD_USING_QSPI /* 如果启用QSPI接口 */sfud_qspi_read_cmd_format read_cmd_format; /* 用于支持QSPI */
#endif#ifdef SFUD_USING_SFDP /* 如果启用SFDP机制 */sfud_sfdp sfdp; /* 存储设备的可发现参数 */
#endif} sfud_flash, *sfud_flash_t;
下面简单的介绍各个成员的含义,后面会详细介绍:
- name:用户自定义的Flash设备的名称,仅仅用于方便调试观察。由用户初始化
- index:当前这个 sfud_flash上下文对象 在 flash_table 中的索引。由SFUD库根据用户配置来初始化
- chip:记录Flash设备的物理信息,例如芯片型号名称,厂商ID,容量ID,擦除粒度,擦除指令等。这些信息可以由用户初始化,也可以交给SFUD库来初始化
- spi:定义了操作Flash设备所使用的SPI接口,如SPI接口名,SPI的读/写操作函数实现等,由用户初始化
- init_ok:代表Flash设备是否初始化成功,由SFUD库内部设置
- addr_in_4_byte:代表Flash芯片是否使用4字节存储地址编码模式,如果芯片容量大于16MB (256Mb),则需要开启4字节地址寻址模式。由SUDP库根据芯片容量来自动初始化
- retry:定义了等待Flash操作执行完成过程中的延时等待和失败尝试次数。SFUD库在操作Flash时,会读取Flash的busy状态,如果Flash处于busy状态,则需要等待Flash退出busy状态。等待过程中,每次先delay(),然后times--,直到Flash变为非busy状态正常退出,或者times减为0则判定Flash设备超时错误。由用户初始化
- user_data:与Flash设备相关的用户自定义数据,由用户初始化
- read_cmd_format:QSPI操作时使用的快速读取指令格式,当启用QSPI模式时由SUDP库内部初始化
- sfdp:存储Flash的可发现参数,当启用SFDP机制时由SUDP库内部初始化
Flash设备表
SFUD支持同时驱动多个Flash物理设备,每一个Flash设备通过结构体 sfud_flash 类型来表示。工程中所有用到的Flash设备通过一个全局数组 flash_table 记录。例如你的工程中只有一个Flash设备,那么 flash_table 数组中就只有一个 sfud_flash 类型元素。
对于工程中用到的每个Flash设备,都需要通过配置文件 sfud_cfg.h 来进行注册,这个过程实际上就是定义并初始化 flash_table 中的每个 sfud_flash 元素。这样SFUD就知道了我们用到了多少Flash设备,以及他们的基本信息。
对于工程中用到的每个Flash设备,需要在 sfud_cfg.h 中:
- 要为他定义一个枚举常量,这个枚举常量的值用于表示Flash设备对应的sfud_flash上下文对象在flash_table 中的索引,也作为这个Flash设备的sfud_flash上下文对象在SFUD库中的ID,这样就可以通过ID很方便的引用这个Flash设备的sfud_flash上下文对象
- 初始化flash_table中这个Flash设备对应的sfud_flash上下文对象的成员
前面我们介绍了sfud_flash 类型的成员的含义,如果每个sfud_flash的全部的成员都需要用户来定义的话,那样实在太繁琐。庆幸的是SFUD库可以自动初始化sfud_flash的绝大部分成员,在最简配置情况下,我们只需定义sfud_flash结构体的name成员即可。
注意,指定数组下标初始化和指定结构体成员初始化方式都是C99才开始支持的语法,因此需要在keil中勾选C99 模式。
/*-----------------------sfud_cfg.h 配置文件-------------------------------------*/
enum
{SFUD_W25Q64CV_DEVICE_INDEX = 0, //定义Flash设备在flash_table中的索引SFUD_GD25Q64B_DEVICE_INDEX = 1, //定义Flash设备在flash_table中的索引
};//定义并初始化flash_table中每个sfud_flash上下文对象
//static sfud_flash flash_table[] = SFUD_FLASH_DEVICE_TABLE;
#define SFUD_FLASH_DEVICE_TABLE \
{ \[SFUD_W25Q64CV_DEVICE_INDEX] = {.name = "my_W25Q64CV", }, \[SFUD_GD25Q64B_DEVICE_INDEX] = {.name = "my_GD25Q64B", }, \
}
SFDP可发现参数
前面我们提到 sfud_flash 上下文结构体中有些通过用户初始化,有些则通过SFUD自动初始化。其中chip成员是非常重要的一个成员,它可以由用户手动初始化(前提是用户通过查询Flash芯片的手册,知晓这些信息并正确填写这些信息),但更方便的情况是由SFUD库来初始化。那么SFUD具体是通过什么手段获取Flash设备的相关信息并正确初始化的呢?这里介绍SFDP机制可以实现这个功能。
SFDP(Serial Flash Discoverable Parameters)是JEDEC(固态技术协会)规范的一个标准,所有遵循这个标准的Flash设备会在芯片内部存储自己的一个参数表,该表中会存放 Flash 容量、写粒度、擦除指令码、地址模式等规格参数。并允许外部主控通过SPI接口,以固定通信协议来读取到这些参数表中的信息。这样就可以在运行时获取到Flash的信息,而无需提前硬编码。
在SFUD库配置文件sfud_cfg.h中,通过开关宏 SFUD_USING_SFDP 来启用对这个特性的支持。当启用这个宏后,SFUD库的源文件 sfud_sfdp.c 将参与编译,在Flash设备初始化时,会调用sfud_read_sfdp()函数执行sfdp可发现参数读取操作,读取到的参数存储在Flash设备上下文结构体的sfud_sfdp sfdp 成员中,然后再用sfdp参数初始化chip成员。这样就完成了chip成员的初始化。
如果你实际使用的Flash设备不支持JEDEC SFDP标准,则SFUD库在运行时会通过日志输出提示。
/*---------------------sfud_cfg.h 配置文件-------------------------*/
//如果需要启用SFDP,则定义这个宏
#define SFUD_USING_SFDP/*--------------------- sfud_sfdp.c SFDP机制实现-------------------------*/
//读取sfdp参数
bool sfud_read_sfdp(sfud_flash *flash) {SFUD_ASSERT(flash);/* JEDEC basic flash parameter header */sfdp_para_header basic_header;if (read_sfdp_header(flash) && read_basic_header(flash, &basic_header)) {return read_basic_table(flash, &basic_header);} else {SFUD_INFO("Warning: Read SFDP parameter header information failed. The %s does not support JEDEC SFDP.", flash->name);return false;}
}
内置的Flash芯片信息表
使用SFDP固然很方便,但是并不是所有的Flash芯片都支持,另外引入SFDP会使得代码体积增加,同时在初始化时也需要额外的SPI通信来完成。如果你不想或者不能通过SFDP来初始化Flash设备,则可以使用另一种方式,即内置的Flash芯片信息表。
这个机制的原理很简单,就是将市面上常见的Flash的信息用表的形式列举出来,如果你使用的Flash的厂商ID,类型ID,容量ID与Flash芯片信息表中列举的某一个匹配,则当前设备就采用表中记录的信息来初始化。这个机制简单粗暴,甚至你还可以继续补全这个表,将你所使用的Flash芯片的信息列举到其中,来使得SFUD库支持你的某个特殊的Flash芯片。缺点也很明显,就是需要定义一个大数组,占用了一定的存储空间。
/*---------------------sfud.c------------------*/
//定义芯片信息表
static const sfud_flash_chip flash_chip_table[] = SFUD_FLASH_CHIP_TABLE;/*----------------sfud_flash_def.h--------------*/
//芯片信息表(部分内容)的初始化
//芯片型号名,厂商ID,类型ID,容量ID,容量(bytes),写模式,擦除粒度,最小擦除粒度使用的指令码。
{"W25Q40BV", SFUD_MF_ID_WINBOND, 0x40, 0x13, 512L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"W25X40CL", SFUD_MF_ID_WINBOND, 0x30, 0x13, 512L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"W25X16AV", SFUD_MF_ID_WINBOND, 0x30, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"W25Q16BV", SFUD_MF_ID_WINBOND, 0x40, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
为了使用Flash芯片信息表功能,需在sfud_cfg.h中定义SFUD_USING_FLASH_INFO_TABLE开关宏。如果同时启用了SFDP和Flash芯片信息表功能,则在初始化时,优先使用SFDP。只有在SFDP不支持时,才去使用Flash芯片信息表。
/*---------------------sfud_cfg.h 配置文件-------------------------*/
//如果需要启用Flash芯片信息表,则定义这个宏
#define SFUD_USING_FLASH_INFO_TABLE
手动chip成员初始化
如果你既不想因为使用SFDP使得代码体积变大,也不想因为使用内置的Flash信息表而导致内存占用,那么就可以将你所使用的Flash芯片的信息通过硬编码的方式手动填写注册到flash_table中,即手动初始化chip成员。但这种方法的缺点就是如果更换Flash芯片型号,可能需要修改代码。
为了验证并实现这一点,我们需要在sfud_cfg.h中取消 SFUD_USING_SFDP 和 SFUD_USING_FLASH_INFO_TABLE宏定义,然后将实际用到的Flash芯片的信息(chip成员)手动填写在 flash_table的SFUD_FLASH_DEVICE_TABLE定义中,如下代码所示:
/*-----------------------sfud_cfg.h 配置文件-------------------------------------*///#define SFUD_USING_SFDP //不使用SFDP
//#define SFUD_USING_FLASH_INFO_TABLE //不使用falsh芯片信息表enum
{SFUD_W25Q64CV_DEVICE_INDEX = 0, //定义Flash设备在flash_table中的索引
};//定义并初始化flash_table中每个sfud_flash上下文对象
//static sfud_flash flash_table[] = SFUD_FLASH_DEVICE_TABLE;
#define SFUD_FLASH_DEVICE_TABLE \
{ \[SFUD_W25Q64CV_DEVICE_INDEX] = {.name = "my_W25Q64CV", \ .chip={"W25Q64CV", SFUD_MF_ID_WINBOND, 0x40, 0x17, 8L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}}, \
}
chip成员
前面我们提到 sfud_flash 上下文对象的chip成员是非常重要的一个成员,之所以重要是因为SFUD库通过他提供的信息来实现芯片的擦除/读/写等基本操作。
下面代码是chip成员的类型定义,它也是一个结构体。chip成员的mf_id、type_id、capacity_id分别是厂商ID,类型ID和容量ID,这三个信息在SFUD中是通过read_jedec_id( ) 函数来获取的。但是从本质上来说这三个参数并不是重要的也不是必须的,SFUD只是依据这三个参数来初始化后面的四个更重要的参数:capacity、write_mode、erase_gran、erase_gran_cmd,这四个参数直接影响了对Flash芯片的擦除/读/写操作的实现。
//chip成员的类型定义
typedef struct {char *name; /**< 芯片型号名称 */uint8_t mf_id; /**< 厂商 ID */uint8_t type_id; /**< 类型 ID */uint8_t capacity_id; /**< 容量 ID */uint32_t capacity; /**< 容量 (bytes) */uint16_t write_mode; /**< 写模式 @see sfud_write_mode */uint32_t erase_gran; /**< 擦除粒度(bytes) */uint8_t erase_gran_cmd; /**< 擦除最小粒度单元使用的指令码*/
} sfud_flash_chip;
简而言之,无论是通过SFDP,还是内置的芯片信息表,还是手动填写注册芯片信息,只要能正确初始化chip成员的capacity、write_mode、erase_gran、erase_gran_cmd这四个参数,SFUD库就可以正常驱动Flash芯片。
SPI互斥锁
假设有这样的应用场景:同一个SPI接口驱动了Flash设备A和B,且在不同的任务中分别读写Flash设备A和Flash设备B,则相当于两个任务竞争使用SPI接口,那么这个时候就要对SPI接口加锁,以保证多任务下串化操作SPI接口,保证使用的正确性和一致性。对SPI加锁可以使用RTOS的互斥锁来实现。如果只有一个Flash设备,或者不同的Flash设备分别使用不同的SPI接口,或者没有多任务抢占长场景,则可以不用加锁。SFUD库考虑到了多任务场景,可以通过实现spi成员的lock和unlock回调函数指针来实现spi接口加锁和释放锁的逻辑。如果不需要,则初始化为NULL即可。
typedef struct __sfud_spi {// .../* SPI接口上锁 */void (*lock)(const struct __sfud_spi *spi);/* SPI接口释放锁 */void (*unlock)(const struct __sfud_spi *spi);// ...
} sfud_spi, *sfud_spi_t;
SFUD初始化流程
当配置文件就绪后,通过调用sfud_init()函数来初始化SFUD库,这是SFUD的初始化入口。初始化过程就是遍历flash_table,对其中注册的每个Flash设备进行初始化。下图演示了设备dev0的初始化过程。
//SFUD总体初始化
sfud_err sfud_init(void)
{sfud_err cur_flash_result = SFUD_SUCCESS, all_flash_result = SFUD_SUCCESS;size_t i;//遍历Flash设备表,初始化所有注册在表中的Flash设备for (i = 0; i < sizeof(flash_table) / sizeof(sfud_flash); i++) {flash_table[i].index = i; //初始化每个sfud_flash对象在表中的索引cur_flash_result = sfud_device_init(&flash_table[i]); //初始化单个Flash设备if (cur_flash_result != SFUD_SUCCESS) {all_flash_result = cur_flash_result;}}return all_flash_result;
}//单个Flash设备的初始化
sfud_err sfud_device_init(sfud_flash *flash)
{sfud_err result = SFUD_SUCCESS;result = hardware_init(flash); //单个Flash设备的硬件部分初始化if (result == SFUD_SUCCESS) { result = software_init(flash); //单个Flash设备的软件部分初始化}if (result == SFUD_SUCCESS) {flash->init_ok = true;SFUD_INFO("%s flash device initialized successfully.", flash->name);} else {flash->init_ok = false;SFUD_INFO("Error: %s flash device initialization failed.", flash->name);}return result;
}
SFUD移植过程
准备SFUD源代码文件
移植的第一步,就是将SFUD库目录下的 所有.c 和 .h 文件(一共7个)都加入到工程中。移植过程中只需修改 sfud_cfg.h 和 sfud_port.c 这2个文件。
- sfud.h
- sfud.c
- sfud_def.h
- sfud_flash_def.h
- sfud_sfdp.c
- sfud_cfg.h //移植配置文件
- sfud_port.c //移植适配文件
主机SPI接口初始化
这部分代码可以放在sfud_port.c中,也可以在MCU的BSP层中。
void RCC_config(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); //使能SPI1时钟
}//SPI1接口的初始化
void SPI1_config(void)
{GPIO_InitTypeDef GPIO_InitStructure;SPI_InitTypeDef SPI_InitStructure;//SCK:PA5 、MOSI:PA7 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);//MISO:PA6GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);//CS:PA2GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);
}
SFUD配置文件:sfud_cfg.h
- 注册Flash设备:通过枚举常量定义Flash设备的ID,也就是这个对象在Flash设备表中的索引,然后在DEVICE_TABLE中注册这个设备,这里只需初始化其name成员即可。
- 启用SFDP:根据需要启用SFDP机制,如果需要则定义SFUD_USING_SFDP宏
- 启用Flash芯片信息表:根据需要启用Flash芯片信息表机制,如果需要则定义 SFUD_USING_FLASH_INFO_TABLE宏
- 启用调试模式:定义宏SFUD_DEBUG_MODE来打开调试模式,可以通过printf输出调试和日志信息
- 启用QSPI:定义宏SFUD_USING_QSPI启用QSPI接口
- 启用Fast Read:定义宏SFUD_USING_FAST_READ启用Fast Read模式
#ifndef _SFUD_CFG_H_
#define _SFUD_CFG_H_#define SFUD_DEBUG_MODE //启用调试模式
//#define SFUD_INFO(...)
#define SFUD_USING_SFDP //启用SFDP
//#define SFUD_USING_FLASH_INFO_TABLE //启用Flash芯片信息表// #define SFUD_USING_FAST_READ //启用Fast Read模式
// #define SFUD_USING_QSPI //启用QSPI接口enum {SFUD_W25Q128_DEVICE_INDEX = 0, //定义Flash设备的ID
};#define SFUD_FLASH_DEVICE_TABLE \
{ \[SFUD_W25Q128_DEVICE_INDEX] = {.name = "W25Q128" },/*注册Flash设备*/ \
}#endif /* _SFUD_CFG_H_ */
SFUD移植文件:sfud_port.c
1、实现sfud_spi_port_init()函数
此函数用于实现硬件平台相关的初始化,主要用于设置sfud_flash *flash 对象的参数。在函数中,根据flash对象在flash_table中的索引(或者说ID)来区分不同的Flash对象,进行针对性的初始化,因为项目可能存在多个Flash设备,且他们的初始化参数不一样。
2、实现spi_write_read()函数
此函数用于实现SPI接口的数据的收发。驱动Flash时,一般过程为,首先是主机通过SPI接口发送指令,然后接收Flash设备的响应数据。由于全双工SPI的收发是同时进行的,因此在接收Flash设备的数据时,主机也要发送数据(dummy write),只不过这个数据没有意义,仅仅是用于让SCK产生同步时钟来驱动从机将响应的数据回传到主机。
3、适配printf()
默认使用printf函数来实现调试和日志信息输出。这里需要对 sfud_log_info和sfud_log_debug函数进行适配,简单情况下可以对printf进行重定向输出到串口。当代码稳定后,可以关闭调试和日志信息输出功能,操作如下:
- 在sfud_cfg.h中取消定义SFUD_DEBUG_MODE,关闭调试模式
- 在sfud_cfg.h中定义#define SFUD_INFO(...) 为空实现,关闭日志输出
- 在sfud_port.c中注释掉 sfud_log_debug 和 sfud_log_info 函数的实现
- 在sfud_port.c中注释掉 static char log_buf[256];
#include <sfud.h>
#include <stdarg.h>
#include <stdio.h>
#include "stm32f10x.h"static char log_buf[256]; //打印日志和调试信息用的buf
void sfud_log_debug(const char *file, const long line, const char *format, ...);/*** @brief SPI用户自定义参数结构体* 用于记录每个Flash设备使用的spi接口是哪个,以及cs引脚是哪个*/
typedef struct {SPI_TypeDef * spix; //驱动Flash芯片使用的SPI接口 GPIO_TypeDef* cs_gpio; //驱动Flash芯片的CS引脚的GPIOuint16_t cs_pin; //驱动Flash芯片的CS引脚的GPIO编号
} spi_user_data_t;/*** @brief 定义SPI用户自定义参数数组* 用于记录每个Flash设备使用的spi接口是哪个,以及cs引脚是哪个*/
static spi_user_data_t spi_user_data_table[] = { [SFUD_W25Q128_DEVICE_INDEX] = {SPI1,GPIOA,GPIO_Pin_2}, };/*** @brief 定义延时等待函数的实现,在RTOS环境下可以使用RTOS的延时机制,这里简单使用软件延时实现*/
static void sfud_delay(void)
{u32 i;for(i=0;i<800;i++);
}/*** @brief SPI接口的数据收发实现* @param spi : sfud_flash对象的spi成员* @param write_buf : 存储主机发送给Flash的指令的缓冲区* @param write_size : write_buf中指令的长度* @param read_buf : 存储Flash响应给主机的数据的缓冲区* @param read_size : Flash响应给主机的数据的长度* @retval 函数执行的状态*/
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) {uint32_t i;sfud_err result = SFUD_SUCCESS;uint8_t send_data, read_data;spi_user_data_t* spi_dev = (spi_user_data_t*)spi->user_data;GPIO_ResetBits(spi_dev->cs_gpio, spi_dev->cs_pin); //CS=0,选中for (i = 0; i < write_size + read_size; i++) {/* 先写缓冲区中的数据到 SPI 总线,数据写完后,再写 (0xFF) 到 SPI 总线 */if (i < write_size) {send_data = *write_buf++;} else {send_data = 0xFF; //dummy write}/* 发送1字节数据 */while (SPI_I2S_GetFlagStatus(spi_dev->spix, SPI_I2S_FLAG_TXE) == RESET) { /*TODO:这里建议加超时判断,不要无限等待*/ }SPI_I2S_SendData(spi_dev->spix, send_data);/* 接收1字节数据 */while (SPI_I2S_GetFlagStatus(spi_dev->spix, SPI_I2S_FLAG_RXNE) == RESET){ /*TODO:这里建议加超时判断,不要无限等待*/ }read_data = SPI_I2S_ReceiveData(spi_dev->spix);/* 写缓冲区中的数据发完后,再读取 SPI 总线中的数据到读缓冲区 */if (i >= write_size) {*read_buf++ = read_data;}} GPIO_SetBits(spi_dev->cs_gpio, spi_dev->cs_pin); //CS=1,取消选中return result;
}#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;return result;
}
#endif /* SFUD_USING_QSPI *//*** @brief 实现单个指定的Flash设备上下文对象 sfud_flash 的初始化* @param flash : 指定的Flash设备上下文对象* @retval 函数执行的状态*/
sfud_err sfud_spi_port_init(sfud_flash *flash)
{sfud_err result = SFUD_SUCCESS;switch(flash->index){case SFUD_W25Q128_DEVICE_INDEX://RCC、GPIO、SPI接口初始化均在BSP层中执行flash->name = "Flashxx"; //Flash设备的名称,非必须,仅仅用于调试flash->user_data = NULL; //用户自定义数据,这里不需要flash->spi.name = "spixx"; //SPI接口的名称,非必须,仅仅用于调试//flash->spi.qspi_read = NULL; //这里不需要flash->spi.wr = spi_write_read; //SPI接口的数据收发函数flash->spi.lock = NULL; //spi接口加锁函数,这里不需要flash->spi.unlock = NULL; //spi接口释放锁函数,这里不需要flash->spi.user_data = &spi_user_data_table[flash->index];//SPI接口相关的用户自定义数据flash->retry.delay = sfud_delay; //执行Flash操作时的延时等待函数flash->retry.times = 1000; //执行flash操作时的等待尝试次数break;default:result = SFUD_ERR_NOT_FOUND;}return result;
}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);printf("[SFUD](%s:%ld) ", file, line);/* must use vprintf to print */vsnprintf(log_buf, sizeof(log_buf), format, args);printf("%s\n", log_buf);va_end(args);
}void sfud_log_info(const char *format, ...) {va_list args;/* args point to the first variable parameter */va_start(args, format);printf("[SFUD]");/* must use vprintf to print */vsnprintf(log_buf, sizeof(log_buf), format, args);printf("%s\n", log_buf);va_end(args);
}
SFUD主要用户接口说明
//描述:初始化所有注册的flash对象。遍历flash_table数组,初始化所有的flash对象。
sfud_err sfud_init(void)//描述:初始化一个指定的flash对象
sfud_err sfud_device_init(sfud_flash *flash);//根据ID(索引)获取一个注册的flash对象
sfud_flash *sfud_get_device(size_t index);//获取flash对象数组,也就是flash_table
const sfud_flash *sfud_get_device_table(void);//描述:flash擦除操作
//参数flash:flash对象
//参数addr:擦除的起始地址
//参数size:擦除的空间大小,字节数
//注意:只要是被参数addr和size覆盖的页,不管地址是否对齐,都会被擦除
sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size)//描述:flash编程操作
//参数flash:flash对象
//参数addr:写数据的起始地址
//参数size:写入数据的长度,字节数
//参数data:存放代写入的数据的缓冲区
sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data)//描述:flash读取操作
//参数addr:读数据的起始地址
//参数size:读取的数据的长度,字节数
//参数data:存放读出的数据的缓冲区
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data)//描述:先擦除再写入操作,等价于sfud_erase + sfud_write
sfud_err sfud_erase_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data);//描述:全片擦除
sfud_err sfud_chip_erase(const sfud_flash *flash);