什么是DMA?
“DMA”是Direct Memory Access的缩写。不使用CPU,而是通过总线直接进行外围功能(模拟功能、通信功能等)和存储器间(闪存、ROM、RAM)的数据传输的功能。
通常,数据传输由CPU执行,而在装有DMA的微型计算机中,DMA代表CPU传输数据。
因此,CPU只需要算术/逻辑运算等CPU才能完成的工作就可以了。其结果是,通过安装DMA,可以综合提高微型计算机的性能。
DMA的最大优势是通过硬件直接传输数据,从而实现高速、大容量的数据传输。您可以在内存和外围功能中自由选择传输源和传输目的地(但受微型计算机的限制)。
但是,由于只有一条总线和CPU分开使用,所以需要调整总线的使用权。这种“总线调整”在英语中被称为“总线仲裁(Bus Arbitration)”。
DMA的基本操作
这是从RAM中提取数据并将其发送到通信功能的情况。通常,当CPU进行数据传输时(图(a)),首先从RAM中读取数据。读取的数据一旦通过CPU中的ALU。然后,ALU直接输出数据,而不对数据进行任何处理,并将数据发送到通信功能。DMA在不通过CPU的情况下从RAM读取数据并将其传输到通信功能。 此时,CPU不会被使用,所以您可以使用ALU进行另一个计算。作为微型计算机,可以并行处理两项工作,非常高效。
首先,CPU在DMA中设置数据数量、源/目的地地址、传输模式等。然后,当DMA传输开始触发时,DMA开始传输。DMA传输启动的触发器可以通过软件或硬件触发。DMA传输结束后,DMA向CPU发出中断,通知传输结束。
仲裁类型(与CPU共享总线权限) DMA很方便但不是万能的。只有一条总线,所以当DMA使用总线时,CPU就不能使用总线了。如果DMA长时间使用总线来进行大量数据传输,CPU会在此期间一直无法使用总线,从而导致无法将计算结果存储在内存中的问题。因此,需要在CPU和DMA之间高效地使用总线进行调整。这叫做总线仲裁。 还有,使用总线的权利叫做总线权利。总线仲裁是协调(调解)谁拥有总线的权利。
总线仲裁的方式有几种。典型的是Round-Robin,Cycle stealing,burst。
Round-Robin是按顺序让出总线权的方式。例如,如果总线主机是两个,如果CPU使用总线,下一个总线循环使用DMA,然后使用总线交替使用CPU使用。
被称为Cycle stealing的方式是在CPU没有访问内存的总线周期之间涂上,DMA使用总线的方式。
burst方式是指在一定时间内,一个bus Master占有总线权的方式。当你想快速传输优先级高的数据时使用。例如,当DMA发送10个高优先级数据时,DMA会占用总线,直到传输完所有这10个数据为止。
使用上的注意事项 举两个使用DMA时出现的问题的例子。一个是超限运行,另一个是与高速缓存一起使用时发生的主内存数据的丢失。
由于通信功能等原因,CPU或DMA在CPU或DMA未读取接收缓冲器中的数据时,会捕获下一个数据,导致前一个接收数据丢失。如果总线仲裁是Round-Robin方式,则很难发生超限运行,但如果是Cycle stealing或burst方式,CPU或DMA就不能使用总线,等待总线权的时间就会变得更长。在此期间,通信功能在接收到以下数据时会出现超限运行。在其他情况下也会发生超限。
在使用高速缓存的系统中,当DMA重写具有与高速缓存所具有的数据相同地址的主存储器时,高速缓存和主存储器的数据的一致性将丢失。
例如,当CPU将数据写入内存时,如果采用直通方式,则将数据写入高速缓存,同时也将相同的数据写入主内存。如果DMA错误地重写了主内存上的数据,则缓存数据和主内存数据将不同。之后,当CPU读取该数据时,缓存中的数据将被读取,并读取与主内存值不同的值。如果你有一个机制来保持缓存和DMA之间的一致性,但如果没有机制,用户必须管理数据的一致性。
STM32的DMA
关于STM32的DMA参考:STM32—DMA存储器到外设_Aspirant-GQ的博客-CSDN博客?
以下为引用内容::
DMA(Direct Memory Access)——直接存储器存取,就像其名称一样,DMA的主要作用是搬数据,DMA可以把数据从存储器搬到外设、从外设搬到存储器、从存储器搬到存储器。DMA的特殊之处就是搬运数据不需要占用CPU,DMA控制器包含了DMA1和DMA2,其中DMA1由7个通道,DMA2有5个通道。 ?
DMA框图
理解其工作框图:
功能框图主要分为三部分:
1.DMA请求 外设如果想要通过DMA传输数据,必先给DMA控制器发送DMA请求,DMA收到请求信号之后会传回给外设一个应答信号,当外设应答后且DMA控制器收到应答信号后,就会启动DMA的传输,直至传输完毕。但DMA有2个DMA控制器,15条通道,不同的通道对应着不同的外设请求,所以必须对通道和外设请求进行一一对口,各个通道对应的外设如下:
2.通道 要注意的是DMA共有12个独立可编程的通道,DMA1有7个、DMA2有5个,每个通道对应着不同的外设的请求,但同一时间只能接收一个。
3.仲裁器 当多个通道同时请求时,就要处理先后响应的问题,就像中断里的优先级分组一样。仲裁器管理DMA通道请求时主要依据俩点: 第一判断:在DMA_CCRx 寄存器中设置有 4 个等级:非常高、高、中和低,先根据此优先级判断响应的先后,如果优先级都一样,进行第二判断。 第二判断:比较通道的编号,编号越低优先权越高。
DMA传输数据分析
使用DMA,核心技术就是配置数据的传输,主要分为三点: 1.传输的方向 2.传输的数量 3.传输的模式
1.传输的方向 DMA有三种数据传输方向: 一:存储器——>外设 当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。 DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目 标地址。 二:外设——>存储器 当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。 三:存储器——>存储器 当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14: MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。
2.传输的数量 以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR 配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据。而且源和目标的数据宽度必须一致,数据宽度可以设置为8/16/32位。 除此之外,还要设置源和目标俩边数据指针的增量模式 ,即传输完一个数据后数据指针的移动模式,是加一?还是不变?以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。
3.传输的模式 数据传输的情况可以通过查询标志位或者通过中断来鉴别,每个DMA通道在DMA传输过半、传输完成、传输错误时都会有相应的标志位,如果使能相关的中断后还会产生中断。一次数据传输完成后还分俩种模式:是一次传输还是循环传输。 一次传输: 传输一次后就停止,要想再传的话,必须关闭DMA使能后重新配置后方能继续传输。 循环传输: 一次传输完成后又恢复第一次传输时的配置循环传输,不断重复。
注:以下基于ADC和DAC来讲DMA。
完成功能:
1、通过DMA来测量电压数据;
2、通过DMA来输出正弦波形;
之前用的是普通的查询模式,可参考:
STM32实战总结:HAL之ADC_路溪非溪的博客-CSDN博客
STM32实战总结:HAL之DAC_路溪非溪的博客-CSDN博客
配置MX
先配置ADC的DMA。
手册中的简介:
ADC实现的是DMA的“从外设到存储器”
过程是这样的,输入的模拟量经过转换,存储在ADC_DR寄存器中,转换结束时产生DMA请求,DMA经过响应等过程后,将数据运输到储存器中(在程序中体现为一个变量地址)
开始配置:
ADC开启连续转换。
之前是单次转换模式,每次转换时都要重新打开ADC来采集,从而不断采集数据。设置成连续模式,则每次转换后不会关闭ADC,也就不用再次开启,而是接着转换。
DMA Settings中配置对应的DMA。
这里有几个地方需要注意。
1、DMA选择循环Circular模式。什么意思呢?这里的循环模式指的是,在一次运输过后,DMA不会被关闭,而是接着运输。
2、地址增长处,外设处是固定的寄存器,内存中就始终有一个变量去接收数据即可,都不必使能自动地址增长。
3、ADC是12位的,存放数据的寄存器是16位的,所以单次传送的数据宽度选择半字即可。
4、上面的优先级有Low/Medium/High/Very High,根据重要程度去选择即可。
开始配置DAC的DMA。
先看手册中的介绍。
根据上方红框中的说明可知,DAC的DMA必须要由外部触发,而不是软件触发。
另外,这里是从存储器到外设的DMA传输。
使能外部触发:
这几个DMA的通道选择是自动的。
为什么是自动的?上面我们讲过,DMA的通道是固定对应着某些外设的。如下表:
继续配置DMA选项:
因为输出一个正弦波,我们需要传输一个数组,所以,每次传输结束后内存地址都要自动+1
另外,既然要外部触发条件,那么,我们这里选择定时器7来触发(定时器6之前已经被用过了)。
配置定时器7:
先查看定时器7的框图:
TRGO配置成一个更新事件:
在定时器计时到了,产生更新事件时,就会触发DAC。
最后,再配置一下中断:
上述配置完成后,则默认会将DMA的中断进行强制打开(灰色代表是系统自行选择的),这里的两个中断的意思是,每次DMA传输完成后,就会产生一个中断通知CPU。因为DMA传输很快,又开了循环模式,所以如果开中断,将会不断地去通知CPU,导致无效地占用CPU。所以,从性能角度来考虑,将这两个中断取消。
先将上方强制选择的选项取消勾选,然后再取消勾选这两个。
注意:TIM7的时钟可以去掉。因为其是触发事件,没用到中断。
至此,完成了ADC和DAC的DMA配置。
在DMA菜单中,能看到配置的两个DMA信息
相对来说,还是比较麻烦的。
确认无误后,生成代码。
DMA相关库函数
ADC相关库函数
/* Non-blocking mode: DMA */
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);
DAC相关库函数
HAL_StatusTypeDef HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, uint32_t Alignment);
HAL_StatusTypeDef HAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel);
这里面的*pData就是变量的指针,也就是DMA传输方向中的存储器。
程序编写
代码生成后,除了ADC和DAC相关文件,还多了dma.c和dma.h文件。
使用DMA函数来开启ADC。这里主要是要定义一个变量(内存),然后将数据放到变量中。
直接在之前的useracd.c里面修改。
static void GetVol(void)
{
uint32_t adcResultNum = 0;
HAL_ADC_Start_DMA(&hadc1, &adcResultNum, 1);
HAL_Delay(1000);
printf("value is %f\n\r", 3.3/4096*adcResultNum);
}
至于DAC:
定时器7启动;
DAC启动,传入要传输的变量的地址;
硬件自动完成,定时器7每100us会产生一个更新事件,同时会触发DMA,所以DAC每100us会输出一个数。具体可用示波器查看。此处代码略过。
|