一、前言:

本开源工程描述含有:

【一、前言】

【二、工程描述(器件,功能,参考资料)】

【三、原理图与PCB】

【四、元件与焊接】

【五、代码详解】

【六、改进方向】

二、工程描述:

1、主要器件:

GD32E230C8T6最小系统板、底板和1.8寸 TFT彩屏三部分。其余元件参见后文BOM

2、最终功能:

测量简单信号的波形、电压和频率。

2-1、屏幕波形和参数显示功能

(1)屏幕显示波形【注意波形为倒相显示】

(2)输出信号参数显示(特指PWM方波)

输出状态【关闭/打开】

输出频率【1kHz,2kHz,4kHz】

占空比

(3)输入信号参数显示

输入幅值【范围:-1.6V~5V】(因为实际上1/50衰减最好别用)

输入频率【理论可测范围:1kHz~10kHz】

2-2、人机交互:调整波形显示和参数功能

(1)【按键KEY1】(左侧按键)

每按一次以5%的步进增加方波占空比,至100%则又变回0%

(2)【按键KEY2】(中间按键)

控制PWM方波的输出与停止,按一次开启,再按一次关闭

(3)【按键KEY3】(右侧按键)

切换PWM方波的输出频率,按动则依次在1Hz,2Hz,4Hz循环切换

(4)【编码器】(右下角)

按压则波形固定;

顺(正)转编码器,波形放大

逆(反)转编码器,波形缩小

3、其他

电源:5V 直流电源

电源接口:type-c

探头类型:BNC转鳄鱼夹

4.参考资料(嘉立创官方):

(1)详细开源文档:后文说“开源文档”都指的这个

https://www.yuque.com/wldz/jlceda/dso

(2)B站免费录播课:

搜索UP主:立创EDA,查看24年初一系列视频即得,与开源文档教程相辅相成

(3)官方参考代码(各部分代码,以及最终代码)

https://gitee.com/chen11232/GD32E230-Oscilloscope

附件处有原版官方最终代码工程,以及一个我加了很多注释的版本的代码工程可以直接下载取用】

二、原理图与PCB板:

电路上的分析在之前总结过,这里主要对代码进行解析:

项目:数字示波器 电路分析

四、元件:

因为是嘉立创的活动结束之后才开始做的,我买元件时需要自己对着元件表输入商城的供应商编号,不过这样也不错,至少保险一点。这里主要要留意一下几个东西需要自己另行购买:

(1)1.8寸TFT显示屏。

这个在立创商城应该是没有的,画原理图时搜的供应商编号C9900080251并不能直接在立创商城下单,我当时自动对应的相似产品买回来是显示屏对应的八脚排母……

直接在某宝搜索关键字“TFT 1.8 128*160”即得。

(2)BNC转鳄鱼夹探头。

这个接头不在BOM单里,很容易忘记,要记得提前买

(3)芯片底座和排母

这个是最容易忽略的部分,视频里也没怎么提到,很容易跟着焊着焊着就突然发现没买,就又要等两三天,真的是很折磨。不过买过一次应该就能用很多很多次了。排母要买LCD屏幕用的8引脚排母和最小系统板用的10引脚排母

以下为板子实物图

五、代码详解:

主要用到的外设是定时器TIMER、模数转换器ADC、串口通信USART、SPI通信。

这里用一个示波器参数结构体把主要的参数集合到一起:

void Init_Oscilloscope(volatile struct Oscilloscope *value)
{
(*value).showbit =0;                                                                     //清除显示标志位
(*value).sampletime =ADC_SAMPLETIME_239POINT5; //adc采样周期
(*value).keyValue =0;                                                                  //清楚按键值
(*value).ouptputbit =0;                                                               //输出标志位
(*value).gatherFreq =0;                                                              //采集频率
(*value).outputFreq =1000;                                                       //输出频率
(*value).pwmOut =500;                                                              //PWM引脚输出的PWM占空比
(*value).timerPeriod=1000;                                                       //PWM输出定时器周期
(*value).vpp =0.0f;                                                                       //峰峰值
}

另外还有 voltage Value[300]                                                    //ADC采集电压值

我们先从信号发出的PWM模块开始:

void Init_PWM_Output(uint32_t period,uint32_t pulse)
{
//定时器输出参数结构体
timer_oc_parameter_struct timer_ocinitpara;
//定时器初始化参数结构体
    timer_parameter_struct timer_initpara;
//使能时钟
rcu_periph_clock_enable(RCU_GPIOA);
//使能定时器14
rcu_periph_clock_enable(RCU_TIMER14);
//GPIO复用模式设置–PA2-TIMER14_CH0
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2);
//输出类型设置
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_2);
//复用模式0
gpio_af_set(GPIOA, GPIO_AF_0, GPIO_PIN_2);
//复位定时器14
timer_deinit(TIMER14);
//初始化定时器结构体参数
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler         = 71;//预分频器参数
timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;                //边沿对齐
timer_initpara.counterdirection  = TIMER_COUNTER_UP;            //向上计数
timer_initpara.period            = period;//周期
timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;            //时钟分频
timer_initpara.repetitioncounter = 0;    //重装载值
timer_init(TIMER14, &timer_initpara);
//初始化定时器通道输出参数结构体
timer_channel_output_struct_para_init(&timer_ocinitpara);
timer_ocinitpara.outputstate  = TIMER_CCX_ENABLE;//输出状态,主输出通道开启
timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;    //互补输出状态关闭
timer_ocinitpara.ocpolarity   = TIMER_OC_POLARITY_LOW;        //输出极性为高
timer_ocinitpara.ocnpolarity  = TIMER_OCN_POLARITY_LOW;        //互补输出极性为高
timer_ocinitpara.ocidlestate  = TIMER_OC_IDLE_STATE_HIGH;        //空闲状态通道输出
timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_HIGH;       //空闲状态互补通道输出
timer_channel_output_config(TIMER14, TIMER_CH_0, &timer_ocinitpara);
//输出比较值
timer_channel_output_pulse_value_config(TIMER14, TIMER_CH_0, pulse);
//输出模式0,当计时器小于比较值时,输出有效电平,为高,大于比较器值时输出为低
timer_channel_output_mode_config(TIMER14, TIMER_CH_0, TIMER_OC_MODE_PWM1);
//影子模式输出关闭
timer_channel_output_shadow_config(TIMER14, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE);
//使能自动重装载
timer_auto_reload_shadow_enable(TIMER14);
//配置定时器为主要输出函数,所有通道使能
timer_primary_output_config(TIMER14, ENABLE);
//失能定时器
timer_disable(TIMER14);
}
void Set_Output_PWMComparex(uint16_t value)
{
timer_channel_output_pulse_value_config(TIMER14, TIMER_CH_0, value);
}
void Set_Output_Freq(uint32_t value)
{
    timer_autoreload_value_config(TIMER14,value);
}
ADC中比较重要的部分:

#define ADC_VALUE_NUM 300U

uint16_t adc_value[ADC_VALUE_NUM];

/*
* 函数内容:得到ADC值
* 函数参数:value–数组下标
* 返回值: 无
*/
uint16_t Get_ADC_Value(uint16_t value)
{
uint16_t returnValue=0;
if(value>ADC_VALUE_NUM)
{
value=0;
}
returnValue=adc_value[value];
adc_value[value]=0;
return returnValue;
}

DMA的设置:

dma_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA); //外设基地址
dma_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //外设地址不自增
dma_data_parameter.memory_addr = (uint32_t)(&adc_value); //内存地址
dma_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //内存地址自增
dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; //外设位宽
dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT; //内存位宽
dma_data_parameter.direction = DMA_PERIPHERAL_TO_MEMORY; //外设到内存
dma_data_parameter.number = ADC_VALUE_NUM; //数量
dma_data_parameter.priority = DMA_PRIORITY_HIGH; //高优先级

dma_init(DMA_CH0, &dma_data_parameter); //DMA通道0初始化

ADC在接收转换完之后,数据就能够被DMA转移到adc_value所在的地址,之后就可以直接拿出来显示了,所用到的函数就是这个Get_ADC_Value了。

extern volatile struct Oscilloscope oscilloscope;

void DMA_Channel0_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA_CH0, DMA_INT_FLAG_FTF)){
oscilloscope.showbit=1;
dma_channel_disable(DMA_CH0);
//清除中断标志位
dma_interrupt_flag_clear(DMA_CH0, DMA_INT_FLAG_G);
}
}

DMA的一次搬运完成后就可以开始显示了,这时候我们可以在DMA一次搬运完成后产生的中断中对示波器参数结构体中的showbit置一,表示我的ADC转换完的数据已经搬运到内存里了,可以进行显示了。

有了需要显示的数据,接下来就是要显示的方法和显示的界面弄好。

这里我们就要参考这个1.8寸TFT屏幕的厂商提中景园提供的代码了。因为这个屏幕使用的是SPI通信,所以我们要先把SPI通信相关的引脚先设置好。其中的SCL和SDA引脚我们分别定义在PA5口和PA7口,因为这个屏幕只需要接收主机传来的数据,不需要向主机传输数据,所以只有MOSI口没有MISO口,就只需要两个SPI通信相关的引脚就够了。设置为复用推完输出模式即可。剩余的三个引脚:

//#define TFT_RES PB5
//#define TFT_CS PB7
//#define TFT_BLK PB8

我们直接使用GPIO口来操作,将其设置为不加上下拉的推挽输出就可以了。其余的初始化配置参考厂商提供的代码即可。

接下来我们就能够利用厂商封装好的函数,来设计示波器的显示界面了。

显示界面我们可以分为显示波形静态UI动态数据

首先来解决显示波形,这里的原理就是一开始先只画一个点,下一次画第二个点的时候顺带把当前点和前一个点之间连线

static uint16_t lastX=0,lastY=0;
static uint8_t firstPoint = 1;
/*
*   函数内容:画折线
*   函数参数:short int rawValue–Y轴参数值
*   返回值:  无
*/
void drawCurve(uint8_t yOffset,short int rawValue)
{
uint16_t x=0,y=0,i=0;
if((rawValue >= 50)||(rawValue < 0)){
rawValue=50;
}
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=30;i<=80;i++)//这里是为了让当前显示的波形不和原来在该处显示的波形重合,提前先将下一列的像素点刷新
{
TFT_DrawPoint(x+1,i,BLACK);//画点
}
lastX=x;
lastY=y;
}
else  //超出屏幕宽度,清屏,从第一个点开始绘制,实现动态更新效果
{
TFT_DrawPoint(0,y,GREEN);
lastX=0;
lastY=y;
}
  }
}

这里我们规定波形显示的区域是长0~100像素点宽30~80像素点。注意提前刷新下一列像素点为背景色。

接下来是静态UI的设置函数:

void TFT_StaticUI(void)
{
uint16_t i=0,j=0;

char showData[32]={0};

TFT_ShowChinese(10,0,(uint8_t *)”简易示波器”,BLACK,GREEN,16,0);

sprintf(showData,” PWM “);//将字符串“PWM”放入showData字符串数组中
TFT_ShowString(110,0,(uint8_t *)showData,BLACK,YELLOW,16,0);
memset(showData,0,32);

TFT_ShowChinese(110,20,(uint8_t *)”输出状态”,WHITE,PURPLE,12,0);

sprintf(showData,” “);
TFT_ShowString(110,36,(uint8_t *)showData,BLACK,YELLOW,16,0);
memset(showData,0,32);

TFT_ShowChinese(110,56,(uint8_t *)”输出频率”,WHITE,PURPLE,12,0);

sprintf(showData,” “);
TFT_ShowString(110,72,(uint8_t *)showData,BLACK,YELLOW,16,0);
memset(showData,0,32);

sprintf(showData,” “);
TFT_ShowString(110,92,(uint8_t *)showData,WHITE,PURPLE,12,0);
memset(showData,0,32);
TFT_ShowChinese(118,92,(uint8_t *)”占空比”,WHITE,PURPLE,12,0);

sprintf(showData,” “);
TFT_ShowString(110,106,(uint8_t *)showData,BLACK,YELLOW,16,0);
memset(showData,0,32);

TFT_ShowChinese(5,92,(uint8_t *)”输入幅值”,WHITE,PURPLE,12,0);

TFT_ShowChinese(55,92,(uint8_t *)”输入频率”,WHITE,PURPLE,12,0);

for(i=0;i<=128;i=i+2)
{
TFT_DrawPoint(106,i,YELLOW);//中间分割线
}

for(i=0;i<100;i++)
{
TFT_DrawPoint(i,81,GREEN);//波形显示区域的底部参考线
}
for(i=30;i<=80;i++)
{
TFT_DrawPoint(0,i,GREEN);
}
for(i=0;i<10;i++)
{
TFT_DrawPoint((i*10)+2,82,GREEN);
TFT_DrawPoint((i*10)+3,82,GREEN);//波形底部的标尺
}
for(i=0;i<10;i++)
{
TFT_DrawPoint((i*10)+2,83,GREEN);
TFT_DrawPoint((i*10)+3,83,GREEN);//波形底部的标尺

}
}

这些区域都是可以自己自行规划的,如果要改变界面UI,直接在这里改就行了。

之后是动态数据的显示

/*
* 函数内容: 显示字符串
* 函数参数: uint16_t vpp–峰峰值
* uint16_t freq-频率
* float DoBias–直流偏执信号
* 返回值: 无
*/
void TFT_ShowUI(volatile const struct Oscilloscope *value)
{
char showData[32]={0};

sprintf(showData,”%1.2fV “,(*value).vpp);
TFT_ShowString(5,106,(uint8_t *)showData,BLACK,GREEN,16,0);//从示波器参数结构体中获取的数据,显示输入幅值在左下角
memset(showData,0,32);

if((*value).gatherFreq>=1000)//如果大于1KHz,就以KHz的单位来显示,避免占据过多范围
{
sprintf(showData,”%2.0fKHz”,(*value).gatherFreq/1000.0f);
TFT_ShowString(55,106,(uint8_t *)showData,BLACK,GREEN,16,0);
memset(showData,0,32);
}
else
{
sprintf(showData,”%3.0fHz “,(*value).gatherFreq);//以Hz的单位来显示
TFT_ShowString(55,106,(uint8_t *)showData,BLACK,GREEN,16,0);
memset(showData,0,32);
}

if((*value).ouptputbit == 1)
{
TFT_ShowChinese(118,36,(uint8_t *)”打开”,BLACK,YELLOW,16,0);//这个outputbit会在按键模块那里接受控制
}
else
{
TFT_ShowChinese(118,36,(uint8_t *)”关闭”,BLACK,YELLOW,16,0);
}

if((*value).outputFreq>=1000)
{
sprintf(showData,”%3dKHz”,(*value).outputFreq/1000);//输出模块是在PWM模块那里获取的数据
TFT_ShowString(110,72,(uint8_t *)showData,BLACK,YELLOW,16,0);
memset(showData,0,32);
}
else
{
sprintf(showData,” %3dHz”,(*value).outputFreq);
TFT_ShowString(110,72,(uint8_t *)showData,BLACK,YELLOW,16,0);
memset(showData,0,32);
}

sprintf(showData,”%3.1f%% “,(((*value).pwmOut)/((*value).timerPeriod+0.0f))*100);//计算占空比
TFT_ShowString(110,106,(uint8_t *)showData,BLACK,YELLOW,16,0);
memset(showData,0,32);
}

到这里显示的模块就完成了,就是还有一些比较底层的代码,我们直接参考中景园提供的代码就OK了。

接下来是人机交互的按键模块

总共是有三个轻触按键和一个旋转编码器

三个轻触按键对应的是GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15

旋转编码器总共有三个引脚要接GPIO口,A相、B相、D相,分别对应是GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_9

其中三个轻触按键和B相D相的引脚可以一起配置,配置为上拉输入模式就好了,同时要记得中断线使能、配置中断线、初始化中断线配置为中断模式上升沿触发

另外的选择A相作为基准信号,要单独配置是因为要给它配置CMP比较器,用于比较两个输入信号的大小,并根据比较结果输出相应的信号,其实让B相来配置CMP也可以,不过方向会反过来。

顺时针旋转时,A相产生上升沿,并且B相会在短时间内保持低电平;逆时针旋转时,A相产生上升沿,并且B相会在短时间内保持高电平,以此我们就能够来区分正转和反转了。

中断触发函数中:

if(RESET != exti_interrupt_flag_get(EXTI_4))
    {
if((gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) && (A_cnt == 0))//A相下降沿触发一次
{
A_cnt++;//计数值加一,表示已经触发了第一次中断
B_value = 0;//读取B相电平,若为高电平则B_level置1,反之保持0
if(gpio_input_bit_get(GPIOB,GPIO_PIN_3) == SET)
{
B_value = 1;
}
}
else if((gpio_input_bit_get(GPIOB,GPIO_PIN_4) == SET) && (A_cnt == 1))//A相上升沿触发一次
{
A_cnt = 0;
if((B_value == 1) && (gpio_input_bit_get(GPIOB,GPIO_PIN_3) == RESET))
{
oscilloscope.keyValue=KEYB;
}
if((B_value == 0) && (gpio_input_bit_get(GPIOB,GPIO_PIN_3) == SET))
{
oscilloscope.keyValue=KEYA;
}
}
        exti_interrupt_flag_clear(EXTI_4);
    }
因为A相对应的引脚设置的中断模式是上下沿都触发的,转一格的话,AB相都会经历一次上升沿下降沿。正转时,在A相下降沿的中断中,B相会在高电平,A相上升沿的中断中,B相会在低电平;反转时,在A相下降沿的中断中,B相会在低电平,A相上升沿的终端中,B相会在高电平中。在各自的条件语句中,会将示波器参数结构体中的keyValue设置成各自的按键码。
其余4个按键(3个轻触按键和一个D相)的中断检测就比较直接了。
if(RESET != exti_interrupt_flag_get(EXTI_9))
{
delay_1ms(5);
if(gpio_input_bit_get(GPIOB,GPIO_PIN_9)==RESET)
{
oscilloscope.keyValue=KEYD;
}
exti_interrupt_flag_clear(EXTI_9);
}
然后是在main函数中调用的Key_Handle
void Key_Handle(volatile struct Oscilloscope *value)
{
uint8_t i=0,j=0;
float tempValue=0;
switch((*value).keyValue)
{
case KEY1://KEY1按钮,每按一次以5%的步进增加方波占空比,至100%则又变回0%
(*value).pwmOut=((*value).timerPeriod*0.05f)+(*value).pwmOut;
if((*value).pwmOut > (*value).timerPeriod)
{
(*value).pwmOut = 0;
}
Set_Output_PWMComparex((*value).pwmOut);
break;
case KEY3://切换PWM方波的输出频率,按动则依次在1Hz,2Hz,4Hz循环切换
tempValue=(*value).pwmOut/((*value).timerPeriod+0.0f);
(*value).timerPeriod = (*value).timerPeriod/2.0f;
if((*value).timerPeriod < 250)
{
(*value).timerPeriod = 1000;
}
(*value).outputFreq=1000000/(*value).timerPeriod;
(*value).pwmOut=(*value).timerPeriod*tempValue;
Set_Output_PWMComparex((*value).pwmOut);
Set_Output_Freq((*value).timerPeriod-1);
tempValue=0;
break;
case KEY2://控制PWM方波的输出与停止,按一次开启,再按一次关闭
if((*value).ouptputbit == 0)
{
(*value).ouptputbit=1;
timer_enable(TIMER14);
}
else
{
(*value).ouptputbit=0;
timer_disable(TIMER14);
}
break;
case KEYA//:顺(正)转编码器,波形变宽
switch((*value).sampletime)
{
case ADC_SAMPLETIME_239POINT5:
(*value).sampletime=ADC_SAMPLETIME_71POINT5;
break;
case ADC_SAMPLETIME_71POINT5:
(*value).sampletime=ADC_SAMPLETIME_55POINT5;
break;
case ADC_SAMPLETIME_55POINT5:
(*value).sampletime=ADC_SAMPLETIME_41POINT5;
break;
case ADC_SAMPLETIME_41POINT5:
(*value).sampletime=ADC_SAMPLETIME_28POINT5;
break;
case ADC_SAMPLETIME_28POINT5:
(*value).sampletime=ADC_SAMPLETIME_28POINT5;
break;
default:
(*value).sampletime=ADC_SAMPLETIME_239POINT5;
break;
}
//ADC常规通道配置–PA3,顺序组0,通道3,采样时间
adc_regular_channel_config(0, ADC_CHANNEL_3, (*value).sampletime);
break;
case KEYB://逆(反)转编码器,波形变窄
switch((*value).sampletime)
{
case ADC_SAMPLETIME_239POINT5:
(*value).sampletime=ADC_SAMPLETIME_239POINT5;
break;
case ADC_SAMPLETIME_71POINT5:
(*value).sampletime=ADC_SAMPLETIME_239POINT5;
break;
case ADC_SAMPLETIME_55POINT5:
(*value).sampletime=ADC_SAMPLETIME_71POINT5;
break;
case ADC_SAMPLETIME_41POINT5:
(*value).sampletime=ADC_SAMPLETIME_55POINT5;
break;
case ADC_SAMPLETIME_28POINT5:
(*value).sampletime=ADC_SAMPLETIME_41POINT5;
break;
default:
(*value).sampletime=ADC_SAMPLETIME_239POINT5;
break;
}
//ADC常规通道配置–PA3,顺序组0,通道3,采样时间
adc_regular_channel_config(0, ADC_CHANNEL_3, (*value).sampletime);
break;
case KEYD:
break;
default:
break;
}
(*value).keyValue=0;
//参数显示UI
TFT_ShowUI(value);
}
要注意的是这里正转KEYA,正转是会从上到下来切换这几个采样频率。
反转KEYB,反转会从下到上来切换这几个采样频率
最后就是我们的主函数了。在这里我们主要要完成各个模块的初始化操作,还要完成adc值到实际电压值的转换。
忽略主循环前的各种初始化函数,主循环中首先要做的就是扫描各个按键。因为屏幕主要显示的除了输出的PWM的数据,就是波形了。我们需要通过按键来开启并调节PWM输出。
//按键扫描处理函数
Key_Handle(&oscilloscope);
//如果获取电压值完成,开始刷屏
        if(oscilloscope.showbit==1)
        {
            oscilloscope.showbit=0;
            oscilloscope.vpp=0;
            //转换电压值
            for(i=0;i<300;i++)
            {
                   adcValue = (Get_ADC_Value(i)*3.3f)/4096.0f;

//(ADC分辨率) / (参考电压) = (ADC获得值) / (实际电压)

//ADC分辨率是设置为12位的4086,参考电压一般为3.3V,我们还通过ADC得到了数据,就可以求出实际电压了。
                   oscilloscope.voltageValue[i] = (5-(2.0f*adcValue));//由【模拟前端处理电路】确定的公式,由adc值反解输入电压值
                if((oscilloscope.vpp) < oscilloscope.voltageValue[i])//更新峰峰值
                {
                    oscilloscope.vpp = oscilloscope.voltageValue[i];
                }
                if(oscilloscope.vpp <= 0.3)//峰峰值过小
                {
                    oscilloscope.gatherFreq=0;
                }
            }
            //刷屏的同时获取电压值
            dma_transfer_number_config(DMA_CH0, 300);
            dma_channel_enable(DMA_CH0);
            //找到起始显示波形值
            for(i=0;i<200;i++)
            {
                if(oscilloscope.voltageValue[i] < max_data)
                {
                    for(;i<200;i++)
                    {
                        if(oscilloscope.voltageValue[i] > max_data)
                        {
                            Trigger_number=i;
                            break;
                        }
                    }
                    break;
                }
            }
            //如果幅值过小,会出现放大倍数过大导致波形显示异常的问题
            if(oscilloscope.vpp > 0.3)
            {
//获取中间值
median = oscilloscope.vpp / 2.0f;
                //放大倍数,需要确定放大之后的区间,我将波形固定显示在(18.75~41.25中),(41.25-18.75)/2=11.25f
                gainFactor = 11.25f/median;
            }
            //依次显示后续100个数据,这样可以防止波形滚动
            for(i=Trigger_number;i<Trigger_number+100;i++)
            {
                if(oscilloscope.keyValue == KEYD)//D相按钮按下,整个程序暂停,再按一次继续
                {
                    oscilloscope.keyValue=0;
                    do
                    {
                        if(oscilloscope.keyValue == KEYD){
                            oscilloscope.keyValue=0;
                            break;
                        }
                    }while(1);
                }
                voltage=oscilloscope.voltageValue[i];
                      //以下if-else代码将实际输入电压值换算成在屏幕显示时的纵坐标
//换算时进行反相伸缩,将图形控制在以y=30为中心,范围不超过18.75~41.25的区域内显示
                if(voltage >= median)
                {
                    voltage = 30 – (voltage – median)*gainFactor;
                }
                else
                {
                    voltage = 30 + (median – voltage)*gainFactor;
                }
                drawCurve(80,voltage);
            }
        }
        //参数显示UI
        TFT_ShowUI(&oscilloscope);

六、改进方向

功能改进

(1)示波器由原来的单通道变为双通道乃至多通道

(2)添加信号发生器功能,此时除了最小系统板外还需要外接的DAC模块

电路改进

(1)输入信号衰减电路改进:
×1/50的衰减对本项目不实用,因为对于5V~25V的电压,如果使用×1档则会超出限额,使用×1/50档则衰减到0.1~0.5V从而易受干扰从而不精确。可以调整诸如1/3,即对应电路(模拟前端处理电路->电压衰减电路)的三个分压电阻阻值要自己调。(但是三个电阻的阻值加起来需要超过1MΩ)

(2)比较器测频电路的改进:
可以通过调整电阻阻值来让滞回比较器的阈值电压U+更高,U-更低,从而让测频效果更好(但电阻阻值调整后依然要满足一定的约束关系)

器件选择改进

直插原件变贴片元件。

*直插元件便于新手焊接,但是占地空间

*贴片元件占地空间,但是焊接需要用到的不只是电烙铁

改变探头:

原来是BNC转鳄鱼夹探头,可以换成专业示波器的无源探头,这样可以利用探头自带的×10档对信号有一个初步的衰减,而不是仅靠衰减电路来衰减了