一、问题现象与背景
在STM32项目中,常使用DMA搬运外设数据(如UART接收、ADC采样),同时开启外设中断处理事件(如UART空闲中断、ADC转换完成中断)。当两者同时访问同一外设时,可能出现:
- 数据丢失或重复处理
- 中断服务函数中调用HAL_UART_Receive_DMA等函数返回HAL_BUSY
- 系统死锁或HardFault
根本原因在于NVIC优先级配置不当导致DMA中断与外设中断的抢占关系混乱,以及HAL库内部状态机未正确同步。
二、原理讲解:优先级与抢占机制
2.1 NVIC优先级分组
Cortex-M内核使用4位优先级(STM32中通常配置为4位全部用于抢占优先级或分组)。HAL库通过HAL_NVIC_SetPriorityGrouping()设置分组。抢占优先级高的中断可以打断抢占优先级低的中断,子优先级仅在同时挂起时决定顺序。
2.2 DMA中断与外设中断的关系
以UART为例:
- USART全局中断:处理RXNE、IDLE、TC等事件。
- DMA通道中断:处理传输完成(TC)、半传输(HT)、错误(TE)。
若USART中断抢占优先级高于DMA中断,则USART中断可以打断DMA中断服务函数;反之亦然。若两者优先级相同,则无法互相抢占,但可能因HAL库状态机竞争导致异常。
2.3 典型冲突场景
- 在USART中断中调用
HAL_UART_Receive_DMA()重启接收,但此时DMA中断正在处理上一次传输完成,状态未更新,返回HAL_BUSY。 - DMA传输完成中断中调用
HAL_UART_Transmit(),而USART中断正在处理发送完成,导致状态冲突。
三、配置步骤与完整代码示例
3.1 步骤概览
- 设置NVIC优先级分组(推荐
NVIC_PRIORITYGROUP_4,4位抢占优先级)。 - 为DMA通道中断和外设中断分配不同的抢占优先级,确保关键中断可抢占。
- 在中断服务函数中仅做标志置位或简短处理,避免调用阻塞型HAL函数。
- 使用
__HAL_LOCK()/__HAL_UNLOCK()保护共享资源(HAL库内部已实现,但需注意调用时机)。
3.2 代码示例:UART空闲中断+DMA接收
/* 优先级分组:4位抢占优先级 */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* USART1中断:抢占优先级1 */
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* DMA1_Stream5中断(UART1_RX):抢占优先级2,低于USART,允许USART抢占 */
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
/* 启动DMA接收 */
uint8_t rx_buf[128];
HAL_UART_Receive_DMA(&huart1, rx_buf, sizeof(rx_buf));
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能空闲中断
/* USART1中断服务函数 */
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
// 仅置位标志,主循环处理
uart_idle_flag = 1;
}
}
/* DMA中断服务函数 */
void DMA1_Stream5_IRQHandler(void)
{
HAL_DMA_IRQHandler(huart1.hdmarx);
}
/* 主循环中处理空闲事件 */
if (uart_idle_flag) {
uart_idle_flag = 0;
HAL_UART_DMAStop(&huart1); // 先停止DMA
uint16_t len = sizeof(rx_buf) - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
// 处理数据...
HAL_UART_Receive_DMA(&huart1, rx_buf, sizeof(rx_buf)); // 重新启动
}
3.3 关键点说明
- USART抢占优先级高于DMA,确保空闲中断能及时响应,但DMA中断不会被长时间阻塞。
- 在USART中断中不调用
HAL_UART_Receive_DMA,避免与DMA中断竞争。 - 主循环中先
HAL_UART_DMAStop再重启,确保状态机复位。
四、注意事项与排查清单
-
优先级分组必须统一:整个工程只调用一次
HAL_NVIC_SetPriorityGrouping,否则优先级解析错乱。 -
避免在中断中调用HAL阻塞函数:如
HAL_UART_Transmit(带超时)会忙等,破坏实时性。 -
检查HAL库状态机:调用
HAL_UART_Receive_DMA前确认huart->RxState == HAL_UART_STATE_READY。 -
使用调试工具:通过
__HAL_DMA_GET_FLAG和__HAL_UART_GET_FLAG检查中断标志,或使用逻辑分析仪观察时序。 - DMA与中断同时操作同一外设时,建议关闭外设中断中的DMA相关操作,将重启DMA的动作放到主循环或低优先级任务中。
- 若必须在外设中断中重启DMA,可临时提升DMA中断优先级至高于外设中断,但需评估实时性影响。
五、总结
DMA与中断同时操作同一外设的优先级冲突,本质是抢占关系与状态机同步问题。合理配置NVIC优先级分组、分离中断处理与DMA重启逻辑、利用HAL库状态检查,可有效避免数据丢失和死锁。建议在项目初期就规划好中断优先级表,并在调试中验证抢占行为。
暂无评论,来抢沙发~