从HAL库SPI状态机机制剖析:为什么你的DMA发送会卡在BUSY状态?

深入HAL库SPI状态机:从BUSY卡顿到DMA流畅发送的实战解析

如果你在STM32项目里同时用到了SPI的阻塞方式和DMA方式,大概率会遇到一个让人头疼的问题:第一次DMA发送正常,第二次调用HAL_SPI_Transmit_DMA()时,函数直接返回HAL_BUSY,程序卡在那里一动不动。更让人困惑的是,明明DMA传输已经完成,SPI的状态却一直停留在BUSY,导致后续的阻塞式SPI读取也受到影响,整个通信链路陷入僵局。

这个问题不是某个特定芯片的缺陷,而是HAL库的SPI状态机机制与开发者使用习惯之间的微妙冲突。今天我们就从HAL库的底层实现出发,结合STM32参考手册,彻底搞懂为什么会出现这种情况,并给出三种经过实战验证的解决方案。

1. HAL库SPI状态机的设计哲学与实现机制

1.1 状态机:HAL库的“交通警察”

HAL库为每个外设维护了一个状态机,这个设计初衷是为了防止资源冲突。想象一下,如果两个任务同时操作同一个SPI外设,数据就会乱套。状态机就像交通警察,确保同一时间只有一个“车辆”(任务)使用SPI这条“道路”。

在SPI的句柄结构体SPI_HandleTypeDef中,关键的状态字段是State

typedef struct __SPI_HandleTypeDef {
  SPI_TypeDef                *Instance;      /* SPI寄存器基地址 */
  SPI_InitTypeDef            Init;           /* SPI配置参数 */
  uint8_t                    *pTxBuffPtr;    /* 发送缓冲区指针 */
  uint16_t                   TxXferSize;     /* 待发送数据大小 */
  __IO uint16_t              TxXferCount;    /* 已发送数据计数 */
  uint8_t                    *pRxBuffPtr;    /* 接收缓冲区指针 */
  uint16_t                   RxXferSize;     /* 待接收数据大小 */
  __IO uint16_t              RxXferCount;    /* 已接收数据计数 */
  void                       (*RxISR)(struct __SPI_HandleTypeDef *hspi); /* 接收中断服务函数 */
  void                       (*TxISR)(struct __SPI_HandleTypeDef *hspi); /* 发送中断服务函数 */
  DMA_HandleTypeDef          *hdmatx;        /* SPI发送DMA句柄 */
  DMA_HandleTypeDef          *hdmarx;        /* SPI接收DMA句柄 */
  HAL_LockTypeDef            Lock;           /* 锁定对象 */
  __IO HAL_SPI_StateTypeDef  State;          /* SPI状态 */
  __IO uint32_t              ErrorCode;      /* 错误代码 */
} SPI_HandleTypeDef;

这个State字段可能的值定义在hal_spi.h中:

typedef enum {
  HAL_SPI_STATE_RESET      = 0x00U,    /* 外设未初始化或已禁用 */
  HAL_SPI_STATE_READY      = 0x01U,    /* 外设已初始化并准备就绪 */
  HAL_SPI_STATE_BUSY       = 0x02U,    /* 内部处理进行中 */
  HAL_SPI_STATE_BUSY_TX    = 0x12U,    /* 发送数据中 */
  HAL_SPI_STATE_BUSY_RX    = 0x22U,    /* 接收数据中 */
  HAL_SPI_STATE_BUSY_TX_RX = 0x32U,    /* 同时收发数据中 */
  HAL_SPI_STATE_ERROR      = 0x03U     /* 错误状态 */
} HAL_SPI_StateTypeDef;

1.2 DMA发送的状态转换流程

当我们调用HAL_SPI_Transmit_DMA()时,HAL库内部发生了什么?让我们跟踪一下关键代码:

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
{
  HAL_StatusTypeDef errorcode = HAL_OK;
  
  /* 检查状态 */
  if (hspi->State == HAL_SPI_STATE_READY)
  {
    /* 锁定SPI资源 */
    if ((hspi->Lock) == HAL_UNLOCKED)
    {
      hspi->Lock = HAL_LOCKED;
      
      /* 设置状态为BUSY_TX */
      hspi->State = HAL_SPI_STATE_BUSY_TX;
      
      /* 配置DMA传输 */
      hspi->pTxBuffPtr = pData;
      hspi->TxXferSize = Size;
      hspi->TxXferCount = Size;
      
      /* 使能DMA传输 */
      SET_BIT(hspi->Instance->CR2, SPI_CR2_TXDMAEN);
      
      /* 启动DMA传输 */
      HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)pData, 
                      (uint32_t)&hspi->Instance->DR, Size);
      
      /* 解锁SPI资源 */
      hspi->Lock = HAL_UNLOCKED;
    }
    else
    {
      errorcode = HAL_BUSY;
    }
  }
  else
  {
    errorcode = HAL_BUSY;  /* 这里就是返回HAL_BUSY的地方! */
  }
  
  return errorcode;
}

关键点来了:状态从READY变为BUSY_TX后,只有在DMA传输完成中断中才会被重置为READY。这个重置操作发生在HAL_SPI_TxCpltCallback()回调函数中,但前提是SPI中断被正确使能和处理。

1.3 为什么状态会卡在BUSY?

根据STM32参考手册和HAL库的实现,状态卡在BUSY通常有以下几个原因:

可能原因 具体表现 影响范围
SPI中断未使能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值