RT-Thread Studio配置QSPI和SFUD
1 前言
本次采用的是正点原子STM32F767系列的板子,区别于F1及F4上的SPI,F7增加了QSPI。其上板载的W25Q256FV型号的Flash也是通过QSPI实现通信读写的。网上关于RT-Thread开启SPI使用SFUD进行FLASH进行读写的例子有很多,但是大部分都是基于ENV的,介于本次自己使用的是RT-Thread新推出的Studio,在配置QSPI及SFUD过程中也踩了不少坑,稍微记录一下学习和配置过程。
参考资料:
SFUD Github原网址
RT-Thread文档中心
IOT-OS之RT-Thread(九)— SPI设备对象管理与SFUD管理框架
Rtthread学习笔记(九)RT-Thread Studio的开启SPI1总线,外部flash(W25Q64)做从机设备
SFUD | 一款串行 Flash 通用驱动库
2 概述
2.1 SPI和QSPI
SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步通信总线,常用于短距离通讯,一般使用 4 根线通信
- MOSI –主机输出 / 从机输入数据线(SPI Bus Master Output/Slave Input)。
- MISO –主机输入 / 从机输出数据线(SPI Bus Master Input/Slave Output)。
- SCLK –串行时钟线(Serial Clock),主设备输出时钟信号至从设备。
- CS –从设备选择线 (Chip select)。也叫 SS、CSB、CSN、EN 等,主设备输出片选信号至从设备
具体工作模式如下:
从设备的时钟由主设备通过 SCLK 提供,MOSI、MISO 则基于此脉冲完成数据传输。SPI 的工作时序模式由 CPOL(Clock Polarity,时钟极性)和 CPHA(Clock Phase,时钟相位)之间的相位关系决定。其中CPOL来决定时钟空闲电平状态,CPOL=0表示空闲状态为低电平,CPOL=1表示空闲状态为高电平。CPHA则表示是在第几个边沿进行采样,CPHA=0表示在第一个时钟变化边沿进行采样,对应下图的红线,CPHA=1则表示在第二个时钟变化边沿进行采样,对应下图中的蓝线。
QSPI则是Queued SPI的简写,是Motorola公司推出的SPI接口的扩展,比SPI应用更加广泛。在SPI协议的基础上,Motorola公司对其功能进行了增强,增加了队列传输机制,推出了队列串行外围接口协议(即QSPI协议)。QSPI 是一种专用的通信接口,连接单、双或四(条数据线) SPI Flash 存储介质。
该接口可以在以下三种模式下工作:
① 间接模式:使用 QSPI 寄存器执行全部操作
② 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)
③ 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器
采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。
可以简单理解为是SPI的高级版本。
2.2 SFUD
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
开源库地址:https://github.com/armink/SFUD
2.3 W25Q256FV
以下节选自正点原子Hal库手册
3 移植过程
移植前请仔细阅读SFUD源码给出的帮助文档以及RT-Thread官方手册。
3.1 开启相关组件
首先是在选择项目文件夹下的RT-Thread Settings进入软件包中心,如下图所示,选中并开启SPI和SFUD两个功能
然后选择右下角的更多配置,或者图中最右边的左箭头符号,进入界面,配置过程如下,配置完成后如图中所示
组件 → 设备驱动程序 → 使用SPI总线 / 设备驱动程序(勾选) → 使用QSPI模式(勾选) → 使用串行Flash通用驱动程序(SFUD)(勾选) → 勾选前三个选项
开启后保存来进行更新,更新完成后退出该界面。
如果是通过ENV配置的话需要从源码网址中下载源程序把里面的文件拷进去(核心源码文件包括:sfud.c,sfud_sfdp.c,sfud_port.c),但是RT-Thread Studio会自动帮你完成配置,这也是这个编辑器的强大之处,不过如果不太熟悉这款软件和相关源码配置的话刚上手时会觉得云里雾里。配置完成之后的SFUD在 “rt-thread → components → drivers → spi” 路径下。主要是 sfud文件夹和 spi_flash_sfud.c、spi_flash_sfud.h文件。
我们先不管SFUD,先去把QSPI进行配置。
3.2 QSPI配置
打开 board.h 文件(在drivers路径下),找到QSPI配置信息如下:
首先是在 board.h文件里面添加:#define BSP_USING_QSPI(就在上图这段文字下面进行配置)。
然后打开CubeMx进行相关配置生成代码。确定需要开启的引脚如下:
注意W25Q256系列为32M字节串行Flash芯片,32M = 3210241024 = 2^25,因此配置Flash Size应该为25-1=24。配置如下:
按照官方说法应该把生成的程序放到 board.c 里面,然后相应的初始化函数放到main文件里面。但是这样子会显得程序有点臃肿复杂,不便查阅,因此我们在application文件加下新建两个文件夹inc和src,在inc里面新建头文件qspi.h,在src下面新建源文件qspi.c来存放相应代码。后续编程过程中inc和src可也用来存放其他的头文件和源文件(记得把这两条路径包含进工作空间内才能找到对应文件编译正确)。内部程序如下:
/* qspi.h 文件内容*/
#ifndef APPLICATIONS_INC_QSPI_H_
#define APPLICATIONS_INC_QSPI_H_
#include <board.h>
void HAL_QSPI_MspInit(QSPI_HandleTypeDef* hqspi);
void MX_QUADSPI_Init(void);
#endif /* APPLICATIONS_INC_QSPI_H_ */
/* qspi.c 文件内容*/
#include "qspi.h"
QSPI_HandleTypeDef hqspi;
/**
* @brief QSPI MSP Initialization
* This function configures the hardware resources used in this example
* @param hqspi: QSPI handle pointer
* @retval None
*/
void HAL_QSPI_MspInit(QSPI_HandleTypeDef* hqspi)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hqspi->Instance==QUADSPI)
{
/* USER CODE BEGIN QUADSPI_MspInit 0 */
/* USER CODE END QUADSPI_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_QSPI_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**QUADSPI GPIO Configuration
PF6 ------> QUADSPI_BK1_IO3
PF7 ------> QUADSPI_BK1_IO2
PF8 ------> QUADSPI_BK1_IO0
PF9 ------> QUADSPI_BK1_IO1
PB2 ------> QUADSPI_CLK
PB6 ------> QUADSPI_BK1_NCS
*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN QUADSPI_MspInit 1 */
/* USER CODE END QUADSPI_MspInit 1 */
}
}
/**
* @brief QUADSPI Initialization Function
* @param None
* @retval None
*/
void MX_QUADSPI_Init(void)
{
/* USER CODE BEGIN QUADSPI_Init 0 */
/* USER CODE END QUADSPI_Init 0 */
/* USER CODE BEGIN QUADSPI_Init 1 */
/* USER CODE END QUADSPI_Init 1 */
/* QUADSPI parameter configuration*/
hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = 2;
hqspi.Init.FifoThreshold = 4;
hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
hqspi.Init.FlashSize = 24;
hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_4_CYCLE;
hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
hqspi.Init.FlashID = QSPI_FLASH_ID_1;
hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
if (HAL_QSPI_Init(&hqspi) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN QUADSPI_Init 2 */
/* USER CODE END QUADSPI_Init 2 */
}
然后相对应的,由于在board.c文件内部没有了函数定义,因此我们要在board.h中声明相关函数才能使整个程序调用正常,配置如下:
此时编译下载程序进单片机内部后,启用终端并调用 list_device 命令则会看到内部qspi总线已经开启
3.3 SFUD配置
当然,开启总线还不够,我们还要把设备挂载到总线上才能正常使用。
正常情况下,经过上述操作之后,我们在rtconfig.h文件内部的配置应该如下(由于我这边又启用了其他功能,所以可能会有所不同,但大致上过于QSPI和SFUD的配置应该是类似的)
/* Device Drivers */
#define RT_USING_DEVICE_IPC
#define RT_PIPE_BUFSZ 512
#define RT_USING_SERIAL
#define RT_SERIAL_RB_BUFSZ 64
#define RT_USING_HWTIMER
#define RT_USING_PIN
#define RT_USING_PWM
#define RT_USING_SPI
#define RT_USING_QSPI
#define RT_USING_SFUD
#define RT_SFUD_USING_SFDP
#define RT_SFUD_USING_FLASH_INFO_TABLE
#define RT_SFUD_USING_QSPI
#define RT_SFUD_SPI_MAX_HZ 50000000
这里先简单说一下,如果是在Studio内部移植的话你会发现是没有sfud_port.c 文件的,相关功能RT-Thread已经帮你集成好实现在spi_flash_sfud.c里面了,因此无需进行配置。就连初始化QSPI的相关函数,也已经在drv_qspi.c文件下面帮你实现了。
关于SFUD更详细的内部实现过程及移植,请大家已经要仔细阅读Github上面的官方文档,很多细节都在里面有所讲述。
注意到相关的芯片信息已经在sfud_flash_def.h里面已经有所定义了,主要如下:
#ifdef SFUD_USING_FLASH_INFO_TABLE
/* SFUD supported flash chip information table. If the flash not support JEDEC JESD216 standard,
* then the SFUD will find the flash chip information by this table. You can add other flash to here then
* notice me for update it. The configuration information name and index reference the sfud_flash_chip structure.
* | name | mf_id | type_id | capacity_id | capacity | write_mode | erase_gran | erase_gran_cmd |
*/
#define SFUD_FLASH_CHIP_TABLE \
{ \
{"AT45DB161E", SFUD_MF_ID_ATMEL, 0x26, 0x00, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_DUAL_BUFFER, 512, 0x81}, \
{"W25Q40BV", SFUD_MF_ID_WINBOND, 0x40, 0x13, 512L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"W25Q16BV", SFUD_MF_ID_WINBOND, 0x40, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"W25Q64DW", SFUD_MF_ID_WINBOND, 0x60, 0x17, 8L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"W25Q128BV", SFUD_MF_ID_WINBOND, 0x40, 0x18, 16L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"W25Q256FV", SFUD_MF_ID_WINBOND, 0x40, 0x19, 32L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"SST25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \
{"M25P32", SFUD_MF_ID_MICRON, 0x20, 0x16, 4L*1024L*1024L, SFUD_WM_PAGE_256B, 64L*1024L, 0xD8}, \
{"M25P80", SFUD_MF_ID_MICRON, 0x20, 0x14, 1L*1024L*1024L, SFUD_WM_PAGE_256B, 64L*1024L, 0xD8}, \
{"M25P40", SFUD_MF_ID_MICRON, 0x20, 0x13, 512L*1024L, SFUD_WM_PAGE_256B, 64L*1024L, 0xD8}, \
{"EN25Q32B", SFUD_MF_ID_EON, 0x30, 0x16, 4L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"GD25Q64B", SFUD_MF_ID_GIGADEVICE, 0x40, 0x17, 8L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"GD25Q16B", SFUD_MF_ID_GIGADEVICE, 0x40, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"S25FL216K", SFUD_MF_ID_CYPRESS, 0x40, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"S25FL032P", SFUD_MF_ID_CYPRESS, 0x02, 0x15, 4L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"A25L080", SFUD_MF_ID_AMIC, 0x30, 0x14, 1L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"F25L004", SFUD_MF_ID_ESMT, 0x20, 0x13, 512L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \
{"PCT25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \
}
#endif /* SFUD_USING_FLASH_INFO_TABLE */
#ifdef SFUD_USING_QSPI
/* This table saves flash read-fast instructions in QSPI mode,
* SFUD can use this table to select the most appropriate read instruction for flash.
* | mf_id | type_id | capacity_id | qspi_read_mode |
*/
#define SFUD_FLASH_EXT_INFO_TABLE \
{ \
/* W25Q40BV */ \
{SFUD_MF_ID_WINBOND, 0x40, 0x13, NORMAL_SPI_READ|DUAL_OUTPUT}, \
/* W25Q80JV */ \
{SFUD_MF_ID_WINBOND, 0x40, 0x14, NORMAL_SPI_READ|DUAL_OUTPUT}, \
/* W25Q16BV */ \
{SFUD_MF_ID_WINBOND, 0x40, 0x15, NORMAL_SPI_READ|DUAL_OUTPUT}, \
/* W25Q32BV */ \
{SFUD_MF_ID_WINBOND, 0x40, 0x16, NORMAL_SPI_READ|DUAL_OUTPUT}, \
/* W25Q64JV */ \
{SFUD_MF_ID_WINBOND, 0x40, 0x17, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO}, \
/* W25Q128JV */ \
{SFUD_MF_ID_WINBOND, 0x40, 0x18, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO}, \
/* W25Q256FV */ \
{SFUD_MF_ID_WINBOND, 0x40, 0x19, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO}, \
/* EN25Q32B */ \
{SFUD_MF_ID_EON, 0x30, 0x16, NORMAL_SPI_READ|DUAL_OUTPUT|QUAD_IO}, \
/* S25FL216K */ \
{SFUD_MF_ID_CYPRESS, 0x40, 0x15, NORMAL_SPI_READ|DUAL_OUTPUT}, \
/* A25L080 */ \
{SFUD_MF_ID_AMIC, 0x30, 0x14, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO}, \
/* A25LQ64 */ \
{SFUD_MF_ID_AMIC, 0x40, 0x17, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_IO}, \
/* MX25L3206E and KH25L3206E */ \
{SFUD_MF_ID_MICRONIX, 0x20, 0x16, NORMAL_SPI_READ|DUAL_OUTPUT}, \
/* GD25Q64B */ \
{SFUD_MF_ID_GIGADEVICE, 0x40, 0x17, NORMAL_SPI_READ|DUAL_OUTPUT}, \
}
#endif /* SFUD_USING_QSPI */
仔细看看内部有没有你所用的芯片,基本上大多数芯片应该都在里面了。
也可以自己进行设备表的相关配置。在sfud_cfg.h里面修改 SFUD_FLASH_DEVICE_TABLE 这个宏定义
enum {
SFUD_W25Q256FV_DEVICE_INDEX = 0,
};
#define SFUD_FLASH_DEVICE_TABLE \
{ \
[SFUD_W25Q256FV_DEVICE_INDEX] = {.name = "W25Q256FV", .spi.name = "qspi1"} \
}
然后我们添加从设备驱动,在application路径下的inc和src里面我们分别添加flash.h和flash.c文件,内部程序如下:
#include "board.h"
#include "drv_qspi.h"
#include "rtdevice.h"
#define QSPI_BUS_NAME "qspi1"
#define QSPI_DEVICE_NAME "qspi10"
#define W25Q_FLASH_NAME "W25Q256FV"
#define QSPI_CS_PIN GET_PIN(B, 6) //此处注意引脚区分,用你自己的
static int rt_hw_qspi_flash_with_sfud_init(void)
{
if (stm32_qspi_bus_attach_device(QSPI_BUS_NAME, QSPI_DEVICE_NAME, (rt_uint32_t)QSPI_CS_PIN, 4, RT_NULL, RT_NULL) != RT_EOK)
return -RT_ERROR;
#ifdef RT_USING_SFUD
if (rt_sfud_flash_probe(W25Q_FLASH_NAME, QSPI_DEVICE_NAME) == RT_NULL)
return -RT_ERROR;
#endif
return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_qspi_flash_with_sfud_init);
编译下载后发现成功读取到32M的芯片,我们在msh里面键入list_device,就得到下图所示结果(请忽略pwm1和pwm8,这是我另外的驱动)
使用sf命令进行设备查看
这样子就移植成功啦!更详细的命令请查看RT官方文档
如有错误请及时指出