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

STM32——多路ADC通道+DMA+定时器循环采集传感器数据(二)

程序员文章站 2024-02-24 09:10:40
...

        承接上一篇,接着讲ADC_DMA功能配置,直接存储器访问(DMA,Direct Memory Access)用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。在配置后,可以在无需任何CPU操作的情况下通过DMA快速移动数据,可以节省CPU资源用于其他操作。

        STM32F407的DMA部分有两个DMA控制器,总共有16个数据流(每个控制器8个),每一个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共有可以多达8个通道(或称请求)。每个通道都有一个仲裁器,用于处理DMA请求间的优先级。

STM32——多路ADC通道+DMA+定时器循环采集传感器数据(二)

        由表中可以看出,ADC1的DMA功能可以用通道0数据流0、数据流4,我这里选择的是数据流0。

STM32——多路ADC通道+DMA+定时器循环采集传感器数据(二)

        “DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;”选择循环模式,这里引用别人的理解,DMA循环模式:有些资料会译为DMA的循环缓存模式,我觉得不太准确,这里循环的意思是指DMA的传输数量计数器会重置初值,由于DMA每传一个数据,传输数量计数器减一,只有在传输数量计数器的值不为零时,才会响应请求。在循环模式下,当传输计数器的值减为0后,会重新装载;而内存(缓存)地址则不管循环非循环模式,都会在每次传输完成后重置为基地址。所以,如果我们把DMA设置会正常模式,那么在下次传输前,只需对DMA的传输数量计数器重新写入就行。 循环模式一般用于数据更新,比如ADC采用需要不停更新数据。“ADC_DMA_Config”函数到这里就结束了。接下来是数据处理函数,使用了简单的取均值滤波,

void GetADCAverageV(void)//获取每个通道采样的平均值
{
	u8 i;
	u8 j;
	u32 sum=0;
	u16 ADCAverageValue[Channel_Num]={0};
	if(DMA_GetFlagStatus(DMA2_Stream0,DMA_FLAG_TCIF0)!=RESET)    //DMA完成了某一次的传输
	{
	    for(i=0;i<Channel_Num;i++)
	    {
		for(j=0;j<Sample_Num;j++)
		{
		    sum=sum+ADC_ConvertedV[j][i];
		}
		ADCAverageValue[i]=sum/Sample_Num;//这个数组是最终的结果,求均值
		sum=0;
	    }
	    for(int i=0;i<Channel_Num;i++)
	    {
		printf("%d\t",ADCAverageValue[i]);//打印到串口查看结果
	    }
	    printf("\r\n");
	    DMA_ClearFlag(DMA2_Stream0,DMA_FLAG_TCIF0);//清楚标志
	}
}

        DMA将ADC_DR寄存器结果存到内存的数组中, 在使用其结果时就跟操作数组一样的。接下来是定时器配置和定时中断函数,定时器的配置没有太多解释的,根据需要设置装载值和分频系数、计数模式即可,达到所设定的时间便触发中断函数,中断函数可以根据自己需要继续扩展。

void TIM3_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  ///使能TIM3时钟
          TIM_TimeBaseInitStructure.TIM_Period = arr; 	//自动重装载值
	  TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //定时器分频
	  TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	  TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
	
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
	TIM_Cmd(TIM3,ENABLE); //使能定时器3
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);	
}
        
//定时器3中断服务函数,该函数可以根据自己的需要添加功能
static u16 countnum=0;//计数用的
void TIM3_IRQHandler(void)//整个程序是基于定时器中断
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
	{
	    countnum++;
            printf("%d\t",countnum);		
	    GetADCAverageV();	//调用上面的ADC转换结果处理函数
	    LED1=!LED1;//DS1翻转
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  //清除中断标志位
}

        最后是主函数,主函数主要就是调用之前各模函数,进行ADC、DMA和定时器配置,看程序的注释差不多就能够理解了。

int main(void)
{ 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);   			     //初始化延时函数,这部分用了正点原子的代码
	ADC_DMA_Config();			    //ADC_DMA功能配置
	usart_init();				//初始化串口波特率为115200
	LED_Init();				//初始化LED 	
	TIM3_Int_Init(5000-1,8400-1);	//定时器时钟84M,分频系数8400,所以84M/8400=10Khz的计数频率,计数5000次为500ms   
	while(1)
	{ 
		LED0=!LED0;//DS0翻转
		delay_ms(500);//延时200ms
		}
}
        到此,利用STM32的ADC模块采集多路传感器数据就结束了。其实总体思路比较简单,就是配置ADC、DMA后,然后定时去内存中的结果数组读取结果即可,有疑惑的点儿大概就是ADC循环模式和扫描模式、DMA的循环模式了,这个在文章里也解释了一些。最后,谢谢阅读,希望能跟大家一起交流。