一、DMA

1.DMA简介

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

        DMA可以提供外设(外设寄存器,一般是外设数据寄存器DR,例如ADC的数据寄存器,串口的数据寄存器)和存储器(运行内存SARM和程序存储器FLASH,存储变量数组和程序代码的地方)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

        STM32具有12个独立可配置的通道(数据转运的路径): DMA1(7个通道), DMA2(5个通道)

        每个通道都支持软件触发(存储器和存储器)和特定的硬件触发(外设和存储器)。

        STM32F103C8T6 DMA资源:DMA1(7个通道)

2.存储器映像

        ROM——只读存储器,非易失性,掉电不丢失的存储器

        RAM——随机存储器,易失性,掉电丢失的存储器

 3.DMA框图

 

        左上角为Cortex-M3内核,里面包含了CPU和内核外设,剩下的全是存储器,Flash是主闪存,SARM是运行内存,各个外设都可以看成是寄存器,也是一种SARM存储器。

        为了高效有条理地访问存储器,设计了一个总线矩阵,总线矩阵的左端,是主动单元,也就是拥有存储器的访问权。内核有DCode和系统总线,可以访问右边的存储器,其中DCode总线专门访问Flash,系统总线是访问其他东西的。此外由于DMA要转运数据,所以DMA也拥有存储器的访问权,由于DMA1有7个通道,但是DMA总线只有一个,所以当多个通道需要同时转运数据时,需要经过仲裁器,进行优先级分组,如果DMA和CPU都要访问同一个目标,那么DMA会暂停CPU的访问,以防止冲突。DMA自身的的寄存器——AHB从设备(配置DMA参数),DMA作为一个外设,自己也会有相应的配置寄存器,这里连接到了总线右边的AHB总线上,所以DMA,既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元,从CPU到AHB从设备的线路,就可以对DMA进行配置。DMA请求就是DMA的硬件触发源。CPU还是DMA对Flash都是可读不可写的状态。

        右边是被动单元,它们的存储器只能被左边的主动单元读写。

4.DMA基本结构

        外设寄存器和Flash以及SRAM为数据转运的两大站点,左边是外设寄储器站点,右边是存储器站点,包括Flash以及SRAM。外设和存储器都有三个参数,第一个参数:起始地址,有外设端的起始地址和存储器端的起始地址,决定数据从哪来到哪去,第二个参数:数据宽度,指定一次转运要按多大的数据宽度来进行,可以选择字节Byte(8位),半字节HalfWod(16位)和字节Word(32位)。第三个参数:地址是否自增,指定一次转运完成后,下一次转运,是不是要把地址移动到下一个位置去。

        下面有传输计数器用来指定总共需要转运几次,是一个自减计数器。右边有自动重装器,也就是说当传输寄存器减到0后,是否要自动恢复到最初的值,决定转运的模式(单次模式或循环模式)。

        最下面为DMA触发控制,触发源有硬件触发和软件触发,具体选择哪个由M2M参数决定,当M2M为1时,DMA选择软件触发(以最快的速度,连续不断的触发DMA,以最快速度把传输计数器清零,完成转换,不能与循环模式连续用,适合存储器到存储器的转运),当M2M为0时,DMA选择硬件触发。

        最后为开关控制,也就是DMA_Cmd,当给DMA使能后,DMA准备就绪,可以进行转运。

5.数据宽度与对齐

        将小的数据转到大的里面去,高位补0,将大的数据转到小的里面去,高位舍弃。

二、DMA库函数介绍

void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);

作用:恢复缺省配置

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

作用:初始化

void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);

作用:结构体初始化

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

作用:使能

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);

作用:中断输出使能

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);

作用:给传输计数器写数据

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

作用:返回传输计数器的值

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

作用:获取标志位状态

void DMA_ClearFlag(uint32_t DMAy_FLAG);

作用:清除标志位

ITStatus DMA_GetITStatus(uint32_t DMAy_IT);

作用:获取中断状态

void DMA_ClearITPendingBit(uint32_t DMAy_IT);

作用:清除中断挂起位

三、DMA代码介绍

1.DMA数据转运——使用DMA进行存储器到存储器的数据转运(将一个数组里的数据,复制到另一个数组里)

初始化DMA步骤:

①RCC开启DMA时钟

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

②调用DMA_Init,初始化各个参数

DMA_InitTypeDef DMA_InitStructure;

(外设站点三个参数)

DMA_InitStructure.DMA_PeripheralBaseAddr = ADDr;将函数参数1改为uint32_t ADDrA;

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;

//存储器站点三个参数

// 起始地址

DMA_InitStructure.DMA_MemoryBaseAddr = ADDrB;将函数参数2改为uint32_t ADDrB

//存储器站点数据宽度

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

// 存储器站点地址是否自增

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  

DMA_InitStructure.DMA_BufferSize = Size; //参数:将函数参数3改为uint16_t Size;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  

参数:外设站点时源端还是目的地

DMA_DIR_PeripheralDST——目的地

DMA_DIR_PeripheralSRC——源端

DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;  

参数:触发方式(硬件触发还是软件)

 DMA_M2M_Enable——软件触发

 DMA_M2M_Disable——硬件触发

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  

参数:工作模式(是否采用自动重装)

DMA_Mode_Circular——循环模式

DMA_Mode_Normal——单次模式

DMA_InitStructure.DMA_Priority =DMA_Priority_Medium;   //优先级

DMA_Init(DMA1_Channel1, &DMA_InitStructure);

③开关控制,DMA_Cmd

DMA_Cmd(DMA1_Channel1, ENABLE);

DMA转运需要三个基本的元素,(1)DMA_Cmd使能(2)传输计数器必须大于0 (3)触发方式选择——必须有触发源

 这里有三个模块首先是MyDMA.c进行DMA初始化,以及专门的DMA传输函数。

#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_M2M = DMA_M2M_Enable;  // 触发方式
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  // 自动重装
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1, DISABLE);
}

void MyDMA_Transfor(void)            // DMA传输函数,改变传输计数器的值
{
	DMA_Cmd(DMA1_Channel1, DISABLE);
	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_Transfor();
		
		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);
	}	
}

        运行,下载后就可以在显示屏看到,第一行显示DataA 后面跟地址,第二行显示DataA的每一位每隔一秒+1,第三行显示DataB后面跟地址,第四行显示DataB的每一位收到DMA转运每隔一秒+1。

        其实CMD的一个主要作用在CMD与AD多通道结合,这一部分代码在下一篇进行深刻学习。

四、总结

        DMA(直接内存访问)是嵌入式系统中无需CPU干预,让外设与内存(或内存间)直接传输数据的技术,能显著减轻CPU负担、提升传输效率,适用于高频大数据场景(如ADC采样、通信传输等)。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐