STM32速成笔记—DMA

2023-11-13

一、什么是DMA

DMA全程Direct Memory Access,即直接存储器访问。简单来讲,它的功能是把数据从一个地址搬运到另一个地址。通常有三个传输方向,分别是内存到内存,内存到外设和外设到内存。
DMA示意图

二、DMA有什么作用

直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。

比如在串口接收或者发送时可以直接利用DMA将接收内容直接搬运到接收数组。或者利用DMA将准备发送的数据搬运到发送的缓冲区。再或者利用DMA把数据搬运到特定的地址,或者从特定的地址利用DMA搬运数据出来。总而言之,在平时的开发过程中,DMA是非常常用的。

三、STM32的DMA

STM32F103ZET6有两个DMA,12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

STM32F103ZET6的DMA特性

3.1 DMA请求

DMA请求

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

前面介绍STM32F103ZET6有两个DMA,12个通道,同的 DMA 控制器的通道对应着不同的外设请求。根据中文参考手册,对应关系如下
DMA1对应外设
DMA1对应外设
DMA2对应外设
DMA2对应外设

3.2 DMA通道

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

3.3 仲裁器

当有多个DMA请求时,需要仲裁器来决定响应的先后顺序。仲裁器决定相应顺序的方法有两种

  • 软件判定
    软件中可以通过设置DMA_CCRx寄存器来设置DMA通道的优先级。共有四个优先级可以设置,分别是非常高,高,中和低。
  • 硬件判定
    当遇到两个或者多个相同优先级的DMA通道请求时,仲裁器根据DMA通道的编号来决定响应顺序。DMA通道编号越低,优先级越高。另外,DMA1拥有比DMA2更高的优先级。
    仲裁器

四、DMA配置

4.1 DMA配置步骤

  • 使能DMA时钟
  • 初始化DMA通道,包括配置通道,外设和内存地址,传输数据量等
  • 使能外设DMA功能
  • 开启DMA通道传输
  • 查询DMA通道状态

4.2 DMA结构体成员

  • DMA_PeripheralBaseAddr:外设地址,外设地址,通过DMA_CPAR寄存器设置,一般设置为外设的数据寄存器地址,比如要进行串口DMA 传输,那么外设基地址为串口接收/发送数据存储器USART1->DR 的地址,表示方法为&USART1->DR。如果是存储器到存储器模式则设置为其中一个存储区地址。
  • DMA_Memory0BaseAddr:存储器地址,通过DMA_CMAR寄存器设置,一般设置为我们自定义存储区的首地址,即我们存放DMA传输数据的内存地址。比如我们定义一个u32类型数组,直接写数组首地址(直接使用数组名)即可,在DMA传输的时候就可以发送数组数据,或者把数组用来接收其他数据。
  • DMA_DIR:数据传输方向选择,可选择外设到存储器、存储器到外设以及存储器到存储器。通过设定DMA_CCR寄存器的DIR[1:0]位的值决定。
  • DMA_BufferSize:用来设置一次传输数据的大小,通过DMA_CNDTR寄存器设置。
  • DMA_PeripheralInc:用来设置外设地址是递增还是不变,通过DMA_CCR寄存器的PINC位设置,如果设置为递增,那么下一次传输的时候地址加1。通常外设只有一个数据寄存器,所以一般不会使能该位,即配置为DMA_PeripheralInc_Disable。
  • DMA_MemoryInc:用来设置内存地址是否递增,通过DMA_CCR寄存器的MINC位设置。我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地址自动递增功能,即配置为DMA_MemoryInc_Enable。
  • DMA_PeripheralDataSize:外设数据宽度选择,可以为字节(8位)、半字(16位)、字(32位),通过DMA_CCR寄存器的PSIZE[1:0]位设置。
  • DMA_Mode:DMA传输模式选择,可选择一次传输或者循环传输,通过DMA_CCR寄存器的CIRC位来设定。比如我们要从内存(存储器)中传输64个字节到串口,如果设置为循环传输,那么它会在64个字节传输完成之后继续从内存的第一个地址传输,如此循环。这里我们设置为一次传输完成之后不循环。所以设置值为DMA_Mode_Normal。
  • DMA_Priority:用来设置DMA通道的优先级,有低,中,高,超高四种级别,可通过DMA_CCR寄存器的PL[1:0]位来设定。DMA优先级只有在多个DMA数据流同时使用时才有意义。
  • DMA_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。

五、DMA配置程序

这里以配置DMA,将ADC采集到的数据搬运到内存中的某一个数组中为例,讲解一下DMA的配置和使用方法。

5.1 ADC1初始化程序

ADC使用TIM4的通道4触发,具体配置可见本系列另一篇文章STM32速成笔记—ADC。这里在之前配置的基础上需要开启ADC的DMA传输,在初始化ADC时加上下面的程序

ADC_DMACmd(ADC1,ENABLE);   // 使能ADC的DMA传输

ADC初始化程序如下

/*
 *==============================================================================
 *函数名称:ADC1_Init
 *函数功能:初始化ADCx
 *输入参数:无
 *返回值:无
 *备  注:TIM4通道4触发AD转换,使能了DMA
 *==============================================================================
 */
void ADC1_Init(void)
{
	// 结构体定义
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	
	// 开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);
	
	// 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	// 规则通道配置
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);
	
	// GPIO配置
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;   //ADC1通道1
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;   // 模拟输入
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	// ADC参数配置
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;   // 独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;   // 非扫描模式	
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;   // 关闭连续转换
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;   // TIM2通道2触发
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;   // 右对齐	
	ADC_InitStructure.ADC_NbrOfChannel = 1;   // 1个转换在规则序列中 也就是只转换规则序列1 
	ADC_Init(ADC1, &ADC_InitStructure);   // ADC初始化
	
	// 使能外部触发
	ADC_ExternalTrigConvCmd(ADC1, ENABLE);
	ADC_DMACmd(ADC1,ENABLE);   // 使能ADC的DMA传输
	ADC_Cmd(ADC1, ENABLE);   // 开启AD转换器
	
	// ADC校准
	ADC_ResetCalibration(ADC1);   // 重置指定的ADC的校准寄存器
	while(ADC_GetResetCalibrationStatus(ADC1));   // 获取ADC重置校准寄存器的状态
	
	ADC_StartCalibration(ADC1);   // 开始指定ADC的校准状态
	while(ADC_GetCalibrationStatus(ADC1));   // 获取指定ADC的校准程序

	ADC_SoftwareStartConvCmd(ADC1, ENABLE);   // 使能或者失能指定的ADC的软件转换启动功能
}

5.2 DMA初始化程序

由上面的介绍可知,ADC1是DMA1的通道1,我们配置一下DMA1的通道1,使能传输完成中断。

/*
 *==============================================================================
 *函数名称:DMA1_Init
 *函数功能:DMA1初始化
 *输入参数:souAddr:数据源地址;desAddr:数据目的地址
 *返回值:无
 *备  注:数据传输宽度为16位,外设到内存,循环传输,使能了传输完成中断
 *==============================================================================
 */
void DMA1_Init (u32 souAddr,u32 desAddr)
{
	// 结构体定义
	DMA_InitTypeDef DMA_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	// 使能DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	//DMA1初始化
	DMA_DeInit(DMA1_Channel1);
	DMA_InitStructure.DMA_PeripheralBaseAddr = souAddr;   // 数据源地址
	DMA_InitStructure.DMA_MemoryBaseAddr = desAddr;   // 目的地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;   //   传输方向(外设到内存)
	DMA_InitStructure.DMA_BufferSize = 128;   // 一次传输数据大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   // 外设地址不自增
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 内存地址自增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;   // 外设数据宽度选择
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   // 内存数据宽度选择
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;   // DMA模式:循环传输
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;   // 优先级:高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   // 禁止内存到内存的传输
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);   // 配置DMA1
	
	// 使能传输完成中断
	DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE);
	
	// NVIC配置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	// 使能DMA1通道1
	DMA_Cmd(DMA1_Channel1,ENABLE);
}

// DMA1中断服务函数
void  DMA1_Channel1_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
	{
		DMA_Cmd(DMA1_Channel1,DISABLE);
		while (1)
		{}
	}
	// 清除中断标志位
	DMA_ClearITPendingBit(DMA1_IT_TC1);
}

定义一个存储AD转换结果的数组,初始化时,程序如下

u16 gAdcAdValue[128];   // 存储AD值

DMA1_Init((u32)(&ADC1->DR),(u32)&gAdcAdValue);   // DMA1初始化

中断服务函数中将存储标志位置1表示存储完成

u8 gDmaAdcSaveFlag = 0;   // ADC数据存储标志位

// DMA1中断服务函数
void  DMA1_Channel1_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
	{
		gDmaAdcSaveFlag = 1;   // 存储标志位置1,表示存储完成
	}
	// 清除中断标志位
	DMA_ClearITPendingBit(DMA1_IT_TC1);
}

上面的配置就可以实现ADC采集,DMA将采集结果搬运到内存中的一个数组里面。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32速成笔记—DMA 的相关文章

  • 斐讯r1换网络_新房装修网络布线方案二:客厅电视柜放单个路由器覆盖全屋

    继续研讨新房装修的家庭网络搭建的问题 我昨天推荐的是一个AC AP的方案 如果是小户型 对网络这块要求并不高 比如 并不要求全屋都5G覆盖满的 基于成本考虑 可以先不上AC AP 先用一个性能稍好的千兆路由器 比如 斐讯的K2p 网件R70
  • WinDbg Command-Line Options

    First time users of WinDbg should begin with the Debugger Operation section The WinDbg command line uses the following s
  • SQL注入点判断及注入方式

    SQL注入类型 一 判断注入点 当参数可控时 看参数是否对数据产生影响 若有影响则可能是注入点 输入SQL看是否可以产生报错 通过报错信息得到数据库部分语句 利用引号 双引号 圆括号进行报对 二 注入方式 get注入 在get传参时写入参数

随机推荐

  • 简便快捷 解决burp suite不能抓本地包的问题

    v 解决burp suite不能抓本地包的问题 蛋黄小课堂开课啦 蛋黄碎碎念 第一次遇到不能抓本地包的时候 通过百度找到用本机实际IP地址替代 127 0 0 1 的方法可以解决不能抓本地包的问题 但是后来又不行了 于是再百度 找了好久 最
  • 伪似然估计(Pseudo Maximum Likelihood Estimation)

    伪似然估计 和 剖面似然估计 伪似然估计 参考文献 Gong G and Samaniego F J 1981 pseudo Maximum Likelihood Estimation Theory and Applications The
  • 属性layout_weight不起作用的解决方法

    在使用线性布局的时候 使用layout weight属性来达到控件自适应屏幕宽度的效果 但是有的时候这个属性没有起作用 这个时候就需要仔细检查一下 1 只有LinearLayout标签支持 2 设置layout weight时要根据布局的方
  • SSM实战项目——Java高并发秒杀API

    SSM实战项目 Java高并发秒杀API 项目截图 秒杀列表 秒杀详情页 错误提示 开始秒杀 秒杀成功 重复秒杀 秒杀倒计时 秒杀结束 项目介绍 何为秒杀 所谓 秒杀 就是网络卖家发布一些超低价格的商品 所有买家在同一时间网上抢购的一种销售
  • c++智能指针

    智能指针是一种用于管理动态分配的内存的工具 它可以自动地不再需要时释放内存 智能指针目的 避免内存泄漏和释放已经释放的内存 用法 会在堆上分配内存 并在不再需要时自动释放 通常会跟踪指向堆上对象的引用计数 并在引用计数为0时自动释放内存
  • linux软连接显示broken link

    解决方案 sudo ln s 源文件 目标文件 注 两者必须为绝对路径
  • 关于测试用例

    测试专栏 软件测试的基本概念 关于软件测试 作为一个测试人员 这些基础知识必不可少 目录 一 测试用例的基本要素 1 什么是测试用例 2 为什么软件测试人员要写测试用例 二 测试用例的设计方法 1 基于需求设计测试用例 2 具体设计测试用例
  • docker制作镜像,导出导入本地镜像等初级指南

    首先安装 docker 1 prepare 更改 yum 源加快安装环境 添加下面 yum 源 docker ce stable name Docker CE Stable basearch baseurl https mirrors al
  • 当绘图遇上Caché之元数据代理

    很久以前到沈阳实习的时候还一个个问度娘C 画图 画了电路图绘制软件的毕业设计 雪花屏保等等 搞LIS软件后绘制各种仪器图 对C 画笔 画字符串 画线 画圆等等耳熟能详 然而却碰到一个问题 我们的仪器大部分是盒子用数据库M连接的 如果盒子仪器
  • micropython RX8025T 驱动简单演示

    我就知道可能八百年会有一位大哥来找这个驱动 让我来猜猜为啥用这个 嫌一般的RTC不够精准是吧 想用个带温度补偿的试试 代码拿去 其实巨简单的 没啥好说的 而且只有基本功能 from micropython import const impo
  • 容器的docker-compose怎样写agent.jar配置

    在 Docker Compose 文件中配置 Java Agent 例如 agent jar 的方式与之前的环境变量类似 您可以使用 environment 字段来设置 Java 环境变量 包括 javaagent 参数来指定 Java A
  • 【C++】string使用

    文章目录 1 为什么要学习string类 2 标准库中的string类 2 1了解string类 2 2string类常用的接口 2 2 1 构造和析构相关 2 2 2 迭代器 2 2 3 容量相关 2 2 4 元素访问 元素遍历 元素访问
  • 生命在于学习——未授权访问漏洞

    声明 本篇文章只是用于记载学习笔记 学习交流 不可用作其他违规用途 一 简介 未授权访问可以理解为需要安全配置或权限认证的地址 授权页面存在缺陷 导致其他用户可以直接访问 从而引发重要权限可被操作 数据库 网站目录等敏感信息泄露 目前主要存
  • 静态对象(全局+局部+静态对象成员)

    所有的静态对象 全局对象都于静态存储区分配 关于全局对象 是在main 函数执行前就分配好了的 其实 在main 函数中的显示代码执行之前 会调用一个由编译器生成的 main 函数 而 main 函数会进行所有全局对象的的构造及初始化工作
  • C++ auto遍历无法直接修改map的数据

    对于std map 当使用for auto it myMap 这种范围循环形式时 实际上是使用了const迭代器进行遍历 这意味着你无法通过该迭代器直接修改std map中的值 范围循环使用的是容器的begin 和end 函数返回的迭代器
  • 【数据结构--链表】反转链表

    题目描述 代码实现 Definition for singly linked list struct ListNode int val struct ListNode next struct ListNode reverseList str
  • JavaScript如何调用摄像头

    如何使用浏览器调用摄像头 在JavaScript中使用浏览器调用摄像头会使用到以下方法 navigator getUserMedia video true audio false success error 参数1 是一个对象包含摄像头和麦
  • 二叉树的重构

    二叉树的重构是指给定二叉树的先序遍历 中序遍历 后序遍历中的任意两者 要求恢复二叉树的结构 其中 除非二叉树是真二叉树 即任一节点要么具有两个子节点 要么没有子节点 否则 必须要有中序遍历才能恢复二叉树的结构 先序遍历 中序遍历 后序遍历
  • 寒假培训——简单搜索

    A 捉迷藏1 dfs bfs简单搜索 题目 Title A 捉迷藏1 Time Limit 1s Description 王吉吉和袁坑坑在一个n m大小的房间里捉迷藏 王吉吉躲起来了 现在袁坑坑要去抓他 地图中 W 代表王吉吉 Y 代表袁坑
  • STM32速成笔记—DMA

    文章目录 一 什么是DMA 二 DMA有什么作用 三 STM32的DMA 3 1 DMA请求 3 2 DMA通道 3 3 仲裁器 四 DMA配置 4 1 DMA配置步骤 4 2 DMA结构体成员 五 DMA配置程序 5 1 ADC1初始化程