SFUD是什么
关于SFUD库的介绍,其开源链接(gitee,github)已经详细的阐述了.
这里是截取自它的一部分介绍:
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
设计思路:
什么是 SFDP :它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准,最新版 V1.6B (点击这里查看)。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
不支持 SFDP 怎么办 :如果该 Flash 不支持 SFDP 标准,SFUD 会查询配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息(添加方法详细见 2.5 添加库目前不支持的 Flash)。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。
详细的可以查看开源链接(gitee,github);
为什么选择 SFUD
避免项目因 Flash 缺货、Flash 停产或产品扩容而带来的风险;
越来越多的项目将固件存储到串行 Flash 中,例如:ESP8266 的固件、主板中的 BIOS 及其他常见电子产品中的固件等等,但是各种 Flash 规格及命令不统一。使用 SFUD 即可避免,在相同功能的软件平台基础下,无法适配不同 Flash 种类的硬件平台的问题,提高软件的可重用性;
简化软件流程,降低开发难度。现在只需要配置好 SPI 通信,即可畅快的开始玩串行 Flash 了;
可以用来制作 Flash 编程器/烧写器
开始copy代码
这篇文章的重点就是来移植这个库,所以我就不多介绍了,直接开搞。
- 首先先下载好立创梁山派附带的资料,等下要用到里面的提供的demo来作为我们的工程模板。
- 我们使用 005-串口打印信息 这个作为我们的工程模板,然后在从015_spi中复制一份spi的代码,作为我们的flash驱动代码,还要下载一份sfud的源代码。
总共就是以上三个东西就ok啦。
- 接下啦,按照下图,将文件添加到你的工程中进来,如果这一部分操作不会的话,可以学一下立创推出的教程,我这里就不再赘述啦。
- 接下来就是对代码进行小小的修改
对bsp_spi.c文件按照我自己的代码风格进行了小小的修改和裁剪,因为这个SFUD十分完善,只需要我们提供这个spi的读取接口函数就ok了。
#include "bsp_spi.h"
void bsp_spi4_init(void)
{
//SPI参数定义结构体
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(RCU_GPIOF); // 使用F端口
rcu_periph_clock_enable(RCU_SPI4); // 使能SPI4
//引脚复用
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_7);
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_8);
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_9);
//引脚模式
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
//输出模式
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
//开启CS引脚时钟
rcu_periph_clock_enable(RCU_GPIOF);
//配置CS引脚模式
gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_6);
//配置CS输出模式
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
//W25Q64不选中
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; //极性相位
spi_init_struct.nss = SPI_NSS_SOFT; //软件cs
spi_init_struct.prescale = SPI_PSC_2; //SPI时钟预分频为2
spi_init_struct.endian = SPI_ENDIAN_MSB; //高位在前
//将参数填入SPI4
spi_init(SPI4, &spi_init_struct);
//使能SPI
spi_enable(SPI4);
}
/******************************************************************
* 函 数 名 称:spi_read_write_byte
* 函 数 说 明:硬件SPI的读写
* 函 数 形 参:dat=发送的数据
* 函 数 返 回:读取到的数据
* 作 者:LCKFB
* 备 注:无
******************************************************************/
uint8_t spi4_read_write_byte(uint8_t dat)
{
//等待发送缓冲区为空
while(RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TBE) );
spi_i2s_data_transmit(SPI4, dat);
//等待接收缓冲区为空
while(RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE) );
return spi_i2s_data_receive(SPI4);
}
uint16_t spi4_flash_readID(void)
{
uint16_t temp = 0;
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi4_read_write_byte(0x90);//发送读取ID命令
spi4_read_write_byte(0x00);
spi4_read_write_byte(0x00);
spi4_read_write_byte(0x00);
//接收数据
temp |= spi4_read_write_byte(0xFF)<<8;
temp |= spi4_read_write_byte(0xFF);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
return temp;
}
void spi4_w25qxx_cs(uint8_t enable)
{
if(enable)
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
else
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
}
对应的bsp_spi.h文件如下:
#ifndef _BSP_SPI_H
#define _BSP_SPI_H
#include "gd32f4xx.h"
#include "systick.h"
void bsp_spi4_init(void);
uint16_t spi4_flash_readID(void);
uint8_t spi4_read_write_byte(uint8_t dat);
void spi4_w25qxx_cs(uint8_t enable);
#endif
-
接下来是对这个sfud的移植
这个是对于sfud_cfg.h的修改
-
剩下对一个sfud_port.c文件进行修改
#include <sfud.h>
#include <stdarg.h>
#include "bsp_spi.h"
#include <string.h>
#define OS 0 // 0:不使用OS,1:使用OS
//这里使用的是freertos,你也可以换成你熟悉的rtos
#if OS
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#endif
static char log_buf[256];
void sfud_log_debug(const char *file, const long line, const char *format, ...);
//这里是自添加的函数,实行对应接口的写buf函数
static void spi_flash_buf_write(const uint8_t *buf, size_t len)
{
while (len--)
{
spi4_read_write_byte(*buf);
buf++;
}
}
//这里是自添加的函数,实行对应接口的读buf函数
static void spi_flash_buf_read(uint8_t *buf, size_t len)
{
while (len--)
{
*buf = spi4_read_write_byte(0xff);
buf++;
}
}
/**
* 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;
// uint8_t send_data, read_data;
/**
* add your spi write and read code
*/
//这里就是我们要自行添加的代码
if (write_size)
SFUD_ASSERT(write_buf);
if (read_size)
SFUD_ASSERT(read_buf);
spi4_w25qxx_cs(0);
if (write_size)
{
spi_flash_buf_write(write_buf, write_size);
}
if (read_size)
{
spi_flash_buf_read(read_buf, read_size);
}
spi4_w25qxx_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;
/**
* add your qspi read flash data code
*/
return result;
}
#endif /* SFUD_USING_QSPI */
#if OS
static SemaphoreHandle_t sfudMutexSemaphor;
static void spi_lock_init(void)
{
sfudMutexSemaphor = xSemaphoreCreateMutex();
if (sfudMutexSemaphor == NULL)
{
SFUD_DEBUG("sfud semaphor create failed");
}
}
#endif
static void spi_lock(const sfud_spi *spi)
{
#if OS
if (sfudMutexSemaphor != NULL)
{
xSemaphoreTake(sfudMutexSemaphor, portMAX_DELAY);
}
#else
__disable_irq(); //? ? ж
#endif
}
static void spi_unlock(const sfud_spi *spi)
{
#if OS
if (sfudMutexSemaphor != NULL)
{
xSemaphoreGive(sfudMutexSemaphor);
}
#else
__enable_irq(); //? ? ж
#endif
}
static void spi_delay(void)
{
#if OS
vTaskDelay(1);
#else
uint32_t delay = 120;
while(delay--)
;
#endif
}
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
*/
//这里也是我们要添加的函数,这里要重点注意这个SPI4,要和sfud_cfg.h中保持一致
if (!strcmp(flash->spi.name, "SPI4"))
{
bsp_spi4_init();
#if OS
spi_lock_init();
#endif
flash->spi.wr = spi_write_read;
flash->spi.lock = spi_lock;
flash->spi.unlock = spi_unlock;
flash->retry.delay = spi_delay;
flash->retry.times = 60*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);
}
- 上面的代码所示,其实也只需要修改 spi_write_read和sfud_spi_port_init这两个函数,就移植完成啦。
接下来我们对移植完成的sfud库进行擦除,读,写功能的测试。
对应的main函数代码。
#include "main.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "gd32f4xx.h"
#include "sys.h"
#include "systick.h"
#include <stdio.h>
#include "bsp_spi.h"
#include <sfud.h>
#define SFUD_DEMO_TEST_BUFFER_SIZE 1024
static void sfud_demo(uint32_t addr, size_t size, uint8_t *data);
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
systick_config();
led_gpio_config(); // led初始化
usart_gpio_config(115200U);
/* SFUD initialize */
if (sfud_init() == SFUD_SUCCESS)
{
sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
}
while (1)
{
}
}
/**
* SFUD demo for the first flash device test.
*
* @param addr flash start address
* @param size test flash size
* @param size test flash data buffer
*/
static void sfud_demo(uint32_t addr, size_t size, uint8_t *data) {
sfud_err result = SFUD_SUCCESS;
const sfud_flash *flash = sfud_get_device_table() + 0;
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 %ld.\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 %ld.\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 %ld. 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);
}
}
效果如下
擦除读写1k字节数据正常,到这里就移植完成啦。
至于如何使用sfud库,可以参考这个测试demo的用法,记得要向flash写入数据,要先擦除哦!