一、为什么需要DMA+空闲中断
在嵌入式应用中,串口接收不定长数据帧非常普遍。常见方案有:
- 逐字节接收中断:每收到一个字节进一次中断,CPU开销大,高波特率下容易丢数据。
- DMA+固定长度:适合定长帧,但无法处理不定长帧。
- DMA+空闲中断:DMA自动搬运数据,总线空闲时触发一次中断,CPU仅在帧结束时处理,效率最高。
STM32的USART外设支持IDLE(空闲)中断:当接收线上连续出现一个字节时间的高电平(即总线空闲)时,硬件置位IDLE标志。结合DMA,可以在DMA搬运数据的同时,由IDLE中断通知“一帧结束”。
二、实现原理
- 初始化时启动DMA接收,将USART数据寄存器(DR)搬运到用户缓冲区,长度设为最大帧长。
- 每接收一个字节,DMA自动搬运,无需CPU干预。
- 总线空闲时,USART产生IDLE中断。
- 在IDLE中断中:
- 读取已接收字节数(通过DMA剩余计数计算)。
- 处理数据。
- 重新启动DMA接收,准备下一帧。
关键点:HAL库的HAL_UART_Receive_DMA()在正常模式下,DMA传输完成会关闭接收,而IDLE中断发生时DMA可能还未完成。因此需要手动停止DMA、计算长度、重启接收。
三、CubeMX配置步骤
- 使能USART1,模式设为Asynchronous,波特率115200。
- 在NVIC Settings中使能USART1全局中断。
- 在DMA Settings中添加USART1_RX的DMA通道,模式为Normal(非循环),数据宽度Byte。
- 生成代码。
四、完整代码示例
4.1 定义缓冲区与标志
#define RX_BUF_SIZE 256
uint8_t rx_buf[RX_BUF_SIZE];
uint8_t rx_frame[RX_BUF_SIZE];
volatile uint16_t rx_len = 0;
volatile uint8_t rx_flag = 0;
4.2 启动接收与IDLE中断使能
void UART_StartReceive(void)
{
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
}
4.3 在USART1_IRQHandler中处理IDLE
void USART1_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
HAL_UART_DMAStop(&huart1);
uint16_t remain = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
rx_len = RX_BUF_SIZE - remain;
if (rx_len > 0 && rx_len <= RX_BUF_SIZE)
{
memcpy(rx_frame, rx_buf, rx_len);
rx_flag = 1;
}
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
}
HAL_UART_IRQHandler(&huart1);
}
4.4 主循环处理
while (1)
{
if (rx_flag)
{
rx_flag = 0;
// 处理rx_frame,长度为rx_len
ProcessFrame(rx_frame, rx_len);
}
}
五、踩坑记录与注意事项
-
坑1:IDLE标志清除方式错误。必须使用
__HAL_UART_CLEAR_IDLEFLAG(),该宏通过读SR再读DR来清除标志。直接写0无效。 -
坑2:DMA未停止就读取计数器。在IDLE中断中应先
HAL_UART_DMAStop(),再读取__HAL_DMA_GET_COUNTER(),否则计数器可能仍在变化。 -
坑3:忘记重启DMA接收。处理完一帧后必须重新调用
HAL_UART_Receive_DMA(),否则后续数据无法接收。 -
坑4:HAL_UART_IRQHandler的调用顺序。如果先调用
HAL_UART_IRQHandler(),它可能清除IDLE标志或产生错误处理,建议先处理IDLE再调用HAL库中断处理函数。 - 坑5:DMA传输完成中断与IDLE冲突。当一帧长度恰好等于缓冲区大小时,DMA完成中断会触发,而IDLE可能不再触发。建议在DMA完成回调中也做处理,或使用循环模式+IDLE。
- 坑6:缓冲区覆盖。处理数据时若耗时较长,新数据可能覆盖缓冲区。建议使用双缓冲或及时拷贝。
- 注意:高波特率下建议增大缓冲区,并确保中断优先级合理,避免DMA被其他中断打断导致数据丢失。
六、总结
DMA+空闲中断是STM32串口不定长接收的高效方案,核心在于正确配置DMA、使能IDLE中断,并在中断中准确计算帧长度、及时重启接收。掌握上述配置与避坑要点,可显著提升串口通信的稳定性和CPU利用率。
暂无评论,来抢沙发~