写点什么

RT-Thread 记录(十六、SFUD 组件 — SPI Flash 的读写)

作者:矜辰所致
  • 2022 年 8 月 26 日
    江苏
  • 本文字数:5814 字

    阅读完需:约 19 分钟

RT-Thread记录(十六、SFUD组件 — SPI Flash的读写)
从本文开始,测试学习一些 RT-Thread 常用的组件与软件包,先从刚学完的 SPI 设备开始。
复制代码


前言

RT-Thread 专栏更新至今,从开发环境到内核到设备模型,其实我们已经把使用 RT-Thread 的基础知识都讲过一遍,认真学习的朋友实际上都已经可以使用 RT-Thread 完成一些实际小项目了。


上一篇文章最后说过,RT-Thread 有一个很大的特点在于他的生态比一般的 RTOS 完善,我们在实际应用中,有许许多多现成的官方或者很多开发者提供的组件或者软件包,我们可以直接导入工程进行使用。


针对我们 RT-Thread 实际应用,很多时候不仅是要知道基本的理论,还需要真正的知道怎么实际“用”起来。基于本专栏的开发环境 RT-Thread Studio,本文开始我们来测试几个典型的 组件与软件包,来看看他们实际是如何使用的。


我们刚讲完 SPI 设备,本文就从与 SPI 设备相关的组件 SFUD 组件说起。


说明,对于 RT-Thread 记录 中组件与软件包部分的文章,我并不计划讲太多的原理,因为我们的最终目的还是在于应用,在之间讲解 RT-Thread 的基础中,为了让大家更明白 RT-Thread 内核以及 I/O 设备模型,也没少分析源码以及讲解实现原理,核心的部分都是自己研究源码。

对于 组件与软件包 部分,我侧重点会在与的记录测试使用的过程,使得我们能够快速上手。


本 RT-Thread 专栏记录的开发环境:

RT-Thread 记录(一、RT-Thread 版本、RT-Thread Studio 开发环境 及 配合 CubeMX 开发快速上手)

https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59

RT-Thread 记录(二、RT-Thread 内核启动流程 — 启动文件和源码分析)

https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59

RT-Thread 内核篇系列博文链接:

RT-Thread 记录(三、RT-Thread 线程操作函数及线程管理与 FreeRTOS 的比较)

https://xie.infoq.cn/article/1d2e8e030ae689d6b8ee44b05

RT-Thread 记录(四、RT-Thread 时钟节拍和软件定时器)

https://xie.infoq.cn/article/3198c9b741782036bfd6e54e9

RT-Thread 记录(五、RT-Thread 临界区保护

https://xie.infoq.cn/article/7a41020e03184950664df7391

RT-Thread 记录(六、IPC 机制之信号量、互斥量和事件集)

https://xie.infoq.cn/article/1f49bfd6c69377deb9eee838f

RT-Thread 记录(七、IPC 机制之邮箱、消息队列)

https://xie.infoq.cn/article/360b04e7bc6024917afecef1d

RT-Thread 记录(八、理解 RT-Thread 内存管理)

https://xie.infoq.cn/article/bd5d8f8a19fa11ea9feacf34d

RT-Thread 记录(九、RT-Thread 中断处理与阶段小结)

https://xie.infoq.cn/article/3da9530cfac06feece3523a0c

RT-Thread 设备篇系列博文链接:

RT-Thread 记录(十、全面认识 RT-Thread I/O 设备模型)

https://xie.infoq.cn/article/40536d29988d683c78b4ba5ff

RT-Thread 记录(十一、I/O 设备模型之 UART 设备 — 源码解析)

https://xie.infoq.cn/article/38bb4bf15cb81f1fdb060b29e

RT-Thread 记录(十二、I/O 设备模型之 UART 设备 — 使用测试)

https://xie.infoq.cn/article/1df47f3ae5174dfcb418b07b6

RT-Thread 记录(十三、I/O 设备模型之 PIN 设备)

https://xie.infoq.cn/article/af3ff1066f61cddeb134c3c88

RT-Thread 记录(十四、I/O 设备模型之 ADC 设备)

https://xie.infoq.cn/article/2eda5e23a77db3a60d2595d97

RT-Thread 记录(十五、I/O 设备模型之 SPI 设备)

https://xie.infoq.cn/article/33dcc6fdc5169c287078a2bbb

一、SFUD 组件简介

SFUD (全称 Serial Flash Universal Driver)是一款开源的串行 SPI Flash 通用驱动库。

1.1 基本简介

基础介绍借用官方的说明:由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。


在 RT-Thread 中,SFUD 组件的 SPI 驱动是以 RTThread 的 I/O 设备模型框架为基础设计的。


使用 SFUD 组件,我们不用自己写 SPI Flash 的驱动。


支持 SPI/QSPI 接口、面向对象(同时支持多个 Flash 对象)、可灵活裁剪、扩展性强、支持 4 字节地址。


☆ SFUD 是个开源的组件,对于该组件真正权威的参考说明就是该组件作者写好的 README.md 文件(永远要记住第一作者的文档、官方的文档永远是最具有参考价值的)。☆


使用 RT-Thread Studio 打开 README 文件如下图,基本的介绍,函数使用,说明该有的都有,大家可自行查看:




本文不深入分析源码实现原理,对于理论只做简单说明。这里关于理论的分析说明推荐几篇文章,在文章说明理论基础的时候也参考了下面的文章:


SFUD源码浅析


RT-Thread中flash管理 — [SFUD组件 和 FAL驱动组件介绍]

1.2 SFUD 对 Flash 的管理

我们以前讲过,面向对象思想的程序设计,一般都会使用一个结构体 表示一个对象,我们讲过的线程、IPC 机制,I/O 设备都有他们的设备控制块结构体。


对于 SPI Flash 设备,SFUD 也定义了一个结构体 sfud_flash 进行管理,其位置和内容如下图:



在这个对象控制块中,有一个成员为 chip ,其类型为芯片信息的结构体sfud_flash_chip,如下图:



在 SFUD 组件中,已经定义好了一些支持的 chip 信息,如下图:



基本上包括了市面上通用的 SPI Flash 芯片,如果使用的 flash 不支持 SFUD 组件,可根据 README 文件自行添加。


简单的概述就到这里,下面我们来看看 SFUD 组件提供的操作函数。

二、SFUD 组件操作函数

根据 SFUD 组件的 README 文件,SFUD 组件提供的 API 框架图如下:



这里要说明一下,上面的 API 是 SFUD 对外标准的通用 API,就是不管用什么系统,或者使用裸机,移植好了 SFUD 组件这些 API 都可以使用。


对于我们使用的 RT-Thread 来说,访问设备的函数就是 SFUD 设备的标准 API。


但是对于初始化相关的部分来说,RT-Thread 官方给我们写好了标准的驱动函数。

2.1 初始化相关函数

在工程文件中,与 RT-Thread 初始化驱动文件如下:



其提供的函数有(对于 RT-Thread 中初始化相关的函数使用,在本文后面使用测试小节会有详细示例说明):


/** * Probe SPI flash by SFUD(Serial Flash Universal Driver) driver library and though SPI device.使用 SFUD 探测 spi_dev_name 从设备,并将 spi_dev_name 连接的 flash 初始化为块设备,名称 spi_flash_dev_name */rt_spi_flash_device_t rt_sfud_flash_probe(const char *spi_flash_dev_name, const char *spi_dev_name);
/** * Probe SPI flash by SFUD (Serial Flash Universal Driver) driver library and though SPI device by specified configuration. * rt_sfud_flash_probe 调用了此函数使得与底层 SFUD 本身的初始化文件关联起来 */rt_spi_flash_device_t rt_sfud_flash_probe_ex(const char *spi_flash_dev_name, const char *spi_dev_name, struct rt_spi_configuration *spi_cfg, struct rt_qspi_configuration *qspi_cfg);
/** * Delete SPI flash device 删除SPI SFUD 设备 */rt_err_t rt_sfud_flash_delete(rt_spi_flash_device_t spi_flash_dev);
/** * Find sfud flash device by SPI device name通过 SPI 设备名称 找到一个 SFUD Flash 设备 */sfud_flash_t rt_sfud_flash_find(const char *spi_dev_name);
/** * Find sfud flash device by flash device name 通过 Flash 设备名称 找到一个 SFUD Flash 设备 */sfud_flash_t rt_sfud_flash_find_by_dev_name(const char *flash_dev_name);
复制代码


函数我们不做深入分析,大家需要学会使用,以前有很多文章都有源码分析说明,源码自己查看,比如其中比较关键的一个函数 rt_sfud_flash_probe_ex


2.2 设备访问函数


设备访问函数,SFUD 组件中 README 文件都有说明的,函数使用的注意事项可查看组件说明文件。


这里统一列一下方便以后复制使用:

2.2.1 读数据

/*参数  描述flash  Flash 设备对象addr  起始地址size  从起始地址开始读取数据的总大小data  读取到的数据*/sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data)
复制代码

2.2.2 擦除数据

部分擦除:


/*参数  描述flash  Flash 设备对象addr  起始地址size  从起始地址开始擦除数据的总大小*/sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size)
复制代码


全片擦除:


/*参数  描述flash  Flash 设备对象*/sfud_err sfud_chip_erase(const sfud_flash *flash)
复制代码

2.2.3 写数据

直接写:


/*参数  描述flash  Flash 设备对象addr  起始地址size  从起始地址开始写入数据的总大小data  待写入的数据*/sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data)
复制代码


先擦除再写:


/*参数  描述flash  Flash 设备对象addr  起始地址size  从起始地址开始写入数据的总大小data  待写入的数据*/sfud_err sfud_erase_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data)
复制代码

2.2.4 Flash 状态相关

读取 Flash 状态:


/*参数  描述flash  Flash 设备对象status  当前状态寄存器值*/sfud_err sfud_read_status(const sfud_flash *flash, uint8_t *status)
复制代码


修改 Flash 状态:


/*参数  描述flash  Flash 设备对象is_volatile  是否为易闪失的,true: 易闪失的,及断电后会丢失status  当前状态寄存器值*/sfud_err sfud_write_status(const sfud_flash *flash, bool is_volatile, uint8_t status)
复制代码

三、使用测试


本小节说明一下在 RT-Thread Studio 上使用 SFUD 组件的步骤,然后我们使用示例进行基本的测试:

3.1 使用步骤

3.1.1 使能 SPI 设备

根据文章 RT-Thread 记录(十五、I/O 设备模型之 SPI 设备)接描述 https://xie.infoq.cn/article/33dcc6fdc5169c287078a2bbb 中 《3.1 SPI 设备使用步骤》说明使能 SPI 总线。


注册 SPI 总线设备,使用list_device可查看结果:



.

3.1.2 使能 SFUD 组件包

和使能 SPI 设备一样,在 RT-Thread Studio 打开 RT-Thread Settings 打开 SFUD 组件使能,如下图:



使能完成,我们在应用层就可直接调用上一小节将的 SFUD 操作函数了。


在工程中, SFUD 组件相关的程序位置如下:



.

3.1.3 挂载 SFUD 设备

在使用 SFUD 设备前,需要挂载,类似把 SPI 设备挂载至 SPI 总线上一样,使用如下操作:



忘了另外一块开发板不是 W25Q128 而是 W25Q64,所以最终找到的是 W25Q64DW。


这里有个小问题说明一下, DBG 定义的问题,自己把 mian 里面的注释稍微修改一下:



.

3.1.4 应用程序查找设备

使用 rt_sfud_flash_find 或者 rt_sfud_flash_find_by_dev_name 获取设备句柄:



.

3.1.5 使用 API 进行读写操作

完成上述步骤,就可以根据自己的应用,使用上面介绍的 SFUD 组件操作函数访问设备部分进行 Flash 的操作了。


比如:


3.2 读写测试

在上面的使用步骤说明中,其实我已经把自己做的简单测试都说了一遍,这里我们上一下测试部分代码,然后看一下测试效果:


#include <rtthread.h>#include "main.h"#include "usart.h"#include "gpio.h"#include <rtdevice.h>#include "board.h"#include "drv_spi.h"#include "spi_flash_sfud.h"

#ifndef DBG_TAG#define DBG_TAG "main"#endif#ifndef DBG_LVL#define DBG_LVL DBG_LOG#endif
#include <rtdbg.h>
//省略...
sfud_flash *test_sfud = NULL;
const uint8_t test_data[] = "this is a test data!";
//省略...
static void key1_thread_entry(void *par){
while(1){ if(key1_read == 0){ rt_thread_mdelay(10); //去抖动 if(key1_read == 0){ rt_kprintf("write flash ..\r\n");// sfud_write(test_sfud, 13, sizeof(test_data), test_data); sfud_erase_write(test_sfud, 13, sizeof(test_data), test_data); } while(key1_read == 0){rt_thread_mdelay(10);//去抖动 } } rt_thread_mdelay(1); }}
static void key2_thread_entry(void *par){ uint8_t read_data[30] = {0};// void *str = RT_NULL; while(1){ if(key2_read == 0){ rt_thread_mdelay(10); //去抖动 if(key2_read == 0){ rt_kprintf("read flash ..\r\n");// sfud_read(test_sfud, 0, sizeof(test_data), (uint8_t *)str); sfud_read(test_sfud, 13, sizeof(test_data), read_data); rt_kprintf("%s",read_data); } while(key2_read == 0){rt_thread_mdelay(10);//去抖动 } } rt_thread_mdelay(1); }}
//省略...
int main(void){
//省略... rt_hw_spi_device_attach("spi1", "spi10", GPIOA, GPIO_PIN_4); // CS 脚:PA4
/* 使用 SFUD 探测 spi10 从设备,并将 spi10 连接的 flash 初始化为块设备,名称 W25Q128 */ if (RT_NULL == rt_sfud_flash_probe("W25Q64", "spi10")) { return -RT_ERROR; };
// test_sfud = rt_sfud_flash_find("spi10"); // test_sfud = rt_sfud_flash_find_by_dev_name("W25Q64");
if(RT_NULL == test_sfud){ LOG_E("find sfud_flash failed!...\n"); }
//省略... return RT_EOK;}
复制代码


测试结果:



测试细节说明:


在测试的时候我使用了一个按键线程写 flash,最开始的时候使用的是 512 字节大小的线程栈:



使用函数sfud_erase_write 会比 sfud_write 函数占用更多的内存。

结语

本文我们从上一篇文章刚学完的 SPI 设备相关的 SFUD 组件开始,接触到了 RT-Thread 的组件与软件包,可以看出,对于常用的设备使用 RT-Thread 开发有多么的方便了。


但是前提当然是得对 RT-Thread 的面向对象的思想,I/O 设备模型等基础有一定的认识,如果只是为了使用,看一篇文章即可,如果是为了理解掌握,还得多多了解 RT-Thread 基础相关知识,比如博主的 RT-Thread 专栏 = =! O(∩_∩)O 哈哈~


再次申明一下,对于组件与软件包,因为都是大佬开发者们写好的驱动,所以我偏向的重点是在于学会使用,说明文档在每个组件或者软件包都有作者详细的说明,那才是最好的参考资料。


希望大家多多支持!本文就到这里,谢谢!

发布于: 刚刚阅读数: 6
用户头像

矜辰所致

关注

不浮夸,不将就,认真对待学知识的我们! 2022.08.02 加入

不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...

评论

发布
暂无评论
RT-Thread记录(十六、SFUD组件 — SPI Flash的读写)_RT-Thread_矜辰所致_InfoQ写作社区