SPI(Serial Peripheral Interface,串行外设接口)是一种同步的串行通信协议,通常用于微控制器和外部设备(如传感器、存储器、显示屏等)之间的高速数据传输。SPI协议由主设备(Master)和从设备(Slave)组成,主设备发起通信并控制时序,而从设备根据主设备的指令进行响应。
SPI使用4根信号线进行通信:
- MOSI (Master Out Slave In):主设备输出,数据从主设备流向从设备。
- MISO (Master In Slave Out):主设备输入,数据从从设备流向主设备。
- SCK (Serial Clock):串行时钟,由主设备提供,用于同步数据传输。
- SS (Slave Select):从设备选择信号,主设备通过拉低此信号来选择与其通信的从设备。
SPI协议基于全双工通信,即数据在传输时可以同时进行发送和接收。SPI数据传输的关键是时钟同步,数据在时钟的每个上升沿或下降沿时进行采样。协议的基本通信流程如下:
- 初始化:
- 主设备选择一个从设备,拉低对应的SS信号线。
- 主设备配置SPI的时钟极性(CPOL)和时钟相位(CPHA),确保双方时钟同步。
- 数据传输:
- 主设备在时钟的上升沿或下降沿发送数据,同时从设备在相同的时钟边缘读取数据。
- 主设备发送数据后,等待从设备通过MISO线发送数据回到主设备。
- 数据传输顺序:
- 在SPI协议中,数据从MOSI线发送到从设备,同时从设备通过MISO线返回数据给主设备。传输顺序和方向在每个时钟周期内互相交替。
SPI的时序是协议的核心,控制着数据如何在主设备与从设备之间传递。以下是SPI的典型时序示例:
-
CPOL = 0,CPHA = 0(时钟极性低,时钟相位为0)
- 在时钟线的上升沿时,数据位从主设备的MOSI线传输到从设备。
- 在时钟线的下降沿时,从设备的MISO线发送数据。
-
CPOL = 0,CPHA = 1(时钟极性低,时钟相位为1)
- 在时钟线的下降沿时,主设备发送数据。
- 在时钟线的上升沿时,从设备的MISO线发送数据。
-
CPOL = 1,CPHA = 0(时钟极性高,时钟相位为0)
- 时钟信号在数据传输时有反向变化。
-
CPOL = 1,CPHA = 1(时钟极性高,时钟相位为1)
- 这种模式通常用于在某些特殊硬件上进行高速数据通信。
时钟极性和相位的选择直接影响数据采样时刻和数据稳定的时序。具体选择哪个时钟模式依赖于具体的应用和从设备的要求。
假设现在主控芯片要传输一个0x56数据给SPI Flash,时序如上:首先CS0先拉低选中SPI Flash,0x56的二进制就是0b0101 0110,因此在每个SCK时钟周期,DO输出对应的电平。 SPI Flash会在每个时钟周期的上升沿读取D0上的电平。
其次再具体说明一下在SPI协议中,有两个值来确定SPI的模式。 CPOL:表示SPICLK的初始电平,0为电平,1为高电平 CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿。
我们常用的是模式0和模式3,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。 极性选什么?格式选什么?通常去参考外接的模块的芯片手册。比如对于OLED,查看它的芯片手册时序部分:
SCLK的初始电平我们并不需要关心,只要保证在上升沿采样数据就行。
在Linux中,SPI协议的实现涉及多个层次,包括驱动程序、硬件控制和协议管理。Linux内核提供了一个标准的SPI子系统,简化了设备驱动的开发,并且允许用户空间通过/dev/spidev
接口与SPI设备进行通信。
SPI驱动通常包括以下几个部分:
- SPI设备描述符:定义设备的相关信息,如SPI总线速率、工作模式、设备ID等。
- SPI设备操作函数:包括发送和接收数据的函数,如
spi_transfer()
。 - SPI总线配置:配置SPI总线参数,如时钟频率、极性、相位、数据位宽等。
Linux SPI驱动工作流程
- SPI总线初始化:SPI总线驱动首先初始化SPI总线,注册SPI总线控制器。
- SPI设备注册:通过SPI设备结构体
spi_device
注册到SPI总线,驱动程序会在设备初始化时设置设备的配置参数。 - 数据传输:通过调用
spi_sync()
或者spi_async()
来同步或异步地进行SPI数据传输。 - 传输过程:
- SPI数据传输通过
spi_message
结构体完成,包含了多个SPI传输操作。 - 数据通过
spi_transfer
结构体传输,每个传输项包含发送和接收数据的缓冲区以及其他控制参数。
- SPI数据传输通过
spidev
是一个Linux设备驱动,允许用户空间程序通过标准文件I/O接口(如read
/write
)与SPI总线设备进行交互。通过spidev
,用户可以控制SPI设备,而不需要编写复杂的内核驱动。应用程序通过打开/dev/spidevX.Y
文件来访问SPI设备,使用标准的ioctl
命令来配置SPI设备。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
int main() {
int fd = open("/dev/spidev0.0", O_RDWR);
if (fd < 0) {
perror("Unable to open SPI device");
return -1;
}
uint8_t mode = SPI_MODE_0;
uint32_t speed = 500000;
uint8_t bits = 8;
// Set SPI mode
if (ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) {
perror("Unable to set SPI mode");
close(fd);
return -1;
}
// Set SPI speed
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1) {
perror("Unable to set SPI speed");
close(fd);
return -1;
}
// Set bits per word
if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) == -1) {
perror("Unable to set bits per word");
close(fd);
return -1;
}
uint8_t tx_buffer[2] = {0x01, 0x02};
uint8_t rx_buffer[2] = {0};
struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(tx_buffer),
.speed_hz = speed,
.bits_per_word = bits
};
// Perform SPI transfer
if (ioctl(fd, SPI_IOC_MESSAGE(1), &transfer) < 0) {
perror("SPI transfer failed");
close(fd);
return -1;
}
printf("Received data: 0x%02x 0x%02x\n", rx_buffer[0], rx_buffer[1]);
close(fd);
return 0;
}
标签:总线,通信协议,SPI,fd,Linux,数据传输,设备,时钟
From: https://blog.csdn.net/weixin_64593595/article/details/145117668