Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread
SPI
w25q128
基于RT-Thread的STM32F4开发第八讲——SPI(普通、DMA、SFUD)
发布于 2025-05-26 22:59:23 浏览:269
订阅该版
[tocm] # 基于RT-Thread的STM32F4开发第八讲——SPI(普通、DMA、SFUD)基于RT-Thread的STM32F4开发第八讲——SPI(普通、DMA、SFUD) --- # 前言 本章是基于RT-Thread studio实现SPI的普通通讯、DMA通讯和SFUD通讯,开发板是正点原子的STM32F4探索者,SPI通讯芯片是W25Q128,使用的RT-Thread驱动是5.1.0。 --- # 一、SPI知识简述 ## 1.文章推荐 对于SPI的介绍这里不解释,可以看看我的往期文章 [STM32LL库编程系列第七讲——SPI通信(W25Q128)](https://blog.csdn.net/weixin_58172114/article/details/146156187?spm=1001.2014.3001.5501) [STM32的SPI通信的NSS引脚设置原理](https://blog.csdn.net/weixin_58172114/article/details/146902830?spm=1001.2014.3001.5501) 一个是通过裸机操作实现W25Q128的读写,对W25Q128的读写读写的时序介绍比较详细。 第二个是关于片选引脚的设置,我们知道SPI的片选有硬件和软件两种,软件很好理解,而硬件片选大家可能不常使用。该文介绍了不同工作模式下硬件片选引脚如何设置。 ## 2.SFUD SFUD (全称 Serial Flash Universal Driver)是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。 以上解释来自官方,本人也是第一次接触SFUD,通俗理解就是它将市面上所有的Flash芯片都写好的驱动服务,我们使用时不再需要管那些繁琐的指令配置,可直接进行数据读写。 ## 3.QSPI QSPI(Quad SPI)与 SPI(Serial Peripheral Interface)有以下区别: **硬件引脚:** SPI:通常由 4 根信号线组成,包括 SCLK(Serial Clock)串行时钟线、MOSI(Master Out Slave In)主设备输出从设备输入线、MISO(Master In Slave Out)主设备输入从设备输出线以及 CS(Chip Select)片选线。 QSPI:在 SPI 的基础上进行了扩展,除了标准的 SCLK 与 CS 外,还增加了四个双向数据引脚,即 IO0、IO1、IO2、IO3,共 6 根线。不过在实际应用中,也有将 QSPI 的片选信号与其他信号复用,从而使用 4 线实现 QSPI 通信的情况。 **数据传输模式:** SPI:是一种全双工同步串行通信协议,同一时刻可以通过 MOSI 和 MISO 线分别进行数据的发送和接收。 QSPI:支持多种数据传输模式,包括单 / 双 / 四线模式。在四线模式下为半双工,即同一时刻数据只能在一个方向上传输,四个数据引脚(IO0、IO1、IO2、IO3)同时向同一个方向传输数据,单次可传输 4bit 数据。 **传输速率:** SPI:数据传输速率取决于时钟频率和数据位宽。一般情况下,SPI 的时钟频率通常可达 20MHz,在高性能系统中能达到 60MHz。假设时钟频率为 10MHz,每次传输 8 位数据,则数据传输速率为 10MB/s。 QSPI:通过并行传输数据,理论上传输速率相比 SPI 能提高 4 倍。其工作频率通常在 133MHz 到 166MHz 之间,优化设置下能超过 200MHz。若时钟频率同样为 10MHz,采用四线模式传输 8 位数据,数据传输速率可达 40MB/s。 **应用场景:** SPI:适用于低速、简单的传感器接口,如温度传感器、压力传感器等;也常用于与小容量的 EEPROM 或 Flash 存储器、ADC 和 DAC 芯片进行通信,以及一些对数据传输速率要求不高的中速外围设备。 QSPI:特别适合大容量 NOR Flash 存储器的快速读写操作,在需要高速数据传输的应用中表现出色,如启动加载程序、操作系统镜像加载、闪存的快速代码执行、多媒体处理和高级物联网设备中的大规模数据处理等场景。 **硬件和软件复杂度:** SPI:接口简单,协议相对容易实现,硬件和软件设计复杂度较低,兼容性良好。 QSPI:实现比传统 SPI 更为复杂,需要更多的硬件支持,如额外的数据引脚处理电路等,软件上也需要更复杂的驱动程序来支持其多种传输模式和功能,并且 QSPI 设备通常比传统 SPI 设备更昂贵。 W25Q128是具备QSPI工作能力的。需要硬件上实现以下引脚复用 SI(Data Input)在 QSPI 模式下作为 IO0。 SO(Data Output)在 QSPI 模式下作为 IO1。 WP#(Write Protect)在 QSPI 模式下作为 IO2。 HOLD#(Hold)在 QSPI 模式下作为 IO3。 但是本人开发板使用的是普通SPI的连接方式,故无法使用QSPI。 # 二、RT-Thread工程创建 从以前文章得到驱动5.1.0不报错工程,先打开cubemx配置SPI,如图。配置详情看[STM32LL库编程系列第七讲——SPI通信(W25Q128)](https://blog.csdn.net/weixin_58172114/article/details/146156187?spm=1001.2014.3001.5501)  接着根据注释STEP x配置好SPI的工作环境   这里有一个参数不匹配的报错  定位到函数`static rt_uint32_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message)` 改正为`static rt_ssize_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message)` 这样SPI的工作环境就完成了 # 三、普通spi通讯 ## 1.u_spi.c 先粘贴代码如下。 ```c #include "u_spi.h" #include "drv_spi.h" #define DBG_TAG "u_spi" #define DBG_LVL DBG_LOG #include
#define SPI_NAME "spi10" #define SPI_BUS_NAME "spi1" struct rt_spi_device *w25q128_handle; void spi10_init(void) { rt_err_t spi10_flag; struct rt_spi_configuration w25q128_cfg; //w25q128_handle = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device)); //spi10_flag = rt_spi_bus_attach_device_cspin(w25q128_handle,SPI_NAME,SPI_BUS_NAME,rt_pin_get("PB.14"),RT_NULL); spi10_flag = rt_hw_spi_device_attach(SPI_BUS_NAME, SPI_NAME, GPIOB, GPIO_PIN_14); if(spi10_flag != RT_EOK){ LOG_D("failed to spi10 register"); return; } w25q128_handle = (struct rt_spi_device *)rt_device_find(SPI_NAME); if(w25q128_handle != RT_NULL){ w25q128_cfg.mode = RT_SPI_MSB | RT_SPI_MASTER | RT_SPI_MODE_0; w25q128_cfg.data_width = 8; w25q128_cfg.max_hz = 42*1000*1000; rt_spi_configure(w25q128_handle, &w25q128_cfg); } } void w25q128_leisure_wait(void) { static uint8_t w25q128_state_order = 0x05; static uint8_t w25q128_return_state = 1; struct rt_spi_message msg1,msg2; msg1.send_buf = &w25q128_state_order; msg1.recv_buf = RT_NULL; msg1.length = 1; msg1.cs_take = 1; msg1.cs_release = 0; msg1.next = &msg2; msg2.send_buf = RT_NULL; msg2.recv_buf = &w25q128_return_state; msg2.length = 1; msg2.cs_take = 0; msg2.cs_release = 0; msg2.next = RT_NULL; rt_spi_transfer_message(w25q128_handle,&msg1); while(w25q128_return_state & 0x01){ rt_spi_transfer_message(w25q128_handle,&msg2); rt_thread_mdelay(5); } rt_spi_release(w25q128_handle); } void w25q128_write(uint32_t addr, size_t size, const uint8_t *data) { static uint8_t w25q128_enable_write = 0x06; static uint8_t w25q128_paper_clear = 0x20; static uint8_t w25q128_paper_write = 0x02; static uint8_t w25q128_write_addr[4] = {0}; struct rt_spi_message msg1, msg2; w25q128_write_addr[0] = w25q128_paper_write; w25q128_write_addr[1] = (addr>>16) & 0xff; w25q128_write_addr[2] = (addr>>8) & 0xff; w25q128_write_addr[3] = (addr>>0) & 0xff; msg1.send_buf = w25q128_write_addr; msg1.recv_buf = RT_NULL; msg1.length = 4; msg1.cs_take = 1; msg1.cs_release = 0; msg1.next = &msg2; msg2.send_buf = data; msg2.recv_buf = RT_NULL; msg2.length = size; msg2.cs_take = 0; msg2.cs_release = 1; msg2.next = RT_NULL; w25q128_leisure_wait(); rt_spi_send(w25q128_handle,&w25q128_enable_write,1); w25q128_leisure_wait(); rt_spi_send_then_send(w25q128_handle,&w25q128_paper_clear,1,w25q128_write_addr,3); w25q128_leisure_wait(); rt_spi_send(w25q128_handle,&w25q128_enable_write,1); w25q128_leisure_wait(); rt_spi_transfer_message(w25q128_handle,&msg1); } void w25q128_read(uint32_t addr, size_t size, uint8_t *data) { static uint8_t w25q128_paper_read = 0x03; static uint8_t w25q128_read_addr[4] = {0}; struct rt_spi_message msg1, msg2; w25q128_read_addr[0] = w25q128_paper_read; w25q128_read_addr[1] = (addr>>16) & 0xff; w25q128_read_addr[2] = (addr>>8) & 0xff; w25q128_read_addr[3] = (addr>>0) & 0xff; msg1.send_buf = w25q128_read_addr; msg1.recv_buf = RT_NULL; msg1.length = 4; msg1.cs_take = 1; msg1.cs_release = 0; msg1.next = &msg2; msg2.send_buf = RT_NULL; msg2.recv_buf = data; msg2.length = size; msg2.cs_take = 0; msg2.cs_release = 1; msg2.next = RT_NULL; w25q128_leisure_wait(); rt_spi_transfer_message(w25q128_handle,&msg1); } ``` 整个代码逻辑是完全按照[STM32LL库编程系列第七讲——SPI通信(W25Q128)](https://blog.csdn.net/weixin_58172114/article/details/146156187?spm=1001.2014.3001.5501)逻辑编程的逻辑编写的,逻辑上的疑问可以看这篇文章。各种函数的使用就参考官方文档就行。 必须要说明的是,在挂载SPI设备时,上面两句注释的函数是可以替代下面一句实现的,而且还是官方推荐的方式。但遗憾的是,如果使用注释的函数挂载SPI设备,虽然不报错,但是SPI通讯无法完成。 我一开始使用的就是注释的函数挂载SPI设备,结果空闲函数无法退出,去除空闲,只能返回0xff,百思不得其解,也是机缘巧合下,试了试`rt_hw_spi_device_attach`函数就没问题。在SFUD实现中,如果使用注释的函数会导致设备都找寻不到,都是泪。 可是注释的函数的挂载方法是官方推荐的,不知道是我缺少了哪一步,知道的朋友可以给我留言一下,万分感谢。  ## 2.u_spi.h ```c #ifndef APP_U_SPI_H_ #define APP_U_SPI_H_ #include
void spi10_init(void); void w25q128_write(uint32_t addr, size_t size, const uint8_t *data); void w25q128_read(uint32_t addr, size_t size, uint8_t *data); #endif /* APP_U_SPI_H_ */ ``` ## 3.main.h ```c #include
#define DBG_TAG "main" #define DBG_LVL DBG_LOG #include
#include
//#include
int main(void) { spi10_init(); uint32_t write_addr = 0x111100; uint8_t write_data[8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; uint32_t read_addr = 0x111100; uint8_t read_data[8] = {0}; w25q128_write(write_addr,8,write_data); w25q128_read(read_addr,8,read_data); rt_kprintf("read data is %d,%d,%d,%d,%d,%d,%d,%d\n",read_data[0],read_data[1],read_data[2],read_data[3], read_data[4],read_data[5],read_data[6],read_data[7]); while (1) { rt_thread_mdelay(1000); } return RT_EOK; } ``` 代码我就不解释了,我变量都不随意命名,就是让大家能够看得懂。 实现效果如下:  # 四、spi搭配DMA通讯 先看spi_config.h函数,可以看到,这里是有SPI的DMA配置,只是缺乏声明。  于是在board.h中声明需要使用的SPI的DMA。  可以看到SPI的DMA的中断回调函数都被弱定义了,也就是说我们需要使用哪种DMA中断,就自行编写中断函数内容。  ## 1.u_spi.c 在普通SPI的函数末尾加上以下语句 ```c #ifdef BSP_SPI1_TX_USING_DMA void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi->TxXferSize == 8){ rt_kprintf("DMA TX finish\n"); } } #endif #ifdef BSP_SPI1_RX_USING_DMA void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi->RxXferSize == 8){ rt_kprintf("DMA RX finish\n"); } } #endif ``` 这里使用预编译指令,这样当不需要使用DMA时,去除预定义,就不会对文件产生影响了。 因为W25Q128需要发送各种指令,所以会频繁的进入DMA中断,我这里使用了发送和接收字节数判断,因为在main函数发送和接收的字节数都是8,所以这里的判断也是8。 其他的同普通SPI通讯设置一致,实现效果如下  我计算了一下,写数据共进入DMA中断11次,读数据共进入DMA中断24次。这么频繁的进入DMA中断,肯定会加大系统开销。所以我建议在实际使用中这种情况下,只在写入数据和读取数据前声明DMA中断,进入中断后就退出声明。减少系统开销。 # 五、SFUD通讯 先把SFUD使能了。  ## 1.u_sfud.c ```c #include "u_sfud.h" #include "drv_spi.h" #define SPI_NAME "spi10" #define SPI_BUS_NAME "spi1" #define SPI10_DEVICE_NAME "W25Q128" void spi10_init(void) { rt_err_t spi10_flag; rt_spi_flash_device_t w25q128_device; spi10_flag = rt_hw_spi_device_attach(SPI_BUS_NAME, SPI_NAME, GPIOB,GPIO_PIN_14); if(spi10_flag != RT_EOK){ LOG_D("failed to spi10 register"); return; } w25q128_device = rt_sfud_flash_probe(SPI10_DEVICE_NAME,SPI_NAME); if(w25q128_device == RT_NULL){ LOG_D("failed to w25q128 device sfup"); } } void w25q128_write(uint32_t addr, size_t size, const uint8_t *data) { sfud_flash_t w25q128_device; w25q128_device = rt_sfud_flash_find(SPI_NAME); if(w25q128_device == RT_NULL){ LOG_D("faile to sfud flash find"); return; } sfud_erase_write(w25q128_device, addr, size, data); } void w25q128_read(uint32_t addr, size_t size, uint8_t *data) { sfud_flash_t w25q128_device; w25q128_device = rt_sfud_flash_find(SPI_NAME); if(w25q128_device == RT_NULL){ LOG_D("faile to sfud flash find"); return; } sfud_read(w25q128_device, addr, size, data); } ``` 可以看到代码简洁了不少,官方对SFUD的说明文档在rt-thread/components/drivers/spi/sfud/README.md  关于代码的说明可以在这里查看。 上面使用的函数`rt_sfud_flash_probe`不在文档中,这是个更全面的SFUD配置函数,里面调用了初始化语句`sfud_err sfud_device_init(sfud_flash *flash)`所以不需要再调用了。 ## 2.u_sfud.h ```c #ifndef APP_U_SFUD_H_ #define APP_U_SFUD_H_ #include
#include
void spi10_init(void); void w25q128_write(uint32_t addr, size_t size, const uint8_t *data); void w25q128_read(uint32_t addr, size_t size, uint8_t *data); #endif /* APP_U_SFUD_H_ */ ``` ## 3.main.h 相比于普通SPI通讯的代码来说是一样的,只要更换头文件的注释就可以更换工作模式了,函数接口都一样。可以看到,所有的自定义函数都在APP文件夹中,使用哪种工作模式对对应的.c的文件添加构建即可。 可以看到,我这有一些警告,这是因为重复定义了`DBG_LVL`和`DBG_TAG`,另一处定义地点在sfud_cfg.h中,这个警告可以不用理会。  结果如下  # 六、工程分享 通过网盘分享的文件:SPI2.zip 链接: https://pan.baidu.com/s/1WnGq3uRwY4JMS3PQMy9dYg?pwd=nr7s 提取码: nr7s --- # 总结 为了完成本文内容,可谓是艰辛,若不是运气好,发现了那部分注释函数错误,我都要难受死了。期间,我也找了一些其他博主的文章来看,他们每个人的文章都有所侧重,推荐给大家。 其他博主优秀文章推荐 [《软饭硬吃666》](https://blog.csdn.net/yutian0606/article/details/135488904) [《矜辰所致》](https://xie.infoq.cn/article/d163b38baf9f10c289cd8a361) [《HUAN~FLY》](https://blog.csdn.net/weixin_42721886/article/details/121753162)
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
Pai同学
这家伙很懒,什么也没写!
文章
10
回答
1
被采纳
0
关注TA
发私信
相关文章
1
BBB的SPI驱动
2
求个SPI上挂两个或多个设备的使用例子
3
SPI设备有个bug
4
spi flash 的fatfs使用一段时间后读写文件出现故障
5
SPI驱动
6
请教rt_spi_configure函数理解
7
SPI FLASH挂载的问题
8
SPI-FLASH 文件系统 SPIFFS
9
求助一个完整的 spi flash 驱动
10
关于同时使用文件系统与SPI FLASH的问题
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
6
五分钟玩转RT-Thread新社区
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
Bootloader
AT
Hardfault
CAN总线
FinSH
ART-Pi
DMA
USB
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
FAL
RTC
rt-smart
I2C_IIC
UART
cubemx
ESP8266
ota在线升级
WIZnet_W5500
BSP
PWM
packages_软件包
flash
freemodbus
潘多拉开发板_Pandora
GD32
ADC
定时器
flashDB
keil_MDK
编译报错
ulog
socket
rt_mq_消息队列_msg_queue
msh
中断
Debug
SFUD
C++_cpp
at_device
本月问答贡献
出出啊
1524
个答案
343
次被采纳
小小李sunny
1444
个答案
290
次被采纳
张世争
821
个答案
179
次被采纳
crystal266
555
个答案
162
次被采纳
whj467467222
1222
个答案
149
次被采纳
本月文章贡献
出出啊
1
篇文章
1
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
1
次点赞
crystal266
2
篇文章
1
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部