STM32—DMA直接存储器访问详解
DMA——直接存储器访问
DMA:Data Memory Access, 直接存储器访问。
DMA和我们之前学过的串口、GPIO都是类似的,都是STM32中的一个外设。串口是用来发送通信数据的,而DMA则是用来把数据从一个地方搬到另一个地方,而且不占用CPU。
举个例子:
我们如果要把一串数据发送给串口 ,CPU先要把这一串数据先一个一个取回来暂存在CPU中的寄存器中,然后再一个一个发送给串口。
这样就会导致CPU不能做其他事情,CPU一直处于被占用的状态。
当DMA出现后,CPU只需要给DMA发送一条命令,如将数据发送给串口,然后DMA就来完成这个上述需要CPU完成的工作了。这就节省了CPU的资源来完成其他操作。
上面我们解释完DMA是什么后,我们接着来看DMA具体有几种方式。
1.P->M(外设到存储器)
后面的ADC数据采集使用的是这一类
2.M->P(存储器到外设)
之前的串口发送实验属于这一类
3.M->M(存储器到存储器)
存储器到存储器的实验一会儿在后面讲
我们先来看一下DMA的功能框图
DMA功能框图讲解

我们先把DMA的功能框图分为三大主要部分来讲解:1.DMA请求。2.通道。3.仲裁器
DMA请求
通过上面的DMA框图我们可以看到,DMA请求通常是由外设发起的,例如串口、GPIO、ADC等等。
那么具体的外设是如何发送DMA请求的呢?
这里就要先提一下通道这个概念,不同的外设通过具体的通道向DMA控制器发送请求,然后DMA控制器再根据通道的优先权来处理请求。
DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应这不同的外设请求。

从上图可以看出,芯片的参考手册已经帮我们把不同的外设分别对应的通道分配好了,我们使用的时候只需要去查询这个表就行了。
仲裁器
虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能接收多个。
当多个通道同时向DMA控制器发送DMA请求的时候,谁可以先使用DMA控制器呢?这时就需要仲裁器来判断通道的优先级了。
仲裁器管理DMA通道请求分为两个阶段。
第一阶段属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。见下图

第二阶段属于硬件阶段, 如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0高于通道1。
DMA数据配置
使用DMA,最核心的就是配置要传输的数据,包括数据从哪里来,要到那里去,传输数据的单位是什么,要传多少数据,是一次传输还是循环传输等等
从哪里来到那里去?
我们知道DMA传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。 具体的传输方向DMA_CCR位4 DIR配置:0表示从外设到存储器,1表示从存储器到外设。 这里面涉及到的外设地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。
我们在使用固件库编程的时候,通常不会直接配置这三个寄存器,都是通过初始化结构体来配置的,这三个寄存器对应的结构体变量如下图:

| 配置内容 | 对应寄存器 |
|---|---|
| 外设地址 | DMA_CPAR |
| 存储器地址 | DMA_CMAR |
| 传输方向 | DMA_CCR:DIR |
外设到存储器
当我们使用从外设到存储器传输时,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。
存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
存储器到存储器
当我们使用从存储器到存储器传输时,以内部FLASH向内部SRAM复制数据为例。 DMA外设寄存器的地址对应的就是内部FLASH(我们这里把内部FALSH当作一个外设来看)的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。 方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M模式。
数据要传多少,传的单位是什么?

| 配置内容 | 对应寄存器 |
|---|---|
| 传输数目 | DMA_CNDTR |
| 外设地址是否递增 | DMA_CCRx:PINC |
| 存储器地址是否递增 | DMA_CCRx:MINC |
| 外设数据宽度 | DMA_CCRx:PSIZE |
| 存储器数据宽度 | DMA_CCRx:MSIZE |
当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。
以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR配置, 这是一个32位的寄存器,一次最多只能传输65535个数据。
要想数据传输正确,源和目标地址存储的数据宽度还必须一致
串口数据寄存器是8位的, 所以我们定义的要发送的数据也必须是8位。
外设的数据宽度由**DMA_CCR的PSIZE[1:0]**配置, 可以是8/16/32位,
存储器的数据宽度由**DMA_CCR的MSIZE[1:0]**配置,可以是8/16/32位。
那么当PSIZE和MSIZE不相同时,也能传输,但是可能传输结果与你的预期不同,我们可以参照下表来查询:

在DMA控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。
外设的地址指针由DMA_CCRx的PINC配置,存储器的地址指针由MINC配置。以串口向电脑发送数据为例,要发送的数据很多, 每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个, 那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
什么时候传输完成?

| 配置内容 | 对应寄存器 |
|---|---|
| 模式选择(一次传输、循环传输) | DMA_CCRx:CIRC |
| 传输过半、传输完成、传输出错标志位 | DMA_ISR |
数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。
每个DMA通道在DMA传输过半、 传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。
有关各个标志位的详细描述请参考DMA中断状态寄存器DMA_ISR的详细描述。
传输完成还分两种模式,是一次传输还是循环传输,
一次传输很好理解,即是传输一次之后就停止,
要想再传输的话, 必须关断DMA使能后再重新配置后才能继续传输。
循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输, 不断的重复。
具体的由DMA_CCR寄存器的CIRC 循环模式位控制。
以上就是所有关于DMA的理论部分,下面我来进行程序编写。我先设计一个程序将存储器中的数据通过DMA的方式发送给串口。
我依然按照我上面讲DMA数据配置的顺序来写程序。
DMA(存储器到外设)实现代码
首先创建一个数组作为数据源,一会儿发送给串口
//准备将存储器中的这个数组中的数据发送到串口
u32 SourceBuffer[500];
然后开始配置DMA,上面我们讲DMA数据配置的时候已经说过了,我们配置DMA的时候是通过向DMA初始化结构体中填数据来配置的,所以我们先定义一个DMA初始化结构体,然后打开DMA外设的时钟信号。
//定义DMA初始化结构体
DMA_InitTypeDef DMA_initStruct;//首先查询DMA挂载在哪根总线上,然后打开DMA时钟信号
RCC_AHBPeriphClockCmd(RCC_AHBENR_DMA1EN,ENABLE);
数据从哪里来,到哪里去?
配置外设基地址
存储器基地址
数据传输方向
//通过上一篇文章对串口(USART)介绍的文章,可以找到串口数据发送的寄存器是放在USART_DR中的
DMA_initStruct.DMA_PeripheralBaseAddr = USART1_BASE + 0x04;//配置存储器的基地址,也就是数组名
DMA_initStruct.DMA_MemoryBaseAddr = (u32)SourceBuffer;//配置DMA数据传输方向,从存储器到外设,所以是外设Destination
DMA_initStruct.DMA_DIR = DMA_DIR_PeripheralDST;
数据要传多少,传的单位是什么?
我就不赘述了,请看代码中的注释
//配置发送的数据大小为500,因为数组大小为500
DMA_initStruct.DMA_BufferSize = 500;//外设地址不用自增,因为串口的发送寄存器只有一个字节这么大
DMA_initStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//配置外设的数据宽度为字节
DMA_initStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//由于数组是按顺序发送,所以存储器的地址可以自增
DMA_initStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//由于数组中的数据类型为u32,所以存储器的数据宽度配置为字
DMA_initStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
什么时候传输完成?
//发送模式配置为发送一次,建议不要配置成循环发送,因为我的查看串口发送信息的工具会卡死
DMA_initStruct.DMA_Mode = DMA_Mode_Normal;//优先级随便配置,没人跟你抢
DMA_initStruct.DMA_Priority = DMA_Priority_High;//存储器到存储器的单独配置位,这里我们是存储器到外设,所以这个位为disable
DMA_initStruct.DMA_M2M = DMA_M2M_Disable;
上面配置好后,才算完成DMA初始化结构体的配置,接下来调用初始化函数。并给通道使能信号
//调用初始化函数
DMA_Init(DMA1_Channel4,&DMA_initStruct);//DMA通道使能信号
DMA_Cmd(DMA1_Channel4,ENABLE);
DMA配置好了,下面来写main函数,注意,串口也是需要配置的。但是由于我在上一篇文章里已经详细讲述过串口的配置方法,在这里我就直接调用串口的配置函数了。
int main(){u16 i;//先用一个for循环向数组中填数据,一会儿这个数据会通过串口发送出去for(i=0; i<500; i++){SourceBuffer[i]=14;}//调用串口配置函数USART_GPIO_Config();USART_Config();//调用DMA配置函数DMA_Config();//串口1向DMA发出TX请求USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);}
相关文章:
STM32—DMA直接存储器访问详解
DMA——直接存储器访问 DMA:Data Memory Access, 直接存储器访问。 DMA和我们之前学过的串口、GPIO都是类似的,都是STM32中的一个外设。串口是用来发送通信数据的,而DMA则是用来把数据从一个地方搬到另一个地方,而且不占用CPU。…...
【JavaEE初阶系列】——网络编程 TCP客户端/服务器 程序实现
目录 🚩TCP流套接字编程 🍭ServerSocket API 🍭Socket API 🍭TCP服务器 🍭TCP客户端 🚩TCP流套接字编程 俩个关键的类 ServerSocket (给服务器使用的类,使用这个类来绑定端口号࿰…...
CMake构建OpenCv并导入QT项目过程中出现的问题汇总
前言 再此之前请确保你的环境变量是否配置,这是总共需要配置的环境变量 E:\cmake\bin E:\OpenCv\opencv\build\x64\vc15\bin F:\Qt\Tools\mingw730_64\bin F:\Qt\5.12.4\mingw73_64\bin 问题一: CMake Error: CMake was unable to find a build program…...
AcWing 796. 子矩阵的和——算法基础课题解
AcWing 796. 子矩阵的和 题目描述 输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。 对于每个询问输出子矩阵中所有数的和。 输入格式 第一行包含三个整数 n&…...
macos 查看 远程服务器是否开放某个端口
想要使用mac查看远程服务器某个端口是否开发,可通过 nc 命令,如下: nc -zv <服务器IP> <端口号>如果该端口开发,结果为:succeeded! Connection to <服务器IP> port <端口号> [类型] succeed…...
GraphQL注入
GraphQL概述 GraphQL是一种查询语言,用于API设计和数据交互,不仅仅用于查询数据库。GraphQL 允许客户端在一个请求中明确地指定需要的数据,并返回预期的结果;并且将数据查询和数据修改分离开,大大增加灵活性。GraphQL…...
以太坊源码阅读01
正所谓区块链,怎能不熟悉区块的数据结构呢?区块的结构体被保存在core/types/block.go文件中,下面是我截取出来的: type Block struct {header *Headeruncles []*Headertransactions Transactionswithdrawals Withdr…...
Spark-Scala语言实战(15)
在之前的文章中,我们学习了如何在spark中使用键值对中的学习键值对方法中的lookup,cogroup两种方法。想了解的朋友可以查看这篇文章。同时,希望我的文章能帮助到你,如果觉得我的文章写的不错,请留下你宝贵的点赞&#…...
【SpringBoot XSS存储漏洞 拦截器】Java纯后端对于前台输入值的拦截校验实现 一个类加一个注解结束
先看效果: 1.js注入拦截: 2.sql注入拦截 生效只需要两步: 1.创建Filter类,粘贴如下代码: package cn.你的包命.filter; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IO…...
【微信小程序】canvas开发笔记
【微信小程序】canvasToTempFilePath:fail fail canvas is empty 看说明书 最好是先看一下官方文档点此前往 如果是canvas 2d 写canvas: this.canvas,,如果是旧版写canvasId: ***, 解决问题 修改对应的代码,如下所示,然后再试试运行&#x…...
TripoSR: Fast 3D Object Reconstruction from a Single Image 论文阅读
1 Abstract TripoSR的核心是一个基于变换器的架构,专为单图像3D重建设计。它接受单张RGB图像作为输入,并输出图像中物体的3D表示。TripoSR的核心包括:图像编码器、图像到三平面解码器和基于三平面的神经辐射场(NeRF)。…...
u盘为什么一插上电脑就蓝屏,u盘一插电脑就蓝屏
u盘之前还好好的,可以传输文件,使用正常,但是最近使用时却出现问题了。只要将u盘一插入电脑,电脑就显示蓝屏。u盘为什么一插上电脑就蓝屏呢?一般,导致的原因有以下几种。一,主板的SATA或IDE控制器驱动损坏…...
【Redis】redis面试相关积累
Redis到底是多线程还是单线程? Redis 在设计上是单线程的,这意味着 Redis 服务器在任何给定时刻只能执行一个命令。然而,这并不意味着 Redis 无法利用多核 CPU,因为 Redis 使用了一些技术来提高性能和并发性,例如非阻…...
【Linux】进程的状态(运行、阻塞、挂起)详解,揭开孤儿进程和僵尸进程的面纱,一篇文章万字讲透!!!!进程的学习②
目录 1.进程排队 时间片 时间片的分配 结构体内存对齐 偏移量补充 对齐规则 为什么会有对齐 2.操作系统学科层面对进程状态的理解 2.1进程的状态理解 ①我们说所谓的状态就是一个整型变量,是task_struct中的一个整型变量 ②.状态决定了接下来的动作 2.2运行状态 2.…...
前端js基础知识(八股文大全)
一、js的数据类型 值类型(基本类型):数字(Number)、字符串(String)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol,大数值类型(BigInt) 引用数据类型:对象(Object)、数组…...
316_C++_xml文件解析成map,可以放到表格上 + xml、xlsx文件互相解析
xml文件例如: <?xml version"1.0" encoding"UTF-8" standalone"yes"?> <TrTable> <tr id"0" label"TR_PB_CH" text"CH%2"/> <tr id"4" label"TR_PB_CHN"…...
未来汽车硬件安全的需求(2)
目录 4.汽车安全控制器 4.1 TPM2.0 4.2 安全控制器的硬件保护措施 5. EVITA HSM和安全控制器结合 6.小结 4.汽车安全控制器 汽车安全控制器是用于汽车工业安全关键应用的微控制器。 他们的保护水平远远高于EVITA HSM。今天的典型应用是移动通信,V2X、SOTA、…...
html+javascript,用date完成,距离某一天还有多少天
图片展示: html代码 如下: <style>* {margin: 0;padding: 0;}.time-item {width: 500px;height: 45px;margin: 0 auto;}.time-item strong {background: orange;color: #fff;line-height: 100px;font-size: 40px;font-family: Arial;padding: 0 10px;margin-right: 10px…...
跟bug较劲的第n天,undefined === undefined
前情提要 场景复现 看到这张图片,有的同学也许不知道这个冷知识,分享一下,是因为我在开发过程中踩到的坑,花了三小时排查出问题的原因在这,你们说值不值。。。 我分享下我是怎么碰到的这个问题,下面看代码…...
数据结构_基于链表的通讯录
顺序表的源代码需要略作修改,如下 将数据类型改为通讯录的结构体。注释掉打印,查找的函数。 SList.h #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> #include<stdlib.h> #include<assert.h> #include"Contact.h"ty…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
