深入解析:【STM32】DMA

1 DMA介绍

1.1 什么是DMA?

令人头秃的描述:

DMA(Direct Memory Access,直接存储器访问)提供在外设与内存、存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用。

简单描述:

就是一个数据搬运工!!

1.2 DMA的意义

代替 CPU 搬运数据,为 CPU 减负。

  1. 数据搬运的工作比较耗时间;
  2. 数据搬运工作时效要求高(有数据来就要搬走);
  3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事);

1.3 搬运什么数据?

存储器、外设

这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。

三种搬运方式:

  • 存储器→存储器(例如:复制某特别大的数据buf)
  • 存储器→外设 (例如:将某数据buf写入串口TDR寄存器)
  • 外设→存储器 (例如:将串口RDR寄存器写入某数据buf)

存储器→存储器

在这里插入图片描述

存储器→外设

在这里插入图片描述

外设→存储器

在这里插入图片描述

2 DMA框图

在这里插入图片描述

3 DMA控制器

STM32F103 有 2 个 DMA 控制器,DMA1 有 7 个通道,DMA 2 有 5 个通道。

一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。

STM32F103C8T6 只有 DMA1!

DMA1有7个通道:

在这里插入图片描述

DMA2 有 5 个通道:

在这里插入图片描述

4 DMA优先级管理

优先级管理采用软件+硬件:

  • 软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级
    最高级>高级>中级>低级
  • 硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。

比如:如果软件优先级相同,通道2优先于通道4

在这里插入图片描述

5 DMA传输方式

  • DMA_Mode_Normal(正常模式)
    一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
  • DMA_Mode_Circular(循环传输模式)
    当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式

6 指针递增模式

外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。

在这里插入图片描述

7 DMA数据对齐方式

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

8 DMA寄存器及库函数介绍

__HAL_RCC_DMA1_CLK_ENABLE()
HAL_DMA_Init()
HAL_DMA_Start()
__HAL_LINKDMA()
HAL_UART_Transmit_DMA()
HAL_UART_Receive_DMA()
__HAL_DMA_GET_FLAG()
__HAL_DMA_ENABLE()
__HAL_DMA_DISABLE()

小实验1:DMA内存到内存数据搬运

实验目的

使用 DMA 将一个大数组的数据搬运到另一个位置。

流程

在这里插入图片描述

代码

main.c

#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "dma.h"
int main(void)
{
HAL_Init();                         /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
//LED初始化
led_init();
//串口1初始化
uart1_init(115200);
//DMA初始化
dma_init();
printf("打印测试:hello world\r\n");
//数据转运
dma_transmit();
while(1)
{
led1_on();
led2_off();
delay_ms(500);
led1_off();
led2_on();
delay_ms(500);
}
}

dma.c

#include "dma.h"
#include <stdio.h>
  #define BUF_SIZE 16
  uint32_t src_buf[BUF_SIZE] = {
  0x00000000,0x11111111,0x22222222,0x33333333,
  0x44444444,0x55555555,0x66666666,0x77777777,
  0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
  0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
  };
  uint32_t dst_buf[BUF_SIZE] = {0};
  DMA_HandleTypeDef dma_handle = {0};
  //DMA初始化
  void dma_init(void)
  {
  __HAL_RCC_DMA1_CLK_ENABLE();
  dma_handle.Instance = DMA1_Channel1;
  dma_handle.Init.Direction = DMA_MEMORY_TO_MEMORY;
  //内存相关配置
  dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;    //数据对齐方式
  dma_handle.Init.MemInc = DMA_MINC_ENABLE;                  //数据增长方式
  //外设相关配置
  dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //数据对齐方式
  dma_handle.Init.PeriphInc = DMA_PINC_ENABLE;               //数据增长方式
  //优先级和模式
  dma_handle.Init.Priority =DMA_PRIORITY_MEDIUM;
  dma_handle.Init.Mode = DMA_NORMAL;
  //初始化函数
  HAL_DMA_Init(&dma_handle);
  }
  //数据转运
  void dma_transmit(void)
  {
  //开始DMA转运
  HAL_DMA_Start(&dma_handle, (uint32_t)src_buf, (uint32_t)dst_buf, BUF_SIZE*sizeof(uint32_t));
  //查看数据转运标志位
  while(__HAL_DMA_GET_FLAG(&dma_handle, DMA_FLAG_TC1) == RESET);
  int i = 0;
  //打印数据
  for(i = 0; i < BUF_SIZE; i++){
  printf("buf[%d] = %x\r\n",i,dst_buf[i]);
  }
  }

dma.h

#ifndef __DMA_H__
#define __DMA_H__
#include "sys.h"
//DMA初始化
void dma_init(void);
//数据转运
void dma_transmit(void);
#endif

小实验2:DMA内存到外设数据搬运

实验目的

使用 DMA 将一个大数组的数据通过串口 1 发送。

流程

在这里插入图片描述

代码

main.c

#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "dma.h"
extern UART_HandleTypeDef uart1_handle;
uint8_t send_buf[1000] = {0};
int main(void)
{
HAL_Init();                         /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
//LED初始化
led_init();
//串口1初始化
uart1_init(115200);
//DMA初始化
dma_init();
printf("打印测试:hello world\r\n");
int i = 0;
for(i = 0;i < 1000;i ++){
send_buf[i] = 'A';
}
//串口DMA发送
HAL_UART_Transmit_DMA(&uart1_handle,send_buf,1000);
while(1)
{
led1_on();
led2_off();
delay_ms(500);
led1_off();
led2_on();
delay_ms(500);
}
}

dma.c

#include "dma.h"
#include <stdio.h>
  extern UART_HandleTypeDef uart1_handle;
  DMA_HandleTypeDef dma_handle = {0};
  //DMA初始化
  void dma_init(void)
  {
  __HAL_RCC_DMA1_CLK_ENABLE();
  dma_handle.Instance = DMA1_Channel4;                //DMA1通道4
  dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;   //从内存到外设
  //内存相关配置
  dma_handle.Init   .MemDataAlignment = DMA_MDATAALIGN_BYTE;    //数据对齐方式
  dma_handle.Init.MemInc = DMA_MINC_ENABLE;                  //数据增长方式
  //外设相关配置
  dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //数据对齐方式
  dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;              //数据增长方式
  //优先级和模式
  dma_handle.Init.Priority =DMA_PRIORITY_MEDIUM;
  dma_handle.Init.Mode = DMA_NORMAL;
  //初始化函数
  HAL_DMA_Init(&dma_handle);
  //链接串口和DMA
  __HAL_LINKDMA(&uart1_handle, hdmatx, dma_handle);
  }

dma.h

#ifndef __DMA_H__
#define __DMA_H__
#include "sys.h"
//DMA初始化
void dma_init(void);
#endif

小实验3:DMA外设到内存数据搬运

实验目的

使用 DMA 接收串口 1 数据。

流程

在这里插入图片描述

代码

main.c

#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "dma.h"
extern UART_HandleTypeDef uart1_handle;
uint8_t send_buf[1000] = {0};
int main(void)
{
HAL_Init();                         /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
//LED初始化
led_init();
//串口1初始化
uart1_init(115200);
//DMA初始化
dma_init();
printf("打印测试:hello world\r\n");
while(1)
{
led1_on();
led2_off();
delay_ms(500);
led1_off();
led2_on();
delay_ms(500);
}
}

dma.c

#include "dma.h"
#include <stdio.h>
  #include "uart1.h"
  extern UART_HandleTypeDef uart1_handle;
  extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];
  DMA_HandleTypeDef dma_handle = {0};
  //DMA初始化
  void dma_init(void)
  {
  __HAL_RCC_DMA1_CLK_ENABLE();
  dma_handle.Instance = DMA1_Channel5;                //DMA1通道5
  dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;   //从外设到内存
  //内存相关配置
  dma_handle.Init   .MemDataAlignment = DMA_MDATAALIGN_BYTE;    //数据对齐方式
  dma_handle.Init.MemInc = DMA_MINC_ENABLE;                  //数据增长方式
  //外设相关配置
  dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //数据对齐方式
  dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;              //数据增长方式
  //优先级和模式
  dma_handle.Init.Priority =DMA_PRIORITY_MEDIUM;
  dma_handle.Init.Mode = DMA_NORMAL;
  //初始化函数
  HAL_DMA_Init(&dma_handle);
  //链接串口和DMA
  __HAL_LINKDMA(&uart1_handle, hdmarx, dma_handle);
  //使能串口DMA接收
  HAL_UART_Receive_DMA(&uart1_handle, uart1_rx_buf, UART1_RX_BUF_SIZE);
  }

dma.h

#ifndef __DMA_H__
#define __DMA_H__
#include "sys.h"
//DMA初始化
void dma_init(void);
#endif
posted @ 2025-12-21 08:08  clnchanpin  阅读(78)  评论(0)    收藏  举报