学习江科大 8-1 DMA直接存储器存取 至 8-2 DMA数据转运&DMA+AD多通道
1、DMA(Direct Memory Access)
(1) DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU资源;
(2)12个独立可配置的通道:DMA1 (7个通道) 、DMA2(5个通道);
(3)每个通道都支持软件触发和特定的硬件触发;
(4)STM32F103C8T6 DMA资源 :DMA1 (7个通道)
(5)DMA框图
其中包括用于访问各个存储器的DMA总线;内部的多个通道可以进行独立的数据转运;仲裁器用于调度各个通道,防止产生冲突;AHB从设备用于配置DMA参数;DMA请求用于硬件触发DMA的数据转运
(6)DMA基本结构
DMA进行转运的条件:
a、开关控制,DMA_Cmd必须使能;
b、传输计数器必须大于0;
c、有触发信号,触发一次,转运一次,传输计数器自减一次。当传输计数器等于0,且没有自动重装时,此时就需要DMA_Cmd,给DISABLE,关闭DMA,再为传输计数器写入一个大于0的数,重新DMA_Cmd,从而开启DMA(写传输计数器时,必须要先关闭DMA,再进行)。
2、存储器映像
3、数据转运 + DMA
其中ADC扫描模式 + DMA
4、代码部分
(1)DMA数据转运
a、MyDMA.c
#include "stm32f10x.h"
uint16_t MyDMA_Size; //定义全局变量
void MyDMA_Init( uint32_t AddrA , uint32_t AddrB , uint16_t Size) //DMA初始化函数
{
MyDMA_Size = Size ; //将函数输入Size写入全局变量
/*开启时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //开启DMA的时钟
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStructure; //定义结构体变量
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA ; //外设基地址,给定形参AddrA
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeriphalDataSize_Byte ; //外设数据宽度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable ; //外设地址自增
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB ; //存储器基地址,给定形参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_Priority_Medium; //优先级选择中等
DMA_Init(DMA1_Channel1,&DMA_InitStructure); //将结构体交给函数DMA_Init
/*使能DMA*/
DMA_Cmd(DMA1_Channel1,DISABLE); //运用转运函数来实现DMA的进一步控制
}
void MyDMA_Transfer(void) //启动DMA数据转运
{
DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能 (在写入传输计数器之前,需要DMA暂停工作)
DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size); //写入传输计数器,指定将要转运的次数
DMA_Cmd(DMA1_Channel1,ENABLE); //DMA使能,开始工作
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET) ; //等待DMA工作完成
DMA_ClearFlag(DMA1_FLAG_TC1); //清除工作完成标志位
}
b、main.c
uint8_t DataA[] = { 0X01 , 0X02 , 0X03 , 0X04 };
uint8_t DataB[] = { 0 , 0 , 0 , 0 };
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4); //DMA初始化
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); //显示数组DataA
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); //显示数组DataB
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000); //延时1s,观察转运前的现象
MyDMA_Transfer(); //使用DMA转运数组,从DataA转运到DataB
OLED_ShowHexNum(2, 1, DataA[0], 2); //显示数组DataA
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); //显示数组DataB
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000); //延时1s,观察转运后的现象
}
}
(2)DMA+AD多通道
a、AD.c
uint16_t AD_Value[4];
void AD_Init(void) //AD初始化
{
/*开启ADC1 GPIOA DMA1的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div2); //选择6分频,ADCCLK = 12MHz
/*GPIO初始化*/
GPIO_InitStructure GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
GPIO_Init(GPIOA , &GPIO_InitStructure ); //将PA0--PA3引脚配置为模拟输入
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1 ,ADC_Channel0 ,1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1 ,ADC_Channel1 ,2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1 ,ADC_Channel2 ,3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1 ,ADC_Channel3 ,4, ADC_SampleTime_55Cycles5);
/*ADC初始化*/
ADC_InitTypeDef ADC_InitStructure ;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent ; //选择独立模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right ;//数据右对齐
ADC_InitStructure.ADC_ExternalTriConv = ADC_ExternalTrigConv_None ; //使用软件触发
ADC_InitStructure.ADC_ContionuousConvMode = ENABLE ; //连续转换使能
ADC_InitStructure.ADC_ScanConvMode = ENABLE ; //扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 4 ; //扫描通道组的前4个通道
ADC_Init(ADC1,&ADC_InitStructure)
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStructure ;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR ;//外设基地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ;//半字
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable ;//外设地址自增
DMA_InitStructure.MemoryBaseAddr = (uint32_t) AD_Value ;//存储器基地址
DMA_InitStructure.MemoryDataSize = DMA_MemoryDataSize_HalfWord ; //半字
DMA_InitStructure.MemoryInc = DMA_MemoryInc_Enable ; 存储器地址自增
DMA_InitStructure.DIR = DMA_DIR_PeripheralSRC ; //选择由外设到存储器的方向
DMA_InitStructure.BufferSize = 4 ;//转运次数为4
DMA_InitStructure.Mode = DMA_Mode_Circular ; //选择循环模式
DMA_InitStructure.M2M =DMA_M2M_Disable ; //存储器到存储器
DMA_InitStructure.Priority = DMA_Priority_Medium ; //优先级选择中等
DMA_Init(DMA_Channel1 ,&DMA_InitStructure); //配置DMA1的通道1
/*使能DMA ADC*/
DMA_Cmd(DMA1_Channel1 ,ENABLE);
ADC_DMACmd(ADC1,ENABLE); //ADC1触发DMA1的信号使能
ADC_Cmd(ADC1,ENABLE);
/*ADC校准*/
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET) ;
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET) ;
/*ADC触发*/
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //软件触发ADC开始工作,且之后ADC就会一直连续不断地工作
}
首先,开启ADC、DMA、GPIOA的时钟,配置ADCCLK的时钟为12MHz;
然后,配置PA0--PA3的引脚为模拟输入;
其次,配置规则组通道(根据引脚与输入通道的表,从而得到PA0--PA3对应的输入通道分别为ADC_Channel_0-----ADC_Channel_3,并将其设置在通道0-3) ;
之后,进行ADC初始化,因为是多通道AD ,所以选择连续转换 扫描模式 ,并且将ADC转换设置为外部触发 ;
再就是,进行DMA初始化,设置外设、存储器的基地址、数据长度、地址自增 ,以及转运次数、循环模式、是否是存储器到存储器、优先级 ;
还有DMA、ADC的使能 ,不仅需要DMA_Cmd、ADC_Cmd函数 ,还需ADC_DMACmd(ADC1触发DMA1的信号使能);
以及就是固定流程 ------ADC校准;
最后就是ADC触发 ,通过软件触发ADC开始工作。