基于STM32F103RCT6的简易示波器详解(ADC,TIM,LCD)

最近在做2019年的电赛D题,其中有一个要求是‘“自动测量并显示该放大器的频幅特性曲线。显示上限频率值,相对误 差的绝对值不超过 25%”  所以学习了一下简易示波器的制作

目录

硬件部分

器材

原理

软件部分(注释详解)

ADC.c部分

ADC.h

TIMER.c部分

TIMER.h部分

main部分

文件需知

实际调测图片

1.等倍率放大

2.正常状况

提示(讲解):

最重要的一点:

资源分享:


硬件部分

器材:STM32F103RCT6(正点原子mini版)  +   输入信号源(我采用了学校的信号发生器)  若干杜邦线  调试器(ST-link  J-link  或者CMSIS-DAP都行)

原理:采用STM32F103RCT6的ADC采集功能,获取并显示输入信号的电压在TFTLCD显示屏上,根据功能需要,做出不同变化。

软件部分(注释详解)

ADC.c部分

#include "stm32f10x.h"                  // Device header

/*基础配置*/
#define GPIO_Pin_ADC 		GPIO_Pin_1
#define GPIO_Port_ADC		GPIOA
#define RCC_Clock			RCC_APB2Periph_GPIOA
#define ADC_Use				ADC1
#define RCC_ADC				RCC_APB2Periph_ADC1


/*ADC初始化*/
/**************************************
*   函 数 名: ADC_init
*   功能说明: ADC初始化
*   形    参: 无
*   返 回 值: 无
*************************************/
void ADC_init(void)
{
	RCC_APB2PeriphClockCmd (RCC_Clock|RCC_APB2Periph_ADC1,ENABLE);  //时钟使能
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN ;		//模拟输入模式  
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_ADC;		//GPIO口配置
	GPIO_Init (GPIO_Port_ADC,&GPIO_InitStructure);		//GPIO配置完成
	
	ADC_DeInit(ADC_Use);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);		//	时钟分频
	
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode =  ADC_Mode_Independent   ;				//独立模式
	ADC_InitStructure.ADC_ContinuousConvMode =  DISABLE  ;				//单次转换模式
	ADC_InitStructure.ADC_ScanConvMode  = DISABLE;						//扫描模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None  ;//转换由软件启动而不是外部
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right  ;			//右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;								//通道数量1
	ADC_Init (ADC_Use,&ADC_InitStructure);
	
	ADC_Cmd(ADC_Use,ENABLE );		//ADC使能
	
	ADC_ResetCalibration(ADC_Use);					//复位校准
	while(ADC_GetResetCalibrationStatus(ADC_Use)); //等待复位校准结束
	ADC_StartCalibration(ADC_Use);
	while(ADC_GetCalibrationStatus(ADC_Use)); //等待校准结束
}
/*获取模拟量*/
/**************************************
*   函 数 名: Get_A
*   功能说明: 获取模拟量  用于转换成电压
*   形    参: 无
*   返 回 值: 模拟量
*************************************/
uint16_t Get_A(uint8_t ADC_Channel)
{
	ADC_RegularChannelConfig(ADC_Use, ADC_Channel, 1, ADC_SampleTime_239Cycles5 );	//ADC_Use,ADC通道,采样时间为239.5周期	  			    
	ADC_SoftwareStartConvCmd(ADC_Use, ENABLE);		//使能指定的ADC_Use的软件转换启动功能	
	while(!ADC_GetFlagStatus(ADC_Use, ADC_FLAG_EOC ));//等待转换结束
	return ADC_GetConversionValue(ADC_Use);	//返回最近一次ADC_Use规则组的转换结果
}

ADC.h

#ifndef __adc_H
#define __adc_H	 

void ADC_init(void);
uint16_t Get_A(uint8_t ADC_Channel);


#endif

TIMER.c部分

#include "stm32f10x.h"                  // Device header

/*基础配置*/
#define TIM_Use				TIM3  			//(如果这里的TIM改了,下面的中断函数也要需要改)
#define RCC_TIM				RCC_APB1Periph_TIM3
#define IRQn_TIM			TIM3_IRQn

/*定时器初始化*/
/**************************************
*   函 数 名: TIM_Init
*   功能说明:定时器初始化
*   形    参: arr
*   形    参: psc
*   返 回 值: 无
*************************************/
void TIM_Init(u16 arr,u16 psc)
{
	
	RCC_APB1PeriphClockCmd (RCC_TIM ,ENABLE);  				//定时器时钟使能
	
	TIM_TimeBaseInitTypeDef TIM_InitStructure;
	TIM_InitStructure.TIM_Period = arr;						//计数值 
	TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up ;  //向上计数模式
	TIM_InitStructure.TIM_Prescaler = psc;  				//  预分频,此值+1为分频的除数
	TIM_InitStructure .TIM_ClockDivision = 0 ;				//时钟因子 
	TIM_TimeBaseInit (TIM_Use,&TIM_InitStructure );		// 将配置应用到定时器中	
	
	NVIC_InitTypeDef NVIC_InitStructure; 					//中断配置
	NVIC_InitStructure .NVIC_IRQChannel = IRQn_TIM ; 
	NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 0;	// 设置抢占优先级
	NVIC_InitStructure .NVIC_IRQChannelSubPriority = 3;			// 设置相应优先级
	NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE ;			// 通道使能
	NVIC_Init (&NVIC_InitStructure);				// 应用配置
	
	TIM_ITConfig (TIM_Use,TIM_IT_Update ,ENABLE );//允许更新中断
	TIM_Cmd (TIM_Use,ENABLE);  //定时器使能
}


/*定时器中断*/
/**************************************
*   函 数 名: TIM_Use_IRQHandler
*   功能说明:定时器中断
*   形    参: 无
*   返 回 值: flag_of_IRQHandler//时钟是否中断的标志flag
*   返 回 值: ms//记录数据个数(中断次数)
*************************************/
int flag_of_IRQHandler=1;//时钟是否中断的标志flag
int ms=0;//记录数据个数(中断次数)

void TIM3_IRQHandler(void)   //TIM_Use中断
{
	if (TIM_GetITStatus(TIM_Use, TIM_IT_Update) != RESET)  //检查TIM_Use更新中断发生与否
	{
		TIM_ClearITPendingBit(TIM_Use, TIM_IT_Update  );  //清除TIMx更新中断标志 
		ms++;
		flag_of_IRQHandler=1;
		//每次中断 		ms++ 电压数据量加一  
	}	
}

TIMER.h部分

#ifndef __TIMER_H
#define __TIMER_H	 

void TIM_Init(u16 arr,u16 psc);
void TIM3_IRQHandler(void);

#endif

main部分

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "lcd.h"
#include "usart.h"
#include "fontupd.h"//字库检查用
#include "adc.h"
#include "TIMER.h"

#define lcd_width 	240 		//LCD的宽
#define lcd_height 	320		//LCD的高
#define VCC_fatual  3300 		//ADC基准电压  不一定都是3300mv
/*画网格线和坐标轴*/
/**************************************
*   函 数 名: net
*   功能说明: 画网格线和坐标轴
*   形    参: 无
*   返 回 值: 无
*************************************/
void net(void)
{
	LCD_Clear(WHITE);  //清屏
	Brush_Color=GREEN;	//绿色笔刷画网格线
	
	for(int t  = 5;t<lcd_height;t=t+20)//画竖着的网格
	{LCD_DrawLine(0, t, lcd_height, t);}
	for(int t  = 5;t<lcd_height;t=t+20)//画横着的网格
	{LCD_DrawLine(t,0, t,lcd_height );}
	
	Brush_Color=RED;//红色笔刷画坐标轴
	LCD_Draw_Circle(10,lcd_height-10,2);//用圆圈标注原点
	//为了凸显出坐标轴   坐标轴离LCD的最大宽和最大高都差10  
	LCD_DrawLine(10,0,10,lcd_height );//y轴
	LCD_DrawLine(0,lcd_height-10,lcd_width,lcd_height-10 );//x轴
	Show_Str(230,305,12,12,"T",12,0);
	Show_Str(0,0,12,12,"V",12,0);
}

//主函数

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(9600);	 	//串口初始化为9600
	LCD_Init();
	ADC_init();
	font_init();//检查汉字库
	
	TIM_Init(100-1,7200-1); //10Khz的计数频率(最小能测到10khz  低于10k会不准    1ms 一个点
	
	extern int flag_of_IRQHandler,ms;//adc.h中的中断标志和数据下标
	float D_arr[lcd_width];//存放D 电压数据的数组
	float X_arr[lcd_width];//用于计算的数据数组
	float max,min,difference;	//电压量的最大值,最小值,最大最小的差值
	
	LCD_Clear(Background_Color);//清屏  
	while(1)
	{
				
		if(flag_of_IRQHandler==1)//如果被中断 则记录一次数据
		{
			flag_of_IRQHandler = 0;//归0 等待下次中断
			D_arr[ms] =  Get_A(ADC_Channel_1)*VCC_fatual/4096;//模拟量换成数字量电压  以1mV为最小量度
		}
		if(ms>=lcd_width-10)//如果显示的个数达到要求,(建议:最大数据个数<=LDC宽度或者高度) 
		{
			TIM_Cmd (TIM3,DISABLE);//定时器暂时关闭,先处理数据
			net();//画出网格线
			for(ms=0;ms<lcd_width-10;ms++)//进行最大值和最小值等处理
			{
				if(ms==0){max=D_arr[ms];min=D_arr[ms];}
				X_arr[ms]=D_arr[ms];
				if(D_arr[ms]>max){max=D_arr[ms];}
				if(D_arr[ms]<min){min=D_arr[ms];}
			}
			difference=max-min;//最大值和最小值的差值计算
			
			//最大值和最小值显示
			Brush_Color=BLACK;//设置画笔为黑色
			Show_Str(0,15,20,12,"max",12,0);
			LCD_ShowNum(0,27,max,4,12);
			Show_Str(0,lcd_height-30-12,20,12,"min",12,0);
			LCD_ShowNum(0,lcd_height-30,min,4,12);
			
			
			//显示处理  看需要等倍率放大显示,还是按照最大值为3.3v显示(二选一)
			for(ms=0;ms<lcd_width-10;ms++)
			{
				//倍率可调 这边选了280 是调试出来的,比较适合我使用的 可以放小点或者放大点
				//X_arr[ms]=D_arr[ms]/difference*280;//等倍率放大(比如3.3V能到LCD最顶部, 换成最高2V的波也能到最顶部
				X_arr[ms]=D_arr[ms]/10/330*(lcd_height-10);//不按倍率放大  最顶部3.3v  最下面是0v  其余在中间
			}
			for(ms=0;ms<lcd_width-10;ms++)//数据显示在LCD上
			{
				LCD_DrawLine(ms+10,lcd_height-10-X_arr[ms],ms+11,lcd_height-10-X_arr[ms+1]);
			}
			ms = 0;//重新进行数据获取
			TIM_Cmd (TIM3,ENABLE);//定时器重新启动
		}	
}	}

文件需知

需要"delay.h"  "lcd.h"   "usart.h"   "fontupd.h"  "adc.h"  "TIMER.h"这几个文件

其中delay.h   lcd   usart  fontupd是采用正点原子的,请自行前往原子哥的网站下载。

delay 延时用    lcd  用于显示lcd屏    usart  用于串口通信  fontupd是汉字库文件(如果不需要显示汉字,可以不要)

adc和TIMER是这篇文章发出来的部分   

实际调测图片

1.等倍率放大(在测量范围内(设定的是0~3.3v) 都能把把最高值显示在最上方,最低值呈现在最下方,等比例放大)

 采用10khz的正弦波,高电平1v  低电平为0v的显示图像

                                         采用10khz的正弦波,高电平2v  低电平为0v的显示图像




2.正常状况(最上方是3.3v  最下方0v  其他的显示在中间)

采用10khz的正弦波,高电平1.1v  低电平为0v的显示图像

 采用10khz的正弦波,高电平2.2v  低电平为0v的显示图像 采用10khz的正弦波,高电平3.3v  低电平为0v的显示图像

提示(讲解):

1.在main文件里  定义了lcd_width和lcd_height  是LCD的宽高,不同尺寸的LCD需要更改

2.如果不要显示汉字的话,可以删去fontupd.h文件和font_init()等语句  就应该不会报错了

3.在main文件里  VCC_fatual是测量的基准电压  不一定都是3.3V  需要根据实际更改,不然显示会有问题的

4.在main文件里  用了TIM_Init(100-1,7200-1);  这里使用的是10khz的频率,所以本简易示波器,适用范围需要大于10khz  本人实测能到200khz左右,200k以上会失真比较严重,10k一下测不准

如果对频率有要求的,自行调整TIM_Init(100-1,7200-1); 的数值

5.adc和timer文件  只需要把上面的基础配置部分更改就可以用,注意TIMER.c中私用TIM3  如果更改了TIM_USE  记得把下面的TIM3_IRQHandler函数也更改一下  

//adc.c
/*基础配置*/
#define GPIO_Pin_ADC 		GPIO_Pin_1
#define GPIO_Port_ADC		GPIOA
#define RCC_Clock			RCC_APB2Periph_GPIOA
#define ADC_Use				ADC1
#define RCC_ADC				RCC_APB2Periph_ADC1

//TIMER.c
/*基础配置*/
#define TIM_Use				TIM3  			
#define RCC_TIM				RCC_APB1Periph_TIM3
#define IRQn_TIM			TIM3_IRQn

最重要的一点:

如果看完上面,觉得对你有帮助的,麻烦点个赞,谢谢!!!祝您生活愉快!

资源分享:

如果需要源文件,可以私信我的账号 

  • 10
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是基于STM32F103RCT6的五路火焰传感器ADC监测火焰的代码,供您参考: ```c #include "stm32f10x.h" void Init_GPIO(void); void Init_ADC1(void); void Init_TIM3(void); void Delay_ms(uint32_t ms); int main(void) { uint16_t adc_value[5] = {0}; uint32_t sum = 0; uint8_t i, j; Init_GPIO(); Init_ADC1(); Init_TIM3(); while(1) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); //开始一次AD转换 while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); //等待转换完成 for(i=0; i<5; i++) { adc_value[i] = ADC_GetConversionValue(ADC1); //获取AD转换结果 sum += adc_value[i]; //将五路结果求和 } sum /= 5; //求平均值 if(sum < 1000) //判断火焰是否存在 { for(j=0; j<10; j++) //火焰存在时LED闪烁 { GPIO_SetBits(GPIOC, GPIO_Pin_13); Delay_ms(50); GPIO_ResetBits(GPIOC, GPIO_Pin_13); Delay_ms(50); } } sum = 0; } } void Init_GPIO(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); } void Init_ADC1(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //开启扫描模式 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换模式 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //软件触发 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐 ADC_InitStructure.ADC_NbrOfChannel = 5; //转换通道数 ADC_Init(ADC1, &ADC_InitStructure); ADC_Cmd(ADC1, ENABLE); //开启AD转换器 } void Init_TIM3(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period = 999; //计数上限 TIM_TimeBaseStructure.TIM_Prescaler = 7199; //分频系数 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_Cmd(TIM3, ENABLE); //开启TIM3 } void Delay_ms(uint32_t ms) { uint32_t i, j; for(i=0; i<ms; i++) for(j=0; j<7200; j++); } ``` 该代码使用了ADC和定时器,通过对五路火焰传感器进行AD转换,计算平均值,判断火焰是否存在,存在时LED闪烁。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值