STM32使用SFUD库驱动W25Q64

发布于:2024-12-19 ⋅ 阅读:(7) ⋅ 点赞:(0)

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 中:

  1. 要为他定义一个枚举常量,这个枚举常量的值用于表示Flash设备对应的sfud_flash上下文对象在flash_table 中的索引,也作为这个Flash设备的sfud_flash上下文对象在SFUD库中的ID,这样就可以通过ID很方便的引用这个Flash设备的sfud_flash上下文对象
  2. 初始化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个文件。

  1. sfud.h
  2. sfud.c
  3. sfud_def.h
  4. sfud_flash_def.h
  5. sfud_sfdp.c
  6. sfud_cfg.h        //移植配置文件
  7. 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:PA6
	GPIO_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:PA2
	GPIO_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引脚的GPIO
	uint16_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);


网站公告

今日签到

点亮在社区的每一天
去签到