SPI总线详解
1 SPI接口简介
SPI全称:Serial Peripheral Interface(串行外设接口),是一种同步、高速、全双工的通信总线。
同步:发送接收端要严格同步,一般有同步时钟线。
高速:理论上,SPI模块的最大时钟频率为外设总线时钟频率的1/2(实际情况低于此,需自行调试)。
全双工:发送的同时可以接收。
1.1 SPI原理简介
SPI是一种master-slave的总线通信,通常是“一master一slave”或“一master多slave”。
标准的SPI需要四根信号线:
SS(Slave Select):从设备选择,也称片选,master通过拉低slave的片选信号选择slave
SCK(Serial Clock):传输时钟的信号线,时钟信号由master产生,类似于I2C的SCL
MOSI(Master Out Slave In):master输出,slave输入,由master向slave发送数据的通道
MISO(Master In Slave Out):master输入,slave输出,由slave向master发送数据的通道
图中是1个master+3个slave的示意,同一时刻只有一个slave可以与master通信。
SPI的工作基于移位寄存器,工作过程就像一个环形传送带,由master逐位将数据放在传送带上,并驱动传送带将数据传送到slave,同时slave也会同步地逐位将数据传送给master。简单的理解:可以认为是数据交换,master向slave发送多少bit数据,就可以由slave收到多少bit数据。
SPI是事实标准,没有被任何的国际委员会承认,这样导致协议在一些地方并没有规定死,一方面使得协议比较灵活,可由厂商来自行定制;另一方面会造成一些混乱。如:
字长:不同的字长是很常见的,常见8bit或者16bit;
字节序:MSB优先还是LSB优先;
片选:片选高有效或者低有效(常见低有效),有时一master一slave不需要片选信号;
信号线:标准是4根信号线,可能无片选信号,只读(无MOSI),只写(无MISO)
时钟:SCK信号idle时为高还是低
采样: 第一个跳变沿采样,还是第二个跳变沿采样,因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出,不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的datasheet,(常见的是在一个时钟沿发送数据到MOSI线上,下个时钟沿从MISO线接收数据)。
以下引用《在ARM Linux下使用GPIO模拟SPI时序详解》的一段话,说得很好(见参考)
SPI是一个环形的总线结构,在SCLK的控制下,两个双向移位寄存器进行数据交换。那么主机和从机在进行交换数据的时候就设计到一个问题:即主机在什么时刻输出到MOSI上而从机在什么时刻采样这个数据,或者从机什么时刻输出到MISO上而主机什么时刻采样这个数据。同步通信的一个特点就是所有数据的变化和采样都是伴随着时钟沿进行的,也就是说数据总是在时钟的边沿附近变化或被采样,而一个完整的时钟周期必定包含了一个上升沿和一个下降沿,只是这两个沿的先后并无规定。又因为数据从产生到它稳定是需要一定的时间,那么如果主机在上升沿输出数据到MOSI,从机就只能在下降沿去采样这个数据了。反之,如果一方在下降沿输出数据,那么另一方就必须在上升沿采样这个数据。
1.2 SPI的CPOL与CPHA
CPOL:Clock Polarity 决定时钟空闲状态电平是高电平还是低电平
CPOL = 1 :时钟空闲时为高,时钟低电平有效
CPOL = 0 :时钟空闲时为低,时钟高电平有效
CPHA:Clock Phase 决定数据传输采样和移位方式
CPHA = 0 :在时钟信号SCK的第一个跳变沿采样
CPHA = 1 :在时钟信号SCK的第二个跳变沿采样
这样便形成了SPI的四种工作模式:
模式 | CPOL | CPHA |
---|---|---|
Mode 0 | 0 | 0 |
Mode 1 | 0 | 1 |
Mode 2 | 1 | 0 |
Mode 3 | 1 | 1 |
常用的模式是Mode0与Mode3。
直接看图更清楚:
详细一点描述如下(假设SS低有效):
Mode0 (CPOL = 0, CPHA = 0):
SS拉低,MISO引脚上的数据在第一个SCK沿跳变之前已经上线,SCK上升沿,(采样MISO数据),准备好要发送的数据,SCK下降沿(发送MOSI),依次发送完一笔数据, SS拉高。 可见是Mode0是在第一个边沿(上升沿)采样的。
Mode1 (CPOL = 0, CPHA = 1):
SS拉低,准备好要发送的数据,SCK上升沿,(发送数据到MOSI),MISO准备好数据,SCK下降沿(采样MISO数据),依次发送完一笔数据,SS拉高。 可见是Mode1是在第二个边沿(下降沿)采样的。
Mode2 (CPOL = 1, CPHA = 0):
SS拉低,MISO引脚上的数据在第一个SCK沿跳变之前已经上线,SCK下降沿,(采样MISO数据),准备好要发送的数据,SCK上升沿(发送MOSI),依次发送完一笔数据, SS拉高。 可见是Mode2是在第一个边沿(下降沿)采样的。
Mode3 (CPOL = 1, CPHA = 1):
SS拉低,准备好要发送的数据,SCK下降沿,(发送数据到MOSI),MISO准备好数据,SCK上升沿(采样MISO数据),依次发送完一笔数据,SS拉高。 可见是Mode3是在第二个边沿(上升沿)采样的。
根据datasheet获取SPI模式:
大多数情况下,SPI从设备是硬件固定的,可以查看相关的硬件datasheet。
SPI slave设备SCL在空闲的时候是高电平还是低电平,决定了CPOL是0还是1;
设备是在上升沿还是下降沿去采样数据,在定了CPOL的值的前提下,可以推算出CPHA是0还是1。
SCL空闲时电平 | 采样 | CPHA | Mode |
---|---|---|---|
低电平 | 上升沿 | CPOL = 0, CPHA = 0 | Mode0 |
低电平 | 下降沿 | CPOL = 0, CPHA = 1 | Mode1 |
高电平 | 下降沿 | CPOL = 1, CPHA = 0 | Mode2 |
高电平 | 上升沿 | CPOL = 1, CPHA = 1 | Mode3 |
表述得很啰嗦,但是理解起来很简单,比如说SCL空闲时电平为高,确定了CPOL = 1,如果
上升沿采样的话,需要SCL由高到低,然后由低到高,即在SCL第二个边沿采样(CPHA=1)
SPI的4个模式,正是由于SCL的空闲状态及采样时机没有规定死导致的,导致一些啰嗦,目前我还想不明白不固定死的原因,希望有知道原理的大佬告知。
1.3 SPI的优缺点
- 在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。但多个从设备系统中,每个从设备需要独立的片选信号,硬件上比I2C系统要稍微复杂一些。
- SPI没有指定的流控制,没有应答机制确认是否收到数据。
2 软件代码
2.1 GPIO模拟SPI
CPOL = 1, CPHA = 1 模式示例代码:
unsigned char SPI_Send_and_Recv_Byte(unsigned char val)
{
int i, j = 1;
unsigned char res = 0;
SPI_Set_CLK(1); // SCK = 1
delay_1us(10);
for (i = 0; i < 8; i++)
{
SPI_Set_DO(val & 0x01); // LSB优先,MOSI准备数据
val >>= 1;
delay_1us(10);
SPI_Set_CLK(0); // SCK = 0, SCL下降沿输出数据
delay_1us(10);
if (SPI_Get_DI())
res += j; // MISO收数据
j = j << 1;
SPI_Set_CLK(1); // SCK = 1, SCL上升沿采样
delay_1us(10);
}
DO_H;
return res;
}
用GD32F103的SPI标准库实现,其初始化代码为:
spi_struct_para_init(&spi_init_struct);
/* configure SPI0 parameters */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全双工
spi_init_struct.device_mode = SPI_MASTER; // GD32F103作为master,提供SCLK
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; //8bit模式
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; // CPOL = 1, CPHA = 1
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.prescale = SPI_PSC_64; // 分频比
spi_init_struct.endian = SPI_ENDIAN_LSB; // LSB优先
spi_init(SPI0, &spi_init_struct);
spi_enable(SPI0);
上述代码是等价的。