SPI(Serial Peripheral Interface,串行外围设备接口)通信协议是一种由Motorola公司首先在其MC68HCXX系列处理器上定义的同步通信协议。该协议广泛应用于MCU、存储芯片、AD转换器和LCD等之间的数据交互。
一、SPI通信的基本组成
SPI总线通常由四根线组成,但在单向传输时可能只需要三根线,包括以下四根线:
- SCLK(Serial Clock,时钟信号):由主设备产生,用于规定数据的传输时间。
- MOSI(Master Output/Slave Input,主设备输出/从设备输入):用于主设备向从设备发送数据。
- MISO(Master Input/Slave Output,主设备输入/从设备输出):用于从设备向主设备发送数据。
- CS/SS(Chip Select/Slave Select,片选/从设备选择信号):由主设备控制,用于选择特定的从设备进行通信。在多从设备系统中,每个从设备可能需要一个独立的CS/SS线
二、工作原理
SPI通信协议是一种主从设备架构,其中主设备负责产生时钟信号并控制通信过程,从设备则根据时钟信号进行数据的接收和发送。在通信过程中,主设备和从设备都拥有串行移位寄存器,用于数据的串行传输。
通信过程通常包括以下几个步骤:
- 主设备发起通信:主设备通过拉低从设备的CS/SS线来启动通信。
- 时钟信号同步:主设备产生时钟信号(SCLK),从设备根据时钟信号的边沿(上升沿或下降沿)进行数据的采样和发送。
- 数据交换:主设备通过MOSI线向从设备发送数据,同时从设备通过MISO线向主设备发送数据。数据的交换是同步进行的,即主设备发送一位数据的同时,从设备也发送一位数据。
三、工作模式
SPI协议定义了四种工作模式,这些模式通过CPOL(Clock Polarity,时钟极性)和CPHA(Clock Phase,时钟相位)的组合来区分。
模式 | CPOL | CPHA | 描述 |
---|---|---|---|
0 | 0 | 0 | 空闲时SCLK为低电平,数据在SCK的上升沿被采样,在下降沿被发送。 |
|
模式 | CPOL | CPHA | 描述 |
---|---|---|---|
2 | 1 | 0 | 空闲时SCLK为高电平,数据在SCK的下降沿被采样,在上升沿被发送。 |
模式 | CPOL | CPHA | 描述 |
---|---|---|---|
3 | 1 | 1 | 空闲时SCLK为高电平,数据在SCK的上升沿被采样,在下降沿被发送。 |
四、优缺点
优点:
- 数据传输速率高,通常能达到甚至超过10Mbps。
- 独立的MISO和MOSI线路,支持全双工通信。
- 硬件连接简单,不需要像I2C那样的从设备寻址系统。
缺点:
- 使用四根信号线,相比I2C和UART等协议较为复杂。
- 无法直接确认数据是否成功接收(虽然可以通过其他机制实现)。
五、时序单元
根据SPI通信协议的通信方式,可以将SPI通信协议拆分为三个部分;以下是主机通过SPI通信协议向从机发送0x06的时序图。
通过观察时许,可以看到该通信模式为模式0。
时许可以拆分为三个部分:开始通信、结束通信、交换数据。
开始通信:
当SS/CS线置为低电平时,表明SPI通信协议通信开始。
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
结束通信:
当SS/CS从低电平置为高电平,表明通信结束
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
交换数据:
红色框部分为数据交换部分,本次通信在模式0的情况下。
通信开始,当时钟线SCK在低电平时,由于SPI通信是高位先行。于是先将高位(B7)发送到MOSI线上,等到SCK上升沿时。对MOSI进行采样,根据移位机制,将MOSI的高位移动到MISO的低位。即MOSI的最高位变为MISO的最低位。如此循环八次即可完成主机跟从机数据的发送/移位。
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t ByteReceive = 0x00;
uint8_t i ;
for(i = 0; i < 8; i++)
{
MySPI_W_MOSI(ByteSend & (0x80>>i));//通过掩码进行数据交换
MySPI_W_SCK(1);
if(MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
至此,SPI通信的时序单元已经写好。
MySPI.c
#include "MySpi.h"
#include "main.h"
void MySPI_W_SS(uint8_t BitValue)
{
HAL_GPIO_WritePin(SPI2_SS_PORT,SPI2_SS_PIN,(GPIO_PinState)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
HAL_GPIO_WritePin(SPI2_SCK_PORT,SPI2_SCK_PIN,(GPIO_PinState)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
HAL_GPIO_WritePin(SPI2_MOSI_PORT,SPI2_MOSI_PIN,(GPIO_PinState)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
uint8_t PIN_STATE;
PIN_STATE = HAL_GPIO_ReadPin(SPI2_MISO_PORT,SPI2_MISO_PIN);
return PIN_STATE;
}
void MySPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = SPI2_MISO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = SPI2_SS_PIN|SPI2_SCK_PIN|SPI2_MOSI_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
MySPI_W_SS(1);//上电初始化 默认不选中从机
MySPI_W_SCK(0);// 默认选择模式0
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t ByteReceive = 0x00;
uint8_t i ;
for(i = 0; i < 8; i++)
{
MySPI_W_MOSI(ByteSend & (0x80>>i));//通过掩码进行数据交换
MySPI_W_SCK(1);
if(MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
数据移位模型
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
// uint8_t ByteReceive = 0x00;
// uint8_t i ;
// for(i = 0; i < 8; i++)
// {
// MySPI_W_MOSI(ByteSend & 0x80);//通过掩码进行数据交换
// ByteSend <<= 1;
// MySPI_W_SCK(1);
// if(MySPI_R_MISO() == 1){ByteSend |= 0x01;}
// MySPI_W_SCK(0);
// }
// return ByteReceive;
//}
MySPI.h
#ifndef __MySpi_H_
#define __MySpi_H_
#include <stdint.h>
#define SPI2_SS_PORT GPIOB
#define SPI2_SS_PIN GPIO_PIN_12
#define SPI2_SCK_PORT GPIOB
#define SPI2_SCK_PIN GPIO_PIN_13
#define SPI2_MISO_PORT GPIOB
#define SPI2_MISO_PIN GPIO_PIN_14
#define SPI2_MOSI_PORT GPIOB
#define SPI2_MOSI_PIN GPIO_PIN_15
void MySPI_W_SS(uint8_t BitValue);
void MySPI_W_SCK(uint8_t BitValue);
void MySPI_W_MOSI(uint8_t BitValue);
uint8_t MySPI_R_MISO(void);
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
//数据移位模型
uint8_t MySPI_SwapByte(uint8_t ByteSend);
#endif