DMA理论部分笔记
1.DMA(Direct Memory Access) 直接存储器存取
DMA的用处就是协助CPU进行数据转运,DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无需CPU干预,节省了CPU的资源
此处外设指外设寄存器,例如数据寄存器DR、ADC的数据寄存器、串口的数据寄存器
存储器指运行内存SRAM或者程序存储器Flash,是存储变量数组和程序代码的地方
DMA共有12个独立可配置的通道,DMA1有七个通道,DMA2有五个通道,这些通道就是数据转运的路径,每个通道都支持软件触发和特定的硬件触发,软件触发会一次性的将数据快速的转运,适合从存储器到存储器的转运,而外设到存储器间的转运则需要时机所以需要使用硬件触发的方式,每个DMA的通道硬件触发源是不同的,如需使用则需要一一对应
此处使用的STM32F103C8T6芯片只有DMA1的七个通道,没有DMA2。
2.存储器映像
计算机系统的五大组成部分为:运算器、控制器、存储器、输入设备和输出设备 运算器和控制器合在一起成为CPU,所以重点在CPU和存储器。
下表为STM32中所有存储器的地址映像和用途:
存储器主要分为两大类:ROM和RAM;
ROM是只读存储器,是一种非易失性、掉电不丢失的存储器,RAM是随机存储器,是一种易失性、掉电丢失的 存储器
DMA基本结构:
在DMA中由M2M选择使用软件触发或者硬件触发,传输计数器通过赋值自减决定传输几个数据,当自动重装器不启动时触发一轮自动结束,当启动自动重装器时传输计数器中数字减为0后自动重装为初始值继续传输,一般搭配ADC扫描转换模式,软件触发本质上就是以最快速度将数据传输完毕,即将传输计数器减到0,所以软件触发不可以与自动重装器一起使用。
DMA进行转运的条件:1.开关控制ENABLE 2.传输计数器中数字大于0 3.必须有触发信号
当传输计数器等于0且未使用自动重装器时无论是否触发DMA都会停止,此时需要通过开关控制先关闭DMA再改变传输计数器的值,再开启才可以使用。注:必须先开关控制关闭DMA再改变传输计数器!
硬件触发通道一览:
当有多通道同时进入总线时,通道数字越小优先级越高
DMA传输时如果目标宽度和源宽度一致时正常传输,目标宽度大于源宽度时高位补0,目标宽度小于源宽度时高位舍弃
DMA数据转运实验
1.开始前小实验
uint8_t aa=0x66;
int main(void)
{
OLED_Init();
OLED_ShowHexNum(1,1,aa,2);
OLED_ShowHexNum(2,1,(uint32_t)&aa,8);//&aa是取地址,一般要放在指针中,此处想要作为数字所以需要强制类型转换
while(1)
{
}
}
此处结果显示地址为20000000,意味着数据存放在SRAM区域中
将定义数据的行改为:
const uint8_t aa=0x66;
此时结果地址为08000808,意味着数据存放至Flash区,const修饰的常量是只能读不能写的,所以存放在同样只读的Flash区,因为Flash中还有程序代码所以常量地址不在Flash区的第一位置
2.DMA数据转运实验
原理图:
根据DMA基本结构初始化DMA,初始化步骤为:
1.RCC开启时钟
2.调用DMA_Init设置各种参数
3.开关控制,使能
部分库函数功能:
//设置当前数据寄存器,用于给传输计数器写数据用
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);
代码部分:
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_A;
DMA_A.DMA_PeripheralBaseAddr=AddrA;//外设地址
DMA_A.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//传输的数据宽度,此处为8位
DMA_A.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//设置是否自增,此处为自增
DMA_A.DMA_MemoryBaseAddr=AddrB;//存储器地址
DMA_A.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//传输的数据宽度,此处为8位
DMA_A.DMA_MemoryInc=DMA_MemoryInc_Enable;//设置是否自增,此处为自增
DMA_A.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向,此处为外设为源头
DMA_A.DMA_BufferSize=Size;//设置传输寄存器,外部获取
DMA_A.DMA_Mode=DMA_Mode_Normal;//设置自动重装器,此处不需要自动重装
DMA_A.DMA_M2M=DMA_M2M_Enable;//选择触发方式,此处为软件触发
DMA_A.DMA_Priority=DMA_Priority_Medium;//优先级设置,此处只有一个通道所以优先级随意
DMA_Init(DMA1_Channel1,&DMA_A);
}
void MyDMA_Transfer(void){//用于开启转运
DMA_Cmd(DMA1_Channel1,DISABLE);//关闭DMA
DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//改写传输计数器
DMA_Cmd(DMA1_Channel1,ENABLE);//开启DMA
while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);//获取通道一成功传输的标志位判断传输是否完成
DMA_ClearFlag(DMA1_FLAG_TC1);//清楚标志位
}
主函数:
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);
//Alt+鼠标左键可以方框选择,单改一列数据
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);
}
}
3.DMA转运ADC多通道转换数据
原理图如下
因为数据是一直被循环转换到ADC_DR中不停覆盖再传入DMA的,所以DMA的设置应该是外设到存储器,外设地址不自增,存储器地址自增;ADC设置为连续扫描转换模式
AD设置代码:
uint16_t AD_Value[4];
void AD_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK时钟预分,时钟源72MHz,6分频
GPIO_InitTypeDef GPIO_N;
GPIO_N.GPIO_Mode=GPIO_Mode_AIN;//模拟输入下GPIO口相当于无效,防止口的输入输出对模拟电压产生影响
GPIO_N.GPIO_Pin=GPIO_Pin_0;
GPIO_N.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_N);
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_D;
ADC_D.ADC_Mode=ADC_Mode_Independent;//设置是独立模式还是双ADC模式,此处为独立模式
ADC_D.ADC_DataAlign=ADC_DataAlign_Right;//数据对齐,右对齐
ADC_D.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//外部触发源选择,此处为使用内部
ADC_D.ADC_ContinuousConvMode=ENABLE;//连续转换/单次转换模式,此处为连续
ADC_D.ADC_ScanConvMode=ENABLE;//扫描转换/非扫描转换模式,此处为扫描
ADC_D.ADC_NbrOfChannel=4;//指定在扫描模式下会用几个通道
ADC_Init(ADC1,&ADC_D);
DMA_InitTypeDef DMA_A;
DMA_A.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//外设地址,
DMA_A.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//传输的数据宽度,此处为16位
DMA_A.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//设置是否自增,此处为不自增,因为要不断读取DR寄存器中的数据
DMA_A.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//存储器地址
DMA_A.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//传输的数据宽度,此处为8位
DMA_A.DMA_MemoryInc=DMA_MemoryInc_Enable;//设置是否自增,此处为自增
DMA_A.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向,此处为外设为源头
DMA_A.DMA_BufferSize=4;//设置传输寄存器,外部获取
DMA_A.DMA_Mode=DMA_Mode_Normal;//设置自动重装器,此处不需要自动重装
DMA_A.DMA_M2M=DMA_M2M_Disable;//选择触发方式,此处为硬件触发
DMA_A.DMA_Priority=DMA_Priority_Medium;//优先级设置,此处只有一个通道所以优先级随意
DMA_Init(DMA1_Channel1,&DMA_A);//此处因为使用的是ADC1硬件触发所以必须选择对应通道
DMA_Cmd(DMA1_Channel1,ENABLE);//DMA上电
ADC_DMACmd(ADC1,ENABLE);//开启ADC到DMA的通道
ADC_Cmd(ADC1,ENABLE);//ADC上电
ADC_ResetCalibration(ADC1);//复位校准
while(ADC_GetResetCalibrationStatus(ADC1)==SET);//检测是否已经复位
ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1)==SET);//检测是否已经校准完成
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
}
在软件触发ADC后DMA自动被触发转运数据,所以不需要单独的转运的函数,可以直接在主函数中读取转运后的数据。
4.本章小作业
鉴于本人学习效率太低,所以设置了这个小模块,强行给自己一个任务去运转,后续会有单独的博客将敲出来的程序进行展示,不过设置的题目不会太难,都是些与当日学习相关的,我会在几天后复习时做这个小作业【嘻】
使用TIM触发ADC带动DMA运作,三外设协作降低CPU的负担