> 技术文档 > 【STM32】Keil + FreeRTOS + HAL DMA + UART 空闲中断 接收异常

【STM32】Keil + FreeRTOS + HAL DMA + UART 空闲中断 接收异常


🧩 项目背景

  • 使用 STM32(如 STM32F4);
  • 开发环境:Keil MDK-ARM + FreeRTOS
  • 通讯方式:USART DMA + 空闲中断(IDLE)接收
  • 使用结构体 UART_DMA_t 管理 DMA 接收缓冲;
  • 接收双缓冲:rx_buffer(DMA) → rx_temp_buffer(临时处理);
  • DMA 接收完成由 IDLE 中断触发处理

❗ 遇到的问题

1. DMA接收缓冲区数据异常
  • 现象:空闲中断触发时 rx_buffer 中数据不正确,全为 0 或乱码;

  • 使用 printf() 打印地址时发现:

    • rx_bufferrx_temp_buffer 均被分配到了 地址 0x1000_xxxx(即 CCMRAM);
    • 这些地址虽然属于 RAM,但并不支持 DMA 读写访问
2. DMA接收失败或 IDLE 后无数据
  • 现象:DMA 收不到数据或 memcpy 出来的数据全是 0;
  • 初步怀疑为 DMA 无法正确访问变量所在区域。

🔍 问题分析

STM32 的 DMA 控制器对内存访问有要求,不能访问所有 RAM 区域,特别是 CCMRAM(0x10000000 起)

  • 该区域属于 Core Coupled Memory,仅供 CPU 访问,不支持 DMA ;
  • 若编译器默认将 .bss/.data 段变量分配至此,会导致 DMA 操作失败;
  • Keil 默认将部分 RW/ZI 数据放入 RW_IRAM2(即 0x10000000 段)除非手动控制。

✅ 解决办法

【步骤 1】定义专属段 .dma_buffer,专用于 DMA 缓冲区

修改 Keil scatter 文件(*.sct):

LR_IROM1 0x08000000 0x00100000 { ER_IROM1 0x08000000 0x00100000 { *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM1 0x20000000 0x00030000 { *(.dma_buffer) ; 一定放在最前,否则无效! .ANY (+RW +ZI) } RW_IRAM2 0x10000000 0x00010000 { .ANY (+RW +ZI) }}

注意:.dma_buffer 段必须放在 RW_IRAM1(0x20000000)中,并且要放在前面,否则可能仍然被 Keil 分配到其他区域。


【步骤 2】变量强制放入 .dma_buffer 并 4 字节对齐
#define RX_BUFFER_SIZE 128__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart1_rx_buffer[RX_BUFFER_SIZE];__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart1_rx_temp_buffer[RX_BUFFER_SIZE];__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart6_rx_buffer[RX_BUFFER_SIZE];__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart6_rx_temp_buffer[RX_BUFFER_SIZE];

解释:

  • section(\".dma_buffer\"):强制编译器将变量放入上文定义的 DMA 可用内存段;
  • aligned(4):确保地址对齐,避免某些 DMA 控制器因未对齐访问而出错。

【步骤 3】FreeRTOS 和 DMA 配合的注意事项
  • DMA 和中断处理在 非阻塞模式下使用;
  • UART_DMA_Idle_Handler() 应在空闲中断中调用;
  • 可在 FreeRTOS 中高优先级任务轮询 rx_flag 处理 rx_temp_buffer

🔑 代码说明

(1)usart.c
/* USER CODE BEGIN Header *//** ****************************************************************************** * @file usart.c * @brief This file provides code for the configuration * of the USART instances. ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** *//* USER CODE END Header *//* Includes ------------------------------------------------------------------*/#include \"usart.h\"/* USER CODE BEGIN 0 */// 声明在 .dma_buffer 段中,并4字节对齐,防止 DMA 错误__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart1_rx_buffer[RX_BUFFER_SIZE];__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart1_rx_temp_buffer[RX_BUFFER_SIZE];__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart6_rx_buffer[RX_BUFFER_SIZE];__attribute__((section(\".dma_buffer\"), aligned(4))) uint8_t uart6_rx_temp_buffer[RX_BUFFER_SIZE];// 结构体初始化UART_DMA_t uart1_dma = { .huart = &huart1, .rx_buffer = uart1_rx_buffer, .rx_temp_buffer = uart1_rx_temp_buffer};UART_DMA_t uart6_dma = { .huart = &huart6, .rx_buffer = uart6_rx_buffer, .rx_temp_buffer = uart6_rx_temp_buffer};/* USER CODE END 0 */UART_HandleTypeDef huart1;UART_HandleTypeDef huart6;DMA_HandleTypeDef hdma_usart1_rx;DMA_HandleTypeDef hdma_usart1_tx;DMA_HandleTypeDef hdma_usart6_rx;DMA_HandleTypeDef hdma_usart6_tx;/* USART1 init function */void MX_USART1_UART_Init(void){ /* USER CODE BEGIN USART1_Init 0 */ /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */ /* USER CODE END USART1_Init 1 */ huart1.Instance = USART1; huart1.Init.BaudRate = USART1BOUNDRATE; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART1_Init 2 */ __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); UART_DMA_Start_Receive(&uart1_dma); /* USER CODE END USART1_Init 2 */}/* USART6 init function */void MX_USART6_UART_Init(void){ /* USER CODE BEGIN USART6_Init 0 */ /* USER CODE END USART6_Init 0 */ /* USER CODE BEGIN USART6_Init 1 */ /* USER CODE END USART6_Init 1 */ huart6.Instance = USART6; huart6.Init.BaudRate = USART6BOUNDRATE; huart6.Init.WordLength = UART_WORDLENGTH_8B; huart6.Init.StopBits = UART_STOPBITS_1; huart6.Init.Parity = UART_PARITY_NONE; huart6.Init.Mode = UART_MODE_TX_RX; huart6.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart6.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart6) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART6_Init 2 */ __HAL_UART_ENABLE_IT(&huart6, UART_IT_IDLE); UART_DMA_Start_Receive(&uart6_dma); /* USER CODE END USART6_Init 2 */}void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle){ GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle->Instance==USART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* USART1 clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USART1 DMA Init */ /* USART1_RX Init */ hdma_usart1_rx.Instance = DMA2_Stream2; hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_rx.Init.Mode = DMA_NORMAL; hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx); /* USART1_TX Init */ hdma_usart1_tx.Instance = DMA2_Stream7; hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_tx.Init.Mode = DMA_NORMAL; hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx); /* USART1 interrupt Init */ HAL_NVIC_SetPriority(USART1_IRQn, 6, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } else if(uartHandle->Instance==USART6) { /* USER CODE BEGIN USART6_MspInit 0 */ /* USER CODE END USART6_MspInit 0 */ /* USART6 clock enable */ __HAL_RCC_USART6_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); /**USART6 GPIO Configuration PC6 ------> USART6_TX PC7 ------> USART6_RX */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF8_USART6; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /* USART6 DMA Init */ /* USART6_RX Init */ hdma_usart6_rx.Instance = DMA2_Stream1; hdma_usart6_rx.Init.Channel = DMA_CHANNEL_5; hdma_usart6_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart6_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart6_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart6_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart6_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart6_rx.Init.Mode = DMA_NORMAL; hdma_usart6_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_usart6_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart6_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart6_rx); /* USART6_TX Init */ hdma_usart6_tx.Instance = DMA2_Stream6; hdma_usart6_tx.Init.Channel = DMA_CHANNEL_5; hdma_usart6_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart6_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart6_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart6_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart6_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart6_tx.Init.Mode = DMA_NORMAL; hdma_usart6_tx.Init.Priority = DMA_PRIORITY_LOW; hdma_usart6_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart6_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart6_tx); /* USART6 interrupt Init */ HAL_NVIC_SetPriority(USART6_IRQn, 5, 0); HAL_NVIC_EnableIRQ(USART6_IRQn); /* USER CODE BEGIN USART6_MspInit 1 */ /* USER CODE END USART6_MspInit 1 */ }}void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle){ if(uartHandle->Instance==USART1) { /* USER CODE BEGIN USART1_MspDeInit 0 */ /* USER CODE END USART1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_USART1_CLK_DISABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10); /* USART1 DMA DeInit */ HAL_DMA_DeInit(uartHandle->hdmarx); HAL_DMA_DeInit(uartHandle->hdmatx); /* USART1 interrupt Deinit */ HAL_NVIC_DisableIRQ(USART1_IRQn); /* USER CODE BEGIN USART1_MspDeInit 1 */ /* USER CODE END USART1_MspDeInit 1 */ } else if(uartHandle->Instance==USART6) { /* USER CODE BEGIN USART6_MspDeInit 0 */ /* USER CODE END USART6_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_USART6_CLK_DISABLE(); /**USART6 GPIO Configuration PC6 ------> USART6_TX PC7 ------> USART6_RX */ HAL_GPIO_DeInit(GPIOC, GPIO_PIN_6|GPIO_PIN_7); /* USART6 DMA DeInit */ HAL_DMA_DeInit(uartHandle->hdmarx); HAL_DMA_DeInit(uartHandle->hdmatx); /* USART6 interrupt Deinit */ HAL_NVIC_DisableIRQ(USART6_IRQn); /* USER CODE BEGIN USART6_MspDeInit 1 */ /* USER CODE END USART6_MspDeInit 1 */ }}/* USER CODE BEGIN 1 */struct __FILE { int handle; }; FILE __stdout; void _sys_exit(int x) { x = x; } int fputc(int ch, FILE *f){ HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0x0001); return ch;}void UART_DMA_Start_Receive(UART_DMA_t *uart){ HAL_UART_Receive_DMA(uart->huart, uart->rx_buffer, RX_BUFFER_SIZE); __HAL_UART_ENABLE_IT(uart->huart, UART_IT_IDLE);}void UART_DMA_Idle_Handler(UART_DMA_t *uart){ if (__HAL_UART_GET_FLAG(uart->huart, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(uart->huart); HAL_UART_DMAStop(uart->huart); uart->rx_len = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(uart->huart->hdmarx); memcpy(uart->rx_temp_buffer, uart->rx_buffer, uart->rx_len); printf(\"data(hex):\"); for (int i = 0; i < uart->rx_len; i++) printf(\"%02X \", uart->rx_temp_buffer[i]); printf(\"\\r\\n\"); printf(\"rx_buffer address = 0x%08X\\r\\n\", (unsigned int)uart->rx_buffer); printf(\"rx_temp_buffer address = 0x%08X\\r\\n\", (unsigned int)uart->rx_temp_buffer); uart->rx_flag = 1; HAL_UART_Receive_DMA(uart->huart, uart->rx_buffer, RX_BUFFER_SIZE); }}HAL_StatusTypeDef UART_DMA_Send(UART_DMA_t *uart, uint8_t *data, uint16_t size){ if (uart->tx_busy) return HAL_BUSY; if (size > TX_BUFFER_SIZE) return HAL_ERROR; memcpy(uart->tx_buffer, data, size); uart->tx_busy = 1; return HAL_UART_Transmit_DMA(uart->huart, uart->tx_buffer, size);}void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){ if (huart->Instance == USART1) uart1_dma.tx_busy = 0; else if (huart->Instance == USART6) uart6_dma.tx_busy = 0;}HAL_StatusTypeDef usartPrintf(UART_DMA_t *uart, const char *fmt, ...){ if (uart->tx_busy) return HAL_BUSY; // �����䣬�����ͻ va_list args; va_start(args, fmt); int len = vsnprintf((char *)uart->tx_buffer, TX_BUFFER_SIZE, fmt, args); va_end(args); if (len <= 0 || len > TX_BUFFER_SIZE) return HAL_ERROR; uart->tx_busy = 1; return HAL_UART_Transmit_DMA(uart->huart, uart->tx_buffer, len);}/* USER CODE END 1 */
(2)usart.h
/* USER CODE BEGIN Header *//** ****************************************************************************** * @file usart.h * @brief This file contains all the function prototypes for * the usart.c file ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** *//* USER CODE END Header *//* Define to prevent recursive inclusion -------------------------------------*/#ifndef __USART_H__#define __USART_H__#ifdef __cplusplusextern \"C\" {#endif/* Includes ------------------------------------------------------------------*/#include \"main.h\"/* USER CODE BEGIN Includes */#include \"stdio.h\"#include \"string.h\"#include \"stdbool.h\"#include /* USER CODE END Includes */extern UART_HandleTypeDef huart1;extern UART_HandleTypeDef huart6;/* USER CODE BEGIN Private defines */#define USART1BOUNDRATE 2000000#define USART6BOUNDRATE 460800#define TX_BUFFER_SIZE 64#define RX_BUFFER_SIZE 64typedef struct { UART_HandleTypeDef *huart; volatile uint8_t rx_flag; volatile uint16_t rx_len; uint8_t *rx_buffer; uint8_t *rx_temp_buffer; volatile uint8_t tx_busy; uint8_t tx_buffer[TX_BUFFER_SIZE];} UART_DMA_t;extern UART_DMA_t uart1_dma;extern UART_DMA_t uart6_dma;/* USER CODE END Private defines */void MX_USART1_UART_Init(void);void MX_USART6_UART_Init(void);/* USER CODE BEGIN Prototypes */void UART_DMA_Start_Receive(UART_DMA_t *uart);void UART_DMA_Idle_Handler(UART_DMA_t *uart);HAL_StatusTypeDef UART_DMA_Send(UART_DMA_t *uart, uint8_t *data, uint16_t size);HAL_StatusTypeDef usartPrintf(UART_DMA_t *uart, const char *fmt, ...);/* USER CODE END Prototypes */#ifdef __cplusplus}#endif#endif /* __USART_H__ */
(3)usart.sct
; ******************************************************************; *** Scatter-Loading Description File generated by Embedded IDE ***; ******************************************************************LR_IROM1 0x08000000 0x00100000 {ER_IROM1 0x08000000 0x00100000 {*.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) }RW_IRAM1 0x20000000 0x00030000 {.ANY (+RW +ZI) *(.dma_buffer)  ; <<< 添加这个}RW_IRAM2 0x10000000 0x00010000 {.ANY (+RW +ZI) }}

📌 总结与建议

项目 说明 问题本质 DMA 缓冲区默认被放入不支持 DMA 的 CCMRAM 根本原因 Keil 默认内存映射未考虑 DMA 可访问区域 关键措施 1. 使用 .dma_buffer 显式控制段位置
2. 指定对齐方式
3. Scatter 文件顺序正确 适用范围 STM32 所有使用 DMA 的应用(UART / SPI / ADC 等) 调试建议 使用 printf(\"%p\", ...) 检查地址是否在 0x2000_xxxx 区间

咖啡网购