目录
一、什么是SPI
SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间, 要求通讯速率较高的场合。SPI接口是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省了空间,提供方便,主要应用在EEPROM,FLASH.实时时钟,AD转换器还有数字信号处理直接。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。 它可用于多种用途,包括使用一条双向数据线的双线单工同步传输,还可使用CRC校验的可靠通信。
1、SPI
下面我们分别对 SPI 协议的物理层及协议层进行讲解。
SPI接口通讯一般使用 3 条总线及片选线,3 条总线分别为 SCK(时钟信号)、MOSI(主设备输出/从设备输入引脚)、MISO(主设备输入/从设备输出引脚),及CS(从设备片选信号)。
MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCLK:串行时钟信号,用于通讯数据同步,由主设备产生。
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
![](https://img-blog.csdnimg.cn/7ca498e6ca2f4a1fa685abd6b4d8aa55.png)
SPI 主要特点有:
- 可以同时发出和接收串行数据;
- 可以当作主机或从机工作;
- 提供频率可编程时钟;
- 发送结束中断标志;
- 写冲突保护;
- 总线竞争保护等
2、SPI工作原理
1、原理
SPI是通信协议,意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写[从设备]时,它首先拉低[从设备]对应的SS线(SS是低电平有效),接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,[主设备]把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”。
工作原理
- 拉低对应SS信号线,表示与该设备进行通信
- 主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据,这里的SCLK时钟信号可以高电平有效也可以低电平有效,SPI组成有四种模式。(时钟的极性与相位)
- 主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
- 从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。
- 外设的读写操作同步完成,只进行写操作,则忽略读操作;主机只进行从机的读操作,则主机须发送一个空字节给从机引发传输。
2、从设备引脚管理(NSS)
①软件模式:
可以设置SPI_CR1寄存器的SSM位来使能这种模式,在这种模式下NSS引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动。
②硬件模式:
第一种情况:NSS输出使能,当STM32工作为SPI模式的时,NSS输出已经通过 SPI_CR2寄存器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的 NSS引脚相连并配置为硬件NSS的SPI设备,将自动变成从的SPI设备。
第二种情况:NSS输出被关闭:允许操作于多主环境。
3、时钟信号的极性和相位
SPI_CR寄存器的CPOL位(时钟极性)和CPHA位(时钟相位)
SPI通信有能够组合成4种可能的时序关系,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:
图片来源:STM32F1xx中文参考手册 >>> 23.3 SPI功能描述>>>时钟信号的相位和极性>>>图212数据时钟时序图
时钟极性(CPOL)定义了时钟空闲状态电平,时钟相位(CPHA)定义数据的采集时间。
CPHA=1,在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样,在第1个边沿发送数据CPHA=0,在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样,在第2个边沿发送数据
所以当CPOL=1,表示当空闲状态SCLK=1时,它的有效状态就是SCLK处于低电平时,反之亦然。
而我们一般用的都是CPHA=1,CPOL=1时,此时空闲状态SCLK处于高电平,在第一个边沿触发(下降沿)时数据发送,在上升沿时进行数据采集。
二、SPI结构体配置
1、主模式数据收发
下图为“主模式”流程,即STM32作为SPI通讯的主机端时的数据收发过程
图片来源:零死角玩转 STM32F103—霸道>>>第25章 SPI>>>25.2.3通讯过程>>>图 25-6 主发送器通讯过程
主从设备必须使用相同的工作模式——SCLK、CPOL 和 CPHA,才能正常工作。如果有多个从设备,并且它们使用了不同的工作模式,那么主设备必须在读写不同从设备时需要重新修改对应从设备的模式。以上SPI总线协议的主要内容。
SPI就是如此,他没有规定最大传输速率,没有地址方案,也没规定通信应答机制,没有规定流控制规则。只要四根信号线连接正确,SPI模式相同,将CS/SS信号线拉低,即可以直接通信,一次一个字节的传输,读写数据同时操作,这就是SPI。SPI有缺点,没有指定的控制流,没有应答机制确认是否接收到数据。
2、结构体配置
图片来源:STM32F1xx中文参考手册 >>> 8.1.11外设的GPIO配置>>>表22SPI
根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1(36KHZ)和APB2(72KHZ)总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。由此我们在进行从设备的分频选择时就要合理选择。
图片来源:STM32F1xx中文参考手册 >>> 23.5 SPI寄存器描述>>>SPI控制寄存器SPI_CR1>
标准库结构体解释
#include "stm32f10x.h" // Device header
#include "spi.h"
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
SPI_InitTypeDef SPI_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_InitStruct);
GPIO_SetBits( GPIOB, GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,SPI设置为双线双向全双工
SPI_InitStruct.SPI_Mode = SPI_Mode_Master; //模式,设置为主模式
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; //数据大小,设置SPI数据大小为8位
SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; //时钟极性,设置SPI默认时钟悬空为高电平
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; //时钟相位,在第二个跳变沿进行数据采样
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //NSS位,从设备片选信号。设置SPI的NSS引脚由软件管理
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //波特率,在APB1上,设置SPI波特率预分频值为256
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行或低位先行,设置SPI模式从高位传输
SPI_InitStruct.SPI_CRCPolynomial = 7; //CRC校验位,设置SPI的CRC值就算的多项式
SPI_Init( SPI2, &SPI_InitStruct );
}
SPI读写一个字节
//SPI2读写一个字节
uint8_t SPI2_ReadWriteByte(uint8_t data)
{
u8 t;
while (SPI_I2S_GetFlagStatus( SPI2, SPI_I2S_FLAG_TXE) == RESET) //等待发送缓存器为空 0
{
t++;
if(t >= 200)
{
return 0; //超时返回错误标志
}
}
SPI_I2S_SendData( SPI2, data );
while (SPI_I2S_GetFlagStatus( SPI2, SPI_I2S_FLAG_RXNE) == RESET) //等待接收缓存器为空 0
{
t++;
if(t >= 200)
{
return 0; //超时返回错误标志
}
}
return SPI_I2S_ReceiveData(SPI2);
}
在主从设备需要达到速度限制,我们还需要进行SPI速度的设置,在控制寄存器SPI_CR1中修改BR[2:0]位,进行分频,以此来达到从设备的最大SPI速度。
图片来源:STM32F1xx中文参考手册 >>> 23.5 SPI寄存器描述>>>SPI控制寄存器SPI_CR1
//设置SPI2的速度
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//找设置SPI_BaudRatePrescaler相关的参数,.c里找
SPI2->CR1 &= 0xFFC7; //修改BR[2:0]值
SPI2->CR1 |= SPI_BaudRatePrescaler ; //设置SPI2的速度
SPI_Cmd( SPI2, ENABLE);
}