🧩 项目背景

  • 使用 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 __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 区间
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐