打印
[STM32F1]

SPI协议详解及CubeMX+HAL函数配置分析

[复制链接]
81|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
pentruman|  楼主 | 2024-3-19 08:00 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

SPI(Serial Peripheral interface)串行外围设备接口是同步全双工的通信总线,在芯片的管脚上只占用四根线。

1.1 物理层

  • SS/NSS/CS:从设备选择信号线(片选信号线)。由主设备控制,选择指定的从设备。

    当主机要选择从设备时,把该从设备的SS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以SS线置低电平为开始信号,以SS线被拉高作为结束信号。

  • SCK (Serial Clock):时钟信号线。用于通讯数据同步,只能由主设备产生,两个设备之间通讯时,通讯速率受限于低速设备

  • MOSI(Master Output, Slave Input):主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

  • MISO(Master Input, Slave Output):主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。


1.2 协议层

SPI总线内部结构:

结构抽象图:

在时钟信号控制下,主机将要发送的数据写到数据缓存区(Memory),缓存区经过8位移位寄存器shift register,串口移位寄存器通过MOSI信号线将数据一位一位的移到从机,从机将MISO接口收到的数据经过移位寄存器一位一位的移到数据缓存区(Memory)。同时从机也将自己移位寄存器数据通过MOSI发送给主机,两个移位寄存器数据完成交互,读写同时进行。

因此,SPI读写操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。

1.2.1 时钟极性与时钟相位
  • 时钟极性CPOL:指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、 CS片选线为高电平时SCK的状态)。
    • CPOL = 0:SCK在空闲状态时为低电平
    • CPOL = 1:SCK在空闲状态时为高电平
  • 时钟相位CPHA:指数据的采样的时刻
    • CPHA = 0:MOSI/MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样(第一个跳变沿开始)
    • CPHA = 1:MOSI/MISO数据线上的信号将会在SCK时钟线的“偶数边沿”被采样(第二个跳变沿开始)

SPI模式
时钟极性CPOL
时钟相位CPHA
空闲时SCK时钟
采样时刻

0
0
0
低电平
第一个边沿(奇)

1
0
1
低电平
第二个边沿(偶)

2
1
0
高电平
第一个边沿(奇)

3
1
1
高电平
第二个边沿(偶)

CPOLCPHA的不同状态,SPI分成了四种模式,主机与从机必须工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式0”与“模式3”。

1.2.2 通信协议

SPI相对于IIC,没有规定最大传输速率、设备地址、通信应答机制、流控制规则;只要四根线连接正确,SPI模式一致,将设备的CS片选线拉低,即可与其直接通信,且读写数据同时进行。

由图知:SCK空闲时为低电平,则CPOL = 0;第二个边沿开始采样,则CPHA = 1;即SPI模式1。

  • 1:NSS片选线由高变低,是SPI通讯的起始信号

  • 2/3/4/5:奇数边沿触发、偶数边沿采样

  • 6:NSS片选线由低变高,是SPI通讯的停止信号


1.3 软件模拟SPI通信

初始化代码:

void SPI_Init(void){    /*##-1- Enable peripherals and GPIO Clocks #########################*/  /* Enable GPIO TX/RX clock */  SPI_SCK_GPIO_CLK_ENABLE();  SPI_MISO_GPIO_CLK_ENABLE();  SPI_MOSI_GPIO_CLK_ENABLE();  SPI_NSS_GPIO_CLK_ENABLE();   /*##-2- Configure peripheral GPIO #######################*/  /* SPI SCK GPIO pin configuration  */  GPIO_InitTypeDef GPIO_InitStruct;    GPIO_InitStruct.Pin       = SPI_SCK_PIN;  GPIO_InitStruct.Mode      = GPIO_MODE_OUTPUT_PP;  GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;  HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);  HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET);        // CLK 初始化低   /* SPI MISO GPIO pin configuration  */  GPIO_InitStruct.Pin = SPI_MISO_PIN;  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;  HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);   /* SPI MOSI GPIO pin configuration  */  GPIO_InitStruct.Pin = SPI_MOSI_PIN;  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);    GPIO_InitStruct.Pin = SPI_NSS_PIN;  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  HAL_GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);  HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);                // NSS 初始化高}

SCK NSS MOSI 为输出,MISO为输入;CLK 初始化低,NSS 初始化高

模拟 SPI 通信(CPOL=0,CPHA=0)

  • 读/写单个字节
void SPI_WriteByte(uint8_t data){    uint8_t i = 0;    uint8_t temp = 0;    for(i = 0; i < 8; i++)    {        temp = ((data & 0x80) == 0x80) ? 1 : 0;        data = data << 1;        SPI_CLK(0);   // CPOL=0        SPI_MOSI(temp);        SPI_Delay();        SPI_CLK(1);   // CPHA=0 第一个边沿采样        SPI_Delay();    }    SPI_CLK(0);                // CPOL=0 SCK空闲电平为低}uint8_t SPI_ReadByte(void){    uint8_t i = 0;        uint8_t data = 0;    for(i = 0; i < 8; i++)    {        data = data << 1;        SPI_CLK(0);        SPI_Delay();        SPI_CLK(1);        SPI_Delay();        if (SPI_MISO())            data++;    }    SPI_CLK(0);    return data;}
  • 读写同时进行
uint8_t SPI_WriteReadByte(uint8_t data){    uint8_t i = 0;    uint8_t temp = 0;        uint8_t read_data = 0;    for(i = 0; i < 8; i++)    {                temp = ((data & 0x80) == 0x80) ? 1 : 0;        data = data << 1;        read_data = read_data << 1;        SPI_CLK(0);        SPI_MOSI(temp);        SPI_Delay();        SPI_CLK(1);        SPI_Delay();        if (SPI_MISO())            read_data++;    }    SPI_CLK(0);    return data;}2 STM32硬件SPI2.1 硬件SPI框架

2.2 SPI模式

2.3 SPI配置步骤
  • 设置 BR[2:0] 位以定义串行时钟波特率(主模式需要,从模式时钟频率由其主机决定)
  • 选择 CPOL 和 CPHA 位
  • 设置 DFF 位,以定义 8 或 16 位数据帧格式
  • 配置 SPI_CR1 寄存器中的 LSBFIRST 位以定义帧格式(先发MSB还是LSB)。(默认为Motorola模式,如果选择 TI 模式,则不需要此步骤)
  • NSS引脚配置
    • 主模式:如果 NSS 引脚配置成输入,在 NSS 硬件模式下,NSS 引脚在整个字节发送序列期间都连接到高电平信号;在 NSS 软件模式下,将 SPI_CR1 寄存器中的 SSMSSI位置1。如果 NSS 引脚配置成输出,只应将 SSOE 位置 1。如果选择 TI 模式,则不需要此 步骤。
    • 从模式:在硬件模式下,NSS 引脚在整个字节 发送序列期间都必须连接到低电平。在 NSS 软件模式下,将 SPI_CR1 寄存器中的 SSM 位置 1,将 SSI 位清零。如果选择 TI 模式,则不需要此步骤。
  • SPE 位置 1 使能SPI(MSTR 位清零表示从模式,置位为主模式)
2.4 事件标志

STM32F4/L0均有TI帧格式错误标志,F1则没有

2.4.1 状态标志
  • 发送缓冲区为空 (TXE)

    此标志置 1 时,表示发送缓冲区为空,可以将待发送的下一个数据加载到缓冲区中。对 SPI_DR 寄存器执行写操作时,将清零 TXE 标志。

  • 接收缓冲区非空 (RXNE)


​ 此标志置 1 时,表示接收缓冲区中存在有效的已接收数据。读取 SPI_DR 时,将清零该标志。

  • BUSY

    BSY 标志由硬件置 1 和清零,用于指示 SPI 通信的状态,BSY = 1:表明SPI正忙于通信。但有个例外:在主模式的双向接收模式下(MSTR=1、BDM=1并且BDOE=0),在接收期间BSY标志保持为低。以下情况硬件将清零该标志:

    • 传输完成时<主模式连续通信除外>
    • 关闭SPI
    • 发生主模式故障时 <MODF=1>
    • 当通信不连续时,BSY 标志在各通信之间处于低电平。
    • 当通信连续时:
      • 在主模式下,BSY 标志在所有传输期间均保持高电平
      • 在从模式下,BSY 标志在各传输之间的一个 SPI 时钟周期内变为低电平


2.4.2 错误标志

主模式故障 (MODF)

  • 错误原因:当主器件的 NSS 引脚拉低(NSS 硬件模式下)或 SSI 位为 0(NSS 软件模式下)时,会发生主模式故障,自动将 MODF 位置 1。
  • 对SPI外设的影响:
    • 如果 ERRIE 位置 1,MODF 位将置 1,并生成 SPI 中断。
    • SPE 位清零。这将关闭器件的所有输出,并关闭 SPI 接口。
    • MSTR 位清零,从而强制器件进入从模式。
  • 清除错误标志:在 MODF 位置 1 时,对 SPI_SR 寄存器执行读或写访问,然后对 SPI_CR1 寄存器执行写操作。

为避免包含多个 MCU 的系统中发生多从模式冲突,必须在 MODF 位清零序列期间将 NSS 引脚拉高。在该清零序列后,可以将 SPE 和 MSTR 位恢复到原始状态。

溢出错误(OVR)

  • 错误原因:当主器件发送完数据字节,而从器件尚未将上一个收到的数据所产生的 RXNE 位清零时,将 出现溢出情况。
  • 对SPI外设的影响:OVR 位置 1 并在 ERRIE 位置 1 时生成一个中断。此时接收器缓冲区内容不会更新,主器件后续发送的数据均丢失
  • 清除错误标志:依次读取 SPI_DR 寄存器和 SPI_SR 寄存器可将 OVR 清除。

CRC错误

  • 错误原因:移位寄存器中接收的值与 SPI_RXCRCR 的值不匹配

对SPI外设的影响及如何清除错误标志 手册未说明。

TI 模式帧格式错误

  • 错误原因:SPI从模式下,且配置为符合 TI 模式协议,则在持续通信期间出现 NSS 脉冲 时,将检测到 TI 模式帧格式错误。
  • 对SPI外设的影响:SPI_SR 寄存器中的 FRE 标志将置 1,发生错误时不会关闭 SPI,但会忽略 NSS 脉冲,并且 SPI 会等待至下一个 NSS 脉冲,然后 再开始新的传输。由于错误检测可能导致丢失两个数据字节,因此数据可能会损坏。
  • 清除错误标志:读取 SPI_SR 寄存器。
2.5 主要寄存器

2.6 CubeMX配置



模式设置

  • 全双工主机/从机模式
  • 办双工主机/从机模式
  • 只接收主机/从机模式
  • 只发送主机/从机模式

硬件NSS信号

  • 不使能
  • NSS输入信号
  • NSS输出信号

基本参数

  • Frame Format帧格式:Motorola摩托罗拉 或 TI
  • Data size数据大小:8位 或 16位
  • First Bit:MSB/LSB先行

时钟参数

  • 波特率分配因子:
  • CPOL时钟极性:low or high
  • CPHA时钟相位:1 edge or 2 edge

高级参数

  • CRC循环校验:使能/失能
  • NSS信号类型:软件
2.7 HAL库SPI函数

MX SPI 初始化参数:

void MX_SPI1_Init(void){  hspi1.Instance = SPI1;                          hspi1.Init.Mode = SPI_MODE_MASTER;                                                 //主模式  hspi1.Init.Direction = SPI_DIRECTION_2LINES;                           // 双向全双工  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;                                         // 8位数据长度          hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;                                 // CPOL=0, CLK极性为低电平  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;                                         // CPHA=0, CLK相位为第一个边沿采样  hspi1.Init.NSS = SPI_NSS_SOFT;                                                         // 软件NSS硬件          hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;        // 波特率 = fpclk / 2  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;                                        // 最高位先发送  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;                                        // 不使用TI模式  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;        // 不使用CRC校验  hspi1.Init.CRCPolynomial = 7;                                                                // CRC多项式为7  if (HAL_SPI_Init(&hspi1) != HAL_OK)  {    Error_Handler();  }}

轮询方式的SPI发送、接收、收发函数:

HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,                                          uint32_t Timeout);

中断、DMA方式也均有此类函数。



使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

10

主题

1052

帖子

1

粉丝