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

USART2 DMA循环模式收任意长度数据转USART1 DMA发送丢字节问题

USART2 DMA循环接收转USART1 DMA发送丢字节问题

需求

通过USART2的DMA循环模式接收任意长度的数据,再通过USART1的DMA普通模式将接收的数据原样发送出去。

问题现象

运行过程中出现概率性丢字节:即便USART2已经完整接收了数据,USART1在发送时仍会丢失部分字节。

问题代码

volatile uint8_t RxMsg_USART2_temp[1];
volatile uint8_t RxMsg_USART2[120];
HAL_UART_Receive_DMA(&huart2, RxMsg_USART2_temp, sizeof(RxMsg_USART2_temp));

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART2){
        if( RxMsg_USART2_temp[0] != '\n' ){
            RxMsg_USART2[idx] = RxMsg_USART2_temp[0];
            idx++;
        }else{
            RxMsg_USART2[idx] = RxMsg_USART2_temp[0];
            HAL_UART_Transmit_DMA(&huart1, RxMsg_USART2, idx+1);
        }
    }
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1){
        memset(RxMsg_USART2_temp, 0, sizeof(RxMsg_USART2_temp));
        memset(RxMsg_USART2, 0, sizeof(RxMsg_USART2));
        idx = 0;
    }
}

测试对比现象

当使用USART2 DMA循环模式接收固定长度数据时,USART1 DMA普通模式转发数据完全正常,对应代码如下:

HAL_UART_Receive_DMA(&huart2, RxMsg_USART2, sizeof(RxMsg_USART2));

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART2){
        HAL_UART_Transmit_DMA(&huart1, RxMsg_USART2, sizeof(RxMsg_USART2));
    }
}

问题原因与解决方法

核心问题

  1. DMA接收未重启:问题代码中,USART2的DMA单次接收1字节完成后,没有重新启动DMA接收,后续字节无法被持续捕获,导致丢包。
  2. 全局变量无保护:idx作为全局变量在中断回调中被修改,若此时有其他中断(比如发送完成回调)同时访问,会导致数值错乱,引发发送长度错误。
  3. 内存操作与DMA冲突:发送完成回调中用memset清空缓冲区时,若USART2的DMA正在接收新数据,会触发内存访问冲突,破坏未处理的接收数据。

修正方案

方案一:修复DMA接收重启与变量保护

volatile uint8_t RxMsg_USART2_temp[1];
volatile uint8_t RxMsg_USART2[120];
volatile uint8_t idx = 0; // 显式初始化全局变量

void USART2_Init_DMA_Receive(void) {
    HAL_UART_Receive_DMA(&huart2, RxMsg_USART2_temp, sizeof(RxMsg_USART2_temp));
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART2){
        // 先重启DMA接收,避免漏接后续字节
        HAL_UART_Receive_DMA(&huart2, RxMsg_USART2_temp, sizeof(RxMsg_USART2_temp));
        
        if( RxMsg_USART2_temp[0] != '\n' ){
            if(idx < sizeof(RxMsg_USART2) - 1){ // 防止缓冲区溢出
                RxMsg_USART2[idx] = RxMsg_USART2_temp[0];
                idx++;
            }
        }else{
            if(idx < sizeof(RxMsg_USART2) - 1){
                RxMsg_USART2[idx] = RxMsg_USART2_temp[0];
                // 发送前禁用USART2接收相关中断,避免修改缓冲区
                __HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);
                HAL_UART_Transmit_DMA(&huart1, RxMsg_USART2, idx+1);
            }
        }
    }
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1){
        idx = 0;
        // 发送完成后重新启用USART2接收
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
        // 无需清空缓冲区,下次接收会自动覆盖,避免DMA冲突
    }
}

方案二:使用DMA循环接收+空闲中断(更可靠的任意长度接收)

STM32 UART支持空闲中断,配合DMA循环接收可稳定处理任意长度数据,避免单字节回调的频繁中断问题:

#define MAX_RX_LEN 120
volatile uint8_t RxMsg_USART2[MAX_RX_LEN];
volatile uint16_t RxLen = 0;

void USART2_Init_DMA_Receive(void) {
    // 启用DMA循环接收
    HAL_UART_Receive_DMA(&huart2, RxMsg_USART2, MAX_RX_LEN);
    // 启用空闲中断
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
}

// 在USART2中断服务函数中添加空闲中断处理
void USART2_IRQHandler(void) {
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET){
        __HAL_UART_CLEAR_IDLEFLAG(&huart2); // 清除空闲标志
        HAL_UART_DMAStop(&huart2); // 停止DMA获取当前接收长度
        RxLen = MAX_RX_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
        // 启动发送
        HAL_UART_Transmit_DMA(&huart1, RxMsg_USART2, RxLen);
        // 重新启动DMA循环接收
        HAL_UART_Receive_DMA(&huart2, RxMsg_USART2, MAX_RX_LEN);
        RxLen = 0;
    }
    HAL_UART_IRQHandler(&huart2); // 处理其他UART中断
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1){
        // 发送完成后可执行后续逻辑
    }
}

关键注意点

  • DMA循环模式下,单次接收完成后必须重新启动DMA接收,否则无法捕获后续数据。
  • 全局变量在中断中操作时,需保证原子性,必要时禁用相关中断。
  • 避免在DMA传输过程中修改缓冲区内容,防止内存访问冲突。

内容的提问来源于stack exchange,提问作者Steven Wang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:25:55