HAL_DMA

文章目录

一、DMA简介

DMA = Direct Memory Access,直接存储器访问,是STM32核心外设之一,核心作用是「硬件自动完成数据搬运」,完全不占用CPU资源,解决CPU在大量数据传输(如ADC采集、UART收发、SPI通信)中被占用、导致效率低下的问题。

1、DMA工作原理图

  

2、核心功能:

  • 实现三大方向数据传输:外设→内存、内存→外设、内存→内存(仅STM32F103大容量型号支持)。
  • 支持单次传输、循环传输,适配不同场景需求。
  • 可配合ADC、UART、SPI、TIM、DAC等外设,实现高速数据自动搬运。
  • 传输过程无需CPU干预,解放CPU执行其他业务逻辑(如中断处理、算法运算)。

3、核心特点

  • 传输速度快,依托硬件实现,比CPU软件搬运效率高10倍以上。
  • 支持多种数据宽度(字节8bit、半字16bit、字32bit),适配不同外设数据格式。
  • 具备传输完成、半传输、传输错误等中断,便于程序监控和处理。
  • STM32F103 包含DMA1(全系列都有,7个通道)和DMA2(仅大容量型号有,5个通道)。

二、DMA模块详解

1、DMA框图

  

1.DMA请求

  • 外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号, 当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。
  • DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求。
  • DMA1请求映射
      
  • DMA2请求映射
      

2.通道

  • DMA具有12个独立可编程的通道,其中DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。
  • 虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

3.仲载器

  • 当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理DMA通道请求分为两个阶段。
  • 第一阶段
    属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。
    1、软件阶段,DMA_CCRx:PL1:0
  • 第二阶段
    属于硬件阶段, 如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高。
    2、硬件阶段,通道编号小的优先级大,DM1的优先级高于DMA2的优先级。

2、核心概念

1.通道(Channel):

  • 每个DMA有多个通道,每个通道对应一个外设(如DMA1_CH1对应ADC1,DMA1_CH4对应USART1_TX),同一时间一个通道只能处理一个传输任务。

2.传输方向:3种核心方向,是DMA配置的核心:

  • 外设→内存(最常用):如ADC采集数据,自动搬运到内存数组 。
  • 内存→外设:如UART发送数据,自动从内存数组搬运到串口发送寄存器 。
  • 内存→内存:仅DMA2支持,实现硬件层面的memcpy,比软件 memcpy 快 。

3.传输模式:

  • Normal(普通模式):传输完成后,DMA停止工作,需手动重启传输 。
  • Circular(循环模式):传输完成后,自动重新开始传输,无需手动重启,适合连续采集/连续发送(如ADC循环采集、DAC波形输出) 。

4.地址自增:

  • 内存地址自增(通常开启):传输时内存地址自动+1(按数据宽度),实现连续地址搬运 。
  • 外设地址自增(通常关闭):外设寄存器地址固定,无需自增(如ADC数据寄存器、UART发送寄存器) 。

3、STM32F103 DMA 通道分配

DMA1(全系列支持,7个通道):

DMA通道 对应外设 常用场景
DMA1_CH1 ADC1 ADC采集数据→内存
DMA1_CH2 TIM1_CH1/TIM1_UP/TIM1_TRIG TIM触发DMA传输
DMA1_CH3 TIM1_CH4/TIM2_UP TIM相关数据传输
DMA1_CH4 USART1_TX 内存→USART1发送
DMA1_CH5 USART1_RX USART1接收→内存
DMA1_CH6 USART2_TX/SPI1_RX USART2发送、SPI1接收
DMA1_CH7 USART2_RX/SPI1_TX USART2接收、SPI1发送

DMA2(仅大容量型号支持,如STM32F103VET6):

  • 支持内存→内存传输,通道对应高端外设(如ADC2/3、SPI2等)。

4、传输优先级

  • 当多个DMA通道同时请求传输时,优先级决定传输顺序,STM32F103 DMA支持4级优先级:
      Very High(最高)> High(高)> Medium(中)> Low(低)
  • 工程配置建议:
      ADC、UART等关键外设的DMA通道设为High/Very High,非关键通道设为Medium/Low。

三、CudeMx配置DMA

内存至内存,适用于大量数据拷贝。

1、开启 DMA 的 Memory to Memory 模式

  

  • 进入 DMA Settings
    Add → 选择 DMA1 通道(随便一个可用 DMA)
    方向:Memory To Memory
    优先级:Low / Medium 都行
    源数据自增:Memory Increment → Enable
    目标数据自增:Memory Increment → Enable
    数据宽度:Byte(8 位)
    模式:Normal(正常模式)
    内存到内存一般不用循环,复制完就结束

2、代码

c 复制代码
		#include "main.h"
		#include "stm32f1xx_hal.h"  // 根据你的芯片修改
		
		//  extern DMA 句柄(CubeMX 生成)
		extern DMA_HandleTypeDef hdma_memtomem_dma1_channel1;
		
		// ==================== 内存数据 ====================
		#define BUF_SIZE    100                 // 数据长度
		uint8_t src_buf[BUF_SIZE] = {0};        // 源数据
		uint8_t dest_buf[BUF_SIZE] = {0};       // 目标数据
		
		int main(void)
		{
			
		  HAL_Init();
		  SystemClock_Config();
		  MX_GPIO_Init();
		  MX_DMA_Init();
		  MX_USART1_UART_Init();
			
			//初始化源数据
			// 给源数据赋值
			for(int i=0; i<BUF_SIZE; i++)
			{
					src_buf[i] = i;  // 0,1,2,3...99
			}
			// ========================
		  // DMA 内存 → 内存 复制
		  // ========================
		  HAL_DMA_Start(
		      &hdma_memtomem_dma1_channel1,    // DMA句柄
		      (uint32_t)src_buf,              // 源地址
		      (uint32_t)dest_buf,             // 目标地址
		      BUF_SIZE                        // 数据长度
		  );
		  // 等待传输完成(可选)
		  HAL_DMA_PollForTransfer(
		      &hdma_memtomem_dma1_channel1,
		      HAL_DMA_FULL_TRANSFER,
		      1000
		  );
		
		  while (1)
		  {
				// dest_buf[] 已经被 DMA 自动复制完成!
		    // 这里可以直接使用
		  }
}

3、中断版代码参考

c 复制代码
		// 启动中断方式
		HAL_DMA_Start_IT(
		    &hdma_memtomem_dma1_channel1,
		    (uint32_t)src_buf,
		    (uint32_t)dest_buf,
		    BUF_SIZE
		);
		
		// DMA 传输完成回调
		void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma)
		{
		    if(hdma == &hdma_memtomem_dma1_channel1)
		    {
		        // 内存拷贝完成!
		        // 在这里处理数据
		    }
		} 

四、DMA编程模块

1、初始化结构体

用于配置DMA的初始化参数,如传输方向、数据宽度、是否使能FIFO、突发传输模式等。

c 复制代码
typedef struct
{
  uint32_t Direction;                 /* 传输方向 */
  uint32_t PeriphInc;                 /* 外设地址增量 */
  uint32_t MemInc;                    /* 内存地址增量 */
  uint32_t PeriphDataAlignment;       /* 外设数据对齐 */
  uint32_t MemDataAlignment;          /* 内存数据对齐 */
  uint32_t Mode;                      /* 工作模式 */
  uint32_t Priority;                  /* 优先级 */
  uint32_t FIFOMode;                  /* FIFO模式 */
  uint32_t FIFOThreshold;             /* FIFO阈值 */
  uint32_t MemBurst;                  /* 内存突发传输 */
  uint32_t PeriphBurst;               /* 外设突发传输 */
} DMA_InitTypeDef;

2、句柄

  • DMA的句柄结构体,它包含了DMA_InitTypeDef实例、DMA实例的寄存器基地址、传输状态、错误码以及各种回调函数指针。
  • 它是所有HAL_DMA API操作的第一个参数。
c 复制代码
			typedef struct __DMA_HandleTypeDef
			{
			  DMA_Stream_TypeDef    *Instance;     /* DMA流/通道寄存器基地址 */
			  DMA_InitTypeDef       Init;          /* DMA初始化参数 */
			  HAL_LockTypeDef       Lock;          /* 锁定对象 */
			  __IO HAL_DMA_StateTypeDef State;     /* DMA状态 */
			  void                  *Parent;        /* 关联的外设句柄 */
			  void                  (* XferCpltCallback)(struct __DMA_HandleTypeDef *hdma);
			  void                  (* XferHalfCpltCallback)(struct __DMA_HandleTypeDef *hdma);
			  void                  (* XferErrorCallback)(struct __DMA_HandleTypeDef *hdma);
			  __IO uint32_t         ErrorCode;     /* 错误代码 */
			  uint32_t              StreamBaseAddress; /* 流基地址 */
			  uint32_t              StreamIndex;   /* 流索引 */
			} DMA_HandleTypeDef;

3、API

1.初始化与反初始化

c 复制代码
			HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);
			HAL_StatusTypeDef HAL_DMA_DeInit(DMA_HandleTypeDef *hdma);

2.启动传输

c 复制代码
			HAL_StatusTypeDef HAL_DMA_Start(
			    DMA_HandleTypeDef *hdma,
			    uint32_t SrcAddress,
			    uint32_t DstAddress,
			    uint32_t DataLength
			);
			HAL_StatusTypeDef HAL_DMA_Start_IT(
			    DMA_HandleTypeDef *hdma,
			    uint32_t SrcAddress,
			    uint32_t DstAddress,
			    uint32_t DataLength
);

3.传输控制

c 复制代码
			HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma);
			HAL_StatusTypeDef HAL_DMA_PollForTransfer(
			    DMA_HandleTypeDef *hdma,
			    uint32_t CompleteLevel,
			    uint32_t Timeout
); 

4.状态与回调

c 复制代码
			HAL_DMA_StateTypeDef HAL_DMA_GetState(DMA_HandleTypeDef *hdma);
			uint32_t HAL_DMA_GetError(DMA_HandleTypeDef *hdma);
			void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma);

4、状态/错误

c 复制代码
		typedef enum
		{
		  HAL_DMA_STATE_RESET      = 0x00U,  /* 未初始化/复位 */
		  HAL_DMA_STATE_READY      = 0x01U,  /* 初始化完成就绪 */
		  HAL_DMA_STATE_BUSY       = 0x02U,  /* 传输进行中 */
		  HAL_DMA_STATE_TIMEOUT    = 0x03U,  /* 超时状态 */
		  HAL_DMA_STATE_ERROR      = 0x04U,  /* 错误状态 */
		  HAL_DMA_STATE_ABORT      = 0x05U,  /* 中止状态 */
		} HAL_DMA_StateTypeDef;

五、总结

1、工作原理

  • CPU 只需配置一次参数(源地址、目标地址、数据长度、模式),后续所有数据搬运全部由 DMA 硬件自动完成,搬运完成后触发中断通知CPU。
  • 整个过程 CPU 无需干预,可同时运行其他代码。

2、两大工作模式

1)Normal 普通模式

  • 搬运完设定长度数据后,自动停止,需要代码重新开启传输。
  • 适用:单次发送、一次性数据传输。

2)Circular 循环模式

  • 搬运完成后自动重置计数器、重新开始搬运,永不停止。
  • 适用:ADC连续采集、串口持续接收、实时数据流。

3、地址递增规则

  • 内存地址:默认开启自增(每搬一个字节地址+1)。
  • 外设地址:默认关闭自增(寄存器地址固定不变)。

4、数据宽度

  • Byte 8位、HalfWord 16位、Word 32位。
  • ADC多用 HalfWord,串口多用 Byte。

5、中断类型

  • 传输完成中断 TC:一整批数据搬运完毕。
  • 半传输中断 HT:搬运一半数据(乒乓缓冲核心)。
  • 错误中断 TE:传输异常、地址错误。