STM32学习笔记12:DMA 的学习和使用_芯片的dma-程序员宅基地

技术标签: stm32  学习  笔记  STM32学习笔记  

芯片型号:STM32F103RC

软件开发包:标准外设库

一、DMA 简介

  • DMA(Direct Memory Access)直接存储器存取
  • DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  • 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道),DMA2 只存在于大容量的单片机中
  • 每个通道都支持软件触发和特定的硬件触发

二、DMA 框图

DMA 框图

2.1 DMA 请求

如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求, DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。

2.2 通道

DMA 具有 12 个独立可编程的通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

2.3 仲裁器

当多个 DMA 通道请求同时发生时,仲裁器负责管理其处理顺序。

仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先级越高,比如通道 0 高于通道 1。此外,在大容量产品和互联型产品中, DMA1 控制器的优先级高于 DMA2 控制器。

三、DMA 基本结构

DMA 基本结构

3.1 站点以及相关参数

如图,外设寄存器、Flash 和 SRAM 分别是数据转运的两大站点,前者是外设寄存器站点,后两者组成存储器站点。在 STM32 中,存储器一般特指 Flash 和 SRAM,不包含外设寄存器,所以就描述成外设到存储器、存储器到存储器。

DMA 的数据转运可以是从外设到存储器,也可以从存储器到外设,通过配置参数来控制方向。另外还可以从存储器转运到存储器,比如 Flash 到 SRAM或者 SRAM 到 SRAM。由于 Flash 是只读的,所以 DMA 不可以进行 SRAM 到 Flash,或者 Flash 到 Flash 的转运操作。

进行数据转运就需要指定起始点和终点以及转运方式,对此,外设和存储器都有 3 个参数,分别是起始地址,数据宽度和地址是否自增

起始地址分为外设端的起始地址和存储器端的起始地址,决定了数据起始点和终点;数据宽度指定了一次转运要按多大的数据宽度来进行,可以选择字节、半字和字;地址是否自增指定一次转运完成后下一次转运是否要移动地址。

如果要进行存储器到存储器的数据转运,就需要把其中一个存储器的地址放在外设的站点。只要在外设起始地址里写 Flash 或者 SRAM 的地址,那它就会去Flash 或者 SRAM 找数据。这个站点只是叫做外设寄存器,并没限制只能指定为外设,存储器站点也是如此。

3.2 传输计数器

下面有个传输计数器,用于指定转运次数的,是一个自减计数器,当减到 0 之后,DMA 就不会再进行数据转运了。另外,它减到 0 之后,之前自增的地址也会恢复到起始地址,以方便新一轮的转换。

在传输寄存器的右边有一个自动重装器,作用为传输计数器减到 0 之后,是否要自动恢复到最初的值。比如最初传输计数器值为 5,如果不使用自动重装器,转运 5 次后,DMA 就停止了;如果使用自动重装器,转运 5 次,计数器减到 0 后,就会立即恢复到 5。所以,自动重装器决定了转运的模式,单次模式或循环模式。

3.3 触发控制

再下面就是 DMA 的触发控制,有硬件触发和软件触发, 由 DMA_CCRx 寄存器的 MEM2MEM 位控制。

软件触发不是调用某个函数一次,触发一次,而是以最快的速度,连续不断地触发 DMA,尽快使传输计数器清零,完成一轮的转换。**软件触发和循环模式不能同时使用,因为软件触发就是想把传输计数器清零,循环模式是清零后自动重装,如果同时用的话,DMA 就停不下来了。**软件触发一般适用于存储器到存储器的转运,因为存储器到存储器的转运是软件启动,不需要时机,并且想尽快完成。

硬件触发源可以选择 ADC、串口、定时器等,使用硬件触发的转运,一般都与外设有关,这些转运需要一定的时机,比如 ADC 转换完成。串口收到数据。定时时间到等,所以需要使用硬件触发,在硬件达到这些时机时,传递信号来触发 DMA 进行转运。

3.4 开关控制

最后还有一个开关控制,也就是 DMA_Cmd 函数。当 DMA 使能后,DMA 就准备就绪了,可以进行转运了。

3.5 转运的条件

DMA 进行转运有几个条件:

  1. 开关控制,必须使能 DMA;
  2. 传输计数器必须大于 0;
  3. 必须有触发信号,触发一次,转运一次,传输计数器自减 1,当传输计数器 = 0,且没有自动重装时,这时无论是否触发,DMA 都不会再进行转运了。此时就需要 DMA_Cmd 给 DISABLE,关闭 DMA,再为传输计数器写如一个大于 0 的数,再 DMA_Cmd 给 ENABLE,使能 DMA。

注意:写传输计数器时,必须先关闭 DMA,再进行。不能在 DMA 开启时,写传输计数器。

四、DMA请求

DMA请求映像

如图是 DMA1 的请求映像,有 7 个通道,每个通道都有一个数据选择器,可以选择软件触发或硬件触发。每个通道的硬件触发源不同,所以需要根据触发源选择对应的通道。如果使用软件触发,就可以任意选择通道,因为每个通道的软件触发都是一样的。

五、数据宽度与对齐

数据宽度与对齐

如图,如果源端宽度等于目标宽度,数据正常转运;如果源端宽度小于目标宽度,目标数据高位就会补 0;如果源端宽度大于目标宽度,目标数据高位就会舍去。

六、DMA 初始化结构体详解

typedef struct
{
    
    uint32_t DMA_PeripheralBaseAddr; 	// 外设地址
    uint32_t DMA_PeripheralDataSize; 	// 外设数据宽度
    uint32_t DMA_PeripheralInc; 		// 外设地址是否自增
    uint32_t DMA_MemoryBaseAddr; 		// 存储器地址
    uint32_t DMA_MemoryDataSize; 		// 存储器数据宽度
    uint32_t DMA_MemoryInc; 			// 存储器地址是否自增
    uint32_t DMA_DIR; 					// 传输方向
    uint32_t DMA_BufferSize; 			// 传输数目,对应结构图的传输计数器的值
    uint32_t DMA_Mode; 					// 模式选择,对应结构图就是是否使用自动重装
    uint32_t DMA_M2M; 					// 选择是否使用存储器到存储器模式,其实就是选择硬件触发还是软件触发    
    uint32_t DMA_Priority; 				// 通道优先级
} DMA_InitTypeDef;
  1. DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
  2. DMA_PeripheralDataSize:外设数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定 DMA_CCR 寄存器的 PSIZE[1:0] 位的值。
  3. DMA_PeripheralInc:使能或失能外设地址自增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不使能该功能。
  4. DMA_MemoryBaseAddr:存储器地址,设定 DMA_CMAR 寄存器的值;一般设置为自定义存储区的首地址。、
  5. DMA_MemoryDataSize:存储器数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定 DMA_CCR 寄存器的 MSIZE[1:0] 位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为相同大小。
  6. DMA_MemoryInc:使能或失能存储器地址自增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自增功能。
  7. DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定 DMA_CCR 寄存器的DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
  8. DMA_BufferSize:设定待传输数据数目,设定 DMA_CNDTR 寄存器的值。
  9. DMA_Mode:DMA 传输模式选择,可选单次传输或者循环传输,它设定 DMA_CCR 寄存器的 CIRC 位的值。
  10. DMA_M2M:存储器到存储器模式,使用存储器到存储器时用到,置位 DMA_CCR 的 MEN2MEN 位即可启动存储器到存储器模式。
  11. DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0] 位的值。 DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。

七、举例(数据转运+DMA)

7.1 任务分析

数据转运+DMA

对照 DMA 的基本结构分析该任务:

  1. 外设站点和存储器站点的三个参数。

    • 在该任务里,外设地址是 DataA 数组的首地址,存储器地址是 DataB 的首地址;
    • 然后是数据宽度,两个数组的类型都是 uint8_t,所以数据宽度设置为 8 位的字节;
    • 之后地址是否自增,图中的效果是 DataA[0] 转到 DataB[0] 、DataA[1] 转到 DataB[1] 等等,两个数组的位置一一对应,所以转运完 DataA[0] 和 DataB[0] 之后,两个站点都应该自增,移动到下一个数据的位置,继续转运。
  2. 数组有 7 个元素,要转运 7 次,所以传输计数器写 7,自动重装暂时不需要。

  3. 触发选择部分,因为是存储器到存储器,所以使用软件触发。

  4. 使能 DMA,这样数据就会从 DataA 转运到 DataB。

这里的数据转运是一种复制转运,转运完成后,DataA 的数据并不会消失。

7.2 软件设计

为了提高程序的复用功能,将外设地址、存储器地址和传输数据数目设为初始化函数 UserDMA_Init 的参数,之后,在调用初始化函数 UserDMA_Init 时,就可以根据具体情况进行传参。同时,为了可以在数据更改后,可以继续使用 DMA 转运数据,编写函数 UserDMA_Transfer,并将使能 DMA 的工作交给该函数。

static uint16_t transfer_size; // 静态变量,存储 DMA 传输数目

void UserDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t size)
{
    
    DMA_InitTypeDef DMA_InitStructure;

    transfer_size = size; // 保存传输大小

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能 DMA1 时钟

    DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; 						// 设置外设基地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度为字节
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; 		// 使能外设地址自增
    DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; 							// 设置存储器基地址
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 		// 存储器数据宽度为字节
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 				// 使能存储器地址自增
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 						// 设置数据传输方向为外设到存储器
    DMA_InitStructure.DMA_BufferSize = size; 								// 设置传输数据数目
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; 							// 设置为单次模式
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; 							// 使能存储器到存储器模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 					// 设置优先级为中等
    DMA_Init(DMA1_Channel1, &DMA_InitStructure); 							// 初始化 DMA1 的通道1
}

void UserDMA_Transfer(void)
{
    
    DMA_Cmd(DMA1_Channel1, ENABLE); 						// 使能 DMA1 的通道1
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); 		// 等待传输完成标志位置位
    DMA_ClearFlag(DMA1_FLAG_TC1); 							// 清除传输完成标志位
    
    DMA_Cmd(DMA1_Channel1, DISABLE); 						// 失能 DMA1 的通道1
    DMA_SetCurrDataCounter(DMA1_Channel1, transfer_size); 	// 设置当前数据传输数目
}

注意: 传给初始化函数 UserDMA_Init 的前两个参数只是表示地址的整形数,并不是真的地址,因为 C 语言中使用指针变量保存地址的,如果直接传地址,会因为类型不一致,而产生警告。比如,想将数组 DataA 的数据转运到数组 DataB 中,那么应该传递的参数是 (uint32_t)DataA(uint32_t)DataB,而不是直接传 DataA 和 DataB。

参考视频源于B站up主: 野火科技、江协科技
参考文档:《STM32库开发实战指南——基于野火MINI开发板》

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_52540136/article/details/135345482

智能推荐

基于内核4.19版本的XFRM框架_linux的xfrm框架-程序员宅基地

文章浏览阅读794次,点赞2次,收藏5次。XFRM框架_linux的xfrm框架

织梦常用标签整理_织梦中什么页面用什么标签教学-程序员宅基地

文章浏览阅读774次。DedeCMS常用标签讲解笔记整理 今天我们主要将模板相关内容,在前面的几节课中已经基本介绍过模板标签的相关内容,大家可以下载天工开物老师的讲课记录:http://bbs.dedecms.com/132951.html,这次课程我们主要讲解模板具体的标签使用,并且结合一些实例来介绍这些标签。 先前课程介绍了,网站的模板就如同一件衣服,衣服的好坏直接决定了网站的好坏,很多网站一看界面_织梦中什么页面用什么标签教学

工作中如何编译开源工具(gdb)_gdb编译-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏15次。编译是大部分工程师的烦恼,大家普遍喜欢去写业务代码。但我觉得基本的编译流程,我们还是需要掌握的,希望遇到相关问题,不要退缩,尝试去解决。天下文章一大抄,百度能解决我们90%的问题。_gdb编译

python简易爬虫v1.0-程序员宅基地

文章浏览阅读1.8k次,点赞4次,收藏6次。python简易爬虫v1.0作者:William Ma (the_CoderWM)进阶python的首秀,大部分童鞋肯定是做个简单的爬虫吧,众所周知,爬虫需要各种各样的第三方库,例如scrapy, bs4, requests, urllib3等等。此处,我们先从最简单的爬虫开始。首先,我们需要安装两个第三方库:requests和bs4。在cmd中输入以下代码:pip install requestspip install bs4等安装成功后,就可以进入pycharm来写爬虫了。爬

安装flask后vim出现:error detected while processing /home/zww/.vim/ftplugin/python/pyflakes.vim:line 28_freetorn.vim-程序员宅基地

文章浏览阅读2.6k次。解决方法:解决方法可以去github重新下载一个pyflakes.vim。执行如下命令git clone --recursive git://github.com/kevinw/pyflakes-vim.git然后进入git克降目录,./pyflakes-vim/ftplugin,通过如下命令将python目录下的所有文件复制到~/.vim/ftplugin目录下即可。cp -R ...._freetorn.vim

HIT CSAPP大作业:程序人生—Hello‘s P2P-程序员宅基地

文章浏览阅读210次,点赞7次,收藏3次。本文简述了hello.c源程序的预处理、编译、汇编、链接和运行的主要过程,以及hello程序的进程管理、存储管理与I/O管理,通过hello.c这一程序周期的描述,对程序的编译、加载、运行有了初步的了解。_hit csapp

随便推点

挑战安卓和iOS!刚刚,华为官宣鸿蒙手机版,P40搭载演示曝光!高管现场表态:我们准备好了...-程序员宅基地

文章浏览阅读472次。点击上方 "程序员小乐"关注,星标或置顶一起成长后台回复“大礼包”有惊喜礼包!关注订阅号「程序员小乐」,收看更多精彩内容每日英文Sometimes you play a..._挑战安卓和ios!华为官宣鸿蒙手机版,p40搭载演示曝光!高管表态:我们准备好了

精选了20个Python实战项目(附源码),拿走就用!-程序员宅基地

文章浏览阅读3.8w次,点赞107次,收藏993次。点击上方“Python爬虫与数据挖掘”,进行关注回复“书籍”即可获赠Python从入门到进阶共10本电子书今日鸡汤昔闻洞庭水,今上岳阳楼。大家好,我是小F。Python是目前最好的编程语言之一。由于其可读性和对初学者的友好性,已被广泛使用。那么要想学会并掌握Python,可以实战的练习项目是必不可少的。接下来,我将给大家介绍20个非常实用的Python项目,帮助大家更好的..._python项目

android在线图标生成工具,图标在线生成工具Android Asset Studio的使用-程序员宅基地

文章浏览阅读1.3k次。在网站的导航资源里看到了一个非常好用的东西:Android Asset Studio,可以在线生成各种图标。之前一直在用一个叫做Android Icon Creator的插件,可以直接在Android Studio的插件里搜索,这个工具的优点是可以生成适应各种分辨率的一套图标,有好几种风格的图标资源,遗憾的是虽然有很多套图标风格,毕竟是有限的。Android Asset Studio可以自己选择其..._在线 android 图标

android 无限轮播的广告位_轮播广告位-程序员宅基地

文章浏览阅读514次。无限轮播广告位没有录屏,将就将就着看,效果就是这样主要代码KsBanner.java/** * 广告位 * * Created by on 2016/12/20. */public class KsBanner extends FrameLayout implements ViewPager.OnPageChangeListener { private List

echart省会流向图(物流运输、地图)_java+echart地图+物流跟踪-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏6次。继续上次的echart博客,由于省会流向图是从echart画廊中直接取来的。所以直接上代码<!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /&_java+echart地图+物流跟踪

Ceph源码解析:读写流程_ceph 发送数据到其他副本的源码-程序员宅基地

文章浏览阅读1.4k次。一、OSD模块简介1.1 消息封装:在OSD上发送和接收信息。cluster_messenger -与其它OSDs和monitors沟通client_messenger -与客户端沟通1.2 消息调度:Dispatcher类,主要负责消息分类1.3 工作队列:1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。1...._ceph 发送数据到其他副本的源码