DMA

一、简介

  1. 使用场景:DMA可以提供外设(指外设的寄存器,一般是数据寄存器DR)和存储器(运行内存SRAM和程序存储器Flash)或者存储器与存储器之间的高速数据传输
  2. 优点:无需CPU干预,节省CPU资源
  3. DMA通道:STM32一共有12个DMA通道,DMA1(7个),DMA2(5个),STM32F103C8T6的DMA资源只有DMA1
  4. DMA触发条件:每个通道都支持软件触发特定的硬件触发
  5. 软件触发:存储器与存储器
  6. 硬件触发:外设和存储器,每次数据的转运都需要触发,每个DMA通道的硬件触发源是不同的

二、STM32存储器映像

​ 计算机系统由运算器、控制器、存储器、输入设备和输出设备五部分组成,运算器和控制器合一起称为CPU,所以计算机的核心组成就是CPU和存储器。存储器最重要的就是存储器的内容和地址,存储器总共分为ROMRAM

  • ROM:只读存储器(read only memory),是一种非易失性、掉电不丢失的存储器,分为3块,ROM的储存介质都是Flash,一般说的Flash都是说主闪存Flash
    1. 程序存储器Flash:就是主闪存,起始地址是0x0800 0000,存储C语言编译后的程序代码
    2. 系统存储器:起始地址是0x1FFF F000,存储BootLoader,用于串口下载,BootLoader程序由芯片出厂自动写入,一般不允许用户自己写入
    3. 选项字节:起始地址是0x1FFF F800,存储C语言编译后的程序代码,主要存的是Flash的读保护、写保护和看门狗等配置
  • RAM:随机存储器,是一种易失性、掉电丢失的存储器,分为3块
    1. 运行内存SRAM:对于电脑来说运行内存就是内存条,起始地址是0x2000 0000,存储运行过程中的临时变量也就是我们在程序中定义变量、数组、结构体的地方

    2. 外设寄存器:起始地址是0x4000 0000,存储各个外设的配置参数

    3. 内核外设寄存器:起始地址是0xE000 0000,存储内核各个外设的配置参数

  • 三、DMA框图

    总线矩阵的左端是主动单元,拥有存储器的访问权;右端是被动单元,它们的存储器只能被左边的主动单元读写;由于DMA需要转运数据,那就必须能够访问被动单元的存储器,所以DMA也必须有访问的主动权,那主动单元除了内核CPU,剩下的就是DMA总线了

  • 主动单元:内核有DCode和系统总线,DCode专门访问Flash,系统总线访问其他的被动单元
  • 被动单元:在图中就是单片机上的各种外设,它们作为DMA的硬件触发源可以触发DMA请求
  • DMA总线:从图中能看出总线矩阵右端总共有3条DMA总线,分别是DMA1、DMA2和以太网的DMA,从DMA1和DMA2的框图中能看见它们分别有7个通道和5个通道,其中的每一条通道都可以单独设置转运数据的原地址和目的地址。

    DMA框图中有一个仲裁器和AHB从设备,仲裁器:虽然DMA有多条通道,但是每个DMA最终都只有一条总线,也就是一个DMA同一时间只能转运一个数据,那么仲裁器的作用就是选择通道,如果同一条DMA总线的通道产生了冲突, 那么就由仲裁器根据通道的优先级来决定数据转运的顺序;AHB从设备:DMA本身作为一个外设,他也会有相应的配置寄存器,在图中它连接在了AHB总线上,所以DMA既是总线的矩阵的主动单元可以读写各种存储器,也是被动单元,CPU可以通过系统总线经过总线矩阵和AHB总线对DMA进行配置。

    DMA数据转运程序

    一、程序功能

    ​ 主程序定义两个数组DataA和DataB,使用DMA将DataA的数据转运到DataB中

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

    二、程序实现

    1.复制OLED的工程文件,重命名为08_1DMA数据转运

    2.在工程的System目录下新建DMA的.c文件和.h文件

    ​ 因为DMA不涉及外围硬件电路,所以建在System目录下

    3.在MyDMA.c文件中编写DMA初始化函数

    ​ 由DMA的基本结构图得到初始化步骤

    1. RCC开启DMA时钟
    2. 配置DMA,初始化结构体
    3. 开启DMA
    #include "stm32f10x.h"                  // Device header
    
    //一、RCC开启DMA时钟
    //二、配置DMA,初始化结构体
    //三、开启DMA,如果想要在初始化之后就立刻使用DMA,就可以在初始化DMA函数中开启DMA
    uint16_t MyDMA_Size = 0;
    void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
    {
    	MyDMA_Size = Size;
    	//一、RCC开启DMA时钟
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    	
    	//二、配置DMA,初始化结构体
    	DMA_InitTypeDef DMA_InitStruct;
    	/* 
    	我们这个程序是要把DataA的数据转运到DataB中,这两个数组会被存放在SRAM中,并且地址是不固定的,由
    	编译器分配,在配置DMA_PeripheralBaseAddr参数时,不能填入一个绝对地址,而是通过数组名来获取,我
    	们把转运数据的源地址放到初始化函数的参数中,这样想转运那个数组就可以将地址传到DMA初始化函数
    	的参数中
    	*/
    	DMA_InitStruct.DMA_PeripheralBaseAddr = AddrA;//源数据的起始地址
    	/* 
    		DMA_PeripheralDataSize_Byte:Byte,字节,就是uint8_t
    		DMA_PeripheralDataSize_HalfWord:HalfWord,半字,就是uint16_t
    		DMA_PeripheralDataSize_Word:Word,字,就是uint32_t
    	由于传递的参数是uint8_t的数组,所以选择第一个
    	*/
    	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//源数据的数据宽度
    	/*  
    		DMA_PeripheralInc_Enable:数据地址自增
    		DMA_PeripheralInc_Disable:不自增
    		由于转运的是数组,它里面存放的数据地址都是自增的,选第一个
    	*/
    	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//源地址是否自增
    	/* 以下三个参数参考上面 */
    	DMA_InitStruct.DMA_MemoryBaseAddr = AddrB;//目的地址
    	DMA_InitStruct.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;//数据宽度
    	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//目的地址是否自增
    	/*  
    		用来指定外设站点是源端还是目的地
    		DMA_DIR_PeripheralDST:DST,destination,目的地,就是外设站点作为目的地,方向为存储器到外设
    		DMA_DIR_PeripheralSRC:SRC,source,源头,就是外设站点作为源地址,方向为外设到存储器
    		我们这里由于是打算把数组A放在外设站点,数组B放在储存器站点,所以是选第二个
    	*/
    	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;//数据传输方向
    	/* 
    		读DMA_Init函数的源码,可以知道DMA_BufferSize参数对应的范围是1~65536,所以这个参数应该是
    		uint16_t类型,把这个参数提取到MyDMA_Init函数的形参去
    	*/
    	DMA_InitStruct.DMA_BufferSize = Size;//缓存区大小,就是传输计数器
    	/* 
    	这个参数的自动重装和软件触发不能同时使用
    	*/
    	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;//是否自动重装
    	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;//硬件和软件触发选择
    	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;//优先级设置
    	DMA_Init(DMA1_Channel1, &DMA_InitStruct);
    	
    	//三、关闭DMA
    	DMA_Cmd(DMA1_Channel1, DISABLE);
    }
    
    //DMA传输函数,调用一次,DMA转运一次数据
    /* 
    由于DMA初始化函数里自动重装选择的是不重装,在一次DMA转运完成后,传输计数器清零,所以在DMA传输函数中
    要对传输计数器重新赋值,在对传输计数器重新赋值前必须要先失能DMA
    */
    void MyDMA_Transfer(void)
    {
    	DMA_Cmd(DMA1_Channel1, DISABLE);//失能DMA
    	/* 
    		这个函数的参数就是转运数据的次数,这个参数在DMA初始化函数中定义了,由于这个参数要在两个
    	不同的函数中调用,所以需要一个变量来储存,在顶部创建一个MyDMA_Size变量
    	*/
    	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);//传输计时器重新赋值
    	DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA
    	
    	//等待转运完成
    	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    	
    	//手动清除标志位
    	DMA_ClearFlag(DMA1_FLAG_TC1);
    }
    
    
    
    4.MyDMA.h
    #ifndef __MYDMA_
    #define __MYDMA_
    
    void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
    void MyDMA_Transfer(void);
    
    #endif
    
    
    5.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);
    	}
    }
    
    

    作者:delay_134

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32+DMA自学笔记

    发表回复