STM32学习笔记(15)——SPI协议

STM32学习笔记(15)——SPI协议

    • 一、SPI协议简介
      • 1. 物理层
      • 2. 协议层
        • (1) 通讯的开始与停止
        • (2)时钟极性CPOL、时钟相位CPHA
    • 二、STM32的SPI外设
      • 1. 通讯引脚
      • 2. 时钟控制逻辑
      • 3. 数据控制逻辑
      • 4. 整体控制逻辑
      • 5. STM32的SPI通讯过程
        • (1)从主机发送数据到从机的详细过程(以 CPHA=1、CPOL=1 为例)
        • (2)从从机接收数据到主机的详细过程(以 CPHA=1、CPOL=1 为例)
    • 三、SPI的初始化结构体和库函数
      • 1. SPI的初始化结构体
      • 2. SPI的常用库函数

一、SPI协议简介

SPI(Serial Peripheral Interface)即串行外设总线接口,是由摩托罗拉公司提出的一种高速通讯协议,采用全双工、同步通信方式。其通讯协议较 I2C 简单,常常在单片机系统和一些要求通讯速率较高的场合中应用。

1. 物理层

STM32学习笔记(15)——SPI协议_第1张图片

SPI的物理层由一个主机和一个或多个从机组成。主机和每台从机之间均有 4 条线组成,其中 SCK、MISO、MOSI 这三条线是共用的,而 SS 线是每台从机与主机单独连接。下面来一一介绍这 4 条线(信号):

  • SS/NSS/CS(片选信号):每个从机都有一条单独的 SS 线与主机相连。当主机要选中某台从机进行通讯时,需要发送低电平信号给从机的 SS,从机的片选信号有效,即从机被选中,接着主机开始与从机进行 SPI 通讯。当 SS 信号为高电平时,从机片选信号为禁止,此时通讯结束。因此,SPI 通讯以 SS 为低电平信号开始,以高电平信号结束。

  • SCK/SCLK(时钟信号):主机产生时钟信号,用于通讯的同步。它决定了通讯的速率,两个设备进行通讯时,通讯速率会受限于低速设备。需要注意的是,只有主机控制时钟信号,从机的时钟完全由主机提供,当时钟信号发生跳变时,从机才会采集数据;当没有发生跳变时,从机不会采集数据。另外,SPI 协议并没有硬性规定时钟的频率范围是多少,这也是 SPI 协议的通讯速率快的原因。通讯状态下 SCK 处于互补推挽输出状态;空闲状态下处于高阻态

  • MOSI(Master Output & Slave Input,主设备输出/从设备输入):主机的数据从 MOSI 线输出后,从机从 MOSI 线读入数据,此时的通讯方向为从主机到从机。通讯状态下 MOSI 处于互补推挽输出状态;空闲状态下处于高阻态

  • MISO(Master Input & Slave Output,主设备输入/从设备输出):从机的数据从 MISO 线输出后,主机从 MISO 线读入数据,此时的通讯方向为从从机到主机。对于主机而言,MISO 总是处于高阻输入状态

2. 协议层

下图为 SPI 协议通讯图,为方便说明已在图中标出红色数字:

STM32学习笔记(15)——SPI协议_第2张图片

(1) 通讯的开始与停止

标号 1 处是NSS信号由高电平跳变为低电平,说明SPI通讯开始。当从机检测到 NSS 信号变为低电平后,就知道自己被主机选中了,开始与主机进行通讯。

标号 6 处是NSS信号由低电平跳变为高电平,说明SPI通讯结束。当从机检测到 NSS 信号变为高电平后,就知道自己的选中状态被取消了,结束与主机进行通讯。

(2)时钟极性CPOL、时钟相位CPHA

SPI 传输协议中一个很重要的问题是:传输的数据是什么时候被采集呢?是上升沿还是下降沿呢?这个与时钟极性和时钟相位有关,首先来介绍一下两者的功能。

  • 时钟极性CPOL:SCLK 在空闲状态下是一直为高电平或低电平的,通过时钟极性 CPOL 可以控制空闲时 SCLK 的极性,进而决定了起始信号与停止信号的触发方式。当 CPOL = 0 时,空闲状态下的 SCLK 处于低电平,起始信号由 SCLK 的上升沿触发,停止信号由 SCLK 的下降沿触发;当 CPOL = 1 时,空闲状态下的 SCLK 处于高电平,起始信号由 SCLK 的下降沿触发,停止信号由 SCLK 的上升沿触发。

  • 时钟相位CPHA:数据的采集可以在 SCLK 的上升沿触发,也可以在下降沿触发,通过时钟相位 CPHA 可以控制 MISO 和 MOSI 数据采样的触发方式。当 CPHA = 0 时,数据采样发生在 SCLK 的奇数边沿,偶数边沿则切换到下一位数据;当 CPHA = 1 时,数据采样发生在 SCLK的偶数边沿,奇数边沿则切换到下一位数据。

按照这种方式,CPHA 和 CPOL 共同决定了数据采样时的触发边沿。下面我们来看看两者是如何影响数据采样的触发方式的:

STM32学习笔记(15)——SPI协议_第3张图片

如上图所示,当 CPHA = 0,CPOL = 0 时,数据采样发生在上升沿,切换数据位则发生在下降沿;当 CPHA = 0,CPOL = 1 时,数据采样发生在下降沿,切换数据位则发生在上升沿。空闲状态时 SCLK 始终为低电平。

STM32学习笔记(15)——SPI协议_第4张图片

如上图所示,当 CPHA = 1,CPOL = 0 时,数据采样发生在下降沿,切换数据位则发生在上升沿;当 CPHA = 1,CPOL = 1 时,数据采样发生在上升沿,切换数据位则发生在下降沿。空闲状态时 SCLK 始终为高电平。

因此,SPI 实际上被分为了四种不同的工作状态,我们以表格形式总结一下:

SPI模式 CPHA(时钟相位) CPOL(时钟极性) 数据采样发生在SCLK 空闲时的SCLK
0 0 0 上升沿 低电平
1 0 1 下降沿 高电平
2 1 0 上升沿 低电平
3 1 1 下降沿 高电平

通常情况下,模式 0 和模式 3 用的较多。只有主机和从机采用相同的模式才能够正常通讯。

二、STM32的SPI外设

STM32 有多个 SPI 外设,STM32F10x 系列就拥有 3 个 SPI。SPI 的功能框图如下图所示:

STM32学习笔记(15)——SPI协议_第5张图片

1. 通讯引脚

SPI 一共有 4 个引脚,不同 SPI 对应的引脚也不同:

引脚 SPI1 SPI2 SPI3
NSS PA4 PB12 PA15下载口的TDI
CLK PA5 PB13 PB3下载口的TDO
MISO PA6 PB14 PB4下载口的NTRST
MOSI PA7 PB15 PB5

实际应用中,一般不使用 SPI 外设的标准 NSS 信号线,而是更简单地使用普通的 GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。

注意 SPI3 中有些引脚与 SWD 下载引脚重合了,这对我们使用和学习有影响,因此在做 SPI 实验时应避免使用 SPI3。STM32中文参考手册也对此给出了提示:

警告:由于SPI3/I2S3的部分引脚与JTAG引脚共享(SPI3_NSS/I2S3_WS与JTDI,SPI3_SCK/I2S3_CK与JTDO),因此这些引脚不受IO 控制器控制,他们(在每次复位后)被默认保留为被默认保留为JTAG用途。如果用户想把引脚配置给SPI3/I2S3必须(在调试时)关闭JTAG并切换至SWD接口,或者(在标准应用时)同时关闭JTAG和SWD接口。

2. 时钟控制逻辑

之前说过,时钟信号由 SCK 提供,而 SCK 又是由波特率发生器提供,它是由控制寄存器SPI_CR1BR[2:0] 控制。可以得知:波特率与PCLK(即APB)的频率有关。

STM32学习笔记(15)——SPI协议_第6张图片

那么如何确定 PCLK 的值呢?我们可以查看总线架构图:

STM32学习笔记(15)——SPI协议_第7张图片

如图所示,SPI1 挂载到 APB1(即PCLK1),SPI2、SPI3挂载到 APB2(即PCLK2)。对于 SPI1,因为 APB1 最高为 36MHz,所以 SPI1 时钟最高为36MHz / 2 = 18MHz;对于SPI2、SPI3,因为 APB2 最高为 72MHz,所以两者时钟最高为72MHz / 2 = 36MHz

3. 数据控制逻辑

SPI 的 MOSI 及 MISO 都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

数据寄存器SPI_DR 对应两个缓冲区:一个用于写(发送缓冲);另外一个用于读(接受缓冲)。写操作将数据写到发送缓冲区;读操作将返回接收缓冲区里的数据。

我们可以通过写数据寄存器SPI_DR 把数据填充到发送缓冲区中;通过读数据寄存器SPI_DR,可以获取接收缓冲区中的内容。

数据帧长度可以通过控制寄存器SPI_CR1DFF配置成8位及16位模式;而 LSBFIRST可配置移位寄存器的发送方向:选择 MSB(高位数据)发送还是 LSB(低位数据)先发送。

4. 整体控制逻辑

控制逻辑根据外设的工作状态修改状态寄存器SPI_SR,只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制 NSS 信号线。

5. STM32的SPI通讯过程

当 STM32 作为主机时,与从机的通讯过程如下图:

STM32学习笔记(15)——SPI协议_第8张图片

首先需要知道一个大概的过程:主机输出的数据都要通过缓冲区(对应数据寄存器SPI_DR)才能进入移位寄存器,最后再发送;主机读入的数据都要通过移位寄存器,再移入缓冲区。因此,缓冲区和移位寄存器中的数据在同一时刻是不一样的,它们正好相差了一次通信时间

(1)从主机发送数据到从机的详细过程(以 CPHA=1、CPOL=1 为例)

STM32学习笔记(15)——SPI协议_第9张图片

  • 往发送缓冲区(即数据寄存器SPI_DR)写入一个数据(比如图中的 0XF1),此时触发 SCK 信号进入忙碌状态。此时状态寄存器SPI_SRBSY(忙标志,Busy flag)位为 1,表示 SPI 开始通信;TXE(发送缓冲为空,Transmit buffer empty)位为 0,表示发送缓冲非空
  • 由于移位寄存器还没有数据,因此发送缓冲区很快将数据 0xF1 转移到移位寄存器中。此时TXE位为 1,表示发送缓冲为空。移位寄存器将数据一位一位传到 MOSI 线。
  • 等待至TXE位为 1后,软件往发送缓冲区写入第二个数据(比如 0xF2),TXE位变为 0,表示发送缓冲非空。由于移位寄存器还没将前一个数据 0xF1 全部发送出去,因此新数据 0xF2 暂时存到缓冲区。
  • 前一个数据发送完毕后,新数据 0xF2 转移到移位寄存器中,此时TXE位为 1,表示发送缓冲为空。移位寄存器将数据一位一位传到 MOSI 线。
  • 如此往复:等待TXE位由0变为 1,软件往发送缓冲区写入新数据,前一个数据发送完,新数据就往移位寄存器补,并将其一位一位发出去。发送出去的数据总比新写入缓冲区的数据少一次发送周期。

(2)从从机接收数据到主机的详细过程(以 CPHA=1、CPOL=1 为例)

STM32学习笔记(15)——SPI协议_第10张图片

  • 必须先使 SPI 启动(即让 SCK 处于忙碌状态),才能接收数据。因此,需要先往发送缓冲随意写入一个数据,以触发 SCK 信号进入忙碌状态。写入数据后,状态寄存器SPI_SRBSY位为 1,表示 SPI 开始通信;RXNE(接收缓冲非空,Receive buffer not empty)位为 0,表示接收缓冲为空
  • 移位寄存器一位位接收到从 MISO 来的数据 0xA1,将数据转移到接收缓冲区中,然后进行下一次数据接收;此时RXNE位为 1,表示接收缓冲非空,表明软件可以开始读取数据 0xA1。
  • 待数据读取完后,RXNE位为 0,表示接收缓冲为空。此时移位寄存器将新数据 0xA2 转移到接收缓冲区中,RXNE位为 1,表示接收缓冲非空,表明软件可以开始读取数据 0xA2
  • 如此往复:移位寄存器一位位接收数据,然后将数据转移到接收缓冲区,然后继续接收下一次数据;软件等待RXNE位由 0 变为 1 后,从接收缓冲区读取数据。接收到的数据总比新读取缓冲区的数据多一个发送周期。

三、SPI的初始化结构体和库函数

1. SPI的初始化结构体

以下为 SPI 的结构体定义:

typedef struct
{
  uint16_t SPI_Direction;           /*!< 配置 SPI 的单/双向模式 */

  uint16_t SPI_Mode;                /*!< 配置 SPI 的主/从机模式 */

  uint16_t SPI_DataSize;            /*!< 配置 SPI 的数据帧长度(8/16位) */

  uint16_t SPI_CPOL;                /*!< 配置 SPI 的时钟极性(高/低电平) */

  uint16_t SPI_CPHA;                /*!< 配置 SPI 的时钟相位(奇/偶边沿采样) */

  uint16_t SPI_NSS;                 /*!< 配置 SPI 的 NSS 引脚由软件控制还是硬件控制 */
 
  uint16_t SPI_BaudRatePrescaler;   /*!< 配置 SPI 的时钟分频因子 */

  uint16_t SPI_FirstBit;            /*!< 配置数据是高位先行(MSB)或低位先行(LSB) */
  
  uint16_t SPI_CRCPolynomial;       /*!< 配置 CRC 校验表达式 */
}SPI_InitTypeDef;
  • SPI_Direction(单/双向模式):可配置为双线全双工、双线只接收、单线只接收、单线只发送模式。
  • SPI_Mode(主/从机模式):若配置为主机模式,则 STM32 将提供 SCLK 时序信号;若配置为从机模式,则 STM32 将接收外来的 SCLK 信号。
  • SPI_DataSize(数据帧长度):可配置 8 位数据帧或 16 位数据帧。
  • SPI_CPOL(时钟极性)和SPI_CPHA(时钟相位):可参考 SPI 相关头文件和本文章的相关内容,不再赘述。
  • SPI_NSS(NSS引脚使用模式):若选择硬件模式,则 SPI 片选信号由 SPI 硬件自动产生;若软件模式,则需要手动把相应的 GPIO 端口置 1 或置 0 以产生禁止片选或允许片选信号。
  • SPI_BaudRatePrescaler(时钟分频因子):可设置为 PCLK 的 2、4、6、8、16、32、64、128、256分频。
  • SPI_FirstBit(数据先行模式):不再赘述。
  • SPI_CRCPolynomial(CRC校验):这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。

2. SPI的常用库函数

// 与初始化相关:
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);

// 与接收、发送数据相关:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

// 与标志位相关:
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);

未完待续···

你可能感兴趣的:(#,STM32/STM8,学习笔记,单片机,stm32,嵌入式)