STM32使用SFUD (Serial Flash Universal Driver) 串行 Flash 通用驱动库驱动W25Q128

1、环境

1、单片机STM32F401RCT6

2、编辑器KEIL

2、准备

1、STM32CUBEMX配置SPI1,使用自定义CS引脚。

生成keil工程。

2、下载SFUD。https://gitee.com/Armink/SFUD[添加链接描述](https://gitee.com/Armink/SFUD)

解压后,获取sfud文件夹。

3、将文件夹复制到keil工程

4、添加sfud文件夹的内容到keil工程。并添加文件目录。

3、移植

1、打开sfud_port.c和sfud_cfg.h文件。

编辑sfud_cfg.h文件(此文件在F7编译一次后,就可以此sfud_port.c的包含文件里面查找到)

我没有使用串口输出调试信息,所以就禁用了调试。主要是2,如果有多个flash,就按顺序定义。步骤3里面的内容只是自己看的,实际意义不大。

c 复制代码
/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2016-2018, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: It is the configure head file for this library.
 * Created on: 2016-04-23
 */

#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 = "W25Q128", .spi.name = "SPI1"},           \
}

//#define SFUD_USING_QSPI

#endif /* _SFUD_CFG_H_ */

2、编辑sfud_port.c文件。

因为禁用了调试,可以注释掉此文件里面的一些内容:


剩下的就是:

定义一个结构体,用来管理flash的CS引脚和spi句柄

c 复制代码
typedef struct {
    SPI_HandleTypeDef *hspi;      // 直接保存HAL句柄指针
    GPIO_TypeDef *cs_gpiox;
    uint16_t cs_gpio_pin;
} spi_user_data, *spi_user_data_t;

实现一个延时100us的函数

c 复制代码
/* about 100 microsecond delay */
/**
 * @brief 100微秒延时函数
 * @note 针对 STM32F401RCT6 @ 84MHz 精确校准
 *       通过逻辑分析仪或示波器实测调整循环次数
 */
static void retry_delay_100us(void) {
    /* 84MHz 下,实测约 100us 的循环次数 */
    /* 编译器优化级别会影响循环次数,建议使用 -O0 或 -O1 */
    for(volatile uint32_t i = 0; i < 210; i++);
}

编辑SPI读写函数

c 复制代码
/**
 * SPI写读函数 - 逐字节方式(兼容旧版SFUD,某些版本需要这种方式)
 * 如果上面的批量方式不行,用这个版本
 */
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;
    spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
    
    if (write_size) {
        SFUD_ASSERT(write_buf);
    }
    if (read_size) {
        SFUD_ASSERT(read_buf);
    }
    
    HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_RESET);
    
    // 逐字节读写
    for (size_t i = 0; i < write_size + read_size; i++) {
        if (i < write_size) {
            send_data = *write_buf++;
        } else {
            send_data = SFUD_DUMMY_DATA;
        }
        
        // 收发一个字节
        if (HAL_SPI_TransmitReceive(spi_dev->hspi, &send_data, &read_data, 1, 
                                      HAL_MAX_DELAY) != HAL_OK) {
            result = SFUD_ERR_TIMEOUT;
            goto exit;
        }
        
        if (i >= write_size) {
            *read_buf++ = read_data;
        }
    }
    
exit:
    HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_SET);
    return result;
}

初始化SFUD

c 复制代码
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
     */
	
	switch(flash->index)
	{
		case SFUD_W25Q128_DEVICE_INDEX:
		{
			static spi_user_data spi1_dev = 
			{
				.hspi = &hspi1,
				.cs_gpiox = GPIOA,
				.cs_gpio_pin = GPIO_PIN_4
			};
			//同步flash移植所需的接口及数量
			flash->spi.wr = spi_write_read;
			flash->spi.lock = spi_lock;
			flash->spi.unlock = spi_unlock;
			flash->spi.user_data = &spi1_dev;
			flash->retry.delay = retry_delay_100us;
			flash->retry.times = 60 * 10000;
			break;
		}
		default:
			result = SFUD_ERR_NOT_FOUND;
			break;
	}

    return result;
}

整个 sfud_port.c文件如下:

c 复制代码
/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2016-2018, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Portable interface for each platform.
 * Created on: 2016-04-23
 */

#include <sfud.h>
#include <stdarg.h>

#include "main.h"
#include "spi.h"

typedef struct {
    SPI_HandleTypeDef *hspi;      // 直接保存HAL句柄指针
    GPIO_TypeDef *cs_gpiox;
    uint16_t cs_gpio_pin;
} spi_user_data, *spi_user_data_t;

//static char log_buf[256];

void sfud_log_debug(const char *file, const long line, const char *format, ...);

static void spi_lock(const sfud_spi *spi) {
    __disable_irq();
}

static void spi_unlock(const sfud_spi *spi) {
    __enable_irq();
}

/* about 100 microsecond delay */
/**
 * @brief 100微秒延时函数
 * @note 针对 STM32F401RCT6 @ 84MHz 精确校准
 *       通过逻辑分析仪或示波器实测调整循环次数
 */
static void retry_delay_100us(void) {
    /* 84MHz 下,实测约 100us 的循环次数 */
    /* 编译器优化级别会影响循环次数,建议使用 -O0 或 -O1 */
    for(volatile uint32_t i = 0; i < 210; i++);
}


///**
// * 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;
//			
//	spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
//	
//	if(write_size)
//	{
//		SFUD_ASSERT(write_buf);
//	}
//	if(read_size)
//	{
//		SFUD_ASSERT(read_buf);
//	}
//			
//			// CS引脚拉低(选中设备)
//	HAL_GPIO_WritePin(spi_dev->cs_gpiox,spi_dev->cs_gpio_pin,GPIO_PIN_RESET);
//	
//	/* 开始读写数据 */		
//    if (write_size > 0 && read_size > 0) {
//        // 同时读写(全双工)
//        if (HAL_SPI_TransmitReceive(spi_dev->hspi, (uint8_t*)write_buf, read_buf, 
//                                      write_size, HAL_MAX_DELAY) != HAL_OK) {
//            result = SFUD_ERR_TIMEOUT;
//        }
//    } else if (write_size > 0) {
//        // 只写
//        if (HAL_SPI_Transmit(spi_dev->hspi, (uint8_t*)write_buf, write_size, 
//                              HAL_MAX_DELAY) != HAL_OK) {
//            result = SFUD_ERR_TIMEOUT;
//        }
//    } else if (read_size > 0) {
//        // 只读
//        if (HAL_SPI_Receive(spi_dev->hspi, read_buf, read_size, 
//                             HAL_MAX_DELAY) != HAL_OK) {
//            result = SFUD_ERR_TIMEOUT;
//        }
//    }
//	// CS引脚拉高(释放设备)
//	HAL_GPIO_WritePin(spi_dev->cs_gpiox,spi_dev->cs_gpio_pin,GPIO_PIN_SET);		
//			
//    /**
//     * add your spi write and read code
//     */

//    return result;
//}

/**
 * SPI写读函数 - 逐字节方式(兼容旧版SFUD,某些版本需要这种方式)
 * 如果上面的批量方式不行,用这个版本
 */
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;
    spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
    
    if (write_size) {
        SFUD_ASSERT(write_buf);
    }
    if (read_size) {
        SFUD_ASSERT(read_buf);
    }
    
    HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_RESET);
    
    // 逐字节读写
    for (size_t i = 0; i < write_size + read_size; i++) {
        if (i < write_size) {
            send_data = *write_buf++;
        } else {
            send_data = SFUD_DUMMY_DATA;
        }
        
        // 收发一个字节
        if (HAL_SPI_TransmitReceive(spi_dev->hspi, &send_data, &read_data, 1, 
                                      HAL_MAX_DELAY) != HAL_OK) {
            result = SFUD_ERR_TIMEOUT;
            goto exit;
        }
        
        if (i >= write_size) {
            *read_buf++ = read_data;
        }
    }
    
exit:
    HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_SET);
    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 */

//static spi_user_data spi1 = {.hspi = &hspi1,.cs_gpiox = GPIOA,.cs_gpio_pin = GPIO_PIN_4};

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
     */
	
	switch(flash->index)
	{
		case SFUD_W25Q128_DEVICE_INDEX:
		{
			static spi_user_data spi1_dev = 
			{
				.hspi = &hspi1,
				.cs_gpiox = GPIOA,
				.cs_gpio_pin = GPIO_PIN_4
			};
			//同步flash移植所需的接口及数量
			flash->spi.wr = spi_write_read;
			flash->spi.lock = spi_lock;
			flash->spi.unlock = spi_unlock;
			flash->spi.user_data = &spi1_dev;
			flash->retry.delay = retry_delay_100us;
			flash->retry.times = 60 * 10000;
			break;
		}
		default:
			result = SFUD_ERR_NOT_FOUND;
			break;
	}

    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);
}

4、使用

1、main.c文件包含#include "sfud.h"

2、初始化sfud,启用读写

相关推荐
LCG元2 小时前
多MCU通信:STM32F1通过I2C/SPI实现数据同步与控制
stm32·单片机·嵌入式硬件
史蒂芬_丁2 小时前
EPWM Global Load
单片机·嵌入式硬件
碎像2 小时前
单片机-数码管显示
单片机·嵌入式硬件
Wave8453 小时前
从裸机到 FreeRTOS:STM32 智能手表重构之路
stm32·重构·智能手表
LCMICRO-1331084774612 小时前
长芯微LPS123完全P2P替代ADP123,高性能、低压差的线性稳压器
单片机·嵌入式硬件·fpga开发·硬件工程·dsp开发·线性稳压器
守护安静星空14 小时前
esp32开发笔记-工程搭建
笔记·单片机·嵌入式硬件·物联网·visual studio code
ACP广源盛1392462567314 小时前
破局 Type‑C 切换器痛点@ACP#GSV6155+LH3828/GSV2221+LH3828 黄金方案
c语言·开发语言·网络·人工智能·嵌入式硬件·计算机外设·电脑
时空自由民.15 小时前
ST7701S 3.5寸显示屏
单片机
金戈鐡馬16 小时前
BetaFlight中的定时器引脚绑定详解
stm32·单片机·嵌入式硬件·无人机