引言
本文为RT-Thread Studio 使用STM32CubeMX联合开发中——基于SPI通信,SFUD驱动,FAL组件使用W25Q32的读写。主要涉及:
- SPI配置:最基本硬件接口;
- SFUD配置:基于与W25Q32交互的驱动;
- FAL配置:SFUD配置的抽象层;
先看层级关系。
一、CubeMX配置SPI
打开全输主SPI模式,我的芯片SPI最高支持18M,这里我设置15M。设置好后生成代码,然后关闭CubeMX。
二、RT-Thread Studio配置
设备驱动里面打开SPI、打开SFUD、显示更多SFUD调试信息(可选)。
打开FAL组件、打开FAL使用SFUD驱动、驱动名称修改为W25Q32(可选)。
保存。编译,然后发现一大堆错误,这是因为还要去修改代码。
三、代码配置
(1)SPI配置
去到CubeMX生成的 main.c 文件里面加入以下代码。这是为了让外部的RT-Thread去调用我们SPI的初始化。如果你的是SPI1就在里面放SPI1的初始化代码。
void RTT_STM32_HAL_SPI_Init(void)
{
MX_SPI2_Init();
}
打开 board.h 文件,打开里面SPI相关的宏。我用的是SPI2,所以打开SPI2。
打开 board.c 文件,调用我们刚才的SPI初始化代码。
(2)FAL配置
移动 fal_cfg.h 到 fal 目录下的 inc 文件夹中。
移动 fal_flash_sfud_port.c 到 fal 目录下的 src 文件夹中。
移动后。
编译。发现有报错的,原因是我们没用用到芯片内部的Flash,只是用外部的,所以会报错。这里把芯片内部相关的删掉后好了。
删掉内容。然后改一下这个宏 NOR_FLASH_DEV_NAME ,改为我们刚刚命名的设备名称,也就是 "W25Q32"。
打开 fal_flash_stm32f2_port.c 文件。修改设备参数,按照自己的设备情况来。
修改完,然后编译。已经没有错误了,接下来开始写应用代码。
四、基础代码
application 文件夹下新建一个 w25q32.c 文件,我们的代码主要放在里面写。
加入以下代码。
/*
* @Author: Troubadour 2276791354@qq.com
* @Date: 2024-04-28 17:51:03
* @LastEditors: Troubadour 2276791354@qq.com
* @LastEditTime: 2024-04-28 18:13:17
* @Version:
* @Description:
*/
#include "drv_spi.h"
#include "spi_flash_sfud.h"
#include "fal.h"
#define W25Q32_CS_GPIOx GPIOB
#define W25Q32_CS_PIN GPIO_PIN_12
rt_spi_flash_device_t rtt_spi_dev = RT_NULL;
int W25Q32_Init(void)
{
/* Step1. 绑定片选引脚,也就是绑定设备。spi20 表示挂载在 spi2 总线上的 0 号设备, PB12是片选,这一步就可以将从设备挂在到总线中 */
if (rt_hw_spi_device_attach("spi2", "spi20", GPIOB, GPIO_PIN_12) != RT_EOK)
{
LOG_E("SPI device attach failed! ");
return -1;
}
/* 通过SFUD(串行闪存通用驱动程序)驱动程序库和SPI设备探测SPI FLASH设备。 */
rtt_spi_dev = rt_sfud_flash_probe("W25Q32", "spi20");
if (rtt_spi_dev == RT_NULL)
{
LOG_E("sfud flash probe failed! ");
return -1;
}
/* ------------------------------------- */
/* Step2. FAL initialization. */
/* ------------------------------------- */
fal_init();
LOG_D("W25Q32 ready! ");
return 0;
}
INIT_COMPONENT_EXPORT(W25Q32_Init);
运行...
五、应用。
(1)命令行测试
注!RT-Thread是带命令行测试功能的,这还是单片机(我的单片机STM32F103ZET6)。接下来我们先来用命令行试一试看看能不能实际的跑。先来个 fal 查看支持的命令。
- fal probe -- 查看分区
- fal read -- 读数据
- fal write -- 写数据
- fal erase -- 擦除数据
- fal bench -- 性能测试
1.1 fal probe -- 查看分区
fal probe 查看所有分区。
fal probe <name> 查看指定设备/分区。
1.2 fal read -- 读数据
fal read <addr> <size> 从指定地址addr开始读取size个字节。
1.3 fal write -- 写数据
1.4 fal erase -- 擦除数据
写数据就要先擦除!
fal write <addr> <size> 从指定地址addr开始写入size个字节。
fal erase <addr> <size> 从指定地址addr开始擦除size个字节。
1.5 fal bench -- 性能测试
fal bench <sector> <yes> 使用指定的大小测试性能(一般选中扇区的大小,我的是4096),然后加一个yes,因为测试性能会导致数据丢失,所以需要加 yes 确定!
测试前需要先使用 fal probe <device / partition> 选中分区或设备。(选中设备就是测试整个设备所有的分区)。
示例:测试单个分区:fal probe easyflash。我的分区设置的是 1M 的大小,所用时间还行,大概几秒钟的时间。
示例: 测试整个设备:fal probe W25Q32。我的整个设备是 4M 的大小,所有用的时间就比较长了。
(2)实际代码测试
加入以下代码测试写入的字符串是否还能成功读取。
/*
* @Author: Troubadour 2276791354@qq.com
* @Date: 2024-04-28 17:51:03
* @LastEditors: Troubadour 2276791354@qq.com
* @LastEditTime: 2024-04-29 09:29:15
* @Version:
* @Description:
*/
#include "drv_spi.h"
#include "spi_flash_sfud.h"
#include "fal.h"
#define W25Q32_CS_GPIOx GPIOB
#define W25Q32_CS_PIN GPIO_PIN_12
rt_spi_flash_device_t rtt_spi_dev = RT_NULL;
int W25Q32_Init(void)
{
/* Step1. 绑定片选引脚,也就是绑定设备。spi20 表示挂载在 spi2 总线上的 0 号设备, PB12是片选,这一步就可以将从设备挂在到总线中 */
if (rt_hw_spi_device_attach("spi2", "spi20", GPIOB, GPIO_PIN_12) != RT_EOK)
{
LOG_E("SPI device attach failed! ");
return -1;
}
/* 通过SFUD(串行闪存通用驱动程序)驱动程序库和SPI设备探测SPI FLASH设备。 */
rtt_spi_dev = rt_sfud_flash_probe("W25Q32", "spi20");
if (rtt_spi_dev == RT_NULL)
{
LOG_E("sfud flash probe failed! ");
return -1;
}
/* ------------------------------------- */
/* Step2. FAL initialization. */
/* ------------------------------------- */
fal_init();
LOG_D("W25Q32 ready! ");
return 0;
}
INIT_COMPONENT_EXPORT(W25Q32_Init);
const rt_uint8_t TestWriteString[] = "Hello! This is test content.";
void W25Q32_WriteStr(int argc, char **argv)
{
rt_uint32_t WriteCount = 0;
/* Step1. Erase to Flash */
LOG_I("Run W25Q32 Erase.");
WriteCount = fal_partition_erase(fal_partition_find("easyflash"), 0, 4096);
if (WriteCount <= 0)
{
LOG_E("fal_partition_write failed! ");
}
else
{
LOG_I("fal_partition_write succeess! Write size: %d", WriteCount);
}
/* Step2. Write to Flash */
LOG_I("Run W25Q32 WriteStr.");
WriteCount = fal_partition_write(fal_partition_find("easyflash"), 0, TestWriteString, sizeof(TestWriteString));
if (WriteCount <= 0)
{
LOG_E("fal_partition_write failed! ");
}
else
{
LOG_I("fal_partition_write succeess! Write size: %d", WriteCount);
}
}
void W25Q32_ReadStr(int argc, char **argv)
{
rt_uint32_t ReadCount = 0;
uint8_t ReadBuff[128] = {0};
/* Step1. Read data from Flash */
LOG_I("Read W25Q32 data.");
ReadCount = fal_partition_read(fal_partition_find("easyflash"), 0, ReadBuff, sizeof(ReadBuff));
if (ReadCount != sizeof(ReadBuff))
{
LOG_E("fal_partition_read failed! ");
}
else
{
LOG_I("fal_partition_read succeess! Read size: %d", ReadCount);
LOG_I("Read data: %s", ReadBuff);
}
}
MSH_CMD_EXPORT(W25Q32_WriteStr, Run W25Q32 WriteStr);
MSH_CMD_EXPORT(W25Q32_ReadStr, Run W25Q32 ReadStr);
可以看到。成功读取!
六、FAL的API讲解。
(1)fal_init
int fal_init(void)
该函数初始化FAL组件。返回值是分区总数。代码中注释的描述是 >=0 有效,但正常情况下<=0 是无效的,也就是初始化失败。
(2)fal_partition_erase
int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size)
擦除操作函数。返回值小于0为失败。可以擦除指定分区,或者指定设备(所有分区)。
- part:分区结构体指针。可以使用 fal_partition_find("easyflash") 获取。
- addr:擦除的起始地址。
- size:擦除大小,单位Byte。一般选择扇区大小的倍数。
(3)fal_partition_write
int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size)
写数据操作。返回值为写入的数据量。如果扇区有数据,需要先擦除再写入!
- part:分区结构体指针。可以使用 fal_partition_find("easyflash") 获取。
- addr:写入的起始地址。
- buf:数据。
- size:数据量,单位Byte。
(4)fal_partition_read
int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size)
读取数据操作。返回值为读取的数据量。
- part:分区结构体指针。可以使用 fal_partition_find("easyflash") 获取。
- addr:读取的起始地址。
- buf:存放读取数据的缓冲区。
- size:数据量,单位Byte。