0%

SPI protocol

SPI协议

SPI简介

串行外设接口(Serial Peripheral Interface)的简称也叫做SPI,是一种高速的、全双工同步通信的一种接口,串行外设接口一般是需要4根线来进行通信(NSS、MISO、MOSI、SCK),但是如果打算实现单向通信(最少3根线),就可以利用这种机制实现一对多或者一对一的通信。

主从模式

SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

引脚定义

SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)

MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCK : 用于 SPI 主器件的串行时钟输出以及 SP从器件的串行时钟输入。
NSS/CS/SS:从器件选择。这是用于选择从器件的可选引脚。此引脚用作“片选”可让 SP主器件与从器件进行单独通信,从而并避免数据线上的竞争。

SPI一对一

SPI一对多


数据收发

SPI总线采用的环形结构,利用的是主从模式(主机—->从机)进行数据的传输,由于是同步通信,所以在主机发送数据的同时也会收到从机发送的数据。

MOSI 引脚连接在一起,MISO 引脚连接在一起。通过这种方式,主器件和从器件之间以串行方式传输数据(最高有效位在前)。

通信始终由主器件发起。当主器件通过 MOSI 引脚向从器件发送数据时,从器件同时通过 MISO 引脚做出响应。这是一个数据输出和数据输入都由同一时钟进行同步的 1 全双工通信过程。


工作模式

SPI通信有4种不同的操作模式,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:

时钟极性(CPOL)定义了时钟空闲状态电平:

CPOL=0,表示处于空闲态时SCK时钟线处于低电平,所以有效状态就是SCK处于高电平时
CPOL=1,表示处于空闲态时SCK时钟线处于高电平,所以有效状态就是SCK处于低电平时

时钟相位(CPHA)定义数据的采集时间。

CPHA=0,在时钟的第一个跳变沿(上升沿或下降沿)进行数据锁存。
CPHA=1,在时钟的第二个跳变沿(上升沿或下降沿)进行数据锁存。

Mode0:CPOL=0,CPHA=0:此时空闲态时,SCK处于低电平,数据采样是在第1个边沿。
Mode1:CPOL=0,CPHA=1:此时空闲态时,SCK处于低电平,数据发送是在第2个边沿。
Mode2:CPOL=1,CPHA=0:此时空闲态时,SCK处于高电平,数据采集是在第1个边沿。
Mode3:CPOL=1,CPHA=1:此时空闲态时,SCK处于高电平,数据发送是在第2个边沿。


模拟SPI时序

以 STM32F407ZET6 和 W25Q128 闪存芯片为例,利用IO口模拟SPI时序实现对存储IC进行读取操作

SCK – PB3 输出模式
MOSI – PB5 输出模式
MISO – PB4 输入模式
CS – PB14 输出模式

1
2
3
4
#define  W25Q128_CS(n)   (n) ? GPIO_SetBits(GPIOB,GPIO_Pin_14) : GPIO_ResetBits(GPIOB,GPIO_Pin_14) 
#define W25Q128_SCK(n) (n) ? GPIO_SetBits(GPIOB,GPIO_Pin_3) : GPIO_ResetBits(GPIOB,GPIO_Pin_3)
#define W25Q128_MOSI(n) (n) ? GPIO_SetBits(GPIOB,GPIO_Pin_5) : GPIO_ResetBits(GPIOB,GPIO_Pin_5)
#define W25Q128_MISO GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4)

采用模式0 SCK引脚空闲高电平,第二个边沿锁存数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
uint8_t W25Q128_SendByte(uint8_t byte)
{
int i = 0;
uint8_t data = 0;

//1.SCK引脚输出低电平
W25Q128_SCK(0);
delay_us(5);

//2.循环发送8次,每次发送一个bit 遵循MSB 高位先出
for(i=7;i>=0;i--)
{
//3.判断待发送的字节的最高位 准备发送数据
if( byte & (1<<i) )
{
W25Q128_MOSI(1);
}
else
W25Q128_MOSI(0);

delay_us(5);

//4.SCK引脚输出高电平,此时第一个边沿出现,进行数据采集
W25Q128_SCK(1);
delay_us(5);

//5.此时从机会响应一个bit,主机需要接收!
if(W25Q128_MISO)
data |= (1<<i);
delay_us(5);

//6.SCK引脚输出低电平,此时第二个边沿出现
W25Q128_SCK(0);
delay_us(5);

}

return data;
}

采用模式3 SCK引脚空闲高电平,第二个边沿锁存数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
uint8_t W25Q128_SendByte(uint8_t byte)
{
int i = 0;
uint8_t data = 0;

//1.SCK引脚输出高电平
W25Q128_SCK(1);
delay_us(5);

//3.循环发送8次,每次发送一个bit 遵循MSB 高位先出
for(i=0;i<8;i++)
{
//2.SCK引脚输出低电平,此时第一个边沿出现
W25Q128_SCK(0);
delay_us(5);

//4.判断待发送的字节的最高位 准备发送数据
if( byte & 0x80 )
{
W25Q128_MOSI(1);
}
else
W25Q128_MOSI(0);

byte <<= 1;
delay_us(5);

//5.SCK引脚输出高电平,此时第二个边沿出现
W25Q128_SCK(1);
delay_us(5);

//6.此时从机会响应一个bit,主机需要接收!
data <<= 1;
data |= W25Q128_MISO;
delay_us(5);
}

return data;
}

参考文章
STM32F4xx中文参考手册