文章目录
- 前言
- 一、扩展SPI协议(Single/Dual/Quad/Octal SPI)
- 二、SPi驱动框架
- 三、SPI应用编程
- 1. SPI相关数据结构与ioctl函数
- 2. 基本函数
前言
与IIC类似,SPI协议也是我们的老朋友了,这里依然不多作赘述,本文将介绍SPI的驱动框架和应用程序编写。
一、扩展SPI协议(Single/Dual/Quad/Octal SPI)
为了适应更高速率的通讯需求,半导体厂商扩展SPI协议,主要发展出了Dual/Quad/Octal SPI协议,加上 标准SPI协议(Single SPI),这四种协议的主要区别是数据线的数量及通讯方式,具体见下表。
协议 | 数据线数量及功能 | 通讯方式 |
---|---|---|
Single SPI(标准SPI) | 1根发送,1根接收 | 全双工 |
Dual SPI(双线SPI) | 收发共用2根数据线 | 半双工 |
Quad SPI(四线SPI) | 收发共用4根数据线 | 半双工 |
Octal SPI(八线SPI) | 收发共用8根数据线 | 半双工 |
扩展的三种SPI协议都是半双工的通讯方式,也就是说它们的数据线是分时进行收发 数据的。例如,标准SPI(Single SPI)与双线SPI(Dual SPI)都是两根数据线,但标准SPI(Single SPI)的其中一根数据线只用来发送,另一根数据线只用来接收,即全双工;而双线SPI(Dual SPI)的两根线都具有收发功能,但在同一时刻只能是发送或者是接收,即半双工,但其主要针对SPI Flash,而不是所有SPI外设;四线SPI(Quad SPI)同样主要针对SPI Flash,其目标是在一个时钟周期内传输4个bit数据;Octal SPI(八线SPI)通同一时钟周期内能够传输更多的数据,从而实现了更高的传输速率,此外Octal SPI还引入了多种时钟模式,以支持不同的数据传输速率和时序要求。
- SDR和DDR模式
扩展的SPI协议还增加了SDR模式(单倍速率Single Data Rate)和DDR模式(双倍速率Double Data Rate)。例如在标准SPI协议的SDR模式下,只在SCK的单边沿进行数据传输,即一个SCK时钟只传输一位数据;而在它的DDR模式下,会在SCK的上升沿和下降沿都进行数据传输,即一个SCK时钟能传输两位数据,传输速率提高一倍。
二、SPi驱动框架
- 用户空间
对于用户而言,SPI驱动模型提供了一种方便、灵活的方式来与外部设备进行通信。用户无需深入了解底层硬件细节,只需通过SPI接口发送相应的指令和数据,即可实现对外部设备的控制或数据的读取。这种抽象化的接口使得用户能够更专注于应用层面的开发,提高了开发效率和便利性。例如,和 MTD 层交互以便把 SPI 接口的存储设备实现为某个文件系统,和TTY 子系统交互把 SPI 设备实现为一个 TTY 设备,和网络子系统交互以便把一个 SPI 设备实现为一个网络设备,等等。当然,如果是一个专有的 SPI 设备,我们也可以按设备的协议要求,实现自己的专有协议驱动。
- 内核方面
在内核层面,SPI驱动模型负责管理和控制SPI接口的工作。Linux内核中的SPI驱动架构通常分为三个层次:SPI核心层、SPI控制器驱动层和SPI设备驱动层。
- SPI核心层(SPI通用接口层)是Linux的SPI核心部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。
- SPI控制器驱动层,每种处理器平台都有自己的控制器驱动,属于平台移植相关层。它的职责是为系统中每条SPI总线实现相应的读写方法。在物理上,每个SPI控制器可以连接若干个SPI从设备。
- SPI设备驱动层则实现了与具体SPI设备的通信协议和数据交换机制。内核通过SPI驱动模型,为上层应用提供了统一的接口,使得应用能够通过SPI接口与底层硬件进行通信。同时,内核还负责处理SPI接口的中断和错误情况,确保数据传输的稳定性和可靠性。
- 硬件方面
在硬件方面,SPI驱动模型与具体的SPI控制器和从设备紧密相关。SPI控制器负责产生时钟信号、控制从设备的使能以及数据的发送和接收。从设备则根据SPI接口的协议进行数据的传输和处理。SPI驱动模型需要与这些硬件设备进行协同工作,确保数据的正确传输和设备的正常工作。
三、SPI应用编程
1. SPI相关数据结构与ioctl函数
编写应用程序需要使用到spi_ioc_transfer结构体,如下所示:
struct spi_ioc_transfer {
__u64 tx_buf; //发送数据缓存
__u64 rx_buf; //接收数据缓存
__u32 len; //数据长度
__u32 speed_hz; //通讯速率
__u16 delay_usecs; //两个spi_ioc_transfer之间的延时,微秒
__u8 bits_per_word; //数据长度
__u8 cs_change; //取消选中片选
__u8 tx_nbits; //单次数据宽度(多数据线模式)
__u8 rx_nbits; //单次数据宽度(多数据线模式)
__u16 pad;
};
在编写应用程序时还需要使用ioctl函数设置spi相关配置,其函数原型如下
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
其中对于终端request的值常用的有以下几种:
参数取值 | 功能 |
---|---|
SPI_IOC_RD_MODE32 | 设置读取SPI模式(对应上文的SPI的四种模式的表格,SPI_MODE_x) |
SPI_IOC_WR_MODE32 | 设置写入SPI模式(对应上文的SPI的四种模式的表格,SPI_MODE_x) |
SPI_IOC_RD_LSB_FIRST | 设置SPI读取数据模式(LSB先行返回1) |
SPI_IOC_WR_LSB_FIRST | 设置SPI写入数据模式。(0:MSB,非0:LSB) |
SPI_IOC_RD_BITS_PER_WORD | 设置SPI读取设备的字长 |
SPI_IOC_WR_BITS_PER_WORD | 设置SPI写入设备的字长 |
SPI_IOC_RD_MAX_SPEED_HZ | 设置读取SPI设备的最大通信频率 |
SPI_IOC_WR_MAX_SPEED_HZ | 设置写入SPI设备的最大通信速率 |
SPI_IOC_MESSAGE(N) | 一次进行双向/多次读写操作 |
2. 基本函数
- 初始化
void spi_init(void)
{
int ret = 0;
//打开 SPI 设备
fd = open(SPI_DEV_PATH, O_RDWR);
if (fd < 0)
printf("can't open %s\n",SPI_DEV_PATH);
//spi mode 设置SPI 工作模式
ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);
if (ret == -1)
printf("can't set spi mode\n");
//bits per word 设置一个字节的位数
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
printf("can't set bits per word\n");
//max speed hz 设置SPI 最高工作频率
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
printf("can't set max speed hz\n");
//打印
printf("spi mode: 0x%x\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed / 1000);
}
- 数据传输
void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
int ret;
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx, //发送缓冲区地址
.rx_buf = (unsigned long)rx, //接收缓冲区地址
.len = len, //一次传输的数据长度
.delay_usecs = delay, //如果不为零则用于设置两次传输之间的时间延迟
.speed_hz = speed, //speed_hz,指定SPI通信的比特率
.bits_per_word = bits, //指定字节长度,既一个字节占用多少比特
.tx_nbits = 1,
.rx_nbits = 1
};
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1)
注:tx_nbits/rx_nbits 指定“写/读”数据宽度,SPI 支持1、2、4位宽度,如果没有特殊数据要求的话,一般设置为1或0(设置为0表示使用默认的宽度既宽度为1)。