【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寄存器及库函数介绍

c 复制代码
__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

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

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

c 复制代码
#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

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

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

c 复制代码
#ifndef __DMA_H__
#define __DMA_H__

#include "sys.h"

//DMA初始化
void dma_init(void);

#endif

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

实验目的

使用 DMA 接收串口 1 数据。

流程

代码

main.c

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

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

c 复制代码
#ifndef __DMA_H__
#define __DMA_H__

#include "sys.h"

//DMA初始化
void dma_init(void);

#endif
相关推荐
淘晶驰AK7 小时前
电机控制是不是就绑死在单片机上了
单片机·嵌入式硬件
CS Beginner7 小时前
【单片机】orange prime pi开发板与单片机的区别
笔记·嵌入式硬件·学习
bubiyoushang88817 小时前
基于STM32F103与A3988驱动芯片的两相四线步进电机控制方案
stm32·单片机·嵌入式硬件
bai54593621 小时前
STM32 备份寄存器
stm32·单片机·嵌入式硬件
cold_Mirac21 小时前
stm32-freertos和逻辑编程下堆栈功能的区分
stm32·单片机·嵌入式硬件
youcans_21 小时前
【动手学STM32G4】(3)上位机实时显示多路波形
stm32·单片机·嵌入式硬件·上位机
铁手飞鹰1 天前
[HAL库分析—GPIO]
c语言·stm32·单片机·嵌入式硬件
徐某人..1 天前
网络编程学习--第一天
arm开发·单片机·学习·arm
yrx0203071 天前
STM32F103通过L298N驱动两相4线步进电机【42步进电机】
stm32·单片机·嵌入式硬件·步进电机
是大强1 天前
3d打印材料asa和abs区别
嵌入式硬件