【STM32】SPI

虽有尽善尽美之心,奈何学识有限,水平一般,不可一蹴而就。故而留待以后不断打磨完善。


一、SPI是什么?

是一种支持主机多从机的全双工 、同步、串行、高速通讯方式。多用于芯片间通讯。

二、发展历史

由美国摩托罗拉公司于20世纪80年代中期开发。

SPI全称 Serial Peripheral Interface Bus,意思为串行外设接口。

三、物理层

在这里插入图片描述
1、总线上可以挂载一个主机和多个从机。
2、4条总线

SCLK(Serial Clock):时钟,由主机发出
MOSI(Master Output, Slave Input):主机输出从机输入信号(数据由主机发出)
MISO(Master Input, Slave Output):主机输入从机输出信号(数据由从机发出)
SS(Slave Select):片选信号,由主机发出,一般是低电位有效

3、没有设备地址,使用SS片选线来寻址,哪个设备的SS引脚被拉低即代表哪个设备被选中。
4、SPI以片选线置0开始通讯,以片选线置1结束通讯。
5、官方没有规定SPI的通讯速率上限,一般可以轻松达到Mb级别,已知的有的器件SPI已达到50Mbps。

注意MB与Mb的区别:一般数据机及网络通讯的传输速率都是以“bps”为单位。大写 B代表 Byte,小写 b 代表 bit。

虽然没有速度上限,但会受到以下几个因素制约:
      (1)SPI的最大时钟频率,如STM32的SPI外设时钟频率最大为系统时钟的一半。
      (2)CPU处理SPI数据的能力
      (3)输出端驱动能力(PCB所允许的最大信号传输速率)

四、协议层

1、基本通讯过程

在这里插入图片描述

2、起始与停止信号

NSS线拉低,表示开始通讯;NSS线拉高,表示结束通讯

3、数据有效性

MOSI与MISO 在SCK的每个时钟周期内都传输一位数据,且数据输入输出是同时进行的。

数据在SCK上升沿期间变化,在下降沿期间采样,即在下降沿期间数据有效。

4、通讯模式

上述通讯方式只是SPI通讯中的一种。
两个概念:
时钟极性CPOL:为0时,SCK在空闲状态下为低电平,为1时则相反。
时钟相位CPHA:为0时,数据线上的信号在SCK的奇数边沿被采样;为1时则在偶数边沿
在这里插入图片描述
在这里插入图片描述
由此SPI的通讯模式可以划分为四种。在这里插入图片描述

五、STM32的SPI外设架构

在这里插入图片描述

1、通信引脚

2、时钟控制逻辑

由CR1寄存器的BR位控制。

3、数据控制逻辑

在这里插入图片描述
发送:将要发送的数据写入DR,外设会自动将DR中的内容填充进入发送缓冲区,通过移位寄存器将数据一位一位通过MOSI引脚发送出去。
接收:通过MISO引脚将采样到的数据发给移位寄存器,移位寄存器将数据一位一位存入接收缓冲区,通过读取DR可以获取接收缓冲区的内容。

4、整体控制逻辑

在这里插入图片描述
CPHA:时钟相位
CPOL:时钟极性
MSTR:主设备选择,为1时为主设备
BR[2:0]:波特率控制
在这里插入图片描述
SPE:SPI使能
LSBFIRST:帧格式,为0时高位先行
SSI:内部从设备选择,决定NSS的电平(只有在SSM位为’1’时有意义)
SSM:软件从设备管理,为1时启用软件从设备管理。
RXONLY:只接收。0:全双工(发送和接收); 1:只接收模式。
DFF:数据帧格式。0:使用8位数据帧格式进行发送/接收;1:使用16位数据帧格式进行发送/接收。
BIDIOE:0:只收模式; 1:只发模式。
BIDIMODE:双向数据模式使能。 0:选择“双线双向”模式;1:选择“单线双向”模式。
在这里插入图片描述
RXDMAEN:接收缓冲区DMA使能
TXDMAEN:发送缓冲区DMA使能
RXNEIE:接收缓冲区非空中断使能
TXEIE:发送缓冲区空中断使能
在这里插入图片描述
RXNE:接收缓冲非空
TXE:发送缓冲为空
BSY:忙标志

六、程序编写

配置SPI引脚为推挽输出模式

  /* 配置SPI的 CS引脚,普通IO即可 */
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);
	
  /* 配置SPI的 SCK引脚*/
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);

  /* 配置SPI的 MISO引脚*/
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
  GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);

  /* 配置SPI的 MOSI引脚*/
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
  GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);

配置SPI外设参数

 /* SPI 模式配置 */
  // FLASH芯片 支持SPI模式0及模式3,据此设置CPOL CPHA
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线全双工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主设备模式
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//数据帧大小为8位(还可以选16位)
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//时钟极性
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位为偶数边沿采样
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS引脚使用软件模式
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;//波特率分频因子
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行
  SPI_InitStructure.SPI_CRCPolynomial = 7;//默认就是7
  SPI_Init(FLASH_SPIx , &SPI_InitStructure);

配置完就可以使用库函数了。

void SPI_I2S_DeInit(SPI_TypeDef* SPIx);//恢复默认设置
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);//初始化
void I2S_Init(SPI_TypeDef* SPIx, I2S_InitTypeDef* I2S_InitStruct);
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);//向初始化结构体中装载默认值
void I2S_StructInit(I2S_InitTypeDef* I2S_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);//使能SPI
void I2S_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);//使能中断
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);//DMA使能
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);//发送数据
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);//接收数据
void SPI_NSSInternalSoftwareConfig(SPI_TypeDef* SPIx, uint16_t SPI_NSSInternalSoft);//硬件设置NSS电平
void SPI_SSOutputCmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);//数据帧尺寸
void SPI_TransmitCRC(SPI_TypeDef* SPIx);
void SPI_CalculateCRC(SPI_TypeDef* SPIx, FunctionalState NewState);
uint16_t SPI_GetCRC(SPI_TypeDef* SPIx, uint8_t SPI_CRC);
uint16_t SPI_GetCRCPolynomial(SPI_TypeDef* SPIx);
void SPI_BiDirectionalLineConfig(SPI_TypeDef* SPIx, uint16_t SPI_Direction);//传输方向
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);//获取标志位
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);//清除标志位
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);//中断标志位
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

六、答疑

1、SPI总线优缺点

优点
(1)SPI协议支持全双工通信。
(2)与开漏输出相反,SPI的推挽输出可提供良好的信号完整性和高速度
(3)速度快。有比I²C或SMBus更高的传输带宽 , 不限于任何最大时钟频率,可实现高速运行
(4)不限于8位字,任意选择消息大小,内容和目的地
(5)没有仲裁或相关的失败模式
(6)从站不需要唯一的地址 - 不像I²C

缺点
(1)只支持一个主设备
(2)没有错误检测机制,不像I²C在每个字节后有回复信号
(3)通讯距离短,与RS-232 , RS-485或CAN总线相比,它只能处理短距离内的数据传输。(距离可以通过使用收发器如RS-422进行扩展)

2、SPI一次可以传输几位数据?

STM32中数据帧可以选择8位或16位。

3、SPI数据引脚为什么配置为推挽模式?

像I2C那样设计之初就是为了做多主多从通讯,里面就涉及到总线仲裁,即多个主机同时发送数据怎么办,这就必须把总线配置为开漏输出,一是怕短路,二是为了配合上拉电阻实现线与。具体可以看【STM32】I2C

而SPI设计之初就是为了做单主多从通讯,不存在需要仲裁的情况,也就不需要专门配置为开漏输出模式,因此推挽输出是更好的选择,它速度更快、驱动能力更强。

参考资料

1、https://zhuanlan.zhihu.com/p/352975221
2、https://blog.csdn.net/wordwarwordwar/article/details/79776140
3、《STM32库开发实战指南》
4、https://blog.csdn.net/qq_25814297/article/details/100772970

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值