SPI协议的时序与模式
UART
在讨论SPI协议前先回顾一下串口UART通讯,有助于SPI通讯的掌握。
通讯特点
- 串口UART通讯,是串行异步通讯协议。异步也就是没有时钟信号,无法控制何时发送数据,也无法保证双方同时接收数据。
- 所以只有发送和接受两根线,UART通常是两根线TX(发送线)、RX(接收线)。
- 那接收端如何知道数据到了呢,需要在通讯前,双方就传输速度达成共识,设置相同的波特率,例如每秒9600位。设置完波特率后,还需要通过起始位和停止位判断数据有没有过来,有没有传完。为了保证接收的数据内容正确,还需要有校验位。所以在传输时有许多额外的开销。
通讯时序
这是在网上找的一张便于理解的图,串行的通讯协议通常会先发低位,图中,传送数据为01010011=0x53,注意:图中箭头不指示传送方向,根据时间来看,左边的是先传送的。
SPI
SPI(Serial Peripheral Interface)同步串行通讯协议,应用于MCU与外设之间或者MCU与MCU之间的通讯。SPI通常需要四根线SCLK(时钟线)、CS(片选线)、MOSI(主设备数据输出线)、MISO(主设备数据输入线),通过时钟极性和相位配置实现不同模式的通信。
特点
- SPI是全双工通讯,可以同时发送和接收数据。
- 用单独的数据线和时钟信号保证发送端和接收端的完美同步。
- 通过时钟的变化,获取数据采样的时机,无需串口那样额外的起始位。产生时钟信号的一侧是主机,另一侧称为从机。总是只有一个主机,可以有多个从机。数据采集时机可能是时钟信号的上升沿或者下降沿,具体要看SPI的配置。
时序
整体的传输过程:
- 主机现将NSS片选信号拉低,这样保证开始接收数据。
- 当接收端检测到时钟的边缘信号时,它立刻读取数据线上的信号,这样就得到了一位数据(1bit)。由于时钟是随数据一起发送的,因此指定数据的传输速度并不重要。
- 主机发送到从机时,主机产生相应的时钟信号,然后数据一位一位地将从MOSI信号线上进行发送到从机。
- 主机接收从机数据,如果从机需要把数据发送回主机,则主机将继续生成预定数量的时钟信号,并且从机会将数据通过MISO信号线发送。
如图:SPI具有单独的发送接收线路,可以在同一时间发送和接收数据。主机输出数据,串行数据发送时,低位在前,实际数据为01010011=0x53,主机接收从机的数据时,低位在前,实际数据为01000110=0x46。图中箭头只表示发送的数据,不指示传输方向。
图中SPI是在SCK时钟信号拉低时唤醒,SCK时钟信号由低转高的上升沿采集数据,这只是SPI的四种工作模式之一,不同的SPI设备可能使用不同的模式,使用时需匹配主从设备的SPI模式。
SPI的四种工作模式
SPI通信由CPOL(时钟极性)和CPHA(时钟相位)决定,SPI的四种模式(时钟极性和相位):
所以,配置主机和从机时,需要保持主从模式一致,否则就无法通信。
应用场景
- 传感器接口:如加速度计(MPU6050)、温湿度传感器等。
- 存储设备:如Flash(W25QXX系列)、EEPROM(AT25系列)。
- 显示屏:如OLED、LCD(ST7789、ILI9341)。
- 通信模块:如WiFi、LoRa、RFID等无线通信模块。
多从机模式
1. 多NSS
每个从机一根单独的SS线,跟对应的从机通讯,把相应的NSS信号拉低即可,其他的NSS保持高电平。如果同时将两个NSS信号线拉低,则可能会出现乱码。
多NSS的缺点是需要占用多硬件的IO口给NSS,另外接线也多,但是软件控制简单。
2. 菊花链
菊花链,信号以串行的方式从一个设备依次传到下一个设备,不断循环直到数据到达目标设备的方式被称为菊花链。
- 如图在菊花链模式下,所有从机的时钟信号和片选信号连接在一起,数据从一个从机传播到下一个从机。在此配置中,所有从机同时接收同一SPI时钟。
- 使用该方法时,由于数据是从一个从机传播到下一个从机,所以传输数据所需的时钟周期数与菊花链中的从机位置成比例。主机将数据传送到从机3时,需要24个时钟脉冲,而常规SPI模式下只需8个时钟脉冲。
- 下图所示时钟周期和通过菊花链的数据传播:数据0xA5先传到从机1,第二个时钟0xA5传到从机2,0x5A传到时钟1,第三个时钟0xA5传到从机3,0x5A传到时钟2,0x0A传到时钟1,在时钟结束后,SDIN3实际保存的数据是0xA5,SDIN2实际保存的数据是0x5A,SDIN1实际保存的数据是0x0A。
注意:并非所有SPI器件都支持菊花链模式。请参阅产品数据手册以确认菊花链是否可用。菊花链的优势就是大大的省掉了硬件接口,接线也变得方便,软件操作起来复杂。
SPI与其他接口的区别
SPI的常见问题和解决方案
SPI标准库初始化示例
/*
SPI初始化
*/
void Spi_Init(void)
{
/*SPI结构体*/
SPI_InitTypeDef SPI_InitStructure;
/*SPI1时钟使能*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE );
/*SPI配置*/
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //片选又软件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//APB2(36M)/256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB(高)位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //写入SPI1里面
/*使能SPI外设*/
SPI_Cmd(SPI1, ENABLE);
/*主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输*/
SPI1_ReadWriteByte(0xFF);
}