当前位置: 首页 > news >正文

stm32_DMA

DMA


1. 概念与基本原理

DMA,全称Direct Memory Access,即直接存储器访问。它是微控制器(MCU)、嵌入式处理器中的一个独立硬件模块,用于在无需CPU干预的情况下,在不同内存区域(包括外设寄存器和SRAM、Flash等)之间进行数据传输。

基本原理: 在没有DMA的情况下,CPU负责所有的数据传输。例如,从ADC读取数据,CPU需要逐个读取ADC寄存器并将数据拷贝到RAM;向UART发送数据,CPU需要逐个将数据从RAM拷贝到UART发送寄存器。这种方式会占用大量CPU时间,尤其是在高速数据传输或大量数据传输的场景下,会严重影响CPU处理其他任务的效率。

DMA模块的出现,就是为了解决这个问题。当DMA被配置并启动后,CPU只需告诉DMA控制器需要传输的数据源、数据目标、数据量和传输方向,DMA控制器就会接管数据传输任务。CPU可以自由地执行其他指令,只有当DMA传输完成(或发生错误)时,DMA控制器才会通过中断通知CPU。

核心思想: 解放CPU,提高系统效率和吞吐量。


2. DMA内部结构(可图示)

想象一个DMA控制器的内部结构,它通常包含以下几个关键部分:

+-----------------------------------------------------+
|                  DMA控制器                         |
+-----------------------------------------------------+
|                                                     |
|  1. DMA通道/(DMA Channel/Stream)                 |  <-- 每个通道处理一个独立的传输任务
|     +-----------------------------------------+     |
|     |                                         |     |
|     |  源地址寄存器 (Source Address Register) |     |  <-- 数据源地址 (如:外设数据寄存器或RAM地址)
|     |  目标地址寄存器 (Destination Address Register)|     |  <-- 数据目标地址 (如:RAM地址或外设数据寄存器)
|     |  数据量寄存器 (Number of Data Register) |     |  <-- 待传输的数据量
|     |  控制寄存器 (Control Register)          |     |  <-- 配置传输模式、数据宽度、增量模式等
|     +-----------------------------------------+     |
|                                                     |
|  2. 仲裁器 (Arbiter)                                |  <-- 解决多个DMA通道同时请求总线访问时的冲突
|                                                     |
|  3. 总线接口 (Bus Interface)                        |  <-- 连接到系统总线,进行实际的数据读写
|                                                     |
|  4. 中断逻辑 (Interrupt Logic)                      |  <-- 传输完成/错误时生成中断请求
|                                                     |
+-----------------------------------------------------+^                ^|                ||                |
+-------+-----+  +-------+-----+
|   外设总线    |  |    内存总线   |
| (Peripherals) |  |   (SRAM/Flash)|
+---------------+  +---------------+

关键部件解释:

  • DMA通道/流: 大多数微控制器会提供多个独立的DMA通道或流(Stream),每个通道可以独立地配置和启动一个DMA传输任务。例如,一个通道可以用于ADC到RAM的传输,另一个通道用于RAM到UART的传输。

  • 源地址寄存器 (Source Address Register - SAR/PAR): 存储数据传输的起始源地址。这可以是内存地址,也可以是外设的数据寄存器地址。

  • 目标地址寄存器 (Destination Address Register - DAR/MAR): 存储数据传输的起始目标地址。同样可以是内存地址或外设的数据寄存器地址。

  • 数据量寄存器 (Number of Data Register - NDTR/CR): 存储本次DMA传输需要传输的数据单位数量。每传输一个数据单位,此寄存器会自动减1,直到减为0时传输完成。

  • 控制寄存器 (Control Register - CR/CCR):

    这是DMA配置的核心。它包含了以下重要配置:

    • 传输模式: 单次传输、循环传输。
    • 数据宽度: 每次传输的数据单位大小(字节、半字、字)。
    • 地址增量模式: 源地址和目标地址在每次传输后是否自动增加(用于连续数据块传输)。
    • 传输方向: 从外设到内存,从内存到外设,或从内存到内存。
    • 优先级: DMA通道之间的优先级。
    • 中断使能: 配置是否在传输完成、一半传输完成、传输错误时触发中断。
  • 仲裁器: 当有多个DMA通道或CPU同时需要访问总线时,仲裁器负责决定哪个请求获得总线访问权,以避免冲突。

  • 总线接口: DMA控制器通过这个接口与系统总线(如AHB/APB总线)连接,从而可以读写内存和外设寄存器。

  • 中断逻辑: DMA传输完成、一半传输完成、传输错误等事件发生时,DMA控制器会生成中断请求,通知CPU进行后续处理。


3. DMA工作模式与传输类型(可图示)

DMA的工作模式和传输类型决定了DMA如何进行数据传输:

+-------------------------------------------------------+
|                       DMA工作模式/传输类型             |
+-------------------------------------------------------+
|                                                       |
|  1. 传输方向 (Transfer Direction)                     |
|     +-----------------------------------------+       |
|     |                                         |       |
|     |  外设到内存 (Peripheral to Memory)      |       |
|     |    - e.g., ADC数据采集到RAM            |       |
|     |                                         |       |
|     |  内存到外设 (Memory to Peripheral)      |       |
|     |    - e.g., RAM数据发送到UART           |       |
|     |                                         |       |
|     |  内存到内存 (Memory to Memory)          |       |
|     |    - e.g., 快速拷贝内存块             |       |
|     +-----------------------------------------+       |
|                                                       |
|  2. 传输模式 (Transfer Mode)                          |
|     +-----------------------------------------+       |
|     |                                         |       |
|     |  普通模式 (Normal Mode)                 |       |
|     |    - 完成一次指定数量的传输后停止      |       |
|     |                                         |       |
|     |  循环模式 (Circular Mode)               |       |
|     |    - 传输完成后自动重置数据量寄存器    |       |
|     |    - 持续循环传输,无需CPU干预           |       |
|     |    - 适用于连续数据流采集或输出         |       |
|     +-----------------------------------------+       |
|                                                       |
|  3. 地址增量模式 (Address Increment Mode)             |
|     +-----------------------------------------+       |
|     |                                         |       |
|     |  源地址增量 (Peripheral Increment / Memory Increment) |
|     |    - 传输后源地址是否递增              |       |
|     |                                         |       |
|     |  目标地址增量 (Memory Increment / Peripheral Increment) |
|     |    - 传输后目标地址是否递增            |       |
|     |                                         |       |
|     |  不增量 (No Increment)                  |       |
|     |    - 地址保持不变,用于读写固定寄存器   |       |
|     +-----------------------------------------+       |
|                                                       |
|  4. 数据宽度 (Data Width)                             |
|     +-----------------------------------------+       |
|     |                                         |       |
|     |  字节 (Byte - 8-bit)                   |       |
|     |  半字 (Half-Word - 16-bit)              |       |
|     |(Word - 32-bit)                     |       |
|     +-----------------------------------------+       |
|                                                       |
|  5. 中断类型 (Interrupt Type)                         |
|     +-----------------------------------------+       |
|     |                                         |       |
|     |  传输完成中断 (Transfer Complete - TC)  |       |
|     |  半传输完成中断 (Half Transfer - HT)    |       |
|     |  传输错误中断 (Transfer Error - TE)     |       |
|     |  直接模式错误中断 (Direct Mode Error - DME) |
|     +-----------------------------------------+       |
+-------------------------------------------------------+

详细说明:

  1. 传输方向: 定义数据从哪里来到哪里去。

  2. 传输模式:

    • 普通模式: 传输指定数量的数据后,DMA控制器自动停止并可以触发传输完成中断。
    • 循环模式: 传输指定数量的数据后,DMA控制器会自动重新加载数据量寄存器并继续传输,形成一个循环。常用于连续采集传感器数据或连续发送波形数据。
  3. 地址增量模式:

    • 源地址增量: 如果源地址是内存地址,通常会配置为增量,以读取连续的数据块。如果源地址是外设寄存器,通常配置为不增量,因为外设数据寄存器地址通常是固定的。
    • 目标地址增量: 同理。
  4. 数据宽度: 指定每次DMA传输的数据单位是8位、16位还是32位。这必须与源和目标数据的实际宽度相匹配。

  5. 中断类型:

    DMA控制器可以根据不同的事件触发中断,方便CPU进行处理,例如:

    • 传输完成中断 (TC): 当所有数据传输完毕时触发。
    • 半传输完成中断 (HT): 在循环模式下,当一半数据传输完毕时触发,常用于“乒乓操作”,即在一个缓冲器被DMA填充时,CPU可以处理另一个缓冲器的数据。
    • 传输错误中断 (TE): 在传输过程中发生错误时触发。

4. DMA操作流程(可图示)

DMA的操作通常遵循以下流程:

+-------------------+
|     开始          |
+-------------------+|V
+-------------------+
|  1. 初始化DMA外设 |
|    - 使能DMA时钟  |
+-------------------+|V
+-------------------+
|  2. 配置DMA通道/|
|    - 选择DMA通道/流号 |
|    - 配置传输方向   |
|    - 配置数据宽度   |
|    - 配置地址增量模式 |
|    - 配置传输模式(普通/循环)|
|    - 配置优先级     |
|    - 配置中断使能(可选)|
+-------------------+|V
+-------------------+
|  3. 关联DMA与外设   |
|    - 例如:配置ADC的DMA请求使能位 |
|    - 例如:配置UART的DMA发送/接收使能位 |
+-------------------+|V
+-------------------+
|  4. 设置DMA传输参数 |
|    - 设置源地址     |
|    - 设置目标地址   |
|    - 设置传输数据量 |
+-------------------+|V
+-------------------+
|  5. 启动DMA传输     |
|    - 使能DMA通道/|
+-------------------+|V
+-------------------+
|  6. (可选)等待DMA传输完成/处理中断 |
|    - 轮询DMA状态标志 |
|    - 或,等待DMA中断并执行中断服务函数 |
+-------------------+|V
+-------------------+
|      结束         |
+-------------------+

流程详解:

  1. 初始化DMA外设: 使能DMA控制器自身的时钟。
  2. 配置DMA通道/流: 这是DMA的核心配置步骤,包括选择合适的通道、传输方向、数据宽度、地址增量方式、传输模式以及中断等。
  3. 关联DMA与外设: 这一步至关重要。DMA传输通常是由外设发起的。例如,ADC完成一次转换后会向DMA控制器发出一个请求信号,告知DMA可以传输数据了。因此,需要在外设的配置中使能DMA请求。
  4. 设置DMA传输参数: 明确本次DMA传输的数据源地址、目标地址以及总共要传输的数据量。
  5. 启动DMA传输: 启用DMA通道/流,使其处于准备就绪状态。一旦外设发出DMA请求,DMA传输就会自动开始。
  6. (可选)等待DMA传输完成/处理中断:
    • 轮询: CPU周期性地检查DMA状态寄存器的标志位,判断传输是否完成。这种方式会占用CPU时间。
    • 中断: 更高效的方式。当DMA传输完成、一半完成或发生错误时,DMA控制器会触发中断,CPU跳转到相应的中断服务函数进行处理,然后可以继续执行其他任务。

5. 完整流程代码案例 (以STM32为例)

这里以STM32微控制器为例,使用HAL库来实现一个简单的DMA功能:通过DMA将ADC采集到的数据自动传输到SRAM中的一个数组中。

硬件连接:

  • ADC: 使用STM32F407的ADC1,连接到PA1引脚(模拟输入)。
  • DMA: ADC1通常连接到DMA2的Stream0。

开发环境: Keil MDK, STM32CubeMX (用于生成初始化代码)

5.1 STM32CubeMX配置步骤
  1. 新建项目并选择芯片。
  2. 配置ADC1:
    • Mode: Independent Mode
    • Scan Conversion Mode: Disable (这里只采集一个通道)
    • Continuous Conversion Mode: Enable (连续采集)
    • Discontinuous Conversion Mode: Disable
    • DMA Continuous Request: Enable (关键!使能ADC的DMA请求)
    • Channel Settings:
      • Rank1: PA1 (Channel 1)
      • Sampling Time: 3 Cycles (或其他合适的值)
  3. 配置DMA2:
    • 在ADC1设置的DMA Settings中,点击Add
    • 选择 DMA2 Stream0 (通常ADC1连接到此Stream)。
    • Mode: NormalCircular (这里我们用Circular模式,实现连续数据采集)。
    • Direction: Peripheral to Memory (ADC是外设,RAM是内存)。
    • Data Width: Half Word (ADC是12位,所以选择16位半字传输)。
    • Increment Address: Memory (目标地址是RAM数组,需要递增)。
    • Increment Address: Peripheral (源地址是ADC数据寄存器,通常不递增,但在这里为了配置一致性,选择No Increment更合理,因为ADC数据寄存器地址固定)。
    • Priority: Low (或Default)。
    • Enable Interrupts: 勾选 DMA2 Stream0 global interrupt (传输完成中断)。
  4. 时钟配置: 保持默认即可,确保ADC和DMA的时钟都被使能。
  5. 生成代码。
5.2 Keil MDK代码实现

在STM32CubeMX生成的基础代码上,我们主要修改 main.c 文件和实现DMA中断服务函数。

/* USER CODE BEGIN Includes */
#include "main.h"
#include <stdio.h> // 用于串口打印,可选
/* USER CODE END Includes *//* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1; // ADC句柄
DMA_HandleTypeDef hdma_adc1; // DMA句柄/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
#define ADC_BUFFER_SIZE 10 // 定义ADC数据缓冲区大小
uint16_t adc_values[ADC_BUFFER_SIZE]; // 存储ADC采集数据的数组
volatile uint8_t adc_dma_transfer_complete = 0; // DMA传输完成标志
/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*//* USER CODE END PFP *//* USER CODE BEGIN 0 */
/* USER CODE END 0 *//*** @brief  The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_DMA_Init(); // DMA初始化必须在ADC初始化之前,因为ADC初始化会用到DMA句柄MX_ADC1_Init();/* USER CODE BEGIN 2 */// 启动ADC的DMA传输// HAL_ADC_Start_DMA(hadc, pData, Length)// hadc: ADC句柄// pData: 目标数据缓冲区地址// Length: 传输数据量if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, ADC_BUFFER_SIZE) != HAL_OK){/* Start Error */Error_Handler();}/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if (adc_dma_transfer_complete){// DMA传输完成,可以处理采集到的数据了// 注意:在循环模式下,这个标志会持续被设置,因为它每次完成一个完整的缓冲区传输就会触发// 这里只是简单打印,实际应用中可以进行数据处理、滤波等printf("ADC Values: ");for (int i = 0; i < ADC_BUFFER_SIZE; i++){printf("%d ", adc_values[i]);}printf("\r\n");adc_dma_transfer_complete = 0; // 清除标志}HAL_Delay(500); // 主循环可以做其他事情,等待DMA中断}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{/* ... (CubeMX生成的时钟配置代码) ... */
}/*** @brief ADC1 Initialization Function* @param None* @retval None*/
static void MX_ADC1_Init(void)
{/* ... (CubeMX生成的ADC1初始化代码) ... */// 注意,CubeMX会在这个函数中关联DMA句柄:// hadc1.DMA_Handle = &hdma_adc1;// hdma_adc1.Parent = &hadc1;
}/*** @brief  DMA Initialization Function* @param  None* @retval None*/
static void MX_DMA_Init(void)
{/* USER CODE BEGIN DMA_Init_First *//* USER CODE END DMA_Init_First *//* DMA controller clock enable */__HAL_RCC_DMA2_CLK_ENABLE(); // 这一行是CubeMX生成的,使能DMA2时钟/* DMA interrupt init *//* DMA2_Stream0_IRQn interrupt configuration */HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0); // 设置DMA中断优先级HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); // 使能DMA中断/* USER CODE BEGIN DMA_Init_Last *//* USER CODE END DMA_Init_Last */
}/*** @brief GPIO Initialization Function* @param None* @retval None*/
static void MX_GPIO_Init(void)
{/* ... (CubeMX生成的GPIO初始化代码) ... */
}/* USER CODE BEGIN 4 */// DMA传输完成中断回调函数
// 这个函数会在HAL_DMA_IRQHandler中被调用
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{/* Prevent unused argument(s) compilation warning */UNUSED(hadc); // 避免编译器警告/* NOTE : This function Should not be modified, when the callback is needed,the HAL_ADC_ConvCpltCallback could be implemented in the user file.*/adc_dma_transfer_complete = 1; // 设置DMA传输完成标志
}/* USER CODE END 4 *//*** @brief  This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */while(1){}/* USER CODE END Error_Handler_Debug */
}#ifdef  USE_FULL_ASSERT
/*** @brief  Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param  file: pointer to the source file name* @param  line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{ /* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT *//************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

中断服务函数 (在 stm32f4xx_it.c 中)

CubeMX会自动生成DMA中断服务函数的框架,你需要在 DMA2_Stream0_IRQHandler 中调用HAL库的DMA处理函数。

/* USER CODE BEGIN Includes */
/* USER CODE END Includes *//* External variables --------------------------------------------------------*/
extern DMA_HandleTypeDef hdma_adc1; // 声明在main.c中定义的DMA句柄
/* USER CODE BEGIN EV */
/* USER CODE END EV *//******************************************************************************/
/* Cortex-M4 Processor Interruption and Exception Handlers          */ 
/******************************************************************************/
/*** @brief This function handles DMA2 Stream0 global interrupt.*/
void DMA2_Stream0_IRQHandler(void)
{/* USER CODE BEGIN DMA2_Stream0_IRQn 0 *//* USER CODE END DMA2_Stream0_IRQn 0 */HAL_DMA_IRQHandler(&hdma_adc1); // 调用HAL库的DMA中断处理函数/* USER CODE BEGIN DMA2_Stream0_IRQn 1 *//* USER CODE END DMA2_Stream0_IRQn 1 */
}/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
5.3 代码解释
  • ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1;: 定义了ADC和DMA的HAL库句柄,用于操作对应的外设。
  • uint16_t adc_values[ADC_BUFFER_SIZE];: 定义了一个数组,用于存储DMA从ADC传输过来的12位(实际存储在16位uint16_t中)数据。
  • volatile uint8_t adc_dma_transfer_complete = 0;: 一个标志位,用于在中断中通知主循环DMA传输完成。volatile关键字很重要,因为它告诉编译器这个变量的值可能在程序执行流程之外(例如中断)被改变。
  • MX_DMA_Init();: STM32CubeMX生成的DMA初始化函数,负责使能DMA时钟,配置DMA通道/流的各项参数(方向、数据宽度、地址增量、模式、优先级),并使能中断。
  • MX_ADC1_Init();: STM32CubeMX生成的ADC初始化函数,其中会配置ADC的通道、采样时间,并且最重要的是,会关联DMA句柄到ADC句柄 (hadc1.DMA_Handle = &hdma_adc1;)。
  • if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, ADC_BUFFER_SIZE) != HAL_OK):
    • 这是启动DMA传输的关键函数。它告诉HAL库:
      • &hadc1: 使用哪个ADC(以及关联的DMA通道)。
      • (uint32_t*)adc_values: DMA的目标地址,即数据将传输到adc_values数组。注意这里需要强制类型转换为uint32_t*,因为HAL库的设计中,pData参数是uint32_t*,但实际传输的是uint16_t
      • ADC_BUFFER_SIZE: 传输的数据量。
    • 一旦此函数被调用,并且ADC配置为连续转换模式和DMA请求使能,每当ADC完成一次转换,它就会自动触发DMA将数据从ADC数据寄存器 (ADC1->DR) 传输到adc_values数组中。
  • HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc):
    • 这是一个用户自定义的ADC转换完成回调函数。当DMA传输了指定数量的数据(ADC_BUFFER_SIZE)后,DMA会触发一个传输完成中断,进而由HAL库的DMA中断处理函数 (HAL_DMA_IRQHandler) 调用这个回调函数。
    • 在这个回调函数中,我们将adc_dma_transfer_complete标志设置为1,通知主循环有新的数据可用。
  • DMA2_Stream0_IRQHandler(void):
    • 这是DMA2 Stream0的中断服务函数。
    • 它由Cortex-M处理器的中断向量表在DMA中断发生时调用。
    • HAL_DMA_IRQHandler(&hdma_adc1);:这个函数是HAL库提供的通用DMA中断处理程序。它会检查DMA的状态标志,并根据配置调用相应的回调函数(例如本例中的HAL_ADC_ConvCpltCallback)。

相关文章:

  • 引领AI安全新时代 Accelerate 2025北亚巡展·北京站成功举办
  • 从失效文档到知识资产:Gitee Wiki 引领研发知识管理变革
  • 模板方法模式:优雅封装不变,灵活扩展可变
  • 电脑定时关机工具推荐
  • Transformer架构解析:Encoder与Decoder核心差异、生成式解码技术详解
  • 浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
  • MySQL技术内幕1:内容介绍+MySQL编译使用介绍
  • 10个成功案例剖析|融质AI创新实践
  • php中实现邮件发送功能
  • Spring Boot 类加载机制深度解析
  • 浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
  • 1、cpp实现Python的print函数
  • http头部注入攻击
  • 多模态大语言模型arxiv论文略读(110)
  • 高温IC设计带来的挑战和问题
  • 2025年06月06日Github流行趋势
  • 【数据结构】_排序
  • windows11右键取消二次加载
  • 跨平台游戏引擎 Axmol-2.6.1 发布
  • 单片机0-10V电压输出电路分享
  • o2o平台有哪些网站/网站优化策划书
  • 有没有做租赁的网站/厦门网络关键词排名
  • 吕梁网站设计/seo网上培训课程
  • 网站建设详细教程视频/企业管理培训课程视频
  • 做美容有哪些网站/网络优化工程师工作内容
  • 做网站要会没软件/广告投放怎么做