Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread一般讨论
RTT的SPI框架之我的理解
发布于 2014-01-21 00:06:16 浏览:8828
订阅该版
学习了一段时间RTT了,但总觉得没做过任何贡献。今天移植了SPI FLASH的系统后想到将SPI部分解读一下和网友交流。写完来发帖才发现大哥们视频都做出来了,虽然做了无用功,但多一点资料多一点参与总归是好的,所以加上“之我的理解”后,也后厚脸皮发上来了。 前言 今天看到群里面有人将SPI的FLASH作为存储挂载在根目录上了,我比较感兴趣。随即找到realtouch中的代码,花了半天时间将代码改到了STM32F103VC上。在修改代码的过程中发现要挂载SPI FLASH还需要将新版本RTT的SPI驱动框架移过来。今天这篇文章就讲一下我对RTT下的SPI驱动框架的理解。本人以硬件为主,学习RTT是兴趣所致,见识比较浅显、写这篇文章的目的也是为那些和我一样软件基础不深的朋友提供个参考。高手路过请抬手留下宝墨指点一二、万分感激。 一,虚拟总线和SPI驱动框架的概述。 首先我想提一下虚拟总线的概念,在很多操作系统中都有虚拟总线的概念。总线一般是指处理器和一个或多个设备之间的通道。为什么叫虚拟总线,因为这个总线不一定是客观存在的硬件。就拿我今天要说的SPI来说确实很多单片机有物理的控制器,但是还有大量的应用是用IO模拟出来的,这样可以来理解虚拟这个词。而从软件结构角度来说,提出虚拟总线的目的是为了抽象出一个模型。一个可以插拔设备,可以作为通讯、管理的桥梁的模型。有了模型我们就可以很容易的将软件分层,使软件结构更加合理。SPI驱动框架一共就四个文件,核心是spi_dev.c和spi_core.c以及他们的头文件spi.h,然后是对应具体硬件的驱动stm32f20x_40x_spi.c。 这里我画了一下我所理解的SPI驱动框架图。 ![SPI驱动.jpg](/uploads/3694_2c0ec62a1d01564169847f84479b9c49.jpg) 我们由上往下看这个图。最上层是RTT的设备管理,我们使用SPI驱动过程中可以都通过RTT这个统一的管理接口来对具体的硬件进行操作和通讯。RTT设备管理这块不是我今天想说的重点,大家请自己查阅相关源代码。 往下走就是核心spi_core.c这个文件,定义了总线(bus)和设备(device)的模型,并将总线和设备都注册到了RTT设备管理中。这个spi_core.c还实现了一些特殊一点的收发数据的方法可以直接通过查找设备后直接调用。 在核心与RTT管理之间,spi_dev.c这个文件仅仅是提供了注册总线和设备时需要的一些规则方法,下面会简单介绍作用。 最底层就是与硬件具体相关的驱动代码,至于里面实现了什么我在后面也会具体说明的。一个总线上可以只有一个设备但更加可以挂载(插入)更多的设备,我这里用一个other device表示了,每个不同的硬件、或者收发格式不同的硬件都需要单独抽象出来。 这个图是让大家首先对文件和程序的分层上有个概念。 二,重要的结构体(类)解读 我个人觉得先讲头文件中的结构体(类)比较好 ,因为这这些结构体表述了程序中有些什么样的模型。理解了这些模型读起程序来就更加容易理解到底在干嘛。我做软件的同事告诉我,接触一个新东西都是先看类和继承的关系以及接口,而不要急于去读代码。我觉得这个建议给到从硬件转型,或者偏底层的人来说还是很中肯的。 好在我们的SPI驱动框架头文件非常简单,就是一个spi.h文件,我们打开文件一个一个分析。 A. 总线结构体 ```struct rt_spi_bus { struct rt_device parent; const struct rt_spi_ops *ops; struct rt_mutex lock; struct rt_spi_device *owner; };``` 这个结构体定义了驱动框架中的总线(BUS),按照我所理解的将其中部分成员的作用描述一下。 1. struct rt_device parent; RTT的设备描述,它包含了一些标记、类型、回调函数指针、设备操作函数指针、对象基本信息等等数据。可以将总线注册成一个设备。为什么将一个虚拟的总线也注册成一个设备,我想这是为了利用RTT的设备管理框架。因为利用这个通用的管理框架后可以使用RTT提供的系统调用rt_device_t rt_device_find(const char *name)来找到设备或者总线。统一了接口,简化了SPI部分的代码(不用单独写SPI总线管理部分的代码)。 2. const struct rt_spi_ops *ops; rt_spi_ops描述了SPI总线抽象出来的两个最基本的操作:配置和收发数据,具体定义如下: ``` struct rt_spi_ops { rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration); rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message); }; ``` 是的,看上去复杂(对于我这种菜鸟来说)的SPI驱动框架实际上操作就只有这两个。仔细一想确实也是如此,SPI是单时钟数据收发同时进行的总线,除了配置就是收发数据。但是这里我有一点疑问,为什么这个对总线操作的方法不是放在每个附着(attach)在总线上的设备(device)中,而是注册在总线上?因为配置的话对于同一个控制器可以用统一的方法配置不同的参数,但是发送和接收的策略我觉得会随着设备(device)的不同而不同。按现在的做法放到总线中,那么写这个xfer函数就得考虑各种配置的收发。比如一个字节到四个字节的位宽不同,用DMA还是软件拷贝数据等等。 3 struct rt_mutex lock; 这个我就很有把握的说这个是用来互斥多线程访问总线的。一个总线上可以附着(attach)多个设备,因此在多线程环境中可能有多个进程竞争获取总线使用权来访问各自需要访问的设备。 4 struct rt_spi_device *owner; 最后是这个struct rt_spi_device *owner,这个成员是指向一个设备的指针。从命名上可以大概猜出这个设备是当前总线正在操作的设备。有什么意义呢,意义在于之前我们说过一条SPI总线上可以挂多个设备(由CS信号区分),不同设备的操作位宽,速度,时钟相位可能是不同的。所以这个指针表示当前正在获取总线操作的设备。如果后一次操作检查这个指针发现之前是本设备在使用这个SPI总线就不需要配置总线,否则将重新配置总线参数。具体代码可以看到spi_core.c中很多地方写到: ``` if (device->bus->owner != device) { result = device->bus->ops->configure(device, &device->config); ...... }``` B. 设备结构体 ```struct rt_spi_device { struct rt_device parent; struct rt_spi_bus *bus; struct rt_spi_configuration config; };``` 这个结构体定义了驱动框架中的设备(device),按照我所理解的将其中部分成员的作用描述一下。 1. struct rt_device parent; RTT的设备描述,它包含了一些标记、类型、回调函数指针、设备操作函数指针、对象基本信息等等数据。可以将SPI总线上的设备单独注册到RTT的设备框架中。 2. struct rt_spi_bus *bus 设备所附着(attach)的总线,从而找到总线上的发送和配置方法。 3. struct rt_spi_configuration config; ``` struct rt_spi_configuration { rt_uint8_t mode; rt_uint8_t data_width; rt_uint16_t reserved; rt_uint32_t max_hz; };``` SPI总线的配置信息描述。 mode 模式选择,用于配置时钟电平,采样边沿极性,最高还是最低位先传。 data_width 数据宽度,单位字节。 reserved 预留。 max_hz 最大总线时钟。 C. 消息结构体 ```struct rt_spi_message { const void *send_buf; void *recv_buf; rt_size_t length; struct rt_spi_message *next; unsigned cs_take : 1; unsigned cs_release : 1; };``` 这个结构体定义了驱动框架中的消息(message ),这个是人为抽象出来的一种SPI收发数据的类(结构体),按照我所理解的将其中部分成员的作用描述一下。 1. const void *send_buf; 发送缓冲区指针,用来指向要发送数据的缓冲区。 2. void *recv_buf; 接收缓冲区指针,这里的指针类型是void,表示接收数据目标是一片内存,而不是固定的8位或者16位宽的数组。 3. rt_size_t length; 发送和接收的数据长度。 4. struct rt_spi_message *next; 嵌入下一个消息的指针,能够构成一个单向链表,将消息串起来统一发送接收。 5 unsigned cs_take : 1; 和 unsigned cs_release : 1; 从字面意思可以理解到这个变量是表示CS的拉高和CS的拉低。因为SPI的设备的收发数据长度没有统一标准,可以任意字节、甚至没有CS。所以cs_take表示该消息发送之前CS信号是否要拉低,而cs_release表示该消息发送之后CS信号是否要拉高。这样一来可以组成任意帧长的SPI通讯时序。 三,程序分析及简述 我们介绍完spi.h头文件后,正式开始分析代码。SPI驱动框架的代码由spi_core.c和spi_dev.c组成,从spi_core.c开始 A. 总线注册函数 ``` 1. rt_err_t rt_spi_bus_register( struct rt_spi_bus *bus, const char *name, const struct rt_spi_ops *ops)``` 框架核心就是虚拟的SPI总线。所以构建这个框架的第一件事就是创建或者说注册一个SPI驱动,这个函数有三个参数。 1. struct rt_spi_bus *bus 表示你要注册的总线的对象。 2. const char *name 总线的名字,这是一个字符串。 3. const struct rt_spi_ops *ops 我们之前所提到的操作总线的方法。 我这边文章着重想讲一下思路,不想长篇幅的贴出代码并注释。我只简略的说明一下函数中所做的事,看官可以结合我的思路去阅读代码。 在rt_spi_bus_register函数中进行了如下步骤的操作。 1. 将这个总线注册成RTT的设备,以便统一管理。 2. 初始化总线对象中的互斥量。 3. 将参数中的总线操作方法保存。 4. 将总线所有者设置空,表示没有被获取。 B. 设备绑定总线函数 ``` rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device, struct rt_spi_device *device const char *bus_name, void *user_data)``` 这个函数是用来创建(或者说绑定)一个设备到指定的SPI总线上去。一个设备对应一个CS引脚。参数说明: 1. struct rt_spi_device *device 要创建(绑定)的设备,这个指针指向一个已经实例化的对象,不能是临时变量。 2. struct rt_spi_device *device 设备的名字 3. const char *bus_name 绑定到哪里总线。 4. void *user_data 用户指针,实际上是传入CS引脚的信息。 在这个函数中: 1. 首先根据字符串名字,通过系统调用查找到要绑定的总线。有兴趣的可以去看一下怎么注册和找到设备的。我大概懂了,找个时间针对这个再写一篇读后感。 2. 将总线保存到设备描述中。 3. 将这个设备注册到RTT的设备管理框架中。 4. 清空配置,因为如果这片内存是从堆中分配的,很可能是一片乱数据。 5. 将CS的IO引脚信息保存到rt_device中的用户数据指针中。这里我有一点不理解,SPI设备肯定数量不会很多,4个字节的内存占用为啥不将CS信息保存在rt_spi_device。这样理解起来很清晰。 C. 总线配置函数 ``` rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) ``` 根据接口我们判断这个函数就是传入配置参数并将总线配置。 函数中device->bus->ops->configure(device, &device->config);调用了配置方法,这个大家顺着我说的几个结构体去找吧,多看几次就有感觉了。 此后spi_core.c文件中的几个函数就没有什么营养了。简单注释一下: rt_spi_send_then_send - 发送完数据send_buf1后再发送数据send_buf2。 rt_spi_send_then_recv - 发送完数据后马上接收数据。 rt_spi_transfer - 发送和接收同时进行。 rt_spi_transfer_message - 发送一则SPI消息数据(简单的说就是发送一片按规则分片的数据,规则是SPI框架定的,消息数据内容是自己填充的) 最后面的获取释放总线和设备函数,这个一般用户应该不用管。 D. 其他 然后是spi_dev.c这个文件,这个文件我一开始从命名上开始理解以为是实现SPI框架的device部分。看懂了才发现这个文件只是SPI驱动与RTT系统的设备管理之间的接口,因为据我了解RTT好像没有虚拟总线的管理,只有设备管理。所以这里命名就是spi_dev了。 注册一个RTT的设备需要实现几个标准的方法,类似linux字符设备的那种打开、关闭、读、写、IO控制。我上面说过SPI驱动框架将总线和设备都注册成了RTT的设备,所以这个文件实现了注册RTT设备的方法,和注册的方法。 四,设备相关的SPI驱动 这里我拿修改过的stm32f20x_40x_spi.c说,我将这个用于F20_40X的文件删减移植到了F103上。这个文件核心就干了两件事,实现了xfer数据收发的函数和配置总线的函数,将总线注册。一笔带过算了,想必说了这么多,大家自己也能看懂了。 五,具体使用 在官网的wiki上有这么一篇将SPI驱动使用的,大家看完我的介绍后可以再看看怎么用。然后动手写几个简单的例子用示波器抓一下波形验证。如果还写不出来,建议反复看我上面的讲解。对于初学者来说反复阅读和思考是最容易悟到其中的门道的,这是我自己的感受。 [rt_device_class_spibus](http://www.rt-thread.org/dokuwiki/doku.php?id=rt_device_class_spibus)
查看更多
8
个回答
默认排序
按发布时间排序
aozima
2014-01-21
调网络不抓包,调I2C等时序不上逻辑分析仪,就像电工不用万用表!多用整理的好的文字,比截图更省流量,还能在整理过程中思考。
写得不错! 提示: STM32 RADIO中有10x的SPI驱动,107的BSP中也有。
tanglj86
2014-01-21
这家伙很懒,什么也没写!
帮助很大,写得很有条理 谢谢
caai
2014-01-24
这家伙很懒,什么也没写!
>写得不错! > >提示: STM32 RADIO中有10x的SPI驱动,107的BSP中也有。 --- 非常感谢aozima,驱动的DMA能够使用。我还想在这个基础上请教一下: ``` #ifdef SPI_USE_DMA if(message->length > 32) { if(config->data_width <= 8) { DMA_Configuration(stm32_spi_bus, message->send_buf, message->recv_buf, message->length); SPI_I2S_DMACmd(SPI, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE); while (DMA_GetFlagStatus(stm32_spi_bus->DMA_Channel_RX_FLAG_TC) == RESET || DMA_GetFlagStatus(stm32_spi_bus->DMA_Channel_TX_FLAG_TC) == RESET); SPI_I2S_DMACmd(SPI, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, DISABLE); } // rt_memcpy(buffer,_spi_flash_buffer,DMA_BUFFER_SIZE); // buffer += DMA_BUFFER_SIZE; } else #endif ``` 这段代码在使用DMA的过程中实际上还是在死等DMA的完成,只是减少了CPU去逐个字节搬运的过程。 我计算了一下W25Q32的4096个字节读写一次至少要0.3个ms,请问在这0.3个ms中我用rt_thead_sleep来调度一下进程,是否在提升实时性上有意义? 开销是否划得来?
aozima
2014-01-24
调网络不抓包,调I2C等时序不上逻辑分析仪,就像电工不用万用表!多用整理的好的文字,比截图更省流量,还能在整理过程中思考。
sleep粒度大于0.3ms,所以对CPU来说是划算的,但对应用来说变慢了。 一般用中断,如completion/sem等。。 但一般也要做些取舍,比如几个字节就不必要用DMA,几十个字节的DMA也不需要用中断,更多的数据才需要中断。 具体要根据实现情况决策,不然可能适得其反。
youth_syg
2015-07-15
这家伙很懒,什么也没写!
有人实现xfer和config吗,借鉴一下
qq_蓝木
2016-03-01
这家伙很懒,什么也没写!
请教一下,视屏在什么地方可以找到。
jackedison911
2017-06-28
这家伙很懒,什么也没写!
mark一下,目前用spi flash搭建好了文件系统,还想继续做其他spi的驱动。
撰写答案
登录
注册新账号
关注者
0
被浏览
8.8k
关于作者
caai
这家伙很懒,什么也没写!
提问
22
回答
43
被采纳
0
关注TA
发私信
相关问题
1
有关动态模块加载的一篇论文
2
最近的调程序总结
3
晕掉了,这么久都不见layer2的踪影啊
4
继续K9ii的历程
5
[GUI相关] FreeType 2
6
[GUI相关]嵌入式系统中文输入法的设计
7
20081101 RT-Thread开发者聚会总结
8
嵌入式系统基础
9
linux2.4.19在at91rm9200 上的寄存器设置
10
[转]基于嵌入式Linux的通用触摸屏校准程序
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
【NXP-MCXA153】 定时器驱动移植
2
GD32F450 看门狗驱动适配
3
【NXP-MCXA153】看门狗驱动移植
4
RT-Thread Studio V2.2.9 Release Note
5
CherryUSB的bootuf2配置
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
PWM
freemodbus
flash
cubemx
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
编译报错
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
踩姑娘的小蘑菇
7
个答案
2
次被采纳
a1012112796
18
个答案
1
次被采纳
Ryan_CW
5
个答案
1
次被采纳
红枫
4
个答案
1
次被采纳
张世争
4
个答案
1
次被采纳
本月文章贡献
YZRD
3
篇文章
6
次点赞
catcatbing
3
篇文章
6
次点赞
lizimu
2
篇文章
9
次点赞
qq1078249029
2
篇文章
2
次点赞
xnosky
2
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部