HAL_SPI_Transmit返回HAL_ERROR的SPI从机全双工通信问题
SPI从机全双工收发HAL_ERROR问题排查
问题描述
使用HAL库实现SPI从机全双工收发时,遇到以下问题:
- 从机采用中断方式,已实现
HAL_SPI_RxCpltCallback回调函数,回调中置位FlagIRQ触发发送操作 - 调用
HAL_SPI_Transmit时返回HAL_ERROR,无数据发送 - 已准备9字节接收数据、9字节发送数据;SPI模式配置为模式0(
SPI_POLARITY_LOW+SPI_PHASE_1EDGE),与主机匹配;主机SPI信号正常,引脚连接无误
现有代码
#include "main.h" SPI_HandleTypeDef hspi1; DMA_HandleTypeDef hdma_spi1_rx; DMA_HandleTypeDef hdma_spi1_tx; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_SPI1_Init(void); #define SPI_TIMEOUT 50000 uint8_t spi_tx_data[9]; uint8_t spi_rx_data[9]; int FlagIRQ = 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_SPI1_Init(); spi_tx_data[0] = 0x34; spi_tx_data[1] = 0x34; spi_tx_data[2] = 0x34; spi_tx_data[3] = 0x34; spi_tx_data[4] = 0x09; spi_tx_data[5] = 0x05; spi_tx_data[6] = 0x05; spi_tx_data[7] = 0x00; spi_tx_data[8] = 0x02; while (1) { HAL_SPI_Receive_IT(&hspi1, spi_rx_data, 9); if (FlagIRQ==1){ if (HAL_SPI_Transmit(&hspi1, spi_tx_data, 9, SPI_TIMEOUT)!= HAL_OK){ Error_Handler(); } FlagIRQ=0; } } } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi1) { HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10); // 检测SPI信号 FlagIRQ= 1; }
问题原因分析
- 从机无法主动发起SPI传输:SPI总线由主机控制时钟,从机没有时钟发起权。
HAL_SPI_Transmit是主动启动传输的API,从机调用该函数时无法生成时钟,必然返回HAL_ERROR。 - 重复注册接收中断:主循环中反复调用
HAL_SPI_Receive_IT会导致SPI外设中断状态混乱,多次注册中断可能覆盖原有配置,引发异常。 - 全双工逻辑错误:SPI全双工是收发同步进行的,从机应在主机发起的同一次传输中同步发送数据,而非单独触发发送操作。
解决方案
方案1:修正中断注册逻辑+改用中断式发送
- 仅在初始化时注册一次接收中断,后续在回调函数中重新注册,避免重复注册
- 将
HAL_SPI_Transmit替换为HAL_SPI_Transmit_IT(中断式发送,依赖主机时钟触发)
修正后代码:
#include "main.h" SPI_HandleTypeDef hspi1; DMA_HandleTypeDef hdma_spi1_rx; DMA_HandleTypeDef hdma_spi1_tx; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_SPI1_Init(void); #define SPI_TIMEOUT 50000 uint8_t spi_tx_data[9]; uint8_t spi_rx_data[9]; int FlagIRQ = 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_SPI1_Init(); spi_tx_data[0] = 0x34; spi_tx_data[1] = 0x34; spi_tx_data[2] = 0x34; spi_tx_data[3] = 0x34; spi_tx_data[4] = 0x09; spi_tx_data[5] = 0x05; spi_tx_data[6] = 0x05; spi_tx_data[7] = 0x00; spi_tx_data[8] = 0x02; // 初始化时仅注册一次接收中断 HAL_SPI_Receive_IT(&hspi1, spi_rx_data, 9); while (1) { if (FlagIRQ == 1){ // 改用中断式发送,依赖主机时钟触发 if (HAL_SPI_Transmit_IT(&hspi1, spi_tx_data, 9) != HAL_OK){ Error_Handler(); } FlagIRQ = 0; } } } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi->Instance == SPI1) { HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10); // 检测SPI信号 FlagIRQ = 1; // 回调末尾重新注册接收中断,等待下一次主机传输 HAL_SPI_Receive_IT(&hspi1, spi_rx_data, 9); } } // 可选:实现发送完成回调,处理后续逻辑 void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi->Instance == SPI1) { // 发送完成后的自定义处理 } }
方案2:使用全双工收发中断API(更推荐)
直接使用HAL_SPI_TransmitReceive_IT,在主机发起的一次传输中同步完成收发,无需分开处理收发逻辑:
#include "main.h" SPI_HandleTypeDef hspi1; DMA_HandleTypeDef hdma_spi1_rx; DMA_HandleTypeDef hdma_spi1_tx; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_SPI1_Init(void); #define SPI_TIMEOUT 50000 uint8_t spi_tx_data[9]; uint8_t spi_rx_data[9]; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_SPI1_Init(); spi_tx_data[0] = 0x34; spi_tx_data[1] = 0x34; spi_tx_data[2] = 0x34; spi_tx_data[3] = 0x34; spi_tx_data[4] = 0x09; spi_tx_data[5] = 0x05; spi_tx_data[6] = 0x05; spi_tx_data[7] = 0x00; spi_tx_data[8] = 0x02; // 注册全双工收发中断 HAL_SPI_TransmitReceive_IT(&hspi1, spi_tx_data, spi_rx_data, 9); while (1) { // 主循环可处理其他逻辑,收发完成由回调处理 } } // 全双工收发完成回调 void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi->Instance == SPI1) { HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10); // 检测SPI信号 // 处理接收的spi_rx_data,更新spi_tx_data(如果需要) // 重新注册收发中断,等待下一次主机传输 HAL_SPI_TransmitReceive_IT(&hspi1, spi_tx_data, spi_rx_data, 9); } }
内容的提问来源于stack exchange,提问作者Diego Rodrigo
相关产品推荐
相关产品推荐

