16. SPI通信协议

一、SPI通信协议简介

  SPI 是 Serial Peripheral interface 缩写,顾名思义就是串行外围设备接口。SPI 通信协议是 Motorola 公司首先在其 MC68HCXX 系列处理器上定义的。SPI 接口是一种高速的全双工同步的通信总线。

SPI总线挂在多个设备

  • SCK(Serial Clock)时钟信号,由主设备产生。
  • MOSI(Master Out / Slave In)主设备数据输出,从设备数据输入。
  • MISO(Master In / Slave Out)主设备数据输入,从设备数据输出。
  • CS(Chip Select)从设备片选信号,由主设备产生。

SPI 总线具有三种传输方式:全双工、单工以及半双工传输方式。

二、SPI工作模式

  SPI 通信协议就具备 4 种工作模式,在讲这 4 种工作模式前,首先先知道两个单词 CPOL 和 CPHA。

  • CPOL,详称 Clock Polarity,就是 时钟极性,当主从机没有数据传输的时候 SCL 线的电平状态(即空闲状态)。假如空闲状态是高电平,CPOL=1;若空闲状态时低电平,那么 CPOL=0。
  • CPHA,详称 Clock Phase,就是 时钟相位。 同步通信时,数据的变化和采样都是在时钟边沿上进行的,每一个时钟周期都会有上升沿和下降沿两个边沿,那么数据的变化和采样就分别安排在两个不同的边沿,由于数据在产生和到它稳定是需要一定的时间,那么假如我们在第 1 个边沿信号把数据输出了,从机只能从第 2 个边沿信号去采样这个数据。

  CPHA 实质指的是数据的采样时刻,CPHA=0 的情况就表示数据的采样是从第 1 个边沿信号上即奇数边沿,具体是上升沿还是下降沿的问题,是由 CPOL 决定的。这里就存在一个问题:当开始传输第一个 bit 的时候,第 1 个时钟边沿就采集该数据了,那数据是什么时候输出来的呢?那么就有两种情况:一是 CS 使能的边沿,二是上一帧数据的最后一个时钟沿。

  CPHA=1 的情况就是表示数据采样是从第 2 个边沿即偶数边沿,它的边沿极性要注意一点,不是和上面 CPHA=0 一样的边沿情况。前面的是奇数边沿采样数据,从 SCL 空闲状态的直接跳变,空闲状态是高电平,那么它就是下降沿,反之就是上升沿。由于 CPHA=1 是偶数边沿采样,所以需要根据偶数边沿判断,假如第一个边沿即奇数边沿是下降沿,那么偶数边沿的边沿极性就是上升沿。

  由于 CPOL 和 CPHA 都有两种不同状态,所以 SPI 分成了 4 种模式。

SPI工作模式表

#define SPI_SCK_GPIO_PORT               GPIOB
#define SPI_SCK_GPIO_PIN                GPIO_PIN_3
#define RCC_SPI_SCK_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOB_CLK_ENABLE()

#define SPI_MISO_GPIO_PORT              GPIOB
#define SPI_MISO_GPIO_PIN               GPIO_PIN_4
#define RCC_SPI_MISO_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOB_CLK_ENABLE()

#define SPI_MOSI_GPIO_PORT              GPIOB
#define SPI_MOSI_GPIO_PIN               GPIO_PIN_5
#define RCC_SPI_MOSI_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOB_CLK_ENABLE()


#define SPI_SCK(x)                      do{ x ? \
                                            HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT,SPI_SCK_GPIO_PIN, GPIO_PIN_SET):\
                                            HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT,SPI_SCK_GPIO_PIN, GPIO_PIN_RESET);\
                                        }while(0)

#define SPI_MISO()                      HAL_GPIO_ReadPin(SPI_MISO_GPIO_PORT, SPI_MISO_GPIO_PIN)

#define SPI_MOSI(x)                     do{ x ? \
                                            HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_GPIO_PIN, GPIO_PIN_SET):\
                                            HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_GPIO_PIN, GPIO_PIN_RESET);\
                                        }while(0)
/**
 * @brief SPI初始化函数
 * 
 */
void SPI_Simulate_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能SPI SCK MISO MOSI对应GPIO引脚的时钟
    RCC_SPI_SCK_GPIO_CLK_ENABLE();
    RCC_SPI_MISO_GPIO_CLK_ENABLE();
    RCC_SPI_MOSI_GPIO_CLK_ENABLE();

    GPIO_InitStruct.Pin = SPI_SCK_GPIO_PIN;                                     // SPI的SCL引脚
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;                                 // 推挽输出
    GPIO_InitStruct.Pull = GPIO_NOPULL;                                         // 不使用上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;                               // 输出速度
    HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = SPI_MOSI_GPIO_PIN;                                    // SPI的MOSI引脚
    HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = SPI_MISO_GPIO_PIN;                                    // SPI的MISO引脚
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;                                     // 输入模式
    HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);


    SPI_SCK(0);                                                                 // SPI的SCK引脚默认为低电平,选择工作模式0或1
    // SPI_SCK(1);                                                                 // SPI的SCK引脚默认为高电平,选择工作模式2或3
}

【1】、工作模式 0:串行时钟的奇数边沿上升沿采样

串行时钟的奇数边沿上升沿采样时序图

  CPOL= 0 && CPHA= 0 的情形,由于配置了 CPOL= 0,可以看到当数据未发送或者发送完毕,SCK 的状态是 低电平,再者 CPHA = 0 即是 奇数边沿采集。所以传输的数据会在 奇数边沿上升沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 奇数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/**
 * @brief SPI交换一个字节函数
 * 
 * @param data 待交换的数据
 * @return uint8_t 交换后的数据
 */
uint8_t SPI_Simulate_SwapOneByte(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        // 移出数据
        SPI_MOSI(data & 0x80);
        data <<= 1;
        // SCK上升沿
        SPI_SCK(1);
        // 移入数据
        if (SPI_MISO())
        {
            data |= 0x01;
        }
        // SCK下降沿
        SPI_SCK(0);
    }

    return data;
}

【2】、工作模式 1:串行时钟的偶数边沿下降沿采样

串行时钟的偶数边沿下降沿采样图

  CPOL= 0 && CPHA= 1 的情形,由于配置了 CPOL= 0,可以看到当数据未发送或者发送完毕,SCK 的状态是 低电平,再者 CPHA = 1 即是 偶数边沿采集。所以传输的数据会在 偶数边沿下降沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 偶数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/**
 * @brief SPI交换一个字节函数
 * 
 * @param data 待交换的数据
 * @return uint8_t 交换后的数据
 */
uint8_t SPI_Simulate_SwapOneByte(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        // SCK上升沿
        SPI_SCK(1);
        // 移出数据
        SPI_MOSI(data & 0x80);
        data <<= 1;
        // SCK下降沿
        SPI_SCK(0);
        // 移入数据
        if (SPI_MISO())
        {
            data |= 0x01;
        }
    }

    return data;
}

【3】、工作模式 2:串行时钟的奇数边沿下降沿采样

串行时钟的奇数边沿下降沿采样图

  CPOL= 1 && CPHA= 0 的情形,由于配置了 CPOL= 1,可以看到当数据未发送或者发送完毕,SCK 的状态是 高电平,再者 CPHA = 0 即是 奇数边沿采集。所以传输的数据会在 奇数边沿下升沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 奇数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/**
 * @brief SPI交换一个字节函数
 * 
 * @param data 待交换的数据
 * @return uint8_t 交换后的数据
 */
uint8_t SPI_Simulate_SwapOneByte(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        // 移出数据
        SPI_MOSI(data & 0x80);
        data <<= 1;
        // SCK下降沿
        SPI_SCK(0);
        // 移入数据
        if (SPI_MISO())
        {
            data |= 0x01;
        }
        // SCK上升沿
        SPI_SCK(1);
    }

    return data;
}

【4】、工作模式 3:串行时钟的偶数边沿上升沿采样

串行时钟的偶数边沿上升沿采样图

  CPOL= 1 && CPHA= 1 的情形,由于配置了 CPOL= 1,可以看到当数据未发送或者发送完毕,SCK 的状态是 高电平,再者 CPHA = 1 即是 偶数边沿采集。所以传输的数据会在 偶数边沿上升沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 偶数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/**
 * @brief SPI交换一个字节函数
 * 
 * @param data 待交换的数据
 * @return uint8_t 交换后的数据
 */
uint8_t SPI_Simulate_SwapOneByte(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        // SCK下降沿
        SPI_SCK(0);
        // 移出数据
        SPI_MOSI(data & 0x80);
        data <<= 1;
        // SCK上升沿
        SPI_SCK(1);
        // 移入数据
        if (SPI_MISO())
        {
            data |= 0x01;
        }
    }

    return data;
}

三、硬件SPI框图

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

SPI框图

  在主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。串行移位寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。如果只是进行写操作,主机只需忽略接收到的字节。反之,若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。

四、SPI常用寄存器

4.1、SPI控制寄存器

SPI控制寄存器1

  该寄存器控制着 SPI 很多相关信息,包括主设备模式选择,传输方向,数据格式,时钟极性、时钟相位和使能等。

  在位 0 CPHA 置 0,数据采样从第一个时钟边沿开始;该位置 1,数据采样从第二个时钟边沿开始;

  在位 1 CPOL 置 0,在空闲状态时,SCK 保持低电平;该位置 1,空闲状态时,SCK 保持高电平;

  在位 2 MSTR 置 0,配置为从设备;该位置 1,配置为主设备;

  在位 [5:3] BR[2:0] 置 7,使用 256 分频,速度最低;

  在位 6 SPE 置 0,关闭 SPI 设备,该位置 1,开启 SPI 设备;

  在位 7 LSBFIRST 置 0,MSB 先传输;该位置 1,LSB 先传输。

  在位 8 SSI 置 1,禁止软件从设备,即做主机;

  在位 9 SSM 置 0,使用硬件 SPI 的片选引脚,该位置 1,使用普通 GPIO 模拟片选引脚;

  在位 11 DFF 置 0,使用 8 位数据帧格式,该位置 1,使用 16 位数据帧格式。

  在位 15 RXONLY 置 0,传输方式采用的是全双工模式,该位置 1,采用单工模式。

SPI控制寄存器2

4.2、SPI状态寄存器

SPI状态寄存器

  该寄存器是查询当前 SPI 的状态的,常用位 0 RXNE 检测接收缓冲区是否为空来判断是否完成接收和 位 1 TXE 检测发送缓冲区是否为空来判断发送是否完成。

4.3、SPI数据寄存器

SPI数据寄存器

  该寄存器是 SPI 数据寄存器,是一个双寄存器,包括了发送缓存和接收缓存。当向该寄存器写数据的时候,SPI 就会自动发送,当收到数据的时候,也是存在该寄存器内。

五、IO引脚复用功能

【1】、SPI1 引脚复用及其重映射功能

功能引脚 复用引脚
CS PA4/PA15
SCK PA5/PB3
MISO PA6/PB4
MOSI PA7/PB5

【2】、SPI2 引脚复用及其重映射功能

功能引脚 复用引脚
CS PB9/PB12
SCK PB13/PB10
MISO PB14/PC2
MOSI PB15/PC3

【3】、SPI3 引脚复用及其重映射功能

功能引脚 复用引脚
CS PA15/PA4
SCK PB3/PC10
MISO PB4/PC11
MOSI PB5/PC12

六、SPI配置步骤

6.1、使能对应的时钟

#define __HAL_RCC_SPI1_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB2ENR, RCC_APB2ENR_SPI1EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_SPI1EN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_SPI2_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB1ENR, RCC_APB1ENR_SPI2EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_SPI2EN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_SPI3_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB1ENR, RCC_APB1ENR_SPI3EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_SPI3EN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)
#define __HAL_RCC_GPIOA_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOB_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOC_CLK_ENABLE()  do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)

6.2、配置SPI工作参数

  要使用一个外设首先要对它进行初始化,SPI 的初始化函数,其声明如下:

HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);

  形参 hspi 是 SPI 的句柄,SPI_HandleTypeDef 结构体类型,其定义如下:

typedef struct __SPI_HandleTypeDef
{
    SPI_TypeDef *Instance;                              //  SPI寄存器基地址
    SPI_InitTypeDef Init;                               // SPI初始化结构体
    uint8_t *pTxBuffPtr;                                // SPI的发送数据缓冲区
    uint16_t TxXferSize;                                // SPI发送数据大小
    __IO uint16_t TxXferCount;                          // SPI发送端计数器
    uint8_t *pRxBuffPtr;                                // SPI的接收数据缓冲区
    uint16_t RxXferSize;                                // SPI接收数据大小
    __IO uint16_t RxXferCount;                          // SPI接收端计数器

    void (*RxISR)(struct __SPI_HandleTypeDef *hspi);    // SPI的接收端中断服务函数
    void (*TxISR)(struct __SPI_HandleTypeDef *hspi);    // SPI 的发送端中断服务函数

    DMA_HandleTypeDef *hdmatx;                          // SPI发送参数设置(DMA)
    DMA_HandleTypeDef *hdmarx;                          // SPI接收参数设置(DMA)
    HAL_LockTypeDef Lock;                               // SPI锁对象
    __IO HAL_SPI_StateTypeDef State;                    // SPI传输状态结构体
    __IO uint32_t ErrorCode;                            // SPI操作错误信息
} SPI_HandleTypeDef;

  InitSPI 初始化结构体,用于配置通讯参数。

#define SPI1                ((SPI_TypeDef *) SPI1_BASE)
#define SPI2                ((SPI_TypeDef *) SPI2_BASE)
#define SPI3                ((SPI_TypeDef *) SPI3_BASE)

  hdmatxhdmarx:配置 SPI 发送接收数据的 DMA 具体参数

  Lock:对资源操作增加操作 锁保护,可选 HAL_UNLOCKED 或者 HAL_LOCKED 两个参数。

  ErrorCode串口错误操作信息。主要用于存放 SPI 操作的错误信息。

  SPI_InitTypeDef 这个结构体类型,该结构体用于配置 SPI 的各个通信参数,具体说明如下:

typedef struct
{
    uint32_t Mode;                  // 设置SPI的主/从机端模式
    uint32_t Direction;             // 设置SPI的单双向模式
    uint32_t DataSize;              // 数据帧格式
    uint32_t CLKPolarity;           // 时钟极性CPOL
    uint32_t CLKPhase;              // 时钟相位CPHA
    uint32_t NSS;                   // 设置NSS引脚由SPI硬件控制还是软件控制
    uint32_t BaudRatePrescaler;     // 设置时钟分频因子
    uint32_t FirstBit;              // 起始位
    uint32_t TIMode;                // 指定是否启用TI模式
    uint32_t CRCCalculation;        // 硬件CRC是否使能
    uint32_t CRCPolynomial;         // 设置 CRC 多项式
} SPI_InitTypeDef;

  Mode:设置 设置SPI的主/从机端模式。可选值如下:

#define SPI_MODE_SLAVE                  (0x00000000U)
#define SPI_MODE_MASTER                 (SPI_CR1_MSTR | SPI_CR1_SSI)

  Direction设置SPI的单双向模式,可选值如下:

#define SPI_DIRECTION_2LINES            (0x00000000U)         // 双线全双工
#define SPI_DIRECTION_2LINES_RXONLY     SPI_CR1_RXONLY        // 双线半双工
#define SPI_DIRECTION_1LINE             SPI_CR1_BIDIMODE      // 单线

  DataSize设置 SPI 的数据帧长度,可选 8/16 位

#define SPI_DATASIZE_8BIT               (0x00000000U)
#define SPI_DATASIZE_16BIT              SPI_CR1_DFF

  CLKPolarity设置时钟极性,可选值如下:

#define SPI_POLARITY_LOW                (0x00000000U)
#define SPI_POLARITY_HIGH               SPI_CR1_CPOL

  CLKPhase设置时钟相位,可选值如下:

#define SPI_PHASE_1EDGE                 (0x00000000U)
#define SPI_PHASE_2EDGE                 SPI_CR1_CPHA

  NSS设置 NSS 引脚由 SPI 硬件控制还是软件控制,可选值如下:

#define SPI_NSS_SOFT                    SPI_CR1_SSM
#define SPI_NSS_HARD_INPUT              (0x00000000U)
#define SPI_NSS_HARD_OUTPUT             (SPI_CR2_SSOE << 16U)

  BaudRatePrescaler设置时钟分频因子,可选值如下:

#define SPI_BAUDRATEPRESCALER_2         (0x00000000U)
#define SPI_BAUDRATEPRESCALER_4         (SPI_CR1_BR_0)
#define SPI_BAUDRATEPRESCALER_8         (SPI_CR1_BR_1)
#define SPI_BAUDRATEPRESCALER_16        (SPI_CR1_BR_1 | SPI_CR1_BR_0)
#define SPI_BAUDRATEPRESCALER_32        (SPI_CR1_BR_2)
#define SPI_BAUDRATEPRESCALER_64        (SPI_CR1_BR_2 | SPI_CR1_BR_0)
#define SPI_BAUDRATEPRESCALER_128       (SPI_CR1_BR_2 | SPI_CR1_BR_1)
#define SPI_BAUDRATEPRESCALER_256       (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)

  FirstBit设置起始位是高位先行还是低位先行,可选值如下:

#define SPI_FIRSTBIT_MSB                (0x00000000U)        // 高位先行
#define SPI_FIRSTBIT_LSB                SPI_CR1_LSBFIRST     // 低位先行

  TIMode指定是否启用 TI 模式,可选值如下:

#define SPI_TIMODE_DISABLE              (0x00000000U)
#define SPI_TIMODE_ENABLE               SPI_CR2_FRF

  CRCCalculation:指定是否启用 CRC 计算。

#define SPI_CRCCALCULATION_DISABLE      (0x00000000U)
#define SPI_CRCCALCULATION_ENABLE       SPI_CR1_CRCEN

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

typedef enum 
{
    HAL_OK = 0x00U,             // 成功
    HAL_ERROR = 0x01U,          // 错误
    HAL_BUSY = 0x02U,           // 忙碌
    HAL_TIMEOUT = 0x03U         // 超时
} HAL_StatusTypeDef;

6.3、SPI底层初始化

  HAL 库中,提供 HAL_GPIO_Init() 函数用于配置 GPIO 功能模式,初始化 GPIO。该函数的声明如下:

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);

  该函数的第一个形参 GPIOx 用来 指定端口号,可选值如下:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)

  第二个参数是 GPIO_InitTypeDef 类型的结构体变量,用来 设置 GPIO 的工作模式,其定义如下:

typedef struct
{
  uint32_t Pin;         // 引脚号
  uint32_t Mode;        // 模式设置
  uint32_t Pull;        // 上下拉设置
  uint32_t Speed;       // 速度设置
  uint32_t Alternate;   // 复用功能设置
}GPIO_InitTypeDef;

  成员 Pin 表示 引脚号,范围:GPIO_PIN_0 到 GPIO_PIN_15。

#define GPIO_PIN_0                 ((uint16_t)0x0001)  /* Pin 0 selected    */
#define GPIO_PIN_1                 ((uint16_t)0x0002)  /* Pin 1 selected    */
#define GPIO_PIN_2                 ((uint16_t)0x0004)  /* Pin 2 selected    */
#define GPIO_PIN_3                 ((uint16_t)0x0008)  /* Pin 3 selected    */
#define GPIO_PIN_4                 ((uint16_t)0x0010)  /* Pin 4 selected    */
#define GPIO_PIN_5                 ((uint16_t)0x0020)  /* Pin 5 selected    */
#define GPIO_PIN_6                 ((uint16_t)0x0040)  /* Pin 6 selected    */
#define GPIO_PIN_7                 ((uint16_t)0x0080)  /* Pin 7 selected    */
#define GPIO_PIN_8                 ((uint16_t)0x0100)  /* Pin 8 selected    */
#define GPIO_PIN_9                 ((uint16_t)0x0200)  /* Pin 9 selected    */
#define GPIO_PIN_10                ((uint16_t)0x0400)  /* Pin 10 selected   */
#define GPIO_PIN_11                ((uint16_t)0x0800)  /* Pin 11 selected   */
#define GPIO_PIN_12                ((uint16_t)0x1000)  /* Pin 12 selected   */
#define GPIO_PIN_13                ((uint16_t)0x2000)  /* Pin 13 selected   */
#define GPIO_PIN_14                ((uint16_t)0x4000)  /* Pin 14 selected   */
#define GPIO_PIN_15                ((uint16_t)0x8000)  /* Pin 15 selected   */

  成员 Mode 是 GPIO 的 模式选择,有以下选择项:

#define  GPIO_MODE_AF_PP                        0x00000002U     // 推挽式复用

  成员 Pull 用于 配置上下拉电阻,有以下选择项:

#define  GPIO_NOPULL        0x00000000U     // 无上下拉
#define  GPIO_PULLUP        0x00000001U     // 上拉
#define  GPIO_PULLDOWN      0x00000002U     // 下拉

  成员 Speed 用于 配置 GPIO 的速度,有以下选择项:

#define  GPIO_SPEED_FREQ_LOW         0x00000000U    // 低速
#define  GPIO_SPEED_FREQ_MEDIUM      0x00000001U    // 中速
#define  GPIO_SPEED_FREQ_HIGH        0x00000002U    // 高速
#define  GPIO_SPEED_FREQ_VERY_HIGH   0x00000003U    // 极速

  成员 Alternate 用于 配置具体的复用功能,不同的 GPIO 口可以复用的功能不同,具体可参考数据手册。

#define GPIO_AF5_SPI1          ((uint8_t)0x05)  /* SPI1 Alternate Function mapping        */
#define GPIO_AF5_SPI2          ((uint8_t)0x05)  /* SPI2/I2S2 Alternate Function mapping   */

#define GPIO_AF6_SPI3          ((uint8_t)0x06)  /* SPI3/I2S3 Alternate Function mapping  */

6.4、SPI发送数据

  HAL 库提供了 HAL_SPI_Transmit() 函数发送数据。该函数的声明如下:

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

  形参 hspiSPI_HandleTypeDef 结构体指针类型的 SPI 句柄。形参 pData要发送数据的缓冲区指针。形参 Size要发送的数据大小,以字节为单位。形参 Timeout 设置 超时时间,以毫秒为单位。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

6.5、SPI读取数据

  HAL 库提供了 HAL_SPI_Receive() 函数接收数据。该函数的声明如下:

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

  形参 hspiSPI_HandleTypeDef 结构体指针类型的 SPI 句柄。形参 pData要接收数据的缓冲区指针。形参 Size要接受收的数据大小,以字节为单位。形参 Timeout 设置 超时时间,以毫秒为单位。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

6.6、SPI发送读取数据

  HAL 库提供了 HAL_SPI_TransmitReceive() 函数发送接收数据。该函数的声明如下:

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

  形参 hspiSPI_HandleTypeDef 结构体指针类型的 SPI 句柄。形参 pTxData要发送数据的缓冲区指针。形参 pRxData要接收数据的缓冲区指针。形参 Size要发送的数据大小,以字节为单位。形参 Timeout 设置 超时时间,以毫秒为单位。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

posted @ 2023-11-20 20:19  星光映梦  阅读(198)  评论(0)    收藏  举报