You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题原因分析

  1. 从机无法主动发起SPI传输:SPI总线由主机控制时钟,从机没有时钟发起权。HAL_SPI_Transmit是主动启动传输的API,从机调用该函数时无法生成时钟,必然返回HAL_ERROR。
  2. 重复注册接收中断:主循环中反复调用HAL_SPI_Receive_IT会导致SPI外设中断状态混乱,多次注册中断可能覆盖原有配置,引发异常。
  3. 全双工逻辑错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 00:17:02