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_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_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_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_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)

#define SPI_MISO()                      HAL_GPIO_ReadPin(SPI_MISO_GPIO_PORT, SPI_MISO_GPIO_PIN)
/**
 * @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 引脚复用及其重映射功能

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

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

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

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

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

六、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  星光映梦  阅读(67)  评论(0编辑  收藏  举报