You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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动态计算。

修复方案

  1. 移除冗余死循环:删掉中断内的while((USART2->ISR & USART_ISR_RXNE)){}语句,读取RDR后RXNE标志会自动清零,无需额外等待。
  2. 中断仅做数据缓存:把DAC写入这类耗时操作移到主循环,中断只负责接收数据、更新缓冲区并设置标志位,由主循环处理后续逻辑。
  3. 优化NVIC配置:用USART2_IRQn计算NVIC寄存器索引,避免硬编码。
  4. 添加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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 14:23:15