STM32 DMA篇

DMA概述

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

DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

2个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发(每个DMA通道的硬件触发源不同),存储器到存储器的转运一般使用软件触发,外设到存储器的数据转运一般使用硬件触发

STM32存储器映像

DMA框图

        为了高效访问存储器,STM32在DMA中设计了一个总线矩阵,总线矩阵的左侧为主动单元,拥有存储器的访问权,右侧为被动单元,器存储器只能被左侧的主动单元读写;

        主动单元中,内核有DCode和系统总线,前者专门访问Flash,后者访问其他存储器;由于DMA要转运数据,故DMA也拥有访问的主动权,DMA中设置了一个仲裁器,用于根据通道的优先级分时复用DMA总线;

        总线矩阵中也有一个仲裁器,当DMA和CPU同时访问同一目标时,DMA会暂停CPU的访问,以避免冲突,但仍会保证CPU得到一般的总线带宽

        AHB从设备用于通过CPU,配置DMA参数

        右侧的被动单元中,各外设能作为DMA的硬件触发源对DMA发起请求

        对于Flash,若通过总线直接访问,则无论是CPU还是DMA,都是只读的,不可写入!

DMA基本结构

        外设寄存器和存储器即为DMA运输的两大目的地和出发地,二者可以相互传输;存储器到存储器的数据传输情况则是单向的,即Flash向sRAM传输,或sRAM到sRAM;

        操作DMA时两边都有三个参数:起始地址、数据宽度、地址是否自增:

  •         起始地址:决定了数据从哪来,到哪去
  •         数据宽度:指定一次转运要按照多大的数据宽度来进行,可选择字节、半字、字
  •         地址是否自增:指定一次转运完成后,下一次转运是否选择将地址移动到下一位置;通常,在寄存器端选择无需自增,存储器看需求选择自增

        传输计数器:自减计数器,用于指定一共需转运几次;当自减至0时,自增的地址会恢复至起始地址

        自动重装器:用于储存传输计数器的初始值;若使用自动重装器,则当传输计数器减到0后,会自动重装初始值,也即循环模式;不使用则为单次模式

        M2M:DMA的触发控制器;当置1时,选择软件触发,通常适用于存储器与存储器的转运,DMA会以最快的速度连续不断的触发DMA,直至传输计数器清零,完成一轮转换,故与自动重装器不能同时使用,否则会循环卡死;置0时,选择硬件触发,通常适用于外设相关的转运

        DMA触发的条件:1、开关使能    2、传输计数器大于0    3、触发源存在触发信号

        其中,写传输计数器时,必须关闭DMA,即使能后再进行,不可在DMA开启时写入

DMA请求映像

        2个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发(每个DMA通道的硬件触发源不同)

数据宽度与对齐

        即,宽度短的数据传输到宽度大的地址中时,高位补0;宽度大的数据传输到宽度短的地址中时,高位舍弃

错误管理

        读写一个保留的地址区域,将会产生DMA传输错误。当在DMA读写操作时发生DMA传输错误 时,硬件会自动地清除发生错误的通道所对应的通道配置寄存器(DMA_CCRx)的EN位,该通道操作被停止。此时,在DMA_IFR寄存器中对应该通道的传输错误中断标志位(TEIF)将被置位, 如果在DMA_CCRx寄存器中设置了传输错误中断允许位,则将产生中断。

实现代码

初始化步骤:

        1、RCC开启DMA时钟

        2、调用DMA_Init,初始化各参数,包括外设/存储器的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、触发源选择以及中断优先级

        3、通过DMA_Cmd使能DMA指定通道

        4、根据需要,硬件触发时,在对应外设调用XXX_DMACmd开启触发信号输出;DMA中断时,调用DMA_ITConfig开启DMA中断输出,并在NVIC中配置相应的中断通道,配置对应的中断函数

        5、转运完成,传输计数器清0后,若再想给传输计数器赋值,则:DMA失能->写传输计数器->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);

代码:

//My_DMA.c
#include "stm32f10x.h"                  // Device header


void MyDMA_Init(uint32_t ADDrA, uint32_t ADDrB, uint16_t Size)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_BufferSize = Size;				//传输计数器值
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;		//外设站点作为数据源
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;				//软件触发,存储器到存储器
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;			//传输计数器不重装
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;	//指定通道的软件优先级为中等
	//存储器站点起始地址、数据宽度、是否自增
	DMA_InitStructure.DMA_MemoryBaseAddr = ADDrB;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//外设站点起始地址、数据宽度、是否自增
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADDrA;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	//DMA_Cmd(DMA1_Channel1, ENABLE);
}

void MyDMA_Transfer(uint8_t Size)
{
	DMA_Cmd(DMA1_Channel1, DISABLE);
	DMA_SetCurrDataCounter(DMA1_Channel1, 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 "LED.h"
#include "KEY.h"
#include "OLED.h"
#include "MyDMA.h"


int main(void)
{
	//LED_Init();
	//KEY_Init();
	delay_init();
	OLED_Init();
	
	uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
	uint8_t DataB[] = {0, 0, 0, 0};
	
	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(4);
		
		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);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值