欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基础自学-关于STM32F103C8T6的DMA_ADC-软件触发的基础应用

程序员文章站 2024-02-24 23:46:10
...

让自己别忘了大学里学的东西,那么就把它记录下下来,效率会比忘记然后重新再去找资料再重头学高的多基础自学-关于STM32F103C8T6的DMA_ADC-软件触发的基础应用


1.首先讲讲它的原理

(1)关于单片机的数据传递(CPU方案)

  我们知道,单片机的结构中有外设(比如说:Uart,Adc,Time等等)和内存两大块器件,而在main函数里面,对于变量的运算都是指内存里面的数据进行运算,比如说加减乘除之类的,而这些数据是哪里来的呢?绝大多数都是从外设的某个寄存器中获取来的,因为你在单片机的运算,往往是把单片机看成一个系统,那么它的使命就是接收外部信号,然后在单片机内进行运算,然后输出。而对于外部信号的接收,就会需要用到外设的功能,外设将数据自动的存到自己的对应数据寄存器内,然后单片机需要将接收到的数据放到内存中,然后再main函数中进行运算,然后直接显示在电脑上或者再发送到另一个外设,然后通过外设输出。

(2)少数据原始传输的例子

  对于单个少量的数据,我们可以直接通过CPU直接搬运的方法来将数据再内存和外设之间传输,举个例子,比如说我要做一个ADC的单通道单次软件触发的功能,那么我们只需要在main函数内通过CPU去询问是否转换完成,并且通过赋值的方式将adc数据寄存器的值赋给内存的某个变量。

(3)有什么问题?

   虽然对于少量的数据而言,这个方法并不会对cpu占用过高,但是,当数据读取的频率和数据量增大,cpu将频繁做ADC的读取而耽误了其他的事情,从而CPU效率降低,举个例子,当我们需要对adc的得到的值进行每100次求一个平均值,那么如果通过cpu搬运的方式去运作,就会发现在main函数内的代码执行中,cpu一直在做adc询问和传输的工作,而不能去做其他事情。所以通过cpu直接搬运会出现的问题是,cpu占用率高,无法去做其他事

(4)怎么解决问题?(DMA方案)

  所以为了解决这个问题,就出现了DMA这个东西,“直接内存存取器”,它的功能很明确,就是在内存和外设之间进行数据的传输,它和外设有类似的性质,做事情时不需要靠cpu去管理,而是直接自己去运作,也就是说,每次去接受和发送数据时,不需要去让cpu去等待,而是自己去完成,也就是说比如说我要采集100次adc的值然后取平均后,输出给其他单片机,那么对于dma而言,就是接收到一个来自cpu的控制指令(命令dma去搬运这100个来自adc外设的数据到内存的指定位置),然后去搬运数据,搬运完以后,可以选择反馈给cpu(告诉它搬运结束)。或者继续搬运,那么对于cpu而言,相比于传统方式,它跳过了大量的工作,只是做了一个控制和确定完成的两个指令,但是却使得单片机系统得到了同样的结果。

(5)DMA传输的比喻

  就比如打个比方,有三个人,分别是领导(adc到内存),专业管理的经理(cpu)和专业搬运的工人(dma)。领导要求搬运40箱水果,那么对于经理而言,如果他去做自己干,那么他只能和常人一样,一次搬一箱水果给领导,然后不能去做其他人的管理,对于工人而言,只能闲着干不了工作。但是如果,经理去做管理的事情,下达命令给工人,那么对于经理,他就可以有其他充足的时间用来去管理其他东西,然后当工人干完以后,只要当时让经理停一下,听一下工人的反馈即可,而对于工人也就得到了充分的工作。那么这样就解决了对于外设到内存通讯之间cpu占有率过高的问题。

基础自学-关于STM32F103C8T6的DMA_ADC-软件触发的基础应用
                                                                          两种方案的对比图

2.再看看它的参考代码


(1)M_adc.c文件

#include <M_all.h>  

u16 adc_get[buff_sizes];

//------------------------------------------ADC校准函数(嵌在ADC_init函数内)-----------------------------------------------------//


void ADC_Reset(ADC_TypeDef* ADCx)
{
		
	ADC_ResetCalibration(ADCx);                                                           
	//复位校准寄存器             寄存器置1
	
	while(ADC_GetResetCalibrationStatus(ADCx));                                           
	//等待校准寄存器复位完成      寄存器置0

  ADC_StartCalibration(ADCx);                                                           
	//ADC校准                    寄存器置1
	
  while(ADC_GetCalibrationStatus(ADCx));                                                
	//等待校准完成                寄存器置0
	
}






//------------------------------------------ADC初始化函数-----------------------------------------------------//


void adc_init(void)
{
	
	ADC_InitTypeDef ADC_InitStructure;                                                    
	//结构体_ADC基础-声明   
	
	GPIO_InitTypeDef GPIO_InitStructure;                                                  
	//结构体_引脚基础-声明   
	
	DMA_InitTypeDef DMA_InitStructure;
  //结构体_DMA基础-声明   	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);           
	//时钟开启_GPIOA,ADC1
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);           
	//时钟开启_DMA1
	
	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;                                             
	//结构体_引脚-PA1
	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                                     
	//结构体_引脚-引脚频率_50Mhz
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;                                         
	//结构体_引脚-引脚模式_模拟输入
	
	GPIO_Init(GPIOA, &GPIO_InitStructure);                                                
	//结构体_引脚_结束配置
	
	

	
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC_1_data_address; 
	
	//设置DMA外设地址
	
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&adc_get; 
	
	//设置DMA内存地址
	
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 
	
	//DMA方向 -- 外设到内存
	
	DMA_InitStructure.DMA_BufferSize = buff_sizes; 
	
	//缓存大小 -- 一次大循环要传的数据大小
	
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 
	
	//DMA外设地址-不扩增
	
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
	
	//DMA内存地址-扩增
	
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; 
	
	//DMA的外设数据的大小格式-- 1个字 32位
	
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; 
	
	//DMA的外设数据的大小格式-- 半个字 16位	
	
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 
	
	//DMA的读取次数,一个周期就结束(无循环)
	
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
	
	//DMA的优先级-中等
	
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 
	
	//DMA的内存到内存传输-关闭
	
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	
	
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                                    
	//结构体_ADC-总模式_独立模式

	ADC_InitStructure.ADC_ScanConvMode = DISABLE;                                         
	//结构体_ADC-是否扫描_单通道
	
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;                                   
	//结构体_ADC-是否连续_单次
	
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;                   
	//结构体_ADC-触发方式_软件触发
	
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                                
	//结构体_ADC-对齐方式_右对齐
	
	ADC_InitStructure.ADC_NbrOfChannel = 1;                                               
	//结构体_ADC-通道个数_单通道
	
	ADC_Init(ADC1, &ADC_InitStructure);                                                   
	//结构体_ADC_结束配置
	
	
	
	//	RCC_ADCCLKConfig(RCC_PCLK2_Div2);                                                 
	//ADC1-时钟分频配置       PS:原始输入的ADC输入时钟是1/2的系统时钟
	
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_1Cycles5);            
	//配置内容和ADC外设的具体对接函数   (ADC端口 ,ADC通道 ,转换序号-第几个转换 ,转换的周期)



  ADC_DMACmd(ADC1,ENABLE);
	//开关_ADC的DMA通道-开关

  DMA_Cmd(DMA1_Channel1,ENABLE);
  //开关_DMA-开关
	
	ADC_Cmd(ADC1,ENABLE);                                                                 
	//开关_ADC-总开关   
	
	
 	ADC_Reset(ADC1);                                                                      
	//ADC校准函数                                               
	                                                                                                                 
  ADC_SoftwareStartConvCmd(ADC1,ENABLE); 
	//开启软件转换

}





//------------------------------------------ADC运算读取数值函数-----------------------------------------------------//


float Read_ADC_data(ADC_TypeDef* ADCx)
{
	float kk=0;                                                                                                                                                                                                                                                                                                                                                               
	
	ADC_SoftwareStartConvCmd(ADCx,ENABLE);                                                
	//开关_ADC软件触发-开关    状态寄存器为0                                                                  
				
	while(!ADC_GetFlagStatus(ADCx,ADC_FLAG_EOC));                                         
	//等待转换结束             寄存器置1                                                                            
			
	kk=(3.3*(((float)ADC_GetConversionValue(ADCx)/4096)));                                
	//百分比值转化成电压值,    因为读取了数据寄存器,状态寄存器自动清0                                                                                    
	
	return kk;                                                                            
	//返回电压值    
	
}



(2)main.c文件

int main(void)
	{    		
		
		SystemInit();                                   
		//初始化系统时钟         自动配置成72Mhz
		
		ADC_DeInit(ADC1);                               
		//默认化ADC1
		
		adc_init();                                     
		//初始化ADC1
		
		while(1);

		
	}

(3)DMA配置图

基础自学-关于STM32F103C8T6的DMA_ADC-软件触发的基础应用
PS: STM32固件库里面的结构体配置往往都只是涉及到模式的配置,而并没有开关使能等内容,所以并不是说结构体配置完就可以用了,所以 特意把对应功能的使能命令放在函数的末尾,把涉及到的寄存器放在最上面,方便理解和记忆,一般涉及到哪些寄存器、外设和功能,那么找对应的使能开关,然后使能它们即可。

此次涉及到的开关

1.adc_dam通道-开关 
2.dma-总开关 
3.adc-总开关 
4.adc_校准-开关 
5.adc_软件触发-开关

关于dma配置函数内的几个参数的注意点:

1. DMA_BufferSize=是指dma干一次活的量,比如说我想收集100个ad值然后求平均值,那么这个值就设为100,并且对应的内存变量数据个数也要是100个(往往是数组变量,个数为100个)
2. PeripheralDataSize和MemoryDataSize=这两个分别是内存和外设的单个数据的大小,比如说adc外设采集到的一个数据是8位的,那么外设的这个值就是8位(byte),内存端也一样。
3. DMA_Mode=是指dma搬运的次数,比如说是循环模式,那么就是每次搬N (缓存量就是 DMA_BufferSize) 个数据到规定的地方,然后搬完一次后,又从外设拿到新的N个数据,把它们放到规定的地方(覆盖掉上一次的数据),然后一直循环。再比如是正常模式,就是说,cpu给了指令搬运N个数据到指定位置,搬了一趟,就结束搬运,直至cpu再次发送指令,