基于stm32f407的示波器


一.设计要求

在这里插入图片描述

二.整体思路

在这里插入图片描述
硬件部分主要负责电压的缩放以及垂直灵敏度的控制,因为stm32的大部分引脚最高输入电压为3.3v,而要求的电压需要50v,需要进行电压缩放。
软件部分主要负责方波的实现,电压采集,显示,水平时基的调整,hold功能等。

三.硬件设计

用到了multisim进行仿真以及立创eda进行制作原理图和pcb板图。这里主要参考了dso138示波器的硬件部分。具体设计如下:

1.功能部分

在这里插入图片描述
开关1: 耦合方式。
开关2和开关3: 垂直灵敏度,开关2和开关3的乘机为示波器一小格所代表的电压值,示波器的网格大小是不变的,变得是波形。

通过采集电压值判断硬件开关1,开关2,开关3所在位置,计算出初始电压,判断耦合方式。

接线图如下所示
前端电路 单片机
3.3<----------->3.3
GMD<-------->GND
Sw1<--------->PA3
Sw2<--------->PA4
Sw3<--------->PA5
Signal<------->PA1 (进行电压采集)

电压计算公式:待测电压=signal*Sw2*sw3(sw2 sw3的值见下表)
如果PA3,PA4,PA5采集的采集的电压为3v左右则为一档,如果电压为1.5v左右则为二档,如果电压为0v左右则为三档。

一档 二档 三档
Sw1 DC模式 AC模式 GND模式
Sw2 1 10 100
Sw3 1 2 5

举个例子: 下图测量结果为1.74v,第二个开关在二档也就是*10,第三个开关在二档也就是*2,要先判断是否超量程(方法就是二档开关倍数*最终电压看是否为3.48v左右),1.74*2=3.48v也就是超量程了需要改第二个开关。
在这里插入图片描述

将第二个开关从二档改到三档,247mv*2<3.48v,原被测电压=247mv*2*100=49.4v
在这里插入图片描述

2.电源部分

电源部分的图如下,负责12v转±5v(给运算放大器供电)和3.3v(给单片机供电)。
在这里插入图片描述

3.stm32f407核心板及3.2寸的TFT显示屏

在这里插入图片描述在这里插入图片描述
原理图自行百度。
这里用到的部分引脚,剩余的见cubemx:

PA0 WKUP按键
PE3 KEY1
PE4 KEY0
PA6 LED2
PA7 LED3
PC6 方波输出
PA1 信号采集

四.软件设计

1.主函数的设计

程序中的主函数主要是对我们所需要的各个外部设计元件进行初始化,绘制开机动画之后,将示波器的核心部分元件进行初始化处理,其中包括了ADC定时器。以及DMA,经过初始化完成之后启动DMA通道转换AD采样结果,当转换结果达到我们与预先的设定值之后就会产生DMA中断,在DMA中断里进行绘制波形等相应的处理,处理完成之后我们还需要进行开始DMA通道转换。DMA中断用LED灯的亮灭来分别指示程序是否正在进行。主要程序在DMA中断里。

2.测试信号方波实现

由于实际使用示波器时不一定有函数发生器来检查示波器自身是否正常工作,所以为了示波器自检的方便,设计了一种1kHz的函数信号进行输出。

采用PWM(定时器)+DWA。将定时器的周期设为1ms,设pwm的占空比为50%即可,也就是在1ms内,高电平占500us低电平占500us。经过我对几个方案的对比,最后决定采用PWM(定时器)+DWA实现完成,下面介绍一下DWA在本次设计中的优势,DMA处于总线矩阵的前级,与内核cortex-M4同级别,属于主设备(Master)。DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。使用DMA的话就可以保证系统的实时性,即在不用操作系统的情况下即能进行信号采集又能输出方波。

cubemx配置定时器为PWM模式
在这里插入图片描述
cubemx配置定时器3的DMA
在这里插入图片描述
系统时钟配置
在这里插入图片描述
这里用到的是定时器3,它的时钟为APB1 Timer clocks也就是84MHZ。(1MHz=1000KHz=1000000Hz)

定时器周期公式:T=(arr+1)*(psc+1)/Tck=(84)*(1000)/(84*1000000)= 0.001s=1ms
其中TCK为时钟频率(单位为HZ),PSC为时钟预分频系数,arr为自动重装载值。
配置和初始化程序由cubemx软件直接生成,需要在主函数中添加的程序如下所示:

uint16_t data[40]={
   500,500,500,500};     //设置占空比
HAL_TIM_OC_Start_DMA(&htim3,TIM_CHANNEL_1,(uint32_t*)data,4);  //开启定时器和DMA并配置占空比。

用示波器对产生的方波进行测试,测试结果如下图所示:
在这里插入图片描述

3.示波器运行界面的设计

示波器的屏幕采用了3.2寸的TFT显示屏,原本选用的方案是采用串口屏进行显示,串口屏的开发简单更适合做UI界面,但是串口屏的更新速率过慢无法满足示波器实时显示的要求。
TFT显示屏采用FSMC进行驱动,不采用GPIO直接驱动是因为这种方法太慢,FSMC是用来外接各种存储芯片的,所以其数据通信速度是比普通GPIO口要快得多的。TFT-LCD 驱动芯片的读写时序和SRAM的差不多,所以就可以用FSMC四块中的SRAM块来驱动LCD。SRAM有数据线和地址线,所以FSMC跟它匹配同样也有数据线和地址线,而LCD数据线跟地址线共用,通信时用RS端来区分线上是数据还是指令,RS高是数据,RS低是指令。我们在给fsmc一个地址写数据的时候,会产生一个时序,而这个时序正好跟我们lcd的读写时序一致,从而通过地址写数据巧妙的实现了lcd的读写时序。采用cubemx对FSMC进行初始化配置如图所示:
cubemx配置FSMC总线在这里插入图片描述
对TFT显示屏进行读写数据时需要注意时序,由于都是微秒级延时而HAL库只提供了毫秒级延时,需要自己对微秒级延时进行编写。重新找一个定时器实现微秒级延时,这里采用系统计数器systick。CM3与CM4包含一个系统计数器SysTick,是一个24位倒计数定时器,当计数到0 时,将从RELOAD寄存器中自动重装载定时初值,只要把它在SysTick->CTRL中的使能位清除,则一直存在。
TFT显示屏对FSMC进行读写程序如下所示:

//写寄存器函数
//regval:寄存器值
void LCD_WR_REG(vu16 regval)
{
   
	regval = regval; //使用-O2优化的时候,必须插入的延时
	TFTLCD->LCD_REG=regval;  //写入要写的寄存器序号	
}

//写LCD数据
//data:要写入的值
void LCD_WR_DATA(vu16 data)
{
   
	data=data;	//使用-O2优化的时候,必须插入的延时
	TFTLCD->LCD_RAM=data;
}

//读LCD数据
//返回值:读到的值
u16 LCD_RD_DATA(void)
{
   
	vu16 ram;  //防止被优化
	ram=TFTLCD->LCD_RAM;
	return ram;
}
//写寄存器
//LCD_Reg:寄存器地址
//LCD_RegValue:要写入的数据
void LCD_WriteReg(vu16 LCD_Reg,u16 LCD_RegValue)
{
   
	TFTLCD->LCD_REG = LCD_Reg;  //写入要写的寄存器序号
	TFTLCD->LCD_RAM = LCD_RegValue;//写入数据
}

//读寄存器
//LCD_Reg:寄存器地址
//返回值:读到的数据
u16 LCD_ReadReg(vu16 LCD_Reg)
{
   
	LCD_WR_REG(LCD_Reg);  //写入要读取的寄存器序号
	delay_us(5);
	return LCD_RD_DATA();  //返回读到的值
}

//开始写GRAM
void LCD_WriteRAM_Prepare(void)
{
   
	TFTLCD->LCD_REG=lcddev.wramcmd;
}

//从ILI93xx读出的数据为GBR格式,而我们写入的时候为RGB格式。
//通过该函数转换
//c:GBR格式的颜色值
//返回值:RGB格式的颜色值
u16 LCD_BGR2RGB(u16 c)
{
   
	u16 r,g,b,rgb;
	b=(c>>0)&0x1f;
	g=(c>>5)&0x3f;
	r=(c>>11)&0x1f;
	rgb=(b<<11)+(g<<5)+(r<<0);
	return (rgb);
}

示波器主界面通过输出字符来显示相应的字母和数组,调整好对应坐标进行显示。波形位置显示是通过符号[-]|按照一定组合成字符串即可得到这种效果。网格是通过画点实现的,横线每隔4像素画一个点,纵向每隔30像素画一条横线。竖线每隔4像素画一个点,横向每隔32像素画一条竖线。画完点阵之后在外部画一个矩形实线框起来,横纵坐标中心位置画十字实线表示中点。主要设计程序如下所示
画网格核心部分代码如下:

	void Lcd_DrawNetwork(void)
{
   
	u16 index_y = 0;
	u16 index_x = 0;	
	
    //画列点	
	for(index_x = 0;index_x <= 288;index_x += 32)
	{
   
		for(index_y = 0;index_y < 240;index_y += 4)
		{
   
			LCD_Fast_DrawPoint(index_x,index_y,0X534c);	
		}
	}
	//画行点
	for(index_y = 0;index_y <=240;index_y += 30)
	{
   
		for(index_x = 0;index_x <288;index_x += 4)
		{
   
			LCD_Fast_DrawPoint(index_x,index_y,0X534c);	
		}
	}
   LCD_DrawLine_color (0 ,120 , 288 ,120,BROWN);//十字叉
   LCD_DrawLine_color (160 ,0 , 160 ,240,BROWN);
}

TFT显示屏显示如下所示:
在这里插入图片描述

4.按键实现

这里采用了stm32f407核心板自带的三个按键,wk_UP、KEY0、KEY1分别对应的功能是切换功能选项,增大、减小。为保证按键的实时性采用外部中断,并将中断优先级设置为最高也就是0。用cubemx对按键进行配置,根据核心板的原理图可知wk_UP设置成下拉模式,KEY0、KEY1设置为上拉模式。
cubemx配置按键
在这里插入图片描述
按键原理图
在这里插入图片描述
cubemx配置中断
在这里插入图片描述
为方便看清功能选项选到了哪一个功能,在选择的功能下面画一条绿色的直线并清除上一次画的横线。当按下一次按键时会让按键对应的按键标志位(key0,key1,key2)加一,如果按下wk_UP切换功能键会通过取余判断现在所选的功能是哪一个并对KEY0、KEY1按键的标志位进行清零。按键中断程序如下:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
   
   if(GPIO_Pin==GPIO_PIN_0)
	{
   
		  delay_us(10);     //防抖
		  key0++;
		  if(key0%6==1)
			{
   
				LCD_DrawLine_color(0,238,25,238,BLACK);      //清除上一次画线
	                 LCD_DrawLine_color(295,30,320,30,GREEN);           //画线
			}
			else if(key0%6==2)
			{
   
	      LCD_DrawLine_color(295,30,320,30,BLACK);
				LCD_DrawLine_color(295,70,320,70,GREEN);
			}
			else if(key0%6==3)
			{
   
			  LCD_DrawLine_color(295,70,320,70,BLACK);
				LCD_DrawLine_color(295,110,320,110,GREEN);
			}
			else if(key0%6==4)
			{
   
				LCD_DrawLine_color(295,110,320,110,BLACK);
				LCD_DrawLine_color(295,150,320,150,GREEN);
			}
			else if(key0%6==5)
			{
   
			  LCD_DrawLine_color(295,150,320,150,BLACK);
				LCD_DrawLine_color(295,190,320,190,GREEN);
			}
			else if(key0%6==0)
			{
   
				LCD_DrawLine_color(295,190,320,190,BLACK);
				LCD_DrawLine_color(0,238,25,238,GREEN);
			}
			key1=0;
			key2=0;
			
	}
	else if(GPIO_Pin==GPIO_PIN_3)
	{
   
		delay_us(10);
		if(key0%6==0)
			 time_flag=0;          //时基调整标志位
		key2++;
	}
  else if(GPIO_Pin==GPIO_PIN_4)
	{
   
		  delay_us(10);
		  if(key0%6==0)
			  time_flag=0;               
	    key1++;
	}
}

5.ADC模数转换

ADC采样是指将连续变量的模拟信号转换为离散的数字信号的器件。典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。由于要保证采样的实时性采用DMA对ADC采集后的数据进行存储,将DMA存储量设为1024字节。用定时器2来改变采样间隔从而改变时基。当DMA中存储的数据达到1024字节时会触发DMA中断,判断此时示波器所处的状态是运行还是停止,如果是停止则会直接跳出终端不在TFT显示屏上显示,如果是运行会关闭定时器2,也就是停止采样,之后读取DMA中的数据并显示到TFT显示屏上,由于画点的速度比画线快所以采用画点的方法。显示完成后再打开定时器2开启下一次采样。

cubemxADC配置(ADC+DMA+TIMER2)
在这里插入图片描述

ADC的DMA中断的程序如下所示:


void DMA2_Stream0_IRQHandler(void)
{
   
	if(mode1==1)
	{
   
  if(HAL_DMA_GetState(&hdma_adc1))
	{
   
		HAL_TIM_Base_Stop(&htim2);     //关闭定时器2
		adc_line();                     //画点
		HAL_TIM_Base_Start(&htim2);     //开启定时器2

	}
  }
   HAL_DMA_IRQHandler(&hdma_adc1);
}

HOLD功能实现代码如下所示:
代码思想:如果按键key_up选择了hold功能并且按下了key1键,就会判断现在所处状态是运行还是停止,如果是运行就改为停止状态,DMA中断就不会在执行显示任务,波形也就不会更新。如果是停止状态就改为运行状态,DMA中断执行显示任务。

if(key0%6==1&&key1==1)                   
{
   
	  if(mode1==1)
	 {
   
			BACK_COLOR=GRAYBLUE;
			LCD_ShowString_COLOR(295,15,200,12,12,"stop",RED);
			mode1=0;
	 }
	else
	{
   
			BACK_COLOR=GRAYBLUE;
			LCD_ShowString_COLOR(295,15,200,12,12," run",GREEN);
			Lcd_DrawNetwork();
			mode1=1;
	}
	key1=0;
}

画点部分程序如下所示(adc_line()中部分程序):
代码思想:清除上一次显示的点,如果清除和显示的速率足够高也就是刷新率足够高人眼就看不出来屏幕一闪一闪的,之后对DMA中存储的数据进行坐标转换,屏幕的分辨率为240*320而ADC采集的点的范围为0-4095,所以转换公式为ADC_Value[t] =120-((ADC_ConvertedValue[i] * 3.3 / 4095)*30)+mode5


for(n = 160;n<1024;n++)
{
   
adc_line_clean();        //清除上次画的点
		 for(i=n-160;i<n+128;i++)
			{
   
			   ADC_Value[t] =120-((ADC_ConvertedValue[i] * 3.3 / 4095)*30)+mode5;    //坐标转换
		        LCD_Fast_DrawPoint(t++,ADC_Value[t],POINT_COLOR);        //画点
			}	 
      
  • 35
    点赞
  • 264
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值