该项目原理图基于立创开源平台
简易数字示波器设计(入门版) – 立创开源硬件平台

一、硬件部分

以下三张分别为原理图、pcb及焊接完成图

a.前端电路

1)模拟前端(拉高电位,适配单片机电压)

在日常波形的检测当中,波形往往是各式各样的,电压幅值也有所不同。但最终是需要让单片机能够采集输入到该信号,并且在屏幕中显示出来。而常见的单片机能够接受的电压范围往往只有0-3.3V或是0-5V,不能满足稍大的电压信号以及负压。因此,需要在在前端对输入信号进行处理,其中两个重要的目标就是,降低电压峰峰值,和整体电压基准的抬升,避免负压传入单片机

该模拟前端可以简单分为一下三个部分

衰减电路

 衰减电路就是通过简单的分压来降低幅值,并且通过单刀双置开关,有两档可选。
 若SW5开关连接至4,相当于未经过衰减,直接采样该信号
 若SW5开关连接至6,相当于电压进行分压衰减倍率为 10K/(10K+20K+20K) = 1/50

电压跟随器

 中间这个电压跟随器,根据虚断可知运算放大器的输入阻抗比较大,所以运算放大器正向输入电流很小,运放输出阻抗小所以输出的电流很大,起到了很好的隔离作用

运算电路

 第三部分是通过运算电路组成的信号调理电路。

 先假设正向端接地,根据运放的虚短虚断,运放的反向端电压为0,可列出式子:
 (Vi-0) / R13 = (0-Vo) / R15

再假设反向端接地,同样根据根据运放的虚短虚断,运放的同向端和反向端电位相同,可列出式子:
Vo\*R13/(R13+R15) = Vcc\*R8/(R4+R8)
根据两个关系式可以得出:Vo = 5/2 – Vi/2

2)比较测频电路

针对测量信号频率的方式,其中比较容易想到的就是设置一个电压比较器,在测量的周期信号,一旦高于高于该值时,进行计次,测量一段时间,取平均测得频率。但在实际的电路当中,信号并非理想的,若在设定的阈值进行小幅抖动,就会严重影响测量结果。因此,常用的方法是通过滞回电压比较器,给设定的值产生一定的阈值,在高于低阈值时计次,只有在高于高阈值时才会进行之后的计次。

滞回电压比较器

滞回电压比较器本质就是通过输出端的反馈,产生不同的阈值。其中R5和R10中间的电位大约为Vcc * 2/3,
若输出端为正Vcc,
则该比较器同向端约为(Vcc-Vcc*2/3 )* R9/(R9+R12) + Vcc*2/3
若输出端为负Vcc,
则该比较器同向端约为 [Vcc*2/3 – (-Vcc)] * R12/(R9+R12) + (-Vcc),
正是通过R12远大于R9,使得产生一个微小的阈值用于过滤。
还有几个小细节,这个电路使用的是比较器,而不是运放,比较器相比于运放,有更高的响应速度,因此更适合用于测量频率。并且,运放是通过推挽输出的,而比较器是开漏输出,因此带有一个R6的1K上拉电阻。
另外,使用了0欧姆电阻来隔离模拟地和信号地,减少对数字信号的干扰

b.电源

该设计需要多种不同的电位,例如mcu、屏幕的供电需要3.3V,运放需要+5V、-5V、3.3V

1)LDO降压电路

LDO电路的大致原理,首先是通过分压采样VIN电压,在通过运放与基准电压,放大误差,最后通过晶体管调整电流

Vout偏低 ->Va偏低 -> Vg偏低 -> Ids偏大 -> Vout
(Pmos位于可变电阻区,Vgs减小,Ids增大)

2)电荷泵负压电路

电荷泵负压电路,首先是打开开关S1和S3给中间的Cf电容充电。

在下一时刻,S1、S3断开,S2、S4闭合,通过Cf电容一方面给Co电容充电,另一方面,为输出提供负压。

并且,在下一时刻,S1、S3闭合,S2、S4断开时,Co能为输出电压提供负压


二.软件部分

整体流程图

a.采样波形-ADC+DMA

其中有两个部分与ADC的采样有关,分别是ADC的采样时钟通道的采样速率,这与之后的屏幕显示波形有关

RCC_ADCCLKConfig(RCC_PCLK2_Div8);   //ADCLK在rcc中,72M/Div
ADC_RegularChannelConfig( ADC1, ADC_Channel_3, 1, ADC_SampleTime_239Cycles5);  // ADC_SampleTime_239Cycles5

如以下的三个图,均是采样了50个点,但通过了不同的采样速率和采样时钟,采样波形精度和长度就会不同。因此,软件也是通过该方式,来采集和显示不同频率下的波形。

由于只有1路ADC采样,所以选用连续转换+非扫描模式

//ADC初始化,连续转换非扫描
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;          	    //持续转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;              //数据对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发方式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;    			    //单独使用ADC
ADC_InitStructure.ADC_NbrOfChannel = 1;                			    //通道数
ADC_InitStructure.ADC_ScanConvMode = DISABLE;        			    //扫描模式
ADC_Init(ADC1, &ADC_InitStructure);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &ADC1->DR; //ADC采样的外设地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //uint16_t
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不自增
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) AD_Value; //DMA转移到的内存地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //uint16_t
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存的地址地址自增
DMA_InitStructure.DMA_BufferSize = 300; //缓存大小
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //DMA方向从外设传输到内存
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //关闭DMA内存到内存
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //自动循环
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //开启高优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);

b.TFT屏幕-SPI

屏幕的移植部分略,重点说明画曲线的函数。
画出的曲线本质就是很多条直线连接起来,因此,只需要在x轴上,随时间向右增加,根据采集到的电压确定y轴。确定了x,y的坐标,再将该点的与上一点使用直线相连即可。
再是确定屏幕显示到的位置,可以分为三个部分–初始点,超出范围和其他部分,分别配置不同的逻辑即可。

void drawCurve(uint8_t yOffset,short int rawValue)  
{
	uint16_t i=0,j=0;
	uint16_t x=0,y=0;
	y = yOffset - rawValue;  	//data processing code
	if(firstPoint)//如果是第一次画点,则无需连线,直接描点即可
	{
		TFT_DrawPoint(0,y,GREEN);   
		lastX=0;
		lastY=y;
		firstPoint=0; 
	}
	else
	{
		x=lastX+1;
        
		if(x<100)  //不超过屏幕宽度
		{
			TFT_DrawLine(lastX,lastY,x,y,GREEN);
			
            for(i=1;i<90;i++)          //对将要显示的下一个点清屏,范围为y:40->90
            {
                TFT_DrawPoint(x+1,i,BLACK);
            }
			lastX=x;
			lastY=y;
		}
		else  //超出屏幕宽度,清屏,从第一个点开始绘制,实现动态更新效果
		{         
			//TFT_Fill(0,0,160,128,WHITE);//清屏,白色背景
			TFT_DrawPoint(0,y,GREEN);   
			lastX=0;
			lastY=y;
		}
  }
}

 c.采样频率-输入捕获

输入捕获初始化部分配置引脚、IC通道、定时中断源、NVIC中断管理,代码略
频率测量使用的是测周法:即两个上升沿内,以标准频率fc计次,得到N ,则频率fx=fc  / N

 (图片取自江科大的ppt)

void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
    {
        // 获取捕获值
        IC1Value = TIM_GetCapture1(TIM3);
        if (IC1Value != 0)
        {
            // 计算频率
            fre = 1000000 / IC1Value;
            NoInputFlag = 0;
            UpdateCounter = 0;
        }
		// 清除捕获中断标志位
		TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
    }
}

d.方波产生-输出比较

重点分别配置OC的模式和极性,其他略

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

e.旋转编码器-外部中断

使用外部中断,不需要主控每次都判断一次,提高了工作效率。同时,若使用定时器的编码器模式,会消耗掉一个定时器,有点耗费资源了

先配置外部中断为下降沿触发,当外部中断触发后,在判断另一引脚的电位。(以下代码为反向旋转示例)

// 反向旋转
EXTI_InitTypeDef EXIT_InitStructure;
EXIT_InitStructure.EXTI_Line = EXTI_Line3 | EXTI_Line4;
EXIT_InitStructure.EXTI_LineCmd = ENABLE;
EXIT_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXIT_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXIT_InitStructure);
void EXTI3_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line3) == SET)
	{
		Delay_ms(10);
		if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_4) == 0)
		{
			adc_div++;
			if (adc_div>5)
			{adc_div = 5;}
		}
		EXTI_ClearITPendingBit(EXTI_Line3);
	}
}

f.主程序处理

PWM输出控制

if (pwm_flag)
{
	TFT_ShowString(110,36," open ",BLACK,YELLOW,16,0);
	TIM_Cmd(TIM2, ENABLE);
	// 占空比
	if (duty_flag == 0)
	{
		TFT_ShowString(110,105," 20%  ",BLACK,YELLOW,16,0); 
		PWM_SetCompare(20);
	}
	else if (duty_flag == 1)
	{
		TFT_ShowString(110,105," 50%  ",BLACK,YELLOW,16,0); 
		PWM_SetCompare(50);
	}
	else if (duty_flag == 2)
	{
		TFT_ShowString(110,105," 80%  ",BLACK,YELLOW,16,0); 
		PWM_SetCompare(80);
	}
	// 频率/Hz
	if (fre_flag == 0)
	{
		TFT_ShowString(110,72,"  1K  ",BLACK,YELLOW,16,0);  
		PWM_SetPrescaler(720-1);
	}
	else if (fre_flag == 1)
	{
		TFT_ShowString(110,72,"  2K  ",BLACK,YELLOW,16,0);  
		PWM_SetPrescaler(360-1);
	}
	else if (fre_flag == 2)
	{
		TFT_ShowString(110,72,"  4K  ",BLACK,YELLOW,16,0); 
		PWM_SetPrescaler(180-1);
	}
}
else
{
	TFT_ShowString(110,36,"close ",BLACK,YELLOW,16,0);
	TFT_ShowString(110,72,"      ",BLACK,YELLOW,16,0);  // f/Hz
	TFT_ShowString(110,105,"      ",BLACK,YELLOW,16,0); // 占空比
	TIM_Cmd(TIM2, DISABLE);
}

频率采集及显示

将数据的每一位以数组的形式存在now_fre[]里,并在最后显示

if (NoInputFlag) //在输入捕获中断添加的标志位,使得无输入时显示归零
{
	TFT_ShowString(55,105," 0.0K", BLACK,GREEN,16,0);
	TFT_ShowString(5,105," 0.0V ",BLACK,GREEN,16,0);
}
else
{
	// 避免小抖动干扰
	if(VppMean <= 0.3)
	{
		fre=0;
	}
	if (fre < 10000)
	{
		now_fre[0] = ' ';
		now_fre[1] = fre/1000 + '0';
		now_fre[3] = fre%1000/100 + '0';
		TFT_ShowString(55,105, now_fre, BLACK,GREEN,16,0);
	}
	else if (fre < 100000)
	{
		now_fre[0] = fre/10000 +'0';
		now_fre[1] = fre%10000/1000 + '0';
		now_fre[3] = fre%1000/100 + '0';
		TFT_ShowString(55,105, now_fre, BLACK,GREEN,16,0);					
	}
}

ADC采样速率调节

switch(adc_div)
{
case 1:
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	ADC_RegularChannelConfig( ADC1, ADC_Channel_3, 1, ADC_SampleTime_239Cycles5);
break;
case 2:
	RCC_ADCCLKConfig(RCC_PCLK2_Div4);
	ADC_RegularChannelConfig( ADC1, ADC_Channel_3, 1, ADC_SampleTime_239Cycles5);
break;
case 3:
	RCC_ADCCLKConfig(RCC_PCLK2_Div2);
	ADC_RegularChannelConfig( ADC1, ADC_Channel_3, 1, ADC_SampleTime_239Cycles5);
break;
case 4:
	RCC_ADCCLKConfig(RCC_PCLK2_Div2);
	ADC_RegularChannelConfig( ADC1, ADC_Channel_3, 1, ADC_SampleTime_71Cycles5);
break;
case 5:
	RCC_ADCCLKConfig(RCC_PCLK2_Div2);
	ADC_RegularChannelConfig( ADC1, ADC_Channel_3, 1, ADC_SampleTime_28Cycles5);
break;
}

电压幅值测量及显示

使用遍历的方式取出这一组中电位最高的值(300个采样点),并且将这些采样点存在voltage[]数组中,在之后的波形显示中使用

Vpp=0;            
for(i=0;i<300;i++)
{
	tempVoltage[i] = (float)AD_Value[i]*3.3f/4095.0f;  //单片机测得的实际电压
	voltage[i] = 5-(2.0f*tempVoltage[i]);				//为前端电路处理前的电压
		 
	if (Vpp < voltage[i])             //取出电压最大值 
	{
		Vpp = voltage[i];
	}
	if (min > voltage[i])
	{
		min = voltage[i];
	}
}
// 电压输入幅值抖动严重,均值滤波
VppSum += Vpp;
j++;
if (j%5 == 0)
{
	VppMean = VppSum/5;
	VppSum=0;
	j=0;
}

now_vpp[1] = (uint8_t)(VppMean)%10 + '0';
now_vpp[3] = (uint8_t)(VppMean*10)%10 + '0';
TFT_ShowString(5,105, now_vpp, BLACK,GREEN,16,0);

波形初始点选取,滤去小噪声

for(i=0;i<200;i++)
{
	if(voltage[i] < max_data)
	{
		for(;i<200;i++)
		{
			if(voltage[i] > max_data)
			{
				Trigger_number=i;
				break;
			}
		}
		break;
	}
}

计算显示的波形与实际电压范围的放大

if(VppMean > 0.3)
{
	if(min < -0.3)
	{
		median = VppMean;
	}
	else
	{
		median = VppMean / 2.0f;
	}
//放大倍数,需要确定放大之后的区间,我将波形固定显示在(18.75~41.25中),(41.25-18.75)/2=11.25f
	gainFactor = 11.25f/median;  // 显示范围宽度/波形的整个高度 = 放大倍数
	
}

波形显示

for(i=Trigger_number;i<Trigger_number+100;i++)
{	
	
	if(min < -0.3)
	{
		voltage_output = voltage[i] + VppMean;
	}
	else
	{
		voltage_output = voltage[i];
	}	
	voltage_output = voltage[i];
	if(voltage_output >= median)
	{
		voltage_output = 30 + (voltage_output - median)*gainFactor;
	}
	else
	{
		voltage_output = 30 - (median - voltage_output)*gainFactor;
	}
	drawCurve(80,voltage_output);
}

作者:空nk.

物联沃分享整理
物联沃-IOTWORD物联网 » STM32示波器使用详解

发表回复