目录
一、概述
1、简介
DMA(Direct Memory Access)直接存储器存取
- DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
- 12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)
- 每个通道都支持软件触发和特定的硬件触发
STM32F103C8T6 DMA资源:DMA1(7个通道)
存储器映像
2、DMA框图
如下:
DCode总线是专门访问Flash的,系统总线是访问其他东西的,由于DMA要转运数据,所以DMA也必须要有访问的主动权。
虽然DMA有多个通道可以独立转运数据,但DMA总线只有一个,仲裁器会根据通道的优先级来决定谁先用,谁后用。在总线矩阵这里也有个仲裁器,当CPU和DMA同时访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突,不过总线仲裁器,仍然会保证CPU得到一半的总线带宽,使CPU也能正常工作。
DMA请求就是DMA的硬件触发源,比如ADC转换数据,需要转运数据就需要通过这条线路,向DMA发出硬件触发信号,之后DMA就可以执行转运的动作了,数据寄存器都是可以正常读写
3、DMA基本结构
如下:
外设寄存器其实就是外设与存储器用于连接的通道,也就是转换机构。外设寄存器也能写Flash 、SRAM的地址,存储器也能写外设寄存器的地址。
传输计数器是用来指定总共需要转运几次的,这个传输寄存器是一个自减寄存器,比如给它指定5次,没转运一次就会减1,如果不使用自动重装器,减为0时就是转运结束。使用这个自动重装器,当传输计数器减为0时会立即重装到初始值为5。如果不重装,就单次模式转运,重装就是循环模式转运。修改传输计数器时,要使DMA_Cmd先使能。
接着就是触发源,有软件触发源和硬件触发源,具体是哪个由M2M(memory to memory)参数决定,当我们给这个M2M为1时,就会进行软件触发,它这个软件触发的逻辑是以最快的速度,连续不断地触发DMA,争取早日把传输计数器清零,完成这一轮的转换。自动重装器和软件触发不能同时使用。软件触发一般就是应用存储器到存储器的转运。硬件(这里硬件可以理解为外设)触发一般与外设有关的转运,比如ADC转换完成,来触发DMA转运。
这开关控制就是用DMA_Cmd()进行使能。
4、DMA请求映像
EN位决定这个寄存器工作不工作。EN位为0表示不工作,EN位为1表示工作。
固定的硬件优先级进行优先级判断,通道号越小,优先级越高。
5、数据宽度与对齐
源端宽度小于目标宽度时,数据的高位补零,也就是右对齐; 源端宽度大于目标宽度时,数据的高位丢弃掉,
数据转运+DMA:
DMA外设地址可以自增,DMA存储器地址也可以自增,
二、存储器到存储器转运数据代码
MYDMA.c:
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size=0;
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t size)
{
DMA_InitTypeDef DMA_InitStruct;
MyDMA_Size=size;
//在AHB总线上
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//DMA初始化
DMA_InitStruct.DMA_BufferSize=size ; //传输次数
DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC ; //方向
DMA_InitStruct.DMA_M2M=DMA_M2M_Enable ;
DMA_InitStruct.DMA_MemoryBaseAddr=AddrB;
DMA_InitStruct.DMA_MemoryDataSize=DMA_PeripheralDataSize_Byte ;
DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;
DMA_InitStruct.DMA_Mode=DMA_Mode_Normal; //单次
DMA_InitStruct.DMA_PeripheralBaseAddr=AddrA;
DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte ;
DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Enable ;
DMA_InitStruct.DMA_Priority=DMA_Priority_Medium ;
DMA_Init(DMA1_Channel1,&DMA_InitStruct);
//打开DMA
DMA_Cmd(DMA1_Channel1,DISABLE);
}
//数据进行转运
void MyDMA_Transfer(void)
{
//修改计数器之前要先失能
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);
}
MyDMA.h:
#ifndef _MYDMA_H
#define _MYDMA_H
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t size);
void MyDMA_Transfer(void);
#endif
main.c:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "delay.h"
#include "MyDMA.h"
//const uint8_t aa=0x66; //加了const,会把数据存到flash中,只读不能写,不加存到SRAM
uint8_t DataA[]={0x01,0x02,0x03,0x08};
uint8_t DataB[]={0,0,0,0};
int main(void)
{
OLED_Init();
OLED_ShowString(1,1,"DataA:");
OLED_ShowHexNum(1,7,(uint32_t)&DataA,8);
OLED_ShowNum(2,1,DataA[0],2);
OLED_ShowNum(2,5,DataA[1],2);
OLED_ShowNum(2,9,DataA[2],2);
OLED_ShowNum(2,13,DataA[3],2);
OLED_ShowString(3,1,"DataB:");
OLED_ShowHexNum(3,7,(uint32_t)&DataB,8);
OLED_ShowNum(4,1,DataB[0],2);
OLED_ShowNum(4,5,DataB[1],2);
OLED_ShowNum(4,9,DataB[2],2);
OLED_ShowNum(4,13,DataB[3],2);
MyDMA_Init((uint32_t)&DataA,(uint32_t)&DataB,4);
while(1)
{
DataA[0]++;
DataA[1]++;
DataA[2]++;
DataA[3]++;
//OLED_ShowHexNum(1,1,aa,2);
OLED_ShowNum(4,1,DataB[0],2);
OLED_ShowNum(4,5,DataB[1],2);
OLED_ShowNum(4,9,DataB[2],2);
OLED_ShowNum(4,13,DataB[3],2);
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowNum(2,1,DataA[0],2);
OLED_ShowNum(2,5,DataA[1],2);
OLED_ShowNum(2,9,DataA[2],2);
OLED_ShowNum(2,13,DataA[3],2);
OLED_ShowNum(4,1,DataB[0],2);
OLED_ShowNum(4,5,DataB[1],2);
OLED_ShowNum(4,9,DataB[2],2);
OLED_ShowNum(4,13,DataB[3],2);
}
}
三、外设设备到存储器转运数据代码
ADC通过DMA请求进行转运数据。每个通道转换完成DMA就会转运1次
AD.c:
#include "stm32f10x.h" // Device header
uint16_t AD_Value[2]={0};
void AD_DMA_Init(void)
{
DMA_InitTypeDef DMA_InitStruct;
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStruct;
//时钟外设初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//GPIO初始化
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN ; //模拟输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
//初始化ADC,配置数据格式,模式
ADC_InitStruct.ADC_Mode=ADC_Mode_Independent ;
ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right ; //数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv= ADC_ExternalTrigConv_None ; //不用外部触发源,用硬件触发
ADC_InitStruct.ADC_NbrOfChannel=2; //多通道
ADC_InitStruct.ADC_ScanConvMode=ENABLE; //扫描
ADC_InitStruct.ADC_ContinuousConvMode=ENABLE; //连续
ADC_Init(ADC1,&ADC_InitStruct);
//DMA存储器的地址变化
//定时器触发ADC,ADC触发DMA
//DMA初始化
DMA_InitStruct.DMA_BufferSize=2 ; //传输次数
DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC ; //方向
DMA_InitStruct.DMA_M2M=DMA_M2M_Disable ;
DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)AD_Value;
DMA_InitStruct.DMA_MemoryDataSize= DMA_MemoryDataSize_HalfWord ;
DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;
DMA_InitStruct.DMA_Mode=DMA_Mode_Circular ; //循环还是单次,这里是循环
DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;
DMA_InitStruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_HalfWord ;
DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable ;
DMA_InitStruct.DMA_Priority=DMA_Priority_Medium ; //软件优先级,比硬件优先级权限高
DMA_Init(DMA1_Channel1,&DMA_InitStruct);
//打开DMA
DMA_Cmd(DMA1_Channel1,ENABLE);
//确定外设触发源ADC1
ADC_DMACmd(ADC1,ENABLE);
//启动ADC
ADC_Cmd(ADC1,ENABLE);
//校准复位寄存器
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET);
//开始校准,为0 校准结束
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
//void AD_GetValue(void)
//{
// DMA_Cmd(DMA1_Channel1,DISABLE);
//DMA_SetCurrDataCounter(DMA1_Channel1,2);
// DMA_Cmd(DMA1_Channel1,ENABLE);
//
//
// while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
// DMA_ClearFlag(DMA1_FLAG_TC1);
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
return ADC_GetConversionValue(ADC1);
//}
AD.h:
#ifndef _AD_H
#define _AD_H
extern uint16_t AD_Value[2];
void AD_DMA_Init(void);
//void AD_GetValue(void);
#endif
main.c:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "delay.h"
#include "AD.h"
int main(void)
{
OLED_Init();
AD_DMA_Init();
OLED_ShowString(1,1,"AD1:");
OLED_ShowString(2,1,"AD2:");
while(1)
{
OLED_ShowNum(1,5,AD_Value[0],4);
OLED_ShowNum(2,5,AD_Value[1],4);
Delay_ms(500);
}
}