STM32 DMA详解

1.DMA简介

DMA (Direct Memory Access) 直接存储器存取

DMA 可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源( 比如想把Flash里的一批数据转运到SRAM里,需要软件触发,使用软件触发之后,DMA会以最快都速度把这批数据转运)如果进行外设到存储器的转运,就不能一股脑地转运了,因为外设的数据是有一定的时机的,这时就需要用硬件触发   (比如转运ADC的数据,就需要每个ADC通道转换完成后,硬件触发一次DMA再转运)触发一次转运一次,这样数据才是正确的,才是我们想要的效果,所以存储器到存储器的数据转运一般使用软件触发,外设到存储器要硬件触发

每个DMA通道硬件触发源是不一样的,要使用某个外设的硬件触发源,就必须使用它连接的那个通道

12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)

每个通道都支持软件触发和特定的硬件触发

C8T6 DMA资源:DMA1 

2、存储器映像

const常量,存储在flash,则地址开头0x0800 0000,节省SRAM,若有很大的查找表或者字库,最好加const

定义一个临时变量,则地址开头0x2000 0000

外设寄存器的地址是固定的,例如ADC1->DR(4001 244C),

在数据手册中,起始地址+偏移地址

ADC1的地址0x4001 2400,DR的地址偏移为4C

 计算机系统5大组成部分:运算器,控制器,存储器,输入设备和输出设备。

3、DMA数据转运

 运算器和控制器一般会合在一起,叫做CPU,所以计算机的核心关键部分就是CPU和存储器

程序代码以及const常量存放在Flash区域

变量存放在RAM区域

存储器的重要知识点:存储的内容和存储器的地址。外设也是存储器

ROM: 只读存储器,是一种非易失性,掉电不丢失的存储器

RAM:随机存储器,易失,掉电丢失

ROM 分为3块,分别为程序存储器Flash(主闪存),系统存储器和选项字节

RAM:运行内存SRAM,外设寄存器,内核外设寄存器。

4、DMA框图 

 在上图当中,数据从Flash或者SRAM寄存器当中传到总线矩阵当中,传到总线矩阵之后又开始分为通道1、2、3再进行传输

A.寄存器是一种特殊的存储器,一方面,CPU可以对寄存器进行读写,就像读写运行内存一样。另一方面,寄存器的每一位背后,都连接了一根导线,可以用于控制外设电路的状态

B.比如:置引脚的高低电平,导通和断开开关,切换数据选择器,或者多位结合起来,当作计数器,数据寄存器等。所以寄存器是连接软件和硬件的桥梁,软件读写寄存器,就相当于再控制硬件的执行。


C.所以使用DMA进行数据转运就相当于:从某个地址取内容,再放到另一个地址里。为了高效有条理地访问存储器,STM32设计了总线矩阵,左端是主动单元,也就是存储器的访问权。右边是被动单元,只能被左边的主动单元读取。主动单元包括内核的DCode和系统总线,可以访问右边的存储器,DCode专门访问Flash,系统总线访问其他东西。

D.由于DMA要访问数据,所以DMA也必须要有访问的主动权主动单元除了内核CPU,剩下的就是DMA了,每个DMA通道可以分别设置他们转运数据的源地址和目的地址,这样他们就可以各自独立的进行工作了。

D.仲裁器的作用:虽然每个通道可以独立进行设置,但是DMA总线只有一条,所以所有的通道都只能分时复用这一条DMA总线。如果产生了冲突,就会由仲裁器,根据通道的优先级决定谁先用谁后用。

E.在总线矩阵里,也会有仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,防止冲突。不过。此时总线仲裁器仍然会保证CPU得到一般的总线带宽,使CPU能够正常的工作。


F.

AHB从设备:DMA自身的寄存器,因为DMA作为一个外设,它自己也会有相应的配置寄存器,上面连接在总线右边的AHB总线上,所以DMA既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元

DMA请求就是触发的意思,右边的触发源就是各个外设,DMA请求就是DMA的硬件触发源,比如ADC转换完成,串口就受到数据等。

需要触发DMA转运数据的时候就通过DMA请求线路向DMA发出硬件触发信号,之后DMA就可以执行数据转运的动作了。

DMA的各个部分和作用:

DMA总线:用于访问各个存储器,内部的多个通道可以进行独立的数据转运

仲裁器:用于调度各个通道,防止产生冲突

AHB从设备:用于配置DMA参数

DMA请求:用于硬件触发DMA的数据转运

STM32的Flash是RAM的一种,如果通过总线直接访问的话,无论是CPU还是DMA,都是只读的,只能读取数据而不能写入,如果DMA的目的地址填写了Flash区域,转运时就会出错

也可以配置Flash接口控制器对Flash进行写入。

5、DMA基本结构 

画圈的是数据转换的站点,左边是外设寄存器站点,右边是存储器站点包括Flash和SRAM。

STM32存储器一般特指Flash和SRAM,不包含外设寄存器,外设寄存器一般直接称为外设,所以就是外设到存储器,存储器到存储器这样来描述。

外设和存储器参数的作用:

外设和存储器起始地址作用:决定了数据从哪里来,到哪里去。

数据宽度:作用是指定一次转运要按多大的数据宽度来进行,可以选择字节Byte,半字HalhWord和字Word。字节是8位,半字是16位

地址是否自增作用:指定一次转运完成后,下一次转运要把地址移动到下一个位置去,相当于指针P++ 。比如ADC扫描模式,用DMA进行数据转运,外设地址是ADC_DR寄存器,寄存器的地址不用自增,不然就会移位到其他寄存器里面了。存储器地址需要自增,每转运一次,就往后移动一位,不然会覆盖上一个数据。

进行存储器到存储器的转运,就需要把其中一个存储器的地址放在外设的这个站点。只要在存储器里写Flash或SRAM的地址,他就会去这两个地方找数据。

站点虽然叫外设存储器,并不是说只能写寄存器的地址只是一个名字而已。

 传输计数器:

用来指定总共需要转运几次,是一个自减计数器,比如写入5,那DMA就只能进行5次数据转运,转运过程中,每转运一次,计数器就会减1,当传输计数器减到0之后,DMA就不会进行数据转运了,转运地址也会恢复为起始地址,方便下一次转运。

自动重装器:

作用:传输计数器减到0之后,是否要恢复到最初的值,比如最初的值是5,如果不使用自动重装器,那么转运5次之后就DMA结束。使用自动重装器,转运5次,计数器减到0之后,计数器就会立即重装到初始值5. 不重装,就是单次模式,重装就是循环模式

比如说要转运一个数组,一般是单次模式,转运一轮就结束了。如果是ADC扫描模式+连续转换,为了配合ADC。DMA也需要使用循环模式。和ADC差不多,都是指定一轮工作完成后,是不是立即开始下一轮工作

DMA触发控制:

决定DMA在什么时机进行转运,触发源有硬件触发和软件触发,具体选择哪个有M2M参数决定。

M2M就是(Memory to Memory)即存储器到存储器,选择1DMA就会选择软件触发

软件触发执行逻辑:以最快的速度,连续不断地触发DMA,争取快速完成传输计数器清零,完成这一轮的转换工作,给0就是硬件触发。

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

6、开关控制:

使用 DMA_Cmd函数,给DMA使能后,DMA准备就绪,可以进行转运

 DMA转运的条件:

1.开关控制,DMA_Cmd必须使能

2.传输计数器必须大于0

3.触发源必须有触发信号,触发一次,转运一次,传输计数器自减一次。当传输计数器等于0,且没有自动重装时,无论是否触发,DMA都不会进行转运,此时需要DMA_Cmd给DISABLE关闭DMA再为传输计数器写入一个大于0的数,再DMA_Cmd,给ENABLE,开启DMA才能继续工作。在写传输计数器时,必须要先关闭DMA,在进行写入,不能开启时写入EN=0时不工作

硬件触发注意事项:使用相应的硬件触发需要选择对应的通道,不然就触发不了,选择哪个触发源由对应的外设是否开启DMA输出决定的。

比如要使用ADC1,就需要使用ADC_DMACmd函数进行使能,必须使用这个库函数开启ADC1这一路输出才有效。使用定时器3,会有TIM_DMACmd函数用来进行DMA输出控制,使用哪一个外设触发源,取决于把哪个通道开启了,全开启理论上都可以使用

开关控制开启之后,这七个触发源进入到仲裁器进行优先级判断,最终产生内部DMA1请求,优先级判断与中断类似,序号越小优先级越高,也可以在程序中配置优先级。

数据宽度与对齐:

如果转运的数据宽度一样,就是正常的一个个转运,如果数据宽度不一样,

比如源宽度是8位,目标宽度是16位,传输B0时,需要在前面高位补上0,传输结果就是00B0.

比如源宽度是16位,目标宽度是8位,传输B1B0时,需要把高位舍去,传输结果就是B0.

7、源码

MyDMA.c

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size = Size;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	DMA_InitTypeDef DMA_InitStructure;
  //前面三个是在调节外设站点
	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);
	
	DMA_Cmd(DMA1_Channel1, DISABLE);  //先中断传输,到后面再进行
}



void MyDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1, DISABLE);
  //函数再重置自动重装器,然后让DMA不断进行
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
	DMA_Cmd(DMA1_Channel1, ENABLE);
	
  //等待转完成
	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	DMA_ClearFlag(DMA1_FLAG_TC1);   //清除标志位,标志位需要手动清除
}

main.c代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] = {0, 0, 0, 0};

int main(void)
{
	OLED_Init();
	
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
	
	OLED_ShowString(1, 1, "DataA");
	OLED_ShowString(3, 1, "DataB");
	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
		
	while (1)
	{
		DataA[0] ++;
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);
		
		MyDMA_Transfer();
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);

		Delay_ms(1000);
	}
}

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32 DMA(Direct Memory Access)是一种硬件机制,它可以在不使用 CPU 的情况下完成数据传输。这种机制可以提高数据传输的效率,从而提高系统的整体性能。在STM32 MCU中,DMA通常用于以下几个方面: 1. 外设数据传输到存储器:例如,将ADC转换结果存储到存储器中。 2. 存储器数据传输到外设:例如,将存储器中的数据传输到DAC输出。 3. 存储器数据传输到存储器:例如,将一个缓冲区的数据拷贝到另一个缓冲区。 STM32 DMA的主要特点有以下几个: 1. 高效性:DMA传输过程不需要CPU参与,可以在后台进行数据传输,从而节约了CPU资源。 2. 灵活性:DMA可以支持多种传输模式,例如内存到内存、内存到外设、外设到内存、外设到外设等。 3. 安全性:DMA传输过程可以通过设置传输长度和传输完成中断等方式来保证数据传输的安全性。 下面是STM32 DMA的基本工作原理: 1. 配置DMA通道:首先需要配置DMA通道的相关寄存器,例如源地址、目的地址、传输长度等等。 2. 启动DMA传输:一旦DMA通道被配置好,就可以启动DMA传输。启动DMA传输后,DMA会自动从源地址读取数据,并将数据写入目的地址,在这个过程中不需要CPU的参与。 3. DMA传输完成:当DMA传输完成后,会产生传输完成中断,CPU可以在中断处理函数中进行后续处理。 总之,STM32 DMA是一种非常有用的机制,可以显著提高系统的整体性能。在使用DMA时,需要仔细考虑传输模式、传输长度、目的地址等参数,确保DMA传输的正确性和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值