【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_buffer
和rx_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 ConfigurationPA9 ------> USART1_TXPA10 ------> 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 ConfigurationPC6 ------> USART6_TXPC7 ------> 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 ConfigurationPA9 ------> USART1_TXPA10 ------> 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 ConfigurationPC6 ------> USART6_TXPC7 ------> 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 __cplusplus
extern "C" {
#endif/* Includes ------------------------------------------------------------------*/
#include "main.h"/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "stdbool.h"
#include <stdarg.h>
/* 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 区间 |