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)); } }
问题原因与解决方法
核心问题
- DMA接收未重启:问题代码中,USART2的DMA单次接收1字节完成后,没有重新启动DMA接收,后续字节无法被持续捕获,导致丢包。
- 全局变量无保护:
idx作为全局变量在中断回调中被修改,若此时有其他中断(比如发送完成回调)同时访问,会导致数值错乱,引发发送长度错误。 - 内存操作与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
相关产品推荐
相关产品推荐

