基于stm32f407实现定时器触发ADC+DMA双通道采集(库函数实现)

1.简介

多通道采集数据时,为了使采样过程尽可能不占用CPU资源,采用定时器触发的多通道ADC扫描采样,利用ADC+DMA可实时,有序的转存多通道数据至程序内存(数组),再加一个定时中断,用来定时读取内存中的数据。

实现双通道采集时,使用ADC(模数转换器)和DMA(直接内存访问)结合的方式可以提高采集效率。而为了触发ADC的采样和将采样数据传输到指定内存区域,通常需要配置定时器。

配置步骤:

  • 配置ADC通道:设置ADC的工作模式、采样率、采样精度等参数,并选择要使用的ADC通道。

  • 配置DMA通道:设置DMA的传输模式、传输方向、数据长度等参数,并选择要使用的DMA通道。

  • 配置定时器:设置定时器的工作模式、时钟源、定时周期等参数。定时器的触发事件可以作为ADC开始采样的触发源。

  • 配置ADC触发源:将定时器的触发事件与ADC的采样触发源关联起来,以便定时器定时到达后触发ADC的采样动作。

  • 配置DMA传输触发源:将ADC转换完成的数据传输触发源与DMA关联起来,以便ADC完成一次采样后自动触发DMA传输数据到指定的内存区域。

2 ADC

ADC具有独立模式,双重模式和三重模式。由于ADC的规则通道有16个,规则数据寄存器只有一个,如果使用多通道转换,转换的数据就全部在DR中,前一个时间点转换的通道数据就会被新的数据覆盖,开启DMA模式传输可以避免这种问题。对于独立模式的多通道ADC转换使用DMA传输非常有必要。

ADC通道介绍
在ADC中,规则通道(Regular Channels)和注入通道(Injected Channels)是两种不同的采样方式。

  • 规则通道是ADC常用的采样方式,它通过配置一组规则通道来进行采样。每个规则通道可以连接一个模拟信号源,并设置相应的采样时间和分辨率。ADC按照规则通道的顺序依次进行采样,并将采样结果保存在相应的数据寄存器中。规则通道适用于周期性或连续的采样。
  • 注入通道是一种特殊的采样方式,它用于在规则通道采样过程中临时插入额外的通道。注入通道通常用于对事件性信号的快速采样,如瞬态信号或突发事件。注入通道的采样过程不会打断规则通道的采样,因此可以在不中断正常采样的情况下,对某些特定事件进行快速响应。

使用规则通道和注入通道时,需要进行相应的配置:

  • 规则通道配置:设置规则通道的采样顺序、通道号、采样时间、分辨率等参数。规则通道的采样结果可以通过数据寄存器或DMA传输保存。

  • 注入通道配置:设置注入通道的通道号、采样时间、分辨率等参数。注入通道的采样结果可以通过数据寄存器或DMA传输保存。

注意: 常见一般使用ADC规则通道。注入通道是在规则通道转换时强行插入转换的通道,注入通道转换完成后,规则通道才能工作,这点与中断程序很像。注入通道只有在规则通道存在时才会出现。
在这里插入图片描述
转换顺序

单通道模式:只转换规则序列中的一个通道。可以选择转换任一规则通道或注入通道。

单排模式:依次转换规则序列中的每个通道,直到转换完所有通道。转换的顺序是按照其在规则序列中的顺序进行。

扫描模式:循环转换规则序列中的所有通道。转换顺序是按照其在规则序列中的顺序进行,然后再次从第一个通道开始。

注入顺序模式:如果使用了注入通道,在完成规则序列的转换之后,可以选择继续转换注入通道,从而形成注入通道的转换顺序。
规则序列
如下图所示,规则序列寄存器有3个,分别为SQR3、SQR2、SQR1,依次用于配置通道1~16的转换顺序(SQ1–SQ16),以及配置有多少个通道需要转换(SQL)
在这里插入图片描述
举个例子,我们要配置3个转换通道依次是CH1、CH5、CH7、CH16,则
SQ1 寄存器设置为 0(代表CH1)
SQ2 寄存器设置为 4(代表CH5)
SQ3 寄存器设置为 6(代表CH7)
SQ4 寄存器设置为 15(代表CH16)
设定顺序长度: ADC模块通常有一个名为L的寄存器(SQRL),用于指定转换序列的长度。假设L的设置是基于0的计数,如果您想要进行4个通道的转换,您将寄存器设置为3(用二进制表示即0011,因为这通常是从0开始计数的,所以设置为3意味着进行4次转换)。如果设置是基于1的计数,则设置为4。
这样的配置将使ADC按照CH1、CH5、CH7和CH16的顺序进行转换。
对应的关键配置代码如下:

ADC_InitStructure.ADC_NbrOfConversion = 4;                     // 设置通道数为4
ADC_Init(ADC1, &ADC_InitStructure);
// 配置ADC通道1、5、7和16
  ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_3Cycles);  // ADC通道1
  ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_3Cycles);  // ADC通道5
  ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 3, ADC_SampleTime_3Cycles);  // ADC通道7
  ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 4, ADC_SampleTime_3Cycles); // ADC通道16

注入序列
在这里插入图片描述
注入序列寄存器含义与上面规则序列类似,此处不再表述怎么配置。

触发源
ADC转换可以由软触发

ADC_SoftwareStartConvCmd(ADC1, ENABLE);

也可以由外部事件触发转换,包括内部定时器触发和外部IO触发

外部触发检测有4种状态:禁止触发检测、上升沿检测、下降沿检测以及上升沿和下降沿均检测。

3 DMA

DMA可以在不需要CPU干预的情况下实现外设直接和存储器之间的数据传输。在STM32中,DMA可以用于加速数据传输,减少CPU占用率,提高系统性能。DMA控制器是一个独立的外设,它可以与其他外设相连,如ADC、DAC、USART、SPI、I2C等。DMA控制器可以直接访问存储器,而不必通过CPU,从而大大提高数据传输效率。

DMA数据流通道如下所示。
在这里插入图片描述在这里插入图片描述

4 ADC+DMA双通道采集代码实现

4.1 DMA配置

配置对应的数据流通道,配置外设地址及存储器地址,设置方向为外设到存储器。
注意搬运模式要设置为循环搬运DMA_Mode_Circular

uint16_t adc_data[20];//存储采集值的缓冲区

void ADC_DMA_Config(void){	//DMA收
  DMA_InitTypeDef  DMA_InitStructure;	
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
  DMA_InitStructure.DMA_Channel =DMA_Channel_2;  //通道选择
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(ADC3->DR));//par;//DMA外设地址
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)adc_data;//DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式
  DMA_InitStructure.DMA_BufferSize = 20;//数据传输量 
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据长度:8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器数据长度:8位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 设置循环搬运
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级设置
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输

  DMA_Init(DMA2_Stream0, &DMA_InitStructure);//初始化DMA Stream
  DMA_Cmd(DMA2_Stream0, ENABLE);
} 

4.2 ADC配置

触发源选择定时器3,TIM3_TRGO事件触发。

//初始化ADC															   
void Adc_Init(void)
{    
  	GPIO_InitTypeDef  GPIO_InitStructure;
	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	ADC_InitTypeDef       ADC_InitStructure;
	ADC_DMA_Config();
	
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE); //使能ADC3时钟

  //先初始化ADC3通道6,7 IO口
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;		 //模拟输入
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不带上下拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);  
 
	
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;										//独立模式
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; 		//DMA失能
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz 
  ADC_CommonInit(&ADC_CommonInitStructure);										//初始化
	
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;					   //12位模式
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;								   //扫描模式	
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;						   //关闭连续转换
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;//外部触发极性设置为上升沿
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;	   //触发源选择TIM3_TRGO
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;						//数据右对齐	
  ADC_InitStructure.ADC_NbrOfConversion = 2;									//两个转换通道									
  ADC_Init(ADC3, &ADC_InitStructure);
  //设置规则通道转换顺序
  ADC_RegularChannelConfig(ADC3,ADC_Channel_4 , 1, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(ADC3,ADC_Channel_5 , 2, ADC_SampleTime_3Cycles);
  ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);				//ADC在DMA传输结束后发出DMA请求
  ADC_DMACmd(ADC3, ENABLE);
  ADC_Cmd(ADC3, ENABLE);
  ADC_SoftwareStartConv(ADC3);									//启动 ADC3 的软件转换
}	

4.3 定时器配置

定时器配置代码如下,要记得调用TIM_ClearITPendingBit(TIM3,TIM_IT_Update)清除中断标志位。

void timer3_init(void){
	TIM_TimeBaseInitTypeDef TimeBaseInitStructrue;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	/*
	*定时器时钟源频率为:TIMxCLK=84MHz
	*定时器频率为:TIMxCLK/(TIM_Prescaler+1)=84/8400=10000Hz
	*即计一个数的耗时为0.1ms
	*总计数10次,则计时周期为1ms
	*/
	//配置预分频系数
	TimeBaseInitStructrue.TIM_Prescaler=8400-1;
	//配置装载值
	TimeBaseInitStructrue.TIM_Period=10-1;
	
	TimeBaseInitStructrue.TIM_CounterMode=TIM_CounterMode_Up;
	TimeBaseInitStructrue.TIM_ClockDivision=TIM_CKD_DIV1;
	
	TIM_TimeBaseInit(TIM3,&TimeBaseInitStructrue);
	
	TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Update);
	
	//配置中断

	NVIC_InitTypeDef NVIC_InitStructrue;
	//选择中断源
	NVIC_InitStructrue.NVIC_IRQChannel=TIM3_IRQn;
	//设置抢占优先级
	NVIC_InitStructrue.NVIC_IRQChannelPreemptionPriority=0;
	//设置子优先级
	NVIC_InitStructrue.NVIC_IRQChannelSubPriority=3;
	//使能中断
	NVIC_InitStructrue.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructrue);			
	//清除定时器更新中断标志位
	TIM_ClearFlag(TIM3,TIM_FLAG_Update);
	//开启定时器更新中断标志
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
	//使能定时器
	TIM_Cmd(TIM3,ENABLE);
}

//定时器3中断
void TIM3_IRQHandler(void){

	if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET){	
		/***********************************/
		
		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	}
}

4.4 处理采集数据

我们建立的缓冲区如下所示

uint16_t adc_data[20];//存储采集值的缓冲区

上面用到了两个通道转换,依次是CH4、CH5,则每次触发转换都是先转换CH4再转换CH5以执行完一次完整转换,也就是说,转换得到的数据是按照CH4、CH5、CH4、CH5…交替存放在adc_data数组里。
我们使用数据时,可以将采集的数据取平均值(由于数组大小为20,因此每个通道各有10个数据)。

void get_rocker_value(uint16_t* rocker1,uint16_t* rocker2){
	uint32_t va1=0,va2=0;
	for(uint8_t i=0;i<20;i+=2){
		va1+=adc_data[i];
		va2+=adc_data[i+1];
	}
	*rocker1=va1/10;
	*rocker2=va2/10;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值