深入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中断未使能 |



被折叠的 条评论
为什么被折叠?



