SPI_DMA配置及异步注意事项
2026/8/9大约 2 分钟
SPI_DMA
配置
除了常规的SPI配置,还有DMA配置
void SPI2_DMA_Tx_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE(); // 使能DMA1时钟
// 1. 配置DMA通道
SPI2_DMA_Tx_Handler.Instance = DMA1_Stream4; // 使用DMA1的Stream4
SPI2_DMA_Tx_Handler.Init.Channel = DMA_CHANNEL_0; // 选择通道0(对应SPI2_TX请求)
SPI2_DMA_Tx_Handler.Init.Direction = DMA_MEMORY_TO_PERIPH; // 内存→外设
SPI2_DMA_Tx_Handler.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不变(固定为SPI2->DR)
SPI2_DMA_Tx_Handler.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增(发送数组)
SPI2_DMA_Tx_Handler.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 字节对齐
SPI2_DMA_Tx_Handler.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
SPI2_DMA_Tx_Handler.Init.Mode = DMA_NORMAL; // 普通模式(传输一次就停)
SPI2_DMA_Tx_Handler.Init.Priority = DMA_PRIORITY_LOW;
SPI2_DMA_Tx_Handler.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&SPI2_DMA_Tx_Handler);
// 2. 将DMA句柄与SPI句柄绑定(★ 核心★)
__HAL_LINKDMA(&SPI2_Handler, hdmatx, SPI2_DMA_Tx_Handler);
// 3. 配置DMA中断优先级并使能
HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);
}
void DMA1_Stream4_IRQHandler(void)
{
HAL_DMA_IRQHandler(&SPI2_DMA_Tx_Handler);
//通过绑定的 SPI2_Handler 调用 HAL_SPI_TxCpltCallback
}| 配置项 | 代码中的体现 | 理论对应 |
|---|---|---|
| 外设中断(SPI的RXNE/TXE) | 没有开启 | ✅ 符合我们的结论:CPU不参与逐字节搬运 |
| 外设DMA请求 | 在 HAL_SPI_TransmitReceive_DMA 内部会自动使能 SPI_CR2 中的 TXDMAEN/RXDMAEN | ✅ 硬件连线开关已打开 |
| DMA传输完成中断 | 使能了 DMA1_Stream4_IRQn,在 DMA1_Stream4_IRQHandler 中调用 HAL_DMA_IRQHandler | ✅ 用于通知CPU传输完成 |
| 回调函数绑定 | __HAL_LINKDMA(&SPI2_Handler, hdmatx, SPI2_DMA_Tx_Handler) | ✅ 将SPI句柄绑定到DMA句柄上,中断触发后自动调用 HAL_SPI_TxCpltCallback |
隐患
u8 SPI2_DMA_ReadWriteByte(u8 TxData)
{
u8 Rxdata; // ⚠️ 这俩货住在“栈”上(临时帐篷)
HAL_SPI_TransmitReceive_DMA(&SPI2_Handler, &TxData, &Rxdata, 1);
return Rxdata; // ❌ 这行代码执行时,DMA可能还没开始搬数据!
}- 函数执行:当你进入这个函数,CPU 在栈上给
TxData和Rxdata划了两块地(比如地址是0x2000 0100)。 - 启动 DMA:你把
0x2000 0100这个地址告诉了 DMA 控制器。然后 CPU 立刻执行return。 - 栈帧销毁(最要命的一步):
return之后,栈指针(SP)回缩,0x2000 0100这块地址立即被系统回收。马上,其他函数、中断里的局部变量,或者系统后台,就会往这块地址乱写数据。 - DMA 姗姗来迟:过了几个微秒,DMA 才慢悠悠地跑到
0x2000 0100去读数据(要发送)或写数据(要接收)。结果:要么发出的全是垃圾,要么 DMA 把收到的好数据强塞进了一个已经被系统占用的地址,直接把内存干崩溃(HardFault)或数据错乱。
修改
// 放在函数外面(全局),或者函数内部加 static
static u8 SPI2_TxData = 0x55;
static u8 SPI2_RxData = 0x00;
void SPI2_DMA_Send(void)
{
// 发起异步传输,DMA会去读写这两个固定地址
HAL_SPI_TransmitReceive_DMA(&SPI2_Handler, &SPI2_TxData, &SPI2_RxData, 1);
// 函数立刻返回,但变量地址永久有效,DMA随便玩
}
