【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
### STM32 DMA配置及使用教程 #### 1. 环境准备 为了成功配置和使用STM32DMA功能,需确保已安装STM32CubeMX工具以及对应的目标芯片软件包。这些工具能够帮助开发者快速完成硬件初始化并生成基础代码框架[^1]。 #### 2. 基础概念理解 DMA (Direct Memory Access),即直接存储器存取技术,在嵌入式系统中用于减少CPU参与外设数据传输的工作量。对于STM32系列微控制器而言,其内部集成了多个独立的DMA通道来支持不同类型的外设操作,比如UART通信、ADC采样等场景下的高速数据搬运任务[^2]。 #### 3. 使用STM32CubeMX进行基本设置 利用STM32CubeMX可以非常方便地完成大部分关于DMA的基础设定工作: - 打开项目后找到目标外设(例如串口或者模拟数字转换模块),激活它; - 进入高级参数选项卡调整具体属性值,如方向(In to Mem/Mem To Out)、优先级等等; - 同时也要记得关联合适的中断服务程序以便后续处理可能发生的错误情况或者是结束标志通知机制[^1]。 #### 4. 编写自定义函数实现特定需求 当初步架构搭建完毕之后,则需要依据实际应用场景编写相应的驱动逻辑代码片段。下面给出了一段基于低层接口(LL Library)实现简单字符串通过指定串口号经由DMA方式发出的例子: ```c void UART_DMA_Send(USART_TypeDef *USARTx, uint8_t *str, uint16_t size){ // 设置内存地址给定的数据缓冲区首址作为源位置 LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_7, (uint32_t)str); // 定义本次要传送字节数目 LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_7, size); // 开启所选流号开始执行真正的物理拷贝过程 LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_7); } ``` 以上示例展示了如何构建一个通用型发送子程序,其中涉及到几个关键动作点均已被封装成单独API调用形式呈现出来便于理解和维护扩展[^2]。 #### 5. 测试验证效果 最后一步就是把前面几步产生的成果部署到真实的硬件平台上加以检验确认预期行为是否达成。这通常包括编译链接整个工程项目得到最终固件镜像文件下载烧录至目标板卡之上运行起来观察现象变化趋势并与理论分析对比找出差异原因所在直至完全匹配为止。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT阳晨。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值