APP下载

RT-Thread的SPI总线驱动结构分析、移植及应用*

2018-01-04,,,

单片机与嵌入式系统应用 2017年12期
关键词:核心层寄存器时钟

,,,

(1.福建商学院,福州 350012;2.福建工程学院)

RT-Thread的SPI总线驱动结构分析、移植及应用*

高培1,何栋炜2,李文翔1,林丹楠1

(1.福建商学院,福州 350012;2.福建工程学院)

介绍了串行外设接口SPI的通信原理,对RT-Thread操作系统下的SPI总线驱动结构进行了详细的研究与分析。以STM32F407ZG对W25Q64的读写为例,介绍了硬件SPI总线驱动的移植步骤,并详细介绍了SPI应用程序设计及技术实施细节。最后通过实验,对SPI驱动移植及程序设计的可行性及有效性进行验证,实验结果表明,RT-Thread操作系统下的SPI总线驱动结构设计合理,移植方便且应用程序设计简单,本文所介绍的移植与应用方法可行有效。

RT-Thread;SPI;驱动结构;分析;移植;应用

引 言

RT-Thread是开源的嵌入式实时操作系统,由国内一些专业开发人员开发、维护,主要优点是实时、小型、可裁剪以及支持的处理器平台广泛[1]。RT-thread不仅是一款高效、稳定的实时核心,也是一套面向嵌入式系统的软件平台,在借鉴其他实时操作系统优点的基础上又具有自己的特点,被广泛应用在工业控制和物联网应用等领域[2-4]。由于SPI在芯片的引脚上只占用4根线,节约了芯片的引脚,同时为PCB的布局节省空间,提供方便。正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,被广泛应用在嵌入式系统中[5]。

本文介绍了SPI驱动的通信原理,对RT-Thread操作系统下SPI驱动结构进行了分析,以STM32F407ZG读写64 Mb的串行Flash存储器W25Q64为例,对SPI总线驱动移植步骤及应用程序设计进行了详细的描述。

1 SPI通信原理

1.1 SPI总线架构

SPI系统可以直接与各个厂家生产的多种标准外围器件直接连接,它以主从方式工作,通常有一个主设备和一个或多个从设备,一般使用4条线,分别是:总线主机输入/从机输出MISO、总线主机输出/从机输入MOSI、时钟信号SCLK、使能信号CS。其中,MISO信号是一个单向信号,它将数据由从设备传输到主设备,MOSI信号也是一个单向信号,它则将数据从主设备传输到从设备。时钟信号SCLK由主设备产生,从设备接收,用于同步SPI接口间数据传输的时钟信号。图1所示为SPI总线架构图,只有片选信号为预先规定的使能信号时(高电位或低电位),对该芯片的操作才有效,这就允许在同一总线上连接多个SPI设备。

图1 SPI总线架构图

1.2 SPI数据传输

SPI通信是通过数据交换完成的,SPI是串行通信协议,数据是一位一位传输的。SCLK提供时钟脉冲,MOSI和MISO基于此脉冲完成数据传输。主设备数据输出通过MOSI线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被从设备读取,经过8个时钟周期完成1个字节的发送。主设备通过对SCLK时钟线的控制可以完成对通信的控制,当没有时钟跳变时,从设备不采集或传送数据。常用CPOL (Clock Polarity)表示时钟极性,CPHA(Clock Phase)表示时钟相位,CPOL和CPHA可以是0或是1,对应有4种组合及说明,如表1所列。[6]

表1 SPI数据与时钟相位关系

图2为CPHA=0时,SPI的数据传输格式时序图,图3为CPHA=1时SPI的数据传输格式时序图。

图2 CPHA=0时SPI的数据传输格式时序图

图3 CPHA=1时SPI的数据传输格式时序图

2 SPI驱动结构分析

RT-Thread下的SPI总线驱动架构(如图4所示)可分为:SPI外设驱动层/应用程序(需要根据所使用硬件设备自行设计或移植)、设备层(spi_dev.c,位于DeviceDrivers目录下)、总线核心层(spi_core.c,位于DeviceDrivers目录下)、SPI硬件驱动层(移植目标,本项目中为stm32_i2c.c)[7-8]。

图4 RT-Thread下SPI总线驱动架构

各层间通过结构体联系,主要结构体之间的关系如图5所示。

rt_spi_device结构体用来描述SPI虚拟总线(SPI总线允许在同一总线上连接多个SPI设备,每个SPI设备对应一个CS引脚,使用虚拟总线可以简化设备层及上层驱动和应用程序设计);rt_spi_bus结构体用来描述设备对应的物理SPI总线,其主要功能是提供具体的SPI总线配置与操作接口;rt_spi_ops结构体用来描述物理总线的操作,主要包括总线的配置(configure)与数据传输接口(xfer)。

2.1 SPI设备层(spi_dev.c)

设备层(spi_dev.c)是操作系统与总线核心层间的接口,它规范了SPI设备的读写接口,使得SPI设备驱动或程序可以通过标准接口实现总线配置及操作,在RT-Thread 1.2.0版本中将设备接口分为总线接口(如:_spi_bus_device_init等)和总线设备接口(如_spidev_device_init等),其中_spi_bus_device_control、_spidev_device_control等并没有实现具体的功能,仅仅是为了兼容RT-Thread低版本(如0.3.x、1.0.x等)。

图5 SPI驱动结构体关系图

设备层(spi_dev.c)将SPI设备的操作分为:总线与设备两种,其操作抽象为4种基本操作:初始化、读操作、写操作、控制等,主要函数见表2。

表2 spi_dev.c主要函数

常用的接口函数如下所示:

① rt_err_t rt_spi_bus_device_init(struct rt_spi_bus *bus, const char *name)。其中,bus为被初始化SPI总线的rt_spi_bus类型结构体指针,name指向注册到系统中的SPI总线的名称,应用程序或驱动可以通过name向系统查找、申请和访问对应的总线。

② rt_err_t rt_spidev_device_init(struct rt_spi_device *dev, const char *name)。其中,dev为被初始化SPI总线设备的rt_spi_device类型结构体指针,name指向注册到系统中的SPI总线设备名称,应用程序或驱动可以通过name向系统查找、申请和访问对应的总线设备,该函数仅由spi_core.c中的rt_spi_bus_attach_device函数调用,完成总线设备向系统注册。

2.2 SPI核心层(spi_core.c)

核心层(spi_core.c)作为应用程序或者SPI外设驱动的接口,规定了SPI总线的初始化、配置及通信的标准接口,是SPI设备驱动及应用程序与SPI硬件驱动层的媒介,主要函数见表3。

表3 核心层(spi_core.c)主要函数

常用的接口函数如下所示:

① rt_err_t rt_spi_bus_register(struct rt_spi_bus *bus, const char *name, const struct rt_spi_ops *ops)。该函数是系统提供给硬件驱动层或应用程序实现向系统注册SPI硬件操作函数的接口。其中,name指向注册到系统中的SPI总线名称,该名称由用户决定并最终通过rt_spi_bus_device_init函数注册到系统中;ops结构体指针用来将移植好的SPI操作接口传递给SPI核心层,具体结构如图5所示。

② rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device, const char *name, const char *bus_name, void *user_data)。SPI总线通过使用多个CS及复用总线的方式实现在同一总线上连接多个SPI设备,即每个CS引脚对应一个SPI设备。因此,在SPI核心层中在对SPI总线进行注册的基础上还需要使用rt_spi_bus_attach_device函数对各个SPI设备进行注册。函数中device指向当前初始化SPI设备对应的rt_spi_device结构体;name指向要向系统进行注册的SPI设备总线的名称;bus_name指向当前设备对应的SPI总线名称,系统可以根据bus_name查找并访问该总线;user_data指向需要传递给系统设备层的自定义用户数据。

③ rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg)。该函数是SPI设备配置接口函数。其中device指向要进行配置的SPI设备;cfg为SPI设备的配置参数,包括:SPI时钟极性、时钟相位和比特序(MSB或LSB),驱动中分别用mode的第0~2位表示,详见spi.h;data_width表示数据帧长度(8或16bits);max_hz为SCLK频率,该结构体如下所示:

struct rt_spi_configuration{

rt_uint8_t mode;

rt_uint8_t data_width;

rt_uint16_t reserved;

rt_uint32_t max_hz;

};

此外,在SPI核心层中没有提供总线或总线设备申请函数,用户向系统申请总线或总线设备需要使用rt_device_find函数来实现,其具体形式为:rt_device_t rt_device_find(const char *name),其中name指向要申请的总线或总线设备名称对应的字符串,该函数返回结果为rt_device_t类型,因此,对于SPI总线或总线设备申请需要进行强制类型转换。

在数据传输方面,SPI核心层提供了rt_spi_send_then_send等4种模式。其中,rt_spi_send_then_send为连续数据段发送操作,rt_spi_send_then_recv为数据段发送后再接收操作,这两种方式较为简单、直接,具体见spi_core.c。rt_spi_transfer提供收发同步操作,即发送数据段的同时进行接收,其接口函数具体为:rt_size_t rt_spi_transfer(struct rt_spi_device *device, const void *send_buf, void *recv_buf, rt_size_t length),该接口形式简洁,不做详细说明。rt_spi_transfer_message提供message数据包形式的数据传输,message数据包结构为:

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;

};

其中,send_buf和recv_buf分别指向发送和接收缓冲区;length为发送数据的长度;next指向存储的下一个message数据包,用来实现队列化数据包传输。message数据包形式的SPI数据传输接口函数为:struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device, struct rt_spi_message *message),只需要根据发送的内容组织message数据结构,然后调用该函数即可快速完成数据传输。message数据包形式的SPI数据传输,实际可以看作对transfer形式进行了对象化,transfer形式程序实现过程中实际上使用了message数据包(详见spi_core.c)。

2.3 SPI硬件驱动层

SPI硬件驱动层是SPI核心层与硬件间的接口,是SPI操作的具体实现部分,也是驱动移植时的主要部分。RT-Thread将具体SPI操作抽象并封装为SPI总线设备配置(configure)和SPI数据传输(xfer)。对用户来说,只需要完成硬件初始化和所需基本操作的驱动编写即可完成驱动的移植工作[9-10]。

SPI总线一般用于高速数据通信,大都采用硬件实现。对于硬件SPI,SPI总线的读写等具体操作都是通过对寄存器的读写来完成的,驱动移植时只需要参照接口格式,依照寄存器手册,即可完成程序编写[11-13]。

若使用软件SPI,则需要根据SPI通信时序规则来编写模拟程序再通过I/O口的输入/输出操作实现通信,并根据SPI核心层接口格式编写操作接口函数,软件SPI的硬件驱动层可以参考软件I2C驱动移植过程,但RT-Thread操作系统并未提供软件SPI标准接口层,因此软件SPI驱动移植工作量相对较大。

3 SPI总线驱动移植及应用

本节将详细介绍RT-Thread下的SPI总线驱动移植,以STM32F407ZG连接W25Q64的读写为例介绍硬件SPI总线驱动移植与应用,实验电路连接如图6所示。

图6 实验电路图

W25Q64是64 Mb的串行Flash存储器,支持2.7~3.6 V的电压,支持标准的SPI,同时支持双输出/四输出的SPI,最大SPI时钟可达80 MHz。为便于描述,SPI总线驱动不使用DMA模式,本文使用STM32F407ZG的SPI2,以主模式连接W25Q64,W25Q64的其他引脚(VCC等)连接较为简单,详见参考文献[11]。

3.1 SPI总线驱动移植

首先,为了使用RT-Thread 1.2.0版本中提供的SPI模块,需要将spi_dev.c、spi_core.c两个文件添加到工程项目中,并在rtconfig.h添加宏定义:#define RT_USING_SPI。接下来进行SPI硬件驱动层的移植,SPI硬件驱动层移植主要工作分为两部分:①SPI硬件初始化;②SPI硬件驱动接口函数移植。

3.1.1 SPI硬件初始化

SPI硬件初始化主要完成SPI总线所涉及的4个引脚和SPI模块时钟等硬件初始化。对于STM32F407ZG,SPI总线驱动硬件初始化函数需要完成的操作如图7所示,具体包括:

① 开启SPI接口所在GPIO引脚及SPI外设时钟。由于硬件SPI接口除了使用到对应GPIO引脚外,还需要芯片中的SPI控制器的支持,因此需要开启对应的两个时钟。

② GPIO引脚模式初始化。所使用的SPI引脚都是复用的,需要对各个引脚(MISO、MOSI、SCLK、CS)初始化为SPI模式,具体设置请参考参考文献[12]。

③ 结构体初始化。SPI驱动中总线及设备的信息是以结构体形式存储并借助结构体完成调用的。这些结构体包括:SPI设备结构体(rt_spi_device)、SPI总线结构体(rt_spi_bus),该结构体只需要完成定义、SPI操作接口结构体(rt_spi_ops),SPI核心层通过该结构体中的函数接口指针完成操作函数的调用,该结构体定义好后需要对其进行初始化,即将移植完成的configure和xfer两个函数入口指针赋值给该结构体。由于这些结构体在系统运行期间一直处于使用状态,所以必须定义为静态形式(static)。

④ 完成以上信息初始化后,接着使用核心层中的rt_spi_bus_register接口函数向系统注册SPI总线,再使用rt_spi_bus_attach_device接口函数向系统注册SPI设备,即可完成SPI总线驱动的硬件及系统初始化工作。初始化流程图如图7所示。

图7 SPI总线驱动硬件初始化流程图

3.1.2 SPI硬件驱动接口函数移植

SPI硬件驱动接口函数所涉及的configure和xfer两个函数是SPI驱动移植工作中最核心的部分,也是工作量最大的部分。其中,configure函数具体形式为:

static rt_err_t configure(struct rt_spi_device* device, struct rt_spi_configuration* configuration)

其作用是根据rt_spi_configuration结构体中SPI设备的配置信息完成对指定的SPI设备的配置操作(具体信息详见第2.2节),对于硬件SPI来说,这些配置操作最后都归结为对应寄存器的配置。configure函数的设计过程为:

① 查找各个SPI设备参数(工作模式mode、数据帧长度data_width、时钟SCLK频率max_hz;)和对应的寄存器。

② 将SPI设备参数转换为对应的寄存器值。

③ 根据寄存器配置规则写入相应寄存器值。

如时钟SCLK频率配置,由于max_hz变量中的数值一般是以Hz为单位,而根据数据手册可知对于STM32F407ZG来说,该频率值设置涉及芯片内核SystemCoreClock、SPI模块所使用的APB总线时钟及SPI_CR1寄存器中的BR[2:0]位。因此,程序设计中需要考虑这些因素,根据max_hz值计算出各个时钟配置的值,并对max_hz超出硬件允许值等进行判断和保护。该函数编写可以参考STMicroelectronics公司提供的固件库。

数据传输函数xfer具体形式为:

static rt_uint32_t xfer(struct rt_spi_device* device, struct rt_spi_message* message)

其功能为:在指定的SPI设备上,完成rt_spi_message结构体中指定的数据传输(包括数据发送和接收)。该功能实现比较简单,只需要根据发送数据长度(length),将发送缓冲区(send_buf)中的数据逐个写入SPI发送寄存器中,在发送的同时读取SPI接收寄存器并将数据存储到接收缓冲区(recv_buf)。需要注意的是,需要根据SPI设备当前工作的数据帧长度,对接收与发送寄存器进行读写才能保证数据的正确传输。

3.2 W25Q64的读写

W25Q64的操作采用“指令+数据”形式,读写操作时序如图8所示。

图8 W25Q64的读写操作时序图

图9 W25Q64读写程序流程

实现W25Q64读写的程序流程如图9所示。

首先调用rt_device_find函数,语句如下:

struct rt_spi_device * spi_dev;

dev = (struct rt_spi_device *)rt_device_find("spi20");

若正确申请到总线设备后,系统会将该SPI总线设备的rt_spi_device类型结构体指针返回,利用该指针和SPI核心层中的接口函数(详见第2.2节)即可实现W25Q64的读写操作。接下来,根据W25Q64的SPI总线特性配置SPI设备。根据W25Q64的数据手册,其SPI采用Mode 0和Mode 3,数据帧长度为8位,MSB模式,SPI时钟最高支持133 MHz。结合硬件情况,进行如下配置:

struct rt_spi_configuration cfg;

cfg.data_width = 8;

cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible: Mode 0 and Mode 3 */

cfg.max_hz = 20 * 1000 * 1000; /* 50M */

rt_spi_configure(spi_dev, &cfg);

最后,以读取W25Q64的JEDEC ID为例(JEDEC用来帮助程序读取Flash的制造商ID和设备ID,以确定Flash的大小和算法)介绍SPI设备的读写操作。根据W25Q64的指令集,JEDEC ID对应指令为0x9F,芯片将返回24位的ID数据,其中三个字节分别代表制造商ID、存储器类型及存储器容量,若操作正确,应读取到0xEF、0x40、0x17。

该操作主要程序如下:

rt_uint8_t cmd = 0x9F;//JEDEC ID指令

rt_uint8_t id_recv[3];//接收缓冲区

rt_spi_send_then_recv(spi_dev, &cmd, 1, id_recv, 3);//发送指令并读取

rt_kprintf(JEDEC ID: %02X %02X %02X ", id_recv[0], id_recv[1], id_recv[2]);//显示返回数据

程序正确申请到SPI设备后,对W25Q64进行JEDEC ID读取操作,通过对比可以看出,读取到的数据与预期数据一致,由此证明所移植的SPI驱动及SPI接口应用正确。综上所述,本次SPI总线驱动移植是成功的,并且利用SPI核心层接口读写W25Q64也是成功的。

结 语

[1] 曹成. 嵌入式实时操作系统RT-Thread原理分析与应用[D]. 青岛:山东科技大学, 2011.

[2] 张丽彪, 骆东佳, 张舰航,等. 基于RT-Thread和Yeelink的物联网平台开发的应用设计[J]. 电子技术与软件工程, 2015(16):70.

[3] 陈伟. 基于RT-thread的远程家用服务机器人系统开发[D]. 杭州:浙江工业大学, 2015.

[4] Yu-Xiang W U, Xin L I, Liu Q. Design of Digital Photo Frame Based on RT-Thread and STM32[J].Electronics World, 2013(18).

[5] 郑毛祥. SPI总线接口扩展与应用[J]. 自动化技术与应用, 2012, 31(9):75-79.

[6] 严海颖, 赵宇枫.ARM嵌入式系统应用开发基础[M].大连:东软电子出版社,2013:211-214.

[7] 高培. RT Thread的I2 C总线驱动结构分析、移植及应用[J]. 单片机与嵌入式系统应用, 2016, 16(12):26-30.

[8] 叶思超. 基于RT-Thread的手持式高性能RFID读写器设计[D]. 成都:电子科技大学, 2015.

[9] 涂撰, 赵标. RT-Thread在LPC2378上的移植与应用[J]. 上海船舶运输科学研究所学报, 2013, 36(1):44-49.

[10] 朱志国. RT-Thread操作系统在STM32中移植的研究[J]. 计算机光盘软件与应用, 2012(22):119-120.

[11] Winbond Inc. 64M-bit Serial Flash Memory with uniform 4KB sectors and Dual/Quad SPI, 2014.

[12] ST Microelectronics. RM0090 Reference manual STM32F405xx, STM32F407xx, STM32F415xx and STM32F417xx advanced ARM-based 32-bit MCUs, 2012.

[13] RT-Thread开发组. RT-Thread编程指南, 2014.

高培(讲师)、何栋炜(副教授),主要研究方向为嵌入式技术与物联网应用。

StructureAnalysis,MigrationandApplicationofSPIBusDriverforRT-Thread

GaoPei1,HeDongwei2,LiWenxiang1,LinDannan1

(1.Fujian Business University,Fuzhou 350012,China;2.Fujian University of Technology)

In the paper,the communication principle of SPI and the SPI bus driver structure of the RT-Thread operating system are researched and analyzed.Taking the example of STM32F407ZG’s read and write operation on W25Q64,the transplant procedure of hardware SPI bus driver,the SPI application design and technical implementation are introduced.Finally,the experiment is carried out to verify the feasibility and effectiveness of driver transplantation and program design of SPI.The experiment results show that the RT-Thread operating system under the SPI bus driver has reasonable structural design,it is convenient to migration and easy for program design,the transplantation and application method introduced in the paper is feasible and effective.

RT-Thread;SPI;driver structure;analysis;migration;application

福建对外经济贸易职业技术学院校级课题《UWB技术在智慧城市中的应用》(KT201612)。

TP316.2

A

杨迪娜

2017-09-07)

猜你喜欢

核心层寄存器时钟
别样的“时钟”
古代的时钟
Lite寄存器模型的设计与实现
分簇结构向量寄存器分配策略研究*
浅谈宽带IP城域网结构规划和建设
有趣的时钟
校园网核心层设计探究
政府办公区域无线网络覆盖的设计
时钟会开“花”
面向TD-LTE的城域传送网核心层组网探讨