SFUD是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。
- 主要特点:支持 SPI/QSPI 接口、面向对象(同时支持多个 Flash 对象)、可灵活裁剪、扩展性强、支持 4 字节地址
- 资源占用
- 标准占用:RAM:0.2KB ROM:5.5KB
- 最小占用:RAM:0.1KB ROM:3.6KB
目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
链接在这里:https://github.com/armink/SFUD
本期环境说明:STM32F407VET6 W25Q128
第一步:使用CUBEMX生成一个带SPI读写和串口发送的工程

第二步:完成串口重定向
关于这点网上有不少的一键复制代码,有开启微库(microlib)的,有AC6的
这里我用AC6不开microlib的做法
cpp
/* ------------------通过重定向将printf函数映射到串口1上-------------------*/
#if !defined(__MICROLIB)
//#pragma import(__use_no_semihosting)
__asm (".global __use_no_semihosting\n\t");
// Stub functions required when disabling semihosting
void _sys_exit(int x) //避免使用半主机模式
{
x = x;
}
void _ttywrch(int ch)
{
ch = ch;
}
// Additional stub functions required by ARM Compiler 6
#include <rt_sys.h>
FILEHANDLE _sys_open(const char *name, int openmode)
{
return -1;
}
int _sys_close(FILEHANDLE fh)
{
return 0;
}
int _sys_write(FILEHANDLE fh, const unsigned char *buf, unsigned len, int mode)
{
return 0;
}
int _sys_read(FILEHANDLE fh, unsigned char *buf, unsigned len, int mode)
{
return -1;
}
int _sys_istty(FILEHANDLE fh)
{
return 0;
}
int _sys_seek(FILEHANDLE fh, long pos)
{
return -1;
}
long _sys_flen(FILEHANDLE fh)
{
return -1;
}
//struct __FILE
//{
// int handle;
//};
// FILE __stdout; // Not needed for ARM Compiler 6 (ARMCLANG)
#endif
#if defined ( __GNUC__ ) && !defined (__clang__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
/* 实现串口发送一个字节数据的函数 */
//serial_write(&serial1, (uint8_t)ch); //发送一个自己的数据到串口
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return ch;
}
然后可以在串口助手测试一下,这里给大家推荐一个在线串口助手
Web Serial Debug - 在线串口调试工具 - 土巴士
**第三步:**将sfud七个文件放进工程中

在这里我们只需要修改sfud_port.c和sfud_cfg.h两个文件
先看sfud_cfg.h,解释一下这里的宏定义
cpp
#ifndef _SFUD_CFG_H_
#define _SFUD_CFG_H_
#define SFUD_DEBUG_MODE
#define SFUD_USING_SFDP
// #define SFUD_USING_FAST_READ
#define SFUD_USING_FLASH_INFO_TABLE
enum {
SFUD_XXXX_DEVICE_INDEX = 0,
};
#define SFUD_FLASH_DEVICE_TABLE \
{ \
[SFUD_XXXX_DEVICE_INDEX] = {.name = "XXXX", .spi.name = "SPIX"}, \
}
#define SFUD_USING_QSPI
#endif /* _SFUD_CFG_H_ */
调试模式:打开/关闭 SFUD_DEBUG_MODE 宏定义
是否使用 SFDP 参数(内置 Flash 信息表)功能:打开/关闭 SFUD_USING_SFDP 宏定义
是否使用快速读模式(SPI模式):打开/关闭 SFUD_USING_FAST_READ 宏定义。
是否使用该库自带的 Flash 参数信息表:打开/关闭 SFUD_USING_FLASH_INFO_TABLE 宏定义
如果产品中存在多个 Flash ,可以添加 Flash 设备表。修改 SFUD_FLASH_DEVICE_TABLE
QSPI 模式:打开/关闭 SFUD_USING_QSPI 宏定义
这里改动为
cpp
#ifndef _SFUD_CFG_H_
#define _SFUD_CFG_H_
#define SFUD_DEBUG_MODE
#define SFUD_USING_SFDP
// #define SFUD_USING_FAST_READ
#define SFUD_USING_FLASH_INFO_TABLE
enum {
SFUD_W25Q128_DEVICE_INDEX = 0,
};
#define SFUD_FLASH_DEVICE_TABLE \
{ \
[SFUD_W25Q128_DEVICE_INDEX] = {.name = "W25Q128BV", .spi.name = "SPI1"}, \
}
//#define SFUD_USING_QSPI
#endif /* _SFUD_CFG_H_ */
然后在sfud_port.c里我们需要修改两个函数
spi_write_read和sfud_spi_port_init,剩下默认就好
cpp
#include <sfud.h>
#include <stdarg.h>
#include "gpio.h"
#include "spi.h"
/* 片选控制宏 */
#define CS_GPIO_PORT GPIOA
#define CS_GPIO_PIN GPIO_PIN_4
#define CS_LOW() HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_RESET)
#define CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_SET)
static char log_buf[256];
void sfud_log_debug(const char *file, const long line, const char *format, ...);
/**
* 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;
/**
* add your spi write and read code
*/
SPI_HandleTypeDef *hspi = (SPI_HandleTypeDef *)spi->user_data;
/* 拉低片选 */
CS_LOW();
/* 发送数据(命令+地址)*/
if (write_size > 0 && write_buf != NULL) {
if (HAL_SPI_Transmit(hspi, (uint8_t *)write_buf, write_size, 1000) != HAL_OK) {
result = SFUD_ERR_TIMEOUT;
goto error_exit;
}
}
/* 接收数据 */
if (read_size > 0 && read_buf != NULL) {
if (HAL_SPI_Receive(hspi, read_buf, read_size, 1000) != HAL_OK) {
result = SFUD_ERR_TIMEOUT;
goto error_exit;
}
}
error_exit:
/* 拉高片选 */
CS_HIGH();
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;
/**
* add your qspi read flash data code
*/
return result;
}
#endif /* SFUD_USING_QSPI */
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
*/
/* flash结构体初始化 */
flash->spi.wr = spi_write_read;
flash->spi.lock = NULL; // 如需线程安全,实现互斥锁
flash->spi.unlock = NULL;
flash->spi.user_data = &hspi1;
flash->retry.delay = NULL; // 如需延时函数,可设置
flash->retry.times = 10000; // 重试次数
return result;
}
/**
* This function is print debug info.
*
* @param file the file which has call this function
* @param line the line number which has call this function
* @param format output format
* @param ... args
*/
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);
}
/**
* 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);
printf("[SFUD]");
/* must use vprintf to print */
vsnprintf(log_buf, sizeof(log_buf), format, args);
printf("%s\n", log_buf);
va_end(args);
}
最后参考SFUD | 一个简洁实用的开源项目,帮你轻松搞定SPI Flash-腾讯云开发者社区-腾讯云的测试demo程序
cpp
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "sfud.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define SFUD_DEMO_TEST_BUFFER_SIZE 1024
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
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_W25Q128_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.\r\n", flash->name, addr, size);
}
else
{
printf("Erase the %s flash data failed.\r\n", 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.\r\n", flash->name, addr, size);
}
else
{
printf("Write the %s flash data failed.\r\n", 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:\r\n", flash->name, addr, size);
printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
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("\r\n");
}
}
printf("\r\n");
}
else
{
printf("Read the %s flash data failed.\r\n", 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.\r\n", flash->name);
break;
}
}
if (i == size)
{
printf("The %s flash test is success.\r\n", flash->name);
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("SFUD port by mculover666.\r\n");
/* SFUD initialize */
if (sfud_init() == SFUD_SUCCESS)
{
/* enable qspi fast read mode, set one data lines width */
sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
}
/* USER CODE END 2 */
没有问题
