STM32定时器ADC构建简易示波器

一、方案

先用 LCD制作 一个格子图形,然后定时器触发ADC采样。再将采集到的数据绘制成曲线显示在格子图形上,通过读取 图形上的点来测量信号。 本文使用的是原子哥的F103ZET6的战舰开发板。

二、具体实施

1.LCD初步显示

LCD的配置代码我是直接复制原子哥的,直接调用了它里面的函数。
因为我的屏幕是480*800,所以为了布局采用了横屏显示。
首先根据方案,我要先制作一个格子图。并且为了观察波形的数据,加入了时间和数值显示。绘制格子是在函数display里面。格子的话时 每隔20绘制一条线,可以根据 自己情况 修改。

void display(void)
{
	uint16_t t;
	LCD_Fill(0,0,800,340,WHITE);
	POINT_COLOR=GREEN;
	for(t  = 0;t<360;t=t+20)
		LCD_DrawLine(0, t, 800, t);
	for(t  = 0;t<800;t=t+20)
		LCD_DrawLine(t,0, t,340 );
	POINT_COLOR=BLUE;
	LCD_DrawLine(0, 180, 800, 180);
	LCD_DrawLine(400,0, 400,340 );
	LCD_ShowNum(84,376,k,2,24);
}
void main(void)
{
  	...
	LCD_Init();
	LCD_Clear(WHITE);
	LCD_Display_Dir(1);
	POINT_COLOR=BLUE;
	LCD_ShowString(60,400,24*3,24,24,"t1:");
	LCD_ShowString(24,376,24*2,24,24,"move:");
	LCD_ShowString(60,424,24*3,24,24,"t2:");
	LCD_ShowString(0,448,24*4,24,24,"|t1-t2|:");
	LCD_ShowString(146,400,24*2,24,24,"ms");
	LCD_ShowString(146,424,24*2,24,24,"ms");
	LCD_ShowString(146,448,24*2,24,24,"ms");
	LCD_ShowString(260,400,24*3,24,24,"V1:");
	LCD_ShowString(260,424,24*3,24,24,"V2:");
	LCD_ShowString(200,448,24*4,24,24,"|V1-V2|:");
	LCD_ShowString(358,400,24*3,24,24,"mV");
	LCD_ShowString(358,424,24*3,24,24,"mV");
	LCD_ShowString(358,448,24*3,24,24,"mV");
	display ();
	...
}

效果展示:

2.ADC配置

接下来就是ADC配置 。先上代码:

void adc_init(void)
{
	GPIO_InitTypeDef adc1io;
	ADC_InitTypeDef adc1;
	
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
	
	adc1io.GPIO_Mode = GPIO_Mode_AIN ;
	adc1io.GPIO_Pin = GPIO_Pin_1 ;
	GPIO_Init (GPIOA,&adc1io);
	
	ADC_DeInit(ADC1);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	adc1.ADC_Mode =  ADC_Mode_Independent   ;
	adc1.ADC_ContinuousConvMode =  ENABLE  ;//连续转换模式
	adc1.ADC_ScanConvMode  = DISABLE;//扫描模式 ,在于单通道还是双通道
	adc1.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None  ;//转换由软件启动而不是外部
	adc1.ADC_DataAlign = ADC_DataAlign_Right  ;
	adc1.ADC_NbrOfChannel = 1;
	ADC_Init (ADC1,&adc1);
	
	ADC_Cmd(ADC1,ENABLE );
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1)); //等待校 AD 准结束

	ADC_RegularChannelConfig(ADC1, 1, 1, ADC_SampleTime_239Cycles5 );//通道 1,规则采样顺序值为 1,采样时间为 239.5 周期 
	ADC_SoftwareStartConvCmd  (ADC1,ENABLE);//使能软件转换功能
	
}

uint16_t Get_adc()
{
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
	return ADC_GetConversionValue(ADC1); //返回最近一次 ADC1 规则组的转换结果
}

uint16_t Get_adcAverage(uint8_t times)//最高255,最少3
{
	uint32_t t;
	uint16_t a[255]={0};
	int i,g1,g2;
	for(i=0;i<times;i++)
	{
		while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
		a[i] = ADC_GetConversionValue(ADC1);
	}
	g1= g2  = a[0];
	for(i = 0;i<times-1;i++)
	{
		if(g1>a[i+1])g1 = a[i+1];//让 g1为最小值
		if(g2<a[i+1])g2 = a[i+1];//让 g2为最大值
	}
	for(i = 0;i<times;i++)
	{
		t += a[i];
	}
	return t = (t-g1-g2)/(times-2);
}

ADC配置简单介绍:
因为只打算做一个通道,所以配置成独立模式 ,开启连续转换模式。这里 的ADC采集我打算放在 主函数里面,所以也是配置成由软件触发。 这里使用了ADC1的通道1。为了采样准确率上升我把 采样时间 配置 239.5个周期 。因为是72M6分频,所以ADCCLK是12M。总转换时间公式是:Tcovn=采样时间+12.5 个周期,计算出来我采样一次时间是18us。计算这个只要是因为使用定时器触发,防止触发时间低于采样时间。
然后我编了一个提高采样精准的函数:连续采样多次(至少3次),去掉最大值和最小值,然后取平均。

3.定时器配置

同样先上代码:

void TIM_Init(void)
{
	TIM_TimeBaseInitTypeDef Tim3;
	NVIC_InitTypeDef NVIC_Tim;


	RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3 ,ENABLE);
	Tim3.TIM_Period = 10;
	Tim3.TIM_CounterMode = TIM_CounterMode_Up ;
	Tim3.TIM_Prescaler = 7199;
	Tim3 .TIM_ClockDivision = TIM_CKD_DIV1 ;
	TIM_TimeBaseInit (TIM3,&Tim3 );
		
	TIM_ITConfig (TIM3,TIM_IT_Update ,ENABLE );//允许更新中断
	
	NVIC_Tim .NVIC_IRQChannel = TIM3_IRQn ;
	NVIC_Tim .NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Tim .NVIC_IRQChannelSubPriority = 0;
	NVIC_Tim .NVIC_IRQChannelCmd = ENABLE ;
	NVIC_Init (&NVIC_Tim);
	
	TIM_Cmd (TIM3,ENABLE);

}


void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
		{
			Z=1;
			TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
			ms++;
		}
}

我这里是打算 1ms采集一个点 ,所以我的采集信号频率只能在 1KHz以下,由于是绘图,所以在一个周期采集越多的点效果回越好。
因为中断不好执行占用CPU过长的任务,所以我在主函数 里面使用ADC采集,这里 只是 给它一个判断标志。

5.按键配置

为了测量周期和峰峰值,我用可移动的两根线在波形上测量。这里就需要 用到按键。

void EXTI0_IRQHandler(void)
{
	for(time = 0;time<40000;time++);
	if(GPIO_ReadInputDataBit (WKUP_GPIO ,WKUP_GPIO_PIN ) == SET)	 	 //WK_UP按键
	{				 
		mode ++;
		if(mode ==5)mode  =0;
	}
	EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位  
}

void EXTI2_IRQHandler(void)
{
	for(time = 0;time<40000;time++);
	if(GPIO_ReadInputDataBit (KEY2_GPIO ,KEY2_GPIO_PIN ) == RESET)
	{
		node  =1;
	}
	EXTI_ClearITPendingBit(EXTI_Line2); //清除 LINE2 上的中断标志位 
}

void EXTI3_IRQHandler(void)
{
	for(time = 0;time<40000;time++);
	if(GPIO_ReadInputDataBit (KEY1_GPIO ,KEY1_GPIO_PIN ) == RESET)
	{
		k++;
		if(k>10)k=1;
		
	}
	EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE3 上的中断标志位 
}

void EXTI4_IRQHandler(void)
{
	for(time = 0;time<40000;time++);
	if(GPIO_ReadInputDataBit (KEY0_GPIO ,KEY0_GPIO_PIN ) == RESET)
	{
		node = 2;
	}
	EXTI_ClearITPendingBit(EXTI_Line4); //清除 LINE4 上的中断标志位 
}

这里也是通过标志来让单片机运行相应功能。
KEY_UP:第一次按下 :暂停波形,选定第一个竖直线。
第二次按下 :选定第二个数值线。
第三次按下 :选定第一个水平线。
第四次按下 :选定第二 个水平线。
第五次按下 :恢复运行波形。

KEY1:修改线移动的格数,这里设置是1~10。
KEY0:**当选定水平线时:向下移动k。
当选定竖直线时:向左移动k。
KEY2:当选定水平线时:向上移动k。
当选定竖直线时:向右移动k。

6.整合

首先是先采集并且 绘制波形 ,这里的 方法 是1ms采集一个点并且采集800个点后开始绘制波形。

void  main(void)
{
	...
	while(1)
	{
		while( mode  ==0)
		{
			if(Z==1)
			{
				Z = 0;
				adcx = Get_adcAverage (5);
				temp[ms] = adcx*(3.3/4096)*100;
				while(temp[ms]>330)
				{
					adcx = Get_adcAverage (3);
					temp[ms] =adcx*(3.3/4096)*100;
				}
			}
			if(ms == 799)
			{
				TIM_Cmd (TIM3,DISABLE);
				display ();
				for(ms=0;ms<800;ms++)xemp[ms]=temp[ms];
				POINT_COLOR=BLACK;
				for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
				ms = 0;
				TIM_Cmd (TIM3,ENABLE);
			}
		}
	...
	}
}

这里的话就是1ms触发定时器中断,然后Z置1,并且变量ms++。Z为 1开启ADC采集 ,将采集的数据存储在数组temp中。当ms为 799时,暂时关闭定时器,将数组temp中的数据转移到xemp中,再根据 xemp绘制曲线(转移到xemp中是为了保证后面绘制的曲线数据 在同一采集周期内)。因为波形需要更新 所以先调用 display更新下次波形。然后调用LCD_DrawLine绘制波形。这样就能将上个0.8s的信号数据绘制出来。将 变为 0并且再次开启定时器。至于要把这些放在while(mode==0)里,是为了后面服务的。
再看后面的代码:

	if(mode == 1)
		{
			TIM_Cmd (TIM3,DISABLE);
			while(mode == 1)
			{
				if(node ==1)
				{
					display  ();
					POINT_COLOR=BLACK;
				for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
					node = 0;
					POINT_COLOR=RED;
					X=X+k;
					if(X  >  800)X = 0;
					LCD_DrawLine(X, 0, X, 340);
					LCD_ShowNum(110,400,(u32)X,3,24);
				}
				if(node ==2)
				{	
					display  ();
					POINT_COLOR=BLACK;
				for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
					node = 0;
					POINT_COLOR=RED;
					X=X-k;
					if(X >  800)X = 800;
					LCD_DrawLine(X, 0, X, 340);
					LCD_ShowNum(110,400,(u32)X,3,24);
				}
			}
		}
		while(mode == 2)
		{
			if(node ==1)
			{
				display  ();
				POINT_COLOR=BLACK;
				for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
				POINT_COLOR=RED;
				LCD_DrawLine(X, 0, X, 340);
				node = 0;
				X1=X1+k;
				if(X1  >  800)X1 = 0;
				LCD_DrawLine(X1, 0, X1, 340);
				LCD_ShowNum(110,424,(u32)X1,3,24);
			}
			if(node ==2)
			{	
				display  ();
				POINT_COLOR=BLACK;
				for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
				POINT_COLOR=RED;
				LCD_DrawLine(X, 0, X, 340);
				node = 0;
				X1=X1-k;
				if(X1 >  800)X1 = 800;
				LCD_DrawLine(X1, 0, X1, 340);
				LCD_ShowNum(110,424,(u32)X1,3,24);
			}
			if(X1>X)LCD_ShowNum(110,448,(u32)X1-X,3,24);
			else LCD_ShowNum(110,448,(u32)X-X1,3,24);
		}
		while(mode == 3)
		{
			if(node ==1)
			{
				display  ();
				POINT_COLOR=BLACK;
				for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
				node = 0;
				POINT_COLOR=RED;
				X=X+k;
				if(X  >  340)X = 0;
				LCD_DrawLine(0,X,  800,X);
				LCD_ShowNum(310,400,(u32)(340-X)*10,4,24);
			
			}
			if(node ==2)
			{	
				display  ();
				POINT_COLOR=BLACK;
				for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
				node = 0;
	
				POINT_COLOR=RED;
				X=X-k;
				if(X >  340)X = 340;
				LCD_DrawLine(0,X,800,X);
				LCD_ShowNum(310,400,(u32)(340-X)*10,4,24);
			}
		}
		if(mode == 4)
		{
			while(mode == 4)
			{
				if(node ==1)
				{
					display  ();
					POINT_COLOR=BLACK;
					for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
					POINT_COLOR=RED;
					LCD_DrawLine(0, X, 800, X);
					node = 0;
					X1=X1+k;
					if(X1  >  340)X1 = 0;
					LCD_DrawLine(0,X1, 800, X1);
					LCD_ShowNum(310,424,(u32)(340-X1)*10,4,24);
				
				}
				if(node ==2)
				{	
					display  ();
					POINT_COLOR=BLACK;
					for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
					POINT_COLOR=RED;
					LCD_DrawLine(0, X, 800, X);
					node = 0;
					X1=X1-k;
					if(X1 >  340)X1 = 340;
					LCD_DrawLine(0,X1, 800, X1);
					LCD_ShowNum(310,424,(u32)(340-X1)*10,4,24);
				}
			if(X1>X)LCD_ShowNum(310,448,(u32)(X1-X)*10,4,24);
				else LCD_ShowNum(310,448,(u32)(X-X1)*10,4,24);
			}
			ms = 0;
			TIM_Cmd (TIM3,ENABLE);
		}

这部分代码思路其实都一样,因 为要关闭和开启 定时器,所以再在mode为1和mode为4外面先加了if语句。这里介绍一下mode为一的情况:
当按下第一次WK_UP的时候,mode为1,主函数进入while(mode==1)的循环。当你不 进行人格 操作 时,此时LCD页面 时 静止的,因为 不会进入里面 的if语句,while相当于空内容。当你按下相应功能按键,比如 KEY0,变量node变为2,进入if:先将 node置0。然后调用 display,更新显示,绘制波形,显示线和数值。图中红线 既是选定 的线。可按KEY2和 KEY0移动,move表示移动的大小。其他mode差不多 是一样的情况。

7.实测效果

用信号发生器产生10Hz的峰峰值为2.8V的正弦波形(因为没搞采集负电压所以需要将低电平调为0):


5Hz1.5V方波:

2Hz三角波1V


总结

这个代码我也只是测了几组,不知道还有没有问题,有的话欢迎大家指出。 然后误差也有,这跟32采集的精准度以及对他的处理还有我设置的分辨率都有关系,再者这里是将采集的两个点直接连线连起来,所以图形也会有误差。不过这个代码可改进的 方面有很多,例如用DMA传输,改变触发采集 时间(应该只要保证采样时间小于触发时间就行) 等等 。


源码连接:链接:ht删tps://pa掉n.baidu.co中m/s/1文ZUNmWp-Sb3nWTmGrRpGSKg 提取码:pn7r


物联沃分享整理
物联沃-IOTWORD物联网 » STM32定时器ADC构建简易示波器

发表评论