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转运数据的流程。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80365.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

聊聊JetCache的缓存构建

序 本文主要研究一下JetCache的缓存构建 invokeWithCached com/alicp/jetcache/anno/method/CacheHandler.java private static Object invokeWithCached(CacheInvokeContext context)throws Throwable {CacheInvokeConfig cic context.getCacheInvokeConfig();CachedAnnoC…

c#队列及其操作

可以用数组、链表实现队列&#xff0c;大致与栈相似&#xff0c;简要介绍下队列实现吧。值得注意的是循环队列判空判满操作&#xff0c;在用链表实现时需要额外思考下出入队列条件。 设计头文件 #ifndef ARRAY_QUEUE_H #define ARRAY_QUEUE_H#include <stdbool.h> #incl…

开源项目实战学习之YOLO11:12.3 ultralytics-models-sam-encoders.py源码分析

👉 点击关注不迷路 👉 点击关注不迷路 👉 另外,前些天发现了一个巨牛的AI人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。感兴趣的可以点击相关跳转链接。 点击跳转到网站。 ultralytics-models-sam 1.sam-modules-encoders.pyblocks.py: 定义模型中的各…

STM32 | FreeRTOS 消息队列

01 一、概述 队列又称消息队列&#xff0c;是一种常用于任务间通信的数据结构&#xff0c;队列可以在任务与任务间、中断和任务间传递信息&#xff0c;实现了任务接收来自其他任务或中断的不固定长度的消息&#xff0c;任务能够从队列里面读取消息&#xff0c;当队列中的消…

Java 安全漏洞扫描工具:如何快速发现和修复潜在问题?

Java 安全漏洞扫描工具&#xff1a;如何快速发现和修复潜在问题&#xff1f; 在当今的软件开发领域&#xff0c;Java 作为一种广泛使用的编程语言&#xff0c;其应用的规模和复杂度不断攀升。然而&#xff0c;随着应用的拓展&#xff0c;Java 应用面临的潜在安全漏洞风险也日益…

Python绘制克利夫兰点图:从入门到实战

Python绘制克利夫兰点图&#xff1a;从入门到实战 引言 克利夫兰点图&#xff08;Cleveland Dot Plot&#xff09;是一种强大的数据可视化工具&#xff0c;由统计学家William Cleveland在1984年提出。这种图表特别适合展示多个类别的数值比较&#xff0c;比传统的条形图更直观…

LVGL- Calendar 日历控件

1 日历控件 1.1 日历背景 lv_calendar 是 LVGL&#xff08;Light and Versatile Graphics Library&#xff09;提供的标准 GUI 控件之一&#xff0c;用于显示日历视图。它支持用户查看某年某月的完整日历&#xff0c;还可以实现点击日期、标记日期、导航月份等操作。这个控件…

多指标组合策略

该策略(MultiConditionStrategy)是一种基于多种技术指标和市场条件的交易策略。它通过综合考虑多个条件来生成交易信号,从而决定买入或卖出的时机。 以下是对该策略的详细分析: 交易逻辑思路 1. 条件1:星期几和价格变化判断 - 该条件根据当前日期是星期几以及价格的变化…

BC 范式与 4NF

接下来我们详细解释 BC 范式&#xff08;Boyce-Codd范式&#xff0c;简称 BCNF&#xff09;&#xff0c;并通过具体例子说明其定义和应用。 一、BC范式的定义 BC范式&#xff08;Boyce-Codd范式&#xff0c;BCNF&#xff09;是数据库规范化理论中的一种范式&#xff0c;它比第…

基于 CSS Grid 的网页,拆解页面整体布局结构

通过以下示例拆解网页整体布局结构&#xff1a; 一、基础结构&#xff08;HTML骨架&#xff09; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"…

采购流程规范化如何实现?日事清流程自动化助力需求、采购、财务高效协作

采购审批流程全靠人推进&#xff0c;内耗严重&#xff0c;效率低下&#xff1f; 花重金上了OA&#xff0c;结果功能有局限、不灵活&#xff1f; 问题出在哪里&#xff1f;是我们的要求太多、太苛刻吗&#xff1f;NO&#xff01; 流程名称&#xff1a; 采购审批管理 流程功能…

全栈项目搭建指南:Nuxt.js + Node.js + MongoDB

全栈项目搭建指南&#xff1a;Nuxt.js Node.js MongoDB 一、项目概述 我们将构建一个完整的全栈应用&#xff0c;包含&#xff1a; 前端&#xff1a;Nuxt.js (SSR渲染)后端&#xff1a;Node.js (Express/Koa框架)数据库&#xff1a;MongoDB后台管理系统&#xff1a;集成在同…

NVMe简介6之PCIe事务层

PCIe的事务层连接了PCIe设备核心与PCIe链路&#xff0c;这里主要基于PCIe事务层进行分析。事务层采用TLP传输事务&#xff0c;完整的TLP由TLPPrefix、TLP头、Payload和TLP Digest组成。TLP头是TLP中最关键的部分&#xff0c;一般由三个或四个双字的长度&#xff0c;其格式定义如…

Python异常模块和包

异常 当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是所谓的“异常”, 也就是我们常说的BUG 例如&#xff1a;以r方式打开一个不存在的文件。 f open(‘python1.txt’,‘r’,encoding‘utf-8’) 当我们…

汇编:循环程序设计

一、 实验要求 熟练掌握循环程序设计的基本方法熟练掌握单片机外部存储空间的访问方法 二、 实验设计 1.整体思路 先初始化一些寄存器和数据存储位置&#xff0c;然后调用两个子程序Procedure1和Procedure2&#xff0c;分别从SRC复制数据到DEST&#xff0c;一个从开头到末尾&…

典籍知识问答模块AI问答bug修改

一、修改流式数据处理问题 1.问题描述&#xff1a;由于传来的数据形式如下&#xff1a; event:START data:350 data:< data:t data:h data:i data:n data:k data:> data: data: data: data: data:嗯 data:&#xff0c; 导致需要修改获取正常的当前信息id并更…

【金仓数据库征文】- 金融HTAP实战:KingbaseES实时风控与毫秒级分析一体化架构

文章目录 引言&#xff1a;金融数字化转型的HTAP引擎革命一、HTAP架构设计与资源隔离策略1.1 混合负载物理隔离架构1.1.1 行列存储分区策略1.1.2 四级资源隔离机制 二、实时流处理与增量同步优化2.1 分钟级新鲜度保障2.1.1 WAL日志增量同步2.1.2 流计算优化 2.2 物化视图实时刷…

季报中的FPGA行业:U型反转,春江水暖

上周Lattice,AMD两大厂商相继发布2025 Q1季报,尽管恢复速度各异,但同时传递出FPGA行业整体回暖的复苏信号。 5月5日,Lattice交出了“勉强及格”的答卷,报告季度营收1亿2000万,与华尔街的预期基本相符。 对于这家聚焦在中小规模器件的领先厂商而言,按照其CEO的预期,长…

使用 javap 深入理解 Java 字节码

引言 Java 是一种广泛使用的高级编程语言,其独特之处在于编译后的代码不是直接的机器码,而是一种称为字节码的中间表示形式。字节码存储在 .class 文件中,由 Java 虚拟机 (JVM) 解释或即时编译为特定平台的机器码。这种设计赋予了 Java 平台无关性,即“一次编写,到处运行…

LeetCode_sql刷题(3482.分析组织层级)

题目描述&#xff1a;3482. 分析组织层级 - 力扣&#xff08;LeetCode&#xff09; 表&#xff1a;Employees ------------------------- | Column Name | Type | ------------------------- | employee_id | int | | employee_name | varchar | | manager_id …