【STM32】Keil + FreeRTOS + HAL DMA + UART 空闲中断 接收异常
项目说明问题本质DMA 缓冲区默认被放入不支持 DMA 的 CCMRAM根本原因Keil 默认内存映射未考虑 DMA 可访问区域关键措施1. 使用显式控制段位置2. 指定对齐方式3. Scatter 文件顺序正确适用范围STM32 所有使用 DMA 的应用(UART / SPI / ADC 等)调试建议使用检查地址是否在区间。
·
🧩 项目背景
- 使用 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 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 __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 64
typedef 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 区间 |
更多推荐



所有评论(0)