STM32G4 USART2中断阻塞程序问题技术求助
STM32G4 USART中断阻塞问题调试
我为STM32G4编写了USART中断代码,用于从串口终端读取输入并传递给其他函数。中断能正常触发,但程序出现阻塞,调试时发现持续卡在USART2_IRQHandler()函数中。相关代码如下:
void USART2_Init(void){ RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; GPIOA->MODER &= ~(GPIO_MODER_MODE2|GPIO_MODER_MODE3|GPIO_MODER_MODE5); GPIOA->MODER |= (0x02 << GPIO_MODER_MODE2_Pos | 0x02 << GPIO_MODER_MODE3_Pos | 0x01 << GPIO_MODER_MODE5_Pos); GPIOA->AFR[0] |= (0x07<<GPIO_AFRL_AFSEL2_Pos | 0x07 <<GPIO_AFRL_AFSEL3_Pos); GPIOA->BSRR |= GPIO_BSRR_BS5; USART2->CR1 |= USART_CR1_TXEIE; USART2->CR1 |= USART_CR1_RXNEIE; USART2->BRR = (uint16_t)(16000000/9600); USART2->CR1 |= USART_CR1_TE; USART2->CR1 |= USART_CR1_RE; USART2->CR1 |= USART_CR1_UE; NVIC->IP[USART2_IRQn] |= (0X01<<4); NVIC->ISER[38>>5] |= (1<<(38%32)); } void USART2_IRQHandler(void){ if (USART2->ISR & USART_ISR_RXNE){ rx = USART2->RDR; rx_counter++; if (rx_counter == 1){ rx_buffer |= (rx & 0x0F) << 8; GPIOA->BRR |= GPIO_BRR_BR5; } else if (rx_counter == 2){ rx_buffer |= rx & 0xFF; #ifdef AD5593R AD5593R_WriteDAC(AD5593R_CH1,rx_buffer); #endif #ifdef MCP4728 MCP4728_GeneralCall(MCP4728_RESET); MCP4728_GeneralCall(MCP4728_WAKEUP); MCP4728_SingleTransmit(CHANNEL_A,rx_buffer); #endif rx_buffer = 0; rx_counter = 0; GPIOA->BSRR |= GPIO_BSRR_BS5; } while((USART2->ISR & USART_ISR_RXNE)){} } }
问题根源
- 死循环阻塞中断:中断函数末尾的
while((USART2->ISR & USART_ISR_RXNE)){}是核心问题。读取USART2->RDR寄存器会自动清除RXNE标志,这个循环完全冗余;若因硬件干扰导致标志位异常置位,会直接引发无限循环,锁死中断。 - 中断内执行耗时操作:在中断中调用
AD5593R_WriteDAC、MCP4728_SingleTransmit这类外设通信函数,会大幅拉长中断服务时间,不仅干扰其他中断响应,还可能因外设通信超时加剧阻塞风险。 - 硬编码NVIC索引:直接用数字38计算NVIC寄存器索引,代码可移植性差,应使用
USART2_IRQn动态计算。
修复方案
- 移除冗余死循环:删掉中断内的
while((USART2->ISR & USART_ISR_RXNE)){}语句,读取RDR后RXNE标志会自动清零,无需额外等待。 - 中断仅做数据缓存:把DAC写入这类耗时操作移到主循环,中断只负责接收数据、更新缓冲区并设置标志位,由主循环处理后续逻辑。
- 优化NVIC配置:用
USART2_IRQn计算NVIC寄存器索引,避免硬编码。 - 添加volatile修饰全局变量:防止编译器优化导致中断与主循环间的数据不同步。
修改后的代码示例
// 全局变量添加volatile修饰,确保跨上下文可见性 volatile uint16_t rx_buffer = 0; volatile uint8_t rx_counter = 0; volatile uint8_t dac_update_flag = 0; void USART2_Init(void){ RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; GPIOA->MODER &= ~(GPIO_MODER_MODE2|GPIO_MODER_MODE3|GPIO_MODER_MODE5); GPIOA->MODER |= (0x02 << GPIO_MODER_MODE2_Pos | 0x02 << GPIO_MODER_MODE3_Pos | 0x01 << GPIO_MODER_MODE5_Pos); GPIOA->AFR[0] |= (0x07<<GPIO_AFRL_AFSEL2_Pos | 0x07 <<GPIO_AFRL_AFSEL3_Pos); GPIOA->BSRR |= GPIO_BSRR_BS5; // 当前逻辑不需要发送中断,关闭TXEIE // USART2->CR1 |= USART_CR1_TXEIE; USART2->CR1 |= USART_CR1_RXNEIE; // 波特率用精确值,16MHz下9600波特对应0x0683 USART2->BRR = 0x0683; USART2->CR1 |= USART_CR1_TE; USART2->CR1 |= USART_CR1_RE; USART2->CR1 |= USART_CR1_UE; // 用USART2_IRQn动态计算NVIC索引,提升可移植性 NVIC->IP[USART2_IRQn] |= (0X01<<4); NVIC->ISER[USART2_IRQn >> 5] |= (1 << (USART2_IRQn % 32)); } void USART2_IRQHandler(void){ if (USART2->ISR & USART_ISR_RXNE){ uint8_t rx = USART2->RDR; // 读取自动清除RXNE标志 rx_counter++; if (rx_counter == 1){ rx_buffer = (rx & 0x0F) << 8; // 直接赋值避免残留数据 GPIOA->BRR |= GPIO_BRR_BR5; } else if (rx_counter == 2){ rx_buffer |= rx & 0xFF; dac_update_flag = 1; // 设置标志位通知主循环处理 rx_buffer = 0; rx_counter = 0; GPIOA->BSRR |= GPIO_BSRR_BS5; } } } // 主循环中处理DAC更新 int main(void){ USART2_Init(); while(1){ if(dac_update_flag){ dac_update_flag = 0; // 清除标志 #ifdef AD5593R AD5593R_WriteDAC(AD5593R_CH1, rx_buffer); #endif #ifdef MCP4728 MCP4728_GeneralCall(MCP4728_RESET); MCP4728_GeneralCall(MCP4728_WAKEUP); MCP4728_SingleTransmit(CHANNEL_A, rx_buffer); #endif } // 其他主循环任务 } }
额外注意事项
- 确保串口波特率计算准确,直接整数除法会引入误差,建议使用手册推荐的精确BRR值。
- 若串口存在噪声干扰,可开启USART的奇偶校验或硬件流控,减少错误触发的RXNE中断。
- 中断优先级配置需结合系统整体需求,避免USART中断抢占关键系统中断。
内容的提问来源于stack exchange,提问作者th3virtuos0
相关产品推荐
相关产品推荐

