SPI 方法详解

SPI 方法详解

SPI(Serial Peripheral Interface)是一种高速全双工同步串行总线,常用于 Flash、显示屏、传感器等外设。SPI 需要至少四根线:SCK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)和 NSS/CS(片选)。

SPI 基本特点

  • 全双工:发送和接收可以同时进行。
  • 主机模式:STM32 通常作为主机,产生 SCK 时钟。
  • 片选控制:多数外设需要软件控制片选引脚。

片选控制

HAL 库的 SPI 函数不自动管理片选引脚,需要用户在代码中手动拉低和拉高 GPIO。

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);  // 选中从机
HAL_SPI_Transmit(&hspi1, cmd, len, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);    // 释放从机

轮询方式

HAL_SPI_Transmit

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

参数拆解

HAL_SPI_Transmit(&hspi1, writeEnableCmd, 1, HAL_MAX_DELAY);
//               │       │                │  │
//               │       │                │  └── 超时时间
//               │       │                └───── 发送字节数
//               │       └────────────────────── 发送缓冲区
//               └────────────────────────────── SPI 句柄

功能

向从机发送数据。在发送过程中,MISO 线上的数据会被忽略。

示例

uint8_t writeEnableCmd[] = {0x06};
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, writeEnableCmd, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);

HAL_SPI_Receive

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

参数拆解

HAL_SPI_Receive(&hspi1, &ledState, 1, HAL_MAX_DELAY);
//              │       │          │  │
//              │       │          │  └── 超时时间
//              │       │          └───── 接收字节数
//              │       └──────────────── 接收缓冲区
//              └──────────────────────── SPI 句柄

功能

从从机接收数据。在接收过程中,MOSI 线上会发送无效数据(通常是 0xFF)。

示例

uint8_t ledState;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_SPI_Receive(&hspi1, &ledState, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);

HAL_SPI_TransmitReceive

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

参数拆解

HAL_SPI_TransmitReceive(&hspi1, txBuf, rxBuf, 4, HAL_MAX_DELAY);
//                      │       │       │      │  │
//                      │       │       │      │  └── 超时时间
//                      │       │       │      └───── 收发字节数
//                      │       │       └──────────── 接收缓冲区
//                      │       └──────────────────── 发送缓冲区
//                      └──────────────────────────── SPI 句柄

功能

同时发送和接收数据,是 SPI 全双工特性的典型用法。发送 pTxData 的同时,把 MISO 线上收到的数据存入 pRxData

示例

读取 Flash 的 JEDEC ID:

uint8_t txBuf[4] = {0x9f, 0xff, 0xff, 0xff};  // 0x9f 是读 ID 命令
uint8_t rxBuf[4];

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, txBuf, rxBuf, 4, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);

// rxBuf[1] ~ rxBuf[3] 为厂商 ID 和设备 ID

中断方式

HAL_SPI_Transmit_IT

HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi,
                                      const uint8_t *pData,
                                      uint16_t Size);

参数拆解

HAL_SPI_Transmit_IT(&hspi1, txBuf, len);
//                  │       │      │
//                  │       │      └── 发送字节数
//                  │       └───────── 发送缓冲区
//                  └───────────────── SPI 句柄

功能

以中断方式发送数据。函数立即返回,发送完成后触发 HAL_SPI_TxCpltCallback

HAL_SPI_Receive_IT

HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi,
                                     uint8_t *pData,
                                     uint16_t Size);

参数拆解

HAL_SPI_Receive_IT(&hspi1, rxBuf, len);
//                 │       │      │
//                 │       │      └── 接收字节数
//                 │       └───────── 接收缓冲区
//                 └───────────────── SPI 句柄

功能

以中断方式接收数据。接收完成后触发 HAL_SPI_RxCpltCallback

HAL_SPI_TransmitReceive_IT

HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi,
                                             const uint8_t *pTxData,
                                             uint8_t *pRxData,
                                             uint16_t Size);

功能

以中断方式同时发送和接收数据。收发完成后触发 HAL_SPI_TxRxCpltCallback

回调函数

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);

DMA 方式

HAL_SPI_Transmit_DMA / HAL_SPI_Receive_DMA / HAL_SPI_TransmitReceive_DMA

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi,
                                       const 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,
                                              const uint8_t *pTxData,
                                              uint8_t *pRxData,
                                              uint16_t Size);

功能

以 DMA 方式进行 SPI 收发。CPU 只需启动一次,数据搬运由 DMA 完成,适合大数据量连续传输。收发完成后触发对应的回调函数。

HAL_SPI_Abort

HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef *hspi);

参数拆解

HAL_SPI_Abort(&hspi1);
//            │
//            └── SPI 句柄

功能

中止正在进行的 SPI 传输。

配置示例

hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;  // CPOL = 1
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;       // CPHA = 1
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
HAL_SPI_Init(&hspi1);

时序模式

SPI 有四种时序模式,由 CPOL(时钟极性)和 CPHA(时钟相位)组合决定:

模式CPOLCPHA说明
模式 000空闲低电平,第一个边沿采样
模式 101空闲低电平,第二个边沿采样
模式 210空闲高电平,第一个边沿采样
模式 311空闲高电平,第二个边沿采样

选择哪种模式取决于从机数据手册的要求。