STM32CubeMX踩坑记录:DMA+空闲中断为何无法触发
STM32CubeMX踩坑记录:DMA+空闲中断为何无法触发?
在使用STM32CubeMX配置串口DMA接收结合空闲中断时,一个常见的困扰是:尽管按照教程完成了所有配置(开启串口、配置DMA、使能全局中断),但程序运行后,空闲中断的回调函数 HAL_UARTEx_RxEventCallback 始终不会被触发。
本文记录了这一问题的排查过程与根本原因,旨在为后续开发提供参考。
问题根源:CubeMX的“分内”与“分外”
STM32CubeMX是一个强大的图形化配置工具,它负责完成所有底层硬件的初始化工作,包括:
- 配置GPIO引脚复用功能。
- 使能外设时钟。
- 配置DMA通道及其基本参数。
- 配置NVIC,设置中断优先级。
然而,CubeMX 并不会启动数据接收这个“动作”本身。它只是把“舞台”搭建好了,但“演员”(DMA和空闲中断)何时上场,需要我们通过调用HAL库函数来明确指示。
当期望使用DMA+空闲中断接收不定长数据时,必须手动启动这个接收任务。这个任务一旦完成(即接收到一帧数据),硬件会停下来等待下一个指令。因此,我们需要一个“启动-处理-再启动”的循环机制。
解决方案:手动启动并循环接收DMA+IDLE
解决问题的关键在于调用 HAL_UARTEx_ReceiveToIdle_DMA() 函数。这个函数的作用是通知HAL库:请使用DMA将数据接收到指定缓冲区,并开启空闲中断检测。
这个调用需要出现在两个关键位置:
1. 在 main() 函数中进行初始启动
在外设初始化完成后,必须调用一次该函数来启动第一次的接收任务。
int main(void)
{
/* ... HAL_Init(), SystemClock_Config(), MX_xxx_Init() ... */
/* USER CODE BEGIN 2 */
// 关键步骤:手动开启DMA接收+空闲中断
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUF_SIZE);
osal_main();
/* USER CODE END 2 */
while (1)
{
/* ... */
}
}
- 作用:这是整个接收流程的“发令枪”。它告诉DMA控制器开始监视串口接收,并将数据搬运到
rx_buffer。同时,它也激活了空闲中断的监测。没有这一步,CPU将永远等待一个从未开始的接收任务。
2. 在 HAL_UARTEx_RxEventCallback() 回调中循环重启
每当一帧数据接收完毕(触发空闲中断)或DMA缓冲区满了(触发TC中断),硬件会自动停止。为了能接收下一帧数据,必须在回调函数中重新启动接收。
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
uint16_t cnt;
if (huart->Instance == USART1)
{
if(huart->RxEventType == HAL_UART_RXEVENT_IDLE)
{
// 计算实际接收到的数据长度
// DMA总传输长度 - DMA剩余未传输长度 = 已接收长度
uint16_t dma_counter = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
cnt = BUF_SIZE - dma_counter;
// 业务逻辑:将接收到的数据回发
HAL_UART_Transmit(&huart1, rx_buffer, cnt, 0xffff);
// 清理接收缓冲区,准备下一次接收
memset(rx_buffer, 0, cnt);
}
}
// 关键步骤:无论发生何种接收事件,都重新启动接收任务
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUF_SIZE);
}
__HAL_DMA_GET_COUNTER(&hdma_usart1_rx):这是一个非常有用的宏,用于获取DMA通道当前还剩下多少字节没有传输。通过(缓冲区总大小 - 剩余字节数),我们可以精确计算出本次帧接收的实际数据长度cnt,这个值通常比回调函数自带的参数Size更可靠。- 重新调用
HAL_UARTEx_ReceiveToIdle_DMA():这是保证程序能够持续接收数据的核心。处理完当前帧数据后,立即再次调用该函数,为下一帧数据的到来做好准备。
总结
这个“坑”的本质在于理解HAL库的工作模式:配置(Configuration)与运行时控制(Runtime Control)是分离的。
- STM32CubeMX 负责 配置:它设置好所有硬件寄存器,让系统具备了接收能力。
- 开发者代码 负责 运行时控制:通过调用
HAL_UARTEx_ReceiveToIdle_DMA()等函数,来实际启动、管理和循环这个接收过程。
记住这个“初始化启动 + 回调中重启”的模式,就能稳定、高效地利用DMA+空闲中断方案来处理不定长串口数据,从而最大程度地解放CPU,提升系统性能。
浙公网安备 33010602011771号