SPI
1. 基本概念
SPI(SerialPeripheralinterface)(串行外设接口):是由 Motorola 公司开发的串行外围设备接口,是一种高速的,全双工,同步的通信总线。主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器等器件。
SPI 接口提供两个主要功能,支持 SPI 协议或 I2S 音频协议。默认情况下,选择的是 SPI 功能。可通过软件将接口从 SPI 切换到 I2S。
串行外设接口 (SPI) 可与外部器件进行半双工/全双工的同步串行通信。该接口可配置为主模式,在这种情况下,它可为外部从器件提供通信时钟 (SCK)。该接口还能够在多主模式配置下工作。
它可用于多种用途,包括基于双线的单工同步传输,其中一条可作为双向数据线,或使用CRC 校验实现可靠通信。
总结一下, SPI的通信特点为同步串行全双工通信;
SPI具备同步位交换特性:每当SPI发送一个单位数据,同时也会收到同样大小的一个单位数据;
通信速度范围为 10MHZ – 100MHZ,在通信协议中属于非常快的,并且没有固定的数据帧格式,数据帧大小:8 / 16位;
其主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器等器件。
1.1 总线构成
(1)五线制接口(四线制SPI)
MOSI:主出从入线(主机输出从机输入)
MISO:主入从出线(主机输入从机输出)
SCK:时钟线(主机向从机发送通信的时钟边沿信号)
CS/NSS:片选线(选择与主机通信的从设备)
GND:接地线
通信特点:同步串行全双工通信
(2)四线制接口(三线制SPI)
IO:双向数据线(同一时间数据只能单向传输)
SCK:时钟线(主机向从机发送通信的时钟边沿信号)
CS/NSS:片选线(选择与主机通信的从设备)
GND:接地线
通信特点:同步串行半双工通信
1.2 SPI同步通信时序
CPOL(极性) 和 CPHA(相位) 共同决定时钟线采集数据的边沿信号。
CPOL(极性)决定时钟线初始化电平状态:
-
CPOL = 0:初始电平为低电平
-
CPOL = 1:初始电平为高电平
CPHA(相位)决定采集数据的时钟边沿:
-
CPHA = 0:时钟线的第一个边沿采集数据
-
CPHA = 1:时钟线的第二个边沿采集数据
由上图我们可以总结一下
极性/相位 | CPHA = 0 | CPHA = 1 |
---|---|---|
CPOL = 0 | 模式0(上升沿采集数据) | 模式1(下降沿采集数据) |
CPOL = 1 | 模式2(下降沿采集数据) | 模式3(上升沿采集数据) |
一般来说,模式0和模式3是相互兼容的,模式1和模式2是相互兼容的。相同边沿采集的模式往往相互兼容。具体使用哪个模式要根据相对应模块手册的描述来决定。
1.3 STM32F407中的SPI资源情况
从图中可以看出:
SPI1挂载在APB2时钟总线上(时钟频率为84MHZ);
SPI2和SPI3挂载在APB1时钟总线上(时钟频率为42MHZ).
2. STM32F407中的SPI
2.1 SPI特性
● 基于三条线(MOSI MISO SCK)的全双工同步传输
● 基于双线(IO SCK)的单工同步传输,其中一条可作为双向数据线(半双工)
● 8 位或 16 位传输帧格式选择
● 主模式或从模式操作
● 多主模式功能
● 8 个主模式波特率预分频器(最大值为 f PCLK /2)—— 具体分频系数的选择应参考模块手册(支持的通信速度)
● 从模式频率(最大值为 f PCLK /2)
● 对于主模式和从模式都可实现更快的通信 —— 两根数据线同时用作发送或接收数据线
● 对于主模式和从模式都可通过硬件或软件进行 NSS 管理:动态切换主/从操作
● 可编程的**时钟极性(CPOL)和相位(CPHA)**
● 可编程的**数据顺序,最先移位 MSB (高位在前)或 LSB(低位在前)**
● 可触发中断的专用发送和接收标志
● SPI 总线忙状态标志
● SPI TI 模式(SPI 全双工模式)
● 用于确保可靠通信的硬件 CRC 功能:
— 在发送模式下可将 CRC 值作为最后一个字节发送
— 根据收到的最后一个字节自动进行 CRC 错误校验
● 可触发中断的主模式故障、上溢和 CRC 错误标志
● 具有 DMA 功能的 1 字节发送和接收缓冲器:发送和接收请求
2.2 SPI框图及其配置流程
1.初始化配置函数
(1)使能要使用的GPIO和SPI的时钟总线
(2)初始化相关GPIO引脚,将SCLK,MOSI,MISO的GPIO模式配置为复用,CS为输出
(3)改变引脚映射
(4)配置SPI
- 波特率预分频
- 通信方式(全双工/半双工/单工)
- 主从模式
- 极性
- 相位
- 软硬件管理
- 数据顺序
- 数据大小
5)使能SPI
2.编写数据接收和发送函数
3. SPI初始化以及数据接收发送代码
//W25Q64_CS_H为拉高片选线电平
#define W25Q64_CS_H GPIO_SetBits(GPIOB,GPIO_Pin_14)
//W25Q64_CS_H为拉低片选线电平
#define W25Q64_CS_L GPIO_ResetBits(GPIOB,GPIO_Pin_14)
//SPI1初始化函数
void SPI1_Init(void)
{
//1.开启时钟线
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
//2.初始化相关GPIO引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB,&GPIO_InitStructure);
W25Q64_CS_H;//初始化完片选线引脚后就将其拉高
//3.引脚重映射
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
//4.配置SPI
SPI_InitTypeDef SPI_InitStruct;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//2分频
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//从第二个边沿开始捕获
SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//时钟线初始电平状态为高电平
SPI_InitStruct.SPI_CRCPolynomial = 0X0007;//这里我们不需要CRC,故设置为此值
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据大小为8位
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工通信
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位先发
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主机模式
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//软件控制NSS
SPI_Init(SPI1, &SPI_InitStruct);
//5.使能SPI
SPI_Cmd(SPI1, ENABLE);
}
//SPI1数据收发函数
u8 SPI1_TransferData(u8 data)
{
//当发送缓冲区为空时再发送数据
while(RESET == SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE));
SPI_I2S_SendData(SPI1,data);
//当接收缓冲区不为空时再接收数据
while(RESET == SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE));
return SPI_I2S_ReceiveData(SPI1)&0xFF;
}