16. SPI通信协议
一、SPI通信协议简介
SPI 是 Serial Peripheral interface 缩写,顾名思义就是串行外围设备接口。SPI 通信协议是 Motorola 公司首先在其 MC68HCXX 系列处理器上定义的。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 种模式。
#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 串行寄存器写入一个字节来发起一次传输。串行移位寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。如果只是进行写操作,主机只需忽略接收到的字节。反之,若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。
四、SPI常用寄存器
4.1、SPI控制寄存器
该寄存器控制着 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,采用单工模式。
4.2、SPI状态寄存器
该寄存器是查询当前 SPI 的状态的,常用位 0 RXNE 检测接收缓冲区是否为空来判断是否完成接收和 位 1 TXE 检测发送缓冲区是否为空来判断发送是否完成。
4.3、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;
Init:SPI 初始化结构体,用于配置通讯参数。
#define SPI1 ((SPI_TypeDef *) SPI1_BASE)
#define SPI2 ((SPI_TypeDef *) SPI2_BASE)
#define SPI3 ((SPI_TypeDef *) SPI3_BASE)
hdmatx,hdmarx:配置 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);
形参 hspi 是 SPI_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);
形参 hspi 是 SPI_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);
形参 hspi 是 SPI_HandleTypeDef 结构体指针类型的 SPI 句柄。形参 pTxData 是 要发送数据的缓冲区指针。形参 pRxData 是 要接收数据的缓冲区指针。形参 Size 是 要发送的数据大小,以字节为单位。形参 Timeout 设置 超时时间,以毫秒为单位。
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。