STM32串口中断只能收一个字节?这3个坑我帮你踩过了

第一次在项目中使用STM32的串口中断接收4G模块数据时,我遇到了一个诡异的问题:发送单个字节完全正常,但只要发送两个字节以上,要么只能收到第一个字节,要么整个程序直接卡死。作为嵌入式开发者,这种问题简直让人抓狂——硬件连接没问题,代码逻辑看起来也没毛病,但就是无法正常工作。

经过整整两天的调试和验证,我终于找到了问题的根源。原来,STM32的串口中断接收有3个常见的"坑",而我的代码不幸踩中了其中两个。下面我就把这些经验分享给大家,希望能帮你节省宝贵的调试时间。

1. 问题现象与初步分析

当时我的项目需要使用EC20 4G模块通过串口向STM32发送数据。测试时发现:

  • 发送单个字节(如'A'):100%正常接收
  • 发送两个字节(如"AB"):有时只能收到'A',有时程序直接卡死
  • 发送更长的数据:基本只能收到第一个字节

使用逻辑分析仪抓取波形确认:发送端确实发出了完整的数据,问题出在接收端。

可能的怀疑点:

  1. 中断标志位未清除
  2. 中断服务程序执行时间过长
  3. 中断优先级配置不当

2. 第一个坑:忘记清除中断标志

检查最初的串口中断服务函数:

void UART4_IRQHandler(void) {
    if (USART_GetITStatus(UART4, USART_IT_RXNE) == SET) {
        uint8_t RxData = USART_ReceiveData(UART4);
        // 忘记清除中断标志!
        UART4_REV_DATA_BUFF[pRxPacket++] = RxData;
    }
}

问题分析

  • 接收中断标志位USART_IT_RXNE在数据接收后由硬件置位
  • 必须在中断服务程序中手动清除该标志
  • 未清除标志会导致中断持续触发,最终卡死程序

解决方案

void UART4_IRQHandler(void) {
    if (USART_GetITStatus(UART4, USART_IT_RXNE) == SET) {
        uint8_t RxData = USART_ReceiveData(UART4);
        USART_ClearITPendingBit(UART4, USART_IT_RXNE); // 关键修复
        UART4_REV_DATA_BUFF[pRxPacket++] = RxData;
    }
}

修复后测试:

  • 程序不再卡死
  • 但长数据仍然只能收到前几个字节

3. 第二个坑:中断服务程序耗时过长

进一步检查中断服务函数,发现其中包含printf语句:

void UART4_IRQHandler(void) {
    if (USART_GetITStatus(UART4, USART_IT_RXNE) == SET) {
        uint8_t RxData = USART_ReceiveData(UART4);
        USART_ClearITPendingBit(UART4, USART_IT_RXNE);
        
        printf("Received: %c\r\n", RxData); // 耗时的调试输出
        
        UART4_REV_DATA_BUFF[pRxPacket++] = RxData;
    }
}

问题分析

  • printf通过串口输出数据,本身是一个耗时操作
  • 在中断服务程序中执行耗时操作会导致:
    • 错过后续字节的中断
    • 可能引发中断嵌套问题
  • 特别是当波特率较高时(如115200),问题更易出现

解决方案

  1. 移除中断服务程序中的所有调试输出
  2. 改为使用标志位+主循环处理的方式:
volatile uint8_t uart4_rx_flag = 0;
volatile uint8_t uart4_rx_data;

void UART4_IRQHandler(void) {
    if (USART_GetITStatus(UART4, USART_IT_RXNE) == SET) {
        uart4_rx_data = USART_ReceiveData(UART4);
        USART_ClearITPendingBit(UART4, USART_IT_RXNE);
        uart4_rx_flag = 1; // 设置标志位
    }
}

// 在主循环中处理
while(1) {
    if(uart4_rx_flag) {
        printf("Received: %c\r\n", uart4_rx_data);
        uart4_rx_flag = 0;
    }
}

修复后测试:

  • 短数据接收正常
  • 但高波特率下长数据仍有丢失

4. 第三个坑:中断优先级配置不当

检查NVIC中断优先级配置:

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

问题分析

  • 当系统中有多个中断源时,低优先级中断可能被高优先级中断打断
  • 串口接收是实时性要求很高的操作,如果被其他中断打断,会导致数据丢失
  • 特别是当接收缓冲区较小时,问题更明显

解决方案

  1. 提高串口接收中断的优先级
  2. 确保没有更高优先级的中断会打断串口接收
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 最高抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

5. 完整解决方案与最佳实践

结合以上分析,给出完整的解决方案:

  1. 中断服务程序模板
#define RX_BUF_SIZE 256
volatile uint8_t rx_buffer[RX_BUF_SIZE];
volatile uint16_t rx_index = 0;

void USART1_IRQHandler(void) {
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        // 1. 读取数据
        uint8_t data = USART_ReceiveData(USART1);
        
        // 2. 立即清除中断标志
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        
        // 3. 简单处理数据(避免耗时操作)
        if(rx_index < RX_BUF_SIZE) {
            rx_buffer[rx_index++] = data;
        }
    }
}
  1. 初始化配置要点
void USART_Config(void) {
    // 1. 使能USART和GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 2. 配置GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;  // TX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 3. 配置USART参数
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    // 4. 配置中断优先级(设置为最高)
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 5. 使能接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    // 6. 使能USART
    USART_Cmd(USART1, ENABLE);
}
  1. 避坑检查清单
    • [ ] 确认已清除接收中断标志(USART_IT_RXNE)
    • [ ] 中断服务程序中无耗时操作(如printf、延时等)
    • [ ] 串口接收中断优先级设置为最高
    • [ ] 接收缓冲区足够大,避免溢出
    • [ ] 主循环及时处理接收到的数据

6. 高级技巧与性能优化

对于需要处理大量数据的应用,可以考虑以下优化方案:

  1. DMA+串口中断组合方案
// DMA配置示例
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = RX_BUF_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);

// 使能DMA
DMA_Cmd(DMA1_Channel5, ENABLE);
// 使能USART的DMA接收
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);

// 配合使用空闲中断检测帧结束
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
  1. 双缓冲技术
#define BUF_SIZE 256
uint8_t rx_buf1[BUF_SIZE], rx_buf2[BUF_SIZE];
uint8_t *active_buf = rx_buf1;
uint16_t active_index = 0;

void USART1_IRQHandler(void) {
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        uint8_t data = USART_ReceiveData(USART1);
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        
        if(active_index < BUF_SIZE) {
            active_buf[active_index++] = data;
        } else {
            // 缓冲区满,切换到备用缓冲区
            uint8_t *temp = (active_buf == rx_buf1) ? rx_buf2 : rx_buf1;
            active_buf = temp;
            active_index = 0;
            active_buf[active_index++] = data;
        }
    }
}
  1. 波特率自适应调整 : 对于不稳定的通信环境,可以实现波特率自动检测:
void AutoBaudRateDetection(void) {
    // 1. 配置定时器捕获上升沿
    // 2. 测量起始位持续时间
    // 3. 计算实际波特率
    // 4. 重新配置USART波特率
}

在实际项目中,根据数据量大小和实时性要求选择合适的方案。对于简单的调试信息传输,基本的中断接收方案足够;对于高速大数据量传输,DMA方案更为适合。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐