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

STM32中的DMA

DMA介绍

什么是DMA?

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

        简单描述: 就是一个数据搬运工

DMA的意义

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

1.数据搬运的工作比较耗时间;

2. 数据搬运工作时效要求高(有数据来就要搬走);

3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。

搬运数据的方式

有三种方式:存储器到存储器、存储器到外设、外设到存储器

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

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

存储器存储器

存储器外设 

外设存储器 

 DMA框图

说明:利用DMA进行外设的数据搬运,首先,外设需向DMA1进项请求,然后,经过DMA的仲裁之后,DMA访问外设的数据进行搬运。 

DMA控制器 

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

注意:

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

  • DMA2 5 个通道: 

DMA优先级管理 

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

软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级:最高级>高级>中级>低级。

硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4。

 DMA传输方式与指针递增模式

  • 传输方式
DMA_Mode_Normal (正常模式)

一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次。

DMA_Mode_Circular (循环传输模式)

当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式。

  • 指针递增模式

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

情况1:

情况2:(目标只有一个存储数据的位置,例如:串口只有一个数据寄存器)

 DMA数据对齐方式

  • 数据宽度大的转移到数据宽度小的时候,低位保留高位截断

DMA寄存器 

  •  DMA中断状态寄存器(DMA_ISR)

  • DMA中断标志清除寄存器(DMA_IFCR) 

  •  DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)

  •  DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)

 16位寄存器,最多可以传输数量65536。

  • DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)

  • DMA通道x存储器地址寄存器(DMA_CMARx)(x = 1…7) 

DMA的库函数

在hal.dma.c文件中的一些常用的函数:

打开dma1时钟的函数: 

在hal.dma.h文件中的一些常用的宏函数: 

在hal_dma_ex.h文件中,获取 传输完成标志位

在hal.def.h文件中还存在所需的下面的函数: 

 若要读取外设的数据还需要相关串口的函数,如下:

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

 实验目的

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

硬件清单

开发板、ST-Link、USB转TTL

配置流程

文件代码 

  • dma.c文件代码
#include "dma.h"
#include "stdio.h"#define BUF_SIZE 16uint32_t src_buf[BUF_SIZE] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};uint32_t dst_buf[BUF_SIZE] = {0};/**
* @breif    DMA的初始化函数
* @note     打开时钟,配置相关参数
* @param    无
* @retval   无
*/
DMA_HandleTypeDef dma_handle = {0};
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;     /* 源:内存数据对齐模式:一般是8位 */dma_handle.Init.MemInc = DMA_MINC_ENABLE;                   /* 源:内存数据指针递增的方式 */dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  /* 目标:外设数据对齐模式:一般是8位 */dma_handle.Init.PeriphInc = DMA_PINC_ENABLE;                /* 目标:外设数据指针递增的方式 */dma_handle.Init.Mode = DMA_NORMAL;                          /* 传输的模式:循环,不循环。内存到内存不支持循环模式 */dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;             /* 通道的优先级设置:有四种*/HAL_DMA_Init(&dma_handle);}/**
* @breif    封装一个函数进行数据的转运
* @note     利用DMA_Start函数进行数据的搬运,当搬运完成后查看标志位是否置1,然后进行打印
* @note     在DMA_Start()函数中,数据的长度要写成sizeof(uint32_t)*BUF_SIZE,不能写成BUF_SIZE
* @param    无
* @retval   无
*/
void dma_transmit(void){HAL_DMA_Start(&dma_handle,(uint32_t)src_buf,(uint32_t)dst_buf,sizeof(uint32_t)*BUF_SIZE);while(__HAL_DMA_GET_FLAG(&dma_handle,DMA_FLAG_TC1) == RESET);for(uint16_t i = 0;i < BUF_SIZE;i++){printf("传输的数据是:%d \r\n",dst_buf[i]);}        
}
  • dma.h文件代码 
#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"void dma_init(void);
void dma_transmit(void);#endif
  • mian.c文件代码 
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "dma.h"int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */led_init();                         /* LED初始化 */uart1_init(115200);printf("hello,world");dma_init();dma_transmit();while(1){ }
}

 注意事项:

  • 关于数据传输中最后一行出现异常值(如从15突然跳变到1073872904),如下所示:

原因:缓冲区溢出或内存越界

解决方式:将上面的等号去掉,就会正常。

  • *****关于dma.c文件中的while循环中的判断条件:==RESET,而不能用!=SET*****

原因:!= SET 还可能代表其他未定义的状态,例如:硬件错误、无效参数,导致条件判断不准确。

因此,要直接使用官方推荐条件,可避免兼容性的问题。

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

 实验目的

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

硬件清单

开发板、ST-Link、USB转TTL

配置流程 

文件代码 

  •  dma.c文件代码
#include "dma.h"
#include "stdio.h"
extern UART_HandleTypeDef uart1_handle;/**
* @breif    DMA的初始化函数
* @note     打开时钟,配置相关参数
* @param    无
* @retval   无
*/
DMA_HandleTypeDef dma_handle = {0};
void dma_init(void){__HAL_RCC_DMA1_CLK_ENABLE();dma_handle.Instance = DMA1_Channel4;                        /* 查看表格:看所需的DMA通道:通道4, */dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;           /* 搬运数据的方式:内存到内存,外设到内存,内存到外设 *///内存相关的配置dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     /* 源:内存数据对齐模式:一般是8位 */dma_handle.Init.MemInc = DMA_MINC_ENABLE;                   /* 源:内存数据指针递增的方式 */dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  /* 目标:外设数据对齐模式:一般是8位 */dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;               /* 目标:串口发送寄存器数据指针是不能递增的 */dma_handle.Init.Mode = DMA_NORMAL;                          /* 传输的模式:循环,不循环。内存到内存不支持循环模式 */dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;             /* 通道的优先级设置:有四种*/HAL_DMA_Init(&dma_handle);__HAL_LINKDMA(&uart1_handle,hdmatx,dma_handle);             /* 将内存和外设的地址进行连接,注意:dma句柄前面不用加&*/   
}/**
* @breif    封装一个函数进行数据的转运
* @note     利用DMA_Start函数进行数据的搬运,当搬运完成后查看标志位是否置1,然后进行打印
* @note     在DMA_Start()函数中,数据的长度要写成sizeof(uint32_t)*BUF_SIZE,不能写成BUF_SIZE
* @param    无
* @retval   无
*/

 dma.h文件代码

#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"void dma_init(void);#endif
  • main.c文件代码 
#include "sys.h"
#include "led.h"
#include "delay.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_init();                         /* LED初始化 */uart1_init(115200);
//    printf("hello,world");dma_init();int i = 0;for(i = 0; i < 1000;i++){send_buf[i] = 'A';}HAL_UART_Transmit_DMA(&uart1_handle,send_buf,1000);while(1){ }
}

总结:

  • 本代码是将内存中的数据转运到串口的发送数据的寄存器中,利用的函数HAL_UART_Transmit_DMA()函数,然后通过串口打印到串口调试助手的界面上。
  • 与利用printf函数不同;
  • 在将数据由内存转运到内存中时,利用的是HAL_DMA_Start()函数,通过判断传输完成标志位的函数__HAL_DMA_GET_FLAG()函数 ==SET(表明数据传输完成)。,然后利用printf()函数将数据打印出来。
  • 在dma.c和main.c文件中用到串口初始化函数的句柄,所以,要注意利用extern声明一下外部变量。
  • 实验现象

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

实验目的

使用DMA接收串口的数据

硬件清单

开发板、ST-Link、USB转TTL 

函数的意义:声明一个接收缓冲区(数组);这个函数返回接收完数据后剩余的数组长度。

可以用来计算,传输数据的长度: 

例:

uart1_rx_len = UART1_RX_BUF_SIZE-__HAL_DMA_GET_COUNTER(&dma_handle);

配置流程 

文件代码 

  • dma.c文件代码
#include "dma.h"
#include "stdio.h"
extern UART_HandleTypeDef uart1_handle;extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];                                    /* UART1接收缓冲区 *//**
* @breif    DMA的初始化函数
* @note     打开时钟,配置相关参数
* @param    无
* @retval   无
*/
DMA_HandleTypeDef dma_handle = {0};
void dma_init(void){__HAL_RCC_DMA1_CLK_ENABLE();dma_handle.Instance = DMA1_Channel5;                        /* 查看表格:UART1_RX的DMA通道:通道5 */dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;           /* 搬运数据的方式:内存到内存,外设到内存,内存到外设 *///内存相关的配置dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     /* 源:内存数据对齐模式:一般是8位 */dma_handle.Init.MemInc = DMA_MINC_ENABLE;                   /* 源:内存数据指针递增的方式 */dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  /* 目标:外设数据对齐模式:一般是8位 */dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;               /* 目标:串口发送寄存器数据指针是不能递增的 */dma_handle.Init.Mode = DMA_NORMAL;                          /* 传输的模式:循环,不循环。内存到内存不支持循环模式 */dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;             /* 通道的优先级设置:有四种*/HAL_DMA_Init(&dma_handle);__HAL_LINKDMA(&uart1_handle,hdmarx,dma_handle);             /* 将内存和外设的地址进行连接,注意:dma句柄前面不用加&*/  HAL_UART_Receive_DMA(&uart1_handle,uart1_rx_buf,UART1_RX_BUF_SIZE);  /* 打开串口的DMA数据转运,*/
}
  •  dma.h文件代码
#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"#define UART1_RX_BUF_SIZE            128
void dma_init(void);#endif
  • uart1.c文件代码 

  • main.c文件代码 
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "dma.h"int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */led_init();                         /* LED初始化 */uart1_init(115200);printf("hello,world\r\n");dma_init();while(1){ }
}

注意事项:

  • 注意在串口中断函数中书写的DMA转运数据的流程。 

相关文章:

  • Vue3学习(组合式API——父、子组件间通信详解)
  • C++学习:六个月从基础到就业——C++11/14:auto类型推导
  • Linux517 rsync同步 rsync借xinetd托管 配置yum源回顾
  • ChatGPT + DeepSeek 联合润色的 Prompt 模板指令合集,用来润色SCI论文太香了!
  • SECERN AI提出3D生成方法SVAD!单张图像合成超逼真3D Avatar!
  • day27 python 装饰器
  • 低空经济发展现状与前景
  • 使用lvm进行磁盘分区
  • 致敬经典 << KR C >> 之打印输入单词水平直方图和以每行一个单词打印输入 (练习1-12和练习1-13)
  • 基于Spring Boot和Vue的在线考试系统架构设计与实现(源码+论文+部署讲解等)
  • DeerFlow试用
  • 基于单片机的防盗报警器设计与实现
  • RT Thread FinSH(msh)调度逻辑
  • 计算机网络体系结构深度解析:从理论到实践的全面梳理
  • UE中的各种旋转
  • 视频下载器 2.3.9 | 自动识别并下载网页视频,界面简洁无广告带私密空间
  • AIStarter Windows 版本迎来重磅更新!模型插件工作流上线,支持 Ollama / ComfyUI 等多平台本地部署模型统一管理
  • c/c++的opencv的轮廓匹配初识
  • 使用 CodeBuddy 开发一款富交互的屏幕录制与注释分享工具开发纪实
  • A级、B级弱电机房数据中心建设运营汇报方案
  • 江南考古文脉探寻
  • 舞者王佳俊谈“与AI共舞”:像多了一个舞伴,要考虑它的“感受”
  • 华东政法与复旦上医签署合作框架协议,医学与法学如何交叉融合?
  • 微软宣布全球裁员约3%:涉及约6000人,侧重经理层
  • 订婚不等于性同意!山西订婚强奸案入选最高法案例
  • 马上评|让查重回归促进学术规范的本意