嵌入式小白跟随江科大学习笔记 -- 2024.3.29

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的负担

  • 56
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值