HAL库学习笔记-12 SPI

目录

  • 前言
  • 一、SPI协议简介
    • SPI物理层
    • SPI协议层
      • 1.基本通讯过程
      • 2. 通讯的起始和停止信号
      • 3. 数据有效性
      • 4. CPOL/CPHA 及通讯模式
  • 二、STM32的SPI特性及架构
    • STM32 SPI架构解析
      • 1.逻辑引脚
      • 2.时钟控制逻辑
      • 3.数据控制逻辑
      • 4.整体控制逻辑
    • STM32 SPI外设通讯过程
  • 三、STM32 HAL库中的SPI
    • 1.SPI初始化结构体
    • 2.接口函数


前言

前边的几篇笔记将STM32HAL片内主要外设的用法总结了一下,然而我们需要很多外围电路进行拓展,比如我们需要外接存储器进行文件或数据存储,需要LCD屏进行交互等待,这些外接设备需要和芯片进行通信,这些通信协议是接下来几篇的内容。


一、SPI协议简介

SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在ADC、LCD 等设备与MCU 间,要求通讯速率较高的场合。

SPI物理层

HAL库学习笔记-12 SPI_第1张图片
SPI 通讯使用3 条总线及片选线,3 条总线分别为SCK、MOSI、MISO,片选线为SS
,它们的作用介绍如下:

  1. SS( Slave Select):从设备选择信号线,常称为片选信号线,也称为NSS、CS,以下用NSS 表示。当有多个SPI 从设备与SPI 主机相连时,设备的其它信号线SCK、MOSI 及MISO 同时并联到相同的SPI 总线上,即无论有多少个从设备,都共同只使用这3 条总线;而每个从设备都有独立的这一条NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI 协议中没有设备地址,它使用NSS 信号线来寻址,当主机要选择从设备时,把该从设备的NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI 通讯。所以SPI 通讯以NSS 线置低电平为开始信号,以NSS 线被拉高作为结束信号。
  2. SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如STM32 的SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
  3. MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
  4. MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

SPI协议层

与I2C 的类似,SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环
节。

1.基本通讯过程

HAL库学习笔记-12 SPI_第2张图片
如图是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与MISO 的信号只在NSS 为低电平的时候才有效,在SCK 的每个时钟周期MOSI 和MISO 传输一位数据。

2. 通讯的起始和停止信号

图中的标号1处,NSS 信号线由高变低,是SPI 通讯的起始信号。NSS 是每个从机各自独占的信号线,当从机检在自己的NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。图中的标号6处,NSS 信号由低变高,是SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

3. 数据有效性

SPI 使用MOSI 及MISO 信号线来传输数据,使用SCK 信号线进行数据同步。MOSI及MISO 数据线在SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或LSB 先行并没有作硬性规定,但要保证两个SPI 通讯设备之间使用同样的协定,一般都会采用图中的MSB 先行模式。
观察图中的2345标号处,MOSI 及MISO 的数据在SCK 的上升沿期间变化输出,在SCK 的下降沿时被采样。即在SCK 的下降沿时刻,MOSI 及MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及MISO为下一次表示数据做准备。
SPI 每次数据传输可以8 位或16 位为单位,每次传输的单位数不受限制。

4. CPOL/CPHA 及通讯模式

上面讲述的图中的时序只是SPI 中的其中一种通讯模式,SPI 一共有四种通讯模式,它们的主要区别是总线空闲时SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性CPOL”和“时钟相位CPHA”的概念。
时钟极性CPOL 是指SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即SPI 通讯开始前、 NSS 线为高电平时SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
时钟相位CPHA 是指数据的采样的时刻,当CPHA=0 时,MOSI 或MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿”被采样。当CPHA=1 时,数据线在SCK的“偶数边沿”采样。
HAL库学习笔记-12 SPI_第3张图片
如图是CHPA为0的时序图,即在时钟线的奇数边沿采样。首先,根据SCK 在空闲状态时的电平,分为两种情况。SCK 信号线在空闲状态为低电平时,CPOL=0;空闲状态为高电平时,CPOL=1。
无论CPOL=0 还是=1,因为我们配置的时钟相位CPHA=0,在图中可以看到,采样时刻都是在SCK 的奇数边沿。注意当CPOL=0 的时候,时钟的奇数边沿是上升沿,而CPOL=1 的时候,时钟的奇数边沿是下降沿。所以SPI 的采样时刻不是由上升/下降沿决定的。MOSI 和MISO 数据线的有效信号在SCK 的奇数边沿保持不变,数据信号将在SCK 奇数边沿时被采样,在非采样时刻,MOSI 和MISO 的有效信号才发生切换。
类似地,当CPHA=1 时,不受CPOL 的影响,数据信号在SCK 的偶数边沿被采样。如下图所示。
HAL库学习笔记-12 SPI_第4张图片
由CPOL 及CPHA 的不同状态,SPI 分成了四种模式,见下表,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式0”与“模式3”。

SPI模式 CPOL CPHA 空闲时SCK时钟 采样时刻
0 0 0 低电平 奇数边沿
1 0 1 低电平 偶数边沿
2 1 0 高电平 奇数边沿
3 1 1 高电平 偶数边沿

二、STM32的SPI特性及架构

STM32 的SPI 外设可用作通讯的主机及从机,支持最高的SCK 时钟频率为fpclk/2,完全支持SPI 协议的4 种模式,数据帧长度可设置为8 位或16 位,可设置数据MSB 先行或LSB 先行。它还支持双线全双工(前面说明的都是这种模式)、双线单向以及单线模式。其中双线单向模式可以同时使用MOSI 及MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。

STM32 SPI架构解析

HAL库学习笔记-12 SPI_第5张图片

1.逻辑引脚

SPI 的所有硬件架构都从图中左侧MOSI、MISO、SCK 及NSS 线展开的。STM32 芯片有多个SPI 外设,它们的SPI 通讯信号引出到不同的GPIO 引脚上,使用时必须配置到这些指定的引脚,具体芯片的引脚复用查询对应的数据手册。

2.时钟控制逻辑

SCK 线的时钟信号,由波特率发生器根据“控制寄存器CR1‖中的BR[0:2]位控制,该位是对fpclk 时钟的分频因子,对fpclk 的分频结果就是SCK 引脚的输出时钟频率。
其中的fpclk 频率是指SPI 所在的APB 总线频率,APB1 为fpclk1,APB2 为fpckl2。通过配置“控制寄存器CR”的“CPOL 位”及“CPHA”位可以把SPI 设置成前面分析的4 种SPI 模式。

3.数据控制逻辑

SPI 的MOSI 及MISO 都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区及发送缓冲区以及MISO、MOSI 线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写SPI 的“数据寄存器DR”把数据填充到发送缓冲区中,通过 “数据寄存器DR”,可以获取接收缓冲区中的内容。其中数据帧长度可以通过“控制寄存器CR1”的“DFF 位”配置成8 位及16 位模式;配置“LSBFIRST 位”可选择MSB 先行还是LSB 先行。

4.整体控制逻辑

整体控制逻辑负责协调整个SPI 外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI 模式、波特率、LSB先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI 中断信号、DMA 请求及控制NSS 信号线。
实际应用中,我们一般不使用STM32 SPI 外设的标准NSS 信号线,而是更简单地使用
普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。

STM32 SPI外设通讯过程

STM32 使用SPI 外设通讯时,在通讯的不同阶段它会对“状态寄存器SR”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。
下图所示为STM32 作为SPI 通讯的主机端时的数据收发过程,采用模式3,空闲状态为高电平,偶数边沿触发。
HAL库学习笔记-12 SPI_第6张图片
主模式收发流程及事件说明如下:

  1. 控制NSS 信号线,产生起始信号(图中没有画出);
  2. 把要发送的数据写入到“数据寄存器DR”中,该数据会被存储到发送缓冲区;
  3. 通讯开始,SCK 时钟开始运行。MOSI 把发送缓冲区中的数据一位一位地传输出去;MISO 则把数据一位一位地存储进接收缓冲区中;
  4. 当发送完一帧数据的时候,“状态寄存器SR”中的“TXE 标志位”会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置1,表示传输完一帧,接收缓冲区非空;
  5. 等待到“TXE 标志位”为1 时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE 标志位”为1 时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。

假如我们使能了TXE 或RXNE 中断,TXE 或RXNE 置1 时会产生SPI 中断信号,进入同一个中断服务函数,到SPI 中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用DMA 方式来收发“数据寄存器DR”中的数据。

三、STM32 HAL库中的SPI

1.SPI初始化结构体

跟其它外设一样,STM32 HAL 库提供了SPI 初始化结构体及初始化函数来配置SPI 外设。初始化结构体及函数定义在库文件“ STM32F4xx_hal_spi.h ” 及“STM32F4xx_hal_spi.c”中,编程时我们可以结合这两个文件内的注释使用或参考库帮助文档。
代码如下(示例):

typedef struct
{
     
  uint32_t Mode;                /*设置SPI 的主/从机端模式*/
  uint32_t Direction;           /*设置SPI 的单双向模式*/
  uint32_t DataSize;            /*设置SPI 的数据帧长度,可选8/16 位*/
  uint32_t CLKPolarity;         /*设置时钟极性CPOL,可选高/低电平*/
  uint32_t CLKPhase;            /*设置时钟相位,可选奇/偶数边沿采样 */
  uint32_t NSS;                 /*设置NSS 引脚由SPI 硬件控制还是软件控制*/
  uint32_t BaudRatePrescaler;   /*设置时钟分频因子,fpclk/分频数=fSCK*/
  uint32_t FirstBit;            /*设置MSB/LSB 先行 */
  uint32_t TIMode;              /*指定是否启用TI 模式*/
  uint32_t CRCCalculation;      /*指定是否启用CRC 计算*/
  uint32_t CRCPolynomial;       /*设置CRC 校验的表达式*/
} SPI_InitTypeDef;

这些结构体成员说明如下,其中括号内的文字是对应参数在STM32 HAL 库中定义的
宏:

  1. Mode:本成员设置SPI 工作在主机模式(SPI_MODE_MASTER)或从机模式(SPI_MODE_SLAVE ),这两个模式的最大区别为SPI 的SCK 信号线的时序,SCK 的时序是由通讯中的主机产生的。若被配置为从机模式,STM32 的SPI 外设将接受外来的SCK信号。
  2. Direction:本成员设置SPI 的通讯方向,可设置为双线全双工(SPI_DIRECTION_2LINES),双线只接收(SPI_DIRECTION_2LINES_RXONLY),单线SPI_DIRECTION_1LINE。
  3. DataSize:本成员可以选择SPI 通讯的数据帧大小是为8 位(SPI_DATASIZE_8BIT)还是16 位(SPI_DATASIZE_16BIT)。
  4. CLKPolarity 和CLKPhase:这两个成员配置SPI 的时钟极性CLKPolarity 和时钟相位CLKPhase,这两个配置影响到SPI 的通讯模式。
    时钟极性CLKPolarity 成员,可设置为高电平(SPI_POLARITY_HIGH)或低电平(SPI_POLARITY_LOW )。
    时钟相位CPHA 则可以设置为SPI_PHASE_1EDGE (在SCK 的奇数边沿采集数据) 或SPI_PHASE_2EDGE (在SCK 的偶数边沿采集数据) 。
  5. NSS:本成员配置NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_HARD )与软件模式( SPI_NSS_SOFT ),在硬件模式中的SPI 片选信号由SPI 硬件自动产生,而软件模式则需要我们亲自把相应的GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
  6. BaudRatePrescaler:本成员设置波特率分频因子,分频后的时钟即为SPI 的SCK 信号线的时钟频率。这个成员参数可设置为fpclk 的2、4、6、8、16、32、64、128、256 分频。
  7. FirstBit:所有串行的通讯协议都会有MSB 先行(高位数据在前)还是LSB 先行(低位数据在前)的问题,而STM32 的SPI 模块可以通过这个结构体成员,对这个特性编程控制。
  8. TIMode:指定是否启用TI 模式。可选择为使能( SPI_TIMODE_ENABLE )与不是能( SPI_TIMODE_DISABLE )。
  9. CRCCalculation和SPI_CRCPolynomial:指定是否启用CRC 计算,若我们使用CRC 校验时,就使用这个成员的参数(多项式),来计算CRC 的值。

配置完这些结构体成员值,调用库函数HAL_SPI_Init 即可把结构体的配置写入到寄存器中,然后调用__HAL_SPI_ENABLE 来使能SPI 外设。
以上这些参数配置均可在CubeMX中完成。

2.接口函数

如下为轮询查找的接口函数,同样也有中断和DMA方式的接口函数。

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
                                          uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                             uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                              uint16_t Size);
HAL_StatusTypeDef HAL_SPI_DMAPause(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAResume(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAStop(SPI_HandleTypeDef *hspi);

你可能感兴趣的:(嵌入式,HAL库学习笔记,HAL)