一、为什么需要DMA+空闲中断?
在嵌入式系统中,串口接收不定长数据帧是高频需求。传统逐字节中断方式每接收一个字节就触发一次中断,高波特率下CPU频繁被打断,容易丢数据。DMA+空闲中断方案利用DMA自动搬运数据,仅在总线空闲时触发一次中断,极大降低CPU负载,同时保证数据完整性。
- DMA:无需CPU干预,自动将串口数据寄存器内容搬运到内存缓冲区。
- 空闲中断:当串口总线检测到空闲(即一个字节时间无新数据)时触发,标志一帧数据接收完成。
二、硬件与软件环境
- MCU:STM32F103C8T6(其他STM32系列类似)
- 开发环境:STM32CubeMX + HAL库 + Keil MDK
- 串口:USART1(PA9 TX,PA10 RX)
- DMA通道:DMA1 Channel5(USART1_RX)
三、CubeMX配置步骤
- 时钟配置:根据实际晶振配置系统时钟(如72MHz)。
-
USART1配置:
- 模式:Asynchronous
- 波特率:115200
- 字长:8位
- 校验:无
- 停止位:1
- 开启USART1全局中断(NVIC中使能USART1中断)
-
DMA配置:
- 添加USART1_RX的DMA请求
- 模式:Circular(循环模式)或Normal(普通模式),推荐Circular避免频繁重启
- 数据宽度:Byte(字节)
- 优先级:中或高
- 生成代码:生成初始化代码,HAL库会自动配置DMA和串口。
四、核心代码实现
4.1 定义缓冲区与变量
#define RX_BUFFER_SIZE 256
uint8_t rx_buffer[RX_BUFFER_SIZE]; // DMA接收缓冲区
volatile uint16_t rx_len = 0; // 本次接收数据长度
volatile uint8_t rx_flag = 0; // 接收完成标志
4.2 启动DMA接收与空闲中断
在main.c的初始化部分调用:
// 启动DMA接收,缓冲区大小设为RX_BUFFER_SIZE
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
// 使能空闲中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
4.3 空闲中断处理函数
在stm32f1xx_it.c中修改USART1_IRQHandler:
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
// 检查空闲中断标志
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲标志
// 停止DMA以获取当前接收长度
HAL_UART_DMAStop(&huart1);
// 计算接收长度:总大小 - DMA剩余计数
rx_len = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
rx_flag = 1; // 置位接收完成标志
// 重新启动DMA接收(循环模式可省略此步)
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
}
4.4 主循环处理数据
while (1)
{
if (rx_flag)
{
// 处理接收到的数据,rx_buffer中前rx_len字节有效
process_data(rx_buffer, rx_len);
rx_flag = 0;
}
}
五、踩坑记录与注意事项
5.1 DMA模式选择
- Circular模式:DMA自动循环填充缓冲区,无需每次重启,但需注意缓冲区覆盖问题。若数据处理不及时,新数据可能覆盖旧数据。
- Normal模式:每次接收完成后需手动重启DMA,否则无法接收下一帧。推荐在空闲中断中重启。
5.2 空闲中断标志清除
必须使用__HAL_UART_CLEAR_IDLEFLAG清除标志,否则会反复进入中断。注意:HAL库没有提供直接的空闲中断回调,需在IRQHandler中手动处理。
5.3 数据长度计算
使用__HAL_DMA_GET_COUNTER获取DMA剩余传输数量,接收长度 = 缓冲区总大小 - 剩余数量。注意:在Circular模式下,若数据超过缓冲区大小,长度计算会出错,需结合环形缓冲区管理。
5.4 中断优先级与实时性
- 空闲中断优先级不宜过低,避免数据被后续帧覆盖。
- 若使用RTOS,建议在中断中发送信号量,由任务处理数据,避免长时间占用中断。
5.5 缓冲区对齐与大小
- DMA缓冲区建议使用
__attribute__((aligned(4)))对齐,提高访问效率。 - 缓冲区大小应为2的幂次,便于环形缓冲区索引计算。
5.6 错误处理
- 使能UART错误中断(如溢出、噪声),在错误回调中重启DMA接收。
- 若DMA传输完成中断也开启,需与空闲中断协调,避免重复处理。
六、总结
DMA+空闲中断方案能高效可靠地接收不定长串口数据,关键在于正确配置DMA模式、及时清除空闲标志、准确计算数据长度。实际项目中还需考虑数据覆盖、错误恢复等细节。掌握此方案后,可轻松应对大多数串口通信场景,显著提升系统实时性。
暂无评论,来抢沙发~