从本文开始,测试学习一些 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开发快速上手)
RT-Thread记录(二、RT-Thread内核启动流程 — 启动文件和源码分析
RT-Thread 内核篇系列博文链接:
RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)
RT-Thread记录(四、RT-Thread 时钟节拍和软件定时器)
RT-Thread记录(五、RT-Thread 临界区保护)
RT-Thread记录(六、IPC机制之信号量、互斥量和事件集)
RT-Thread记录(七、IPC机制之邮箱、消息队列)
RT-Thread记录(八、理解 RT-Thread 内存管理)
RT-Thread记录(九、RT-Thread 中断处理与阶段小结)
RT-Thread 设备篇系列博文链接:
RT-Thread记录(十、全面认识 RT-Thread I/O 设备模型)
RT-Thread记录(十一、I/O 设备模型之UART设备 — 源码解析)
RT-Thread记录(十二、I/O 设备模型之UART设备 — 使用测试)
RT-Thread记录(十三、I/O 设备模型之PIN设备)
RT-Thread记录(十四、I/O 设备模型之ADC设备)
RT-Thread记录(十五、I/O 设备模型之SPI设备)
RT-Thread 组件与软件包系列博文链接:
本文是第一篇
一、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 文件如下图,基本的介绍,函数使用,说明该有的都有,大家可自行查看:
本文不深入分析源码实现原理,对于理论只做简单说明。这里关于理论的分析说明推荐几篇文章,在文章说明理论基础的时候也参考了下面的文章:
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设备)接描述 中 《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 开发有多么的方便了。
<3
但是前提当然是得对 RT-Thread 的面向对象的思想,I/O 设备模型等基础有一定的认识,如果只是为了使用,看一篇文章即可,如果是为了理解掌握,还得多多了解 RT-Thread 基础相关知识,比如博主的 RT-Thread 专栏 = =! O(∩_∩)O哈哈~
再次申明一下,对于组件与软件包,因为都是大佬开发者们写好的驱动,所以我偏向的重点是在于学会使用,说明文档在每个组件或者软件包都有作者详细的说明,那才是最好的参考资料。
<3
希望大家多多支持!本文就到这里,谢谢!