【示波器扩展板】资料
示波器扩展板资料下载
gitee资料盘链接
一、示波器扩展模块概述
1.示波器的应用场景
示波器是一种观测电信号的仪器,能将人眼看不见摸不着的电子信号通过屏幕显示出来,让我们能直观的了解和分析当前电路的工作状态。目前主流的示波器主要分为模式示波器和数字示波器。
模拟示波器主要是是直接将输入的电子信号经过运放等处理后直接输出给电子显像管的上下偏置电压,由左右方向的刷新形成一个波形显示。
数字示波器主要是通过ADC对处理好的模拟信号进行采集,经过控制器的运算后,输出到屏幕上面显示。为便于大家分析和记录保存,数字示波器的占比相对较大。
2.动手DIY示波器的价值
相对于传统的控制电路来说,DIY数字示波器难度会高一些,其中包含了模拟信号处理(运放电路),模拟信号采集(ADC),数字信号处理(DSP),屏幕界面设计,波形发生器(DAC),按键功能控制等。
通过DIY示波器,能很好的理解模拟信号的缩放以及放大器的使用,还有数字信号处理能力,和单片机ADC、DAC、屏幕UI设计能力。还可以将做好的DIY投入到实际的使用场景中,这可能也是自制示波器层出不穷的原因吧。
3.DIY示波器的几大主流方案
目前笔者主要针对网络上现有的DIY示波器大致分为三类:(分类可能有失颇偏,以大家自行见解为主。) 第一类为专业示波器:主要采用RAM作为主控,FPGA+高速ADC芯片作为模拟信号采集,高速DAC作为波形发生器,高精度运放前级电路。
第二类为实用示波器:主要采用单片机作为主控,高速ADC作为模拟信号采集,高速DAC作为波形发生器,运放控制前级电路。
第三类为学习示波器:主要采用单片机为主控,内置ADC作为模拟信号采集,简单运放前级电路。
4.示波器扩展板的几大亮点
4.1 小巧方便,让工具装进口袋
4.2 高度开发的软件框架,学习进阶的开发技巧
将重复性工作全部交给DMA,将缓冲区和CPU算力有效利用。
4.3 简洁的电路设计,快速上手示波器DIY
采用了学习级示波器设计方案,配合梁山派GD32F450的内置ADC和DAC制作的DIY示波器。(在模拟电路的设计部分有深度学习借鉴安富莱的开源示波器模块电路,已经和安富莱那边沟通取得使用许可。)
5.示波器扩展板的功能
软硬件全开源,更多有意思的功能自由定义。
- 采样的开始/停止:启动和停止ADC触发定时器,暂停查看时可以看到最大最小等详细信息值。
- 缓冲滚动条:采集状态通过计算波形触发的位置显示,暂停状态通过调节的偏移位置显示。
- 上升/下降沿:通过软件的比较方法,确定采样值和触发条件符合时,进行位置确定和显示。
- AC/DC :通过IO控制示波器前级耦合继电器的通断。
- 电压刻度:1/2V/div 每格电压赋值,通过IO控制模拟开关控制运放倍数。
- 时间刻度:通过控制ADC触发定时器周期时间改变采样的时间刻度。
- 触发幅值:进行软件的触发值设置和横幅位置绘制,蓝色横虚线提示。
- 波形偏移:经行软件处理的显示位置上下偏移,白色横虚线提示。
- 波形的显示:不再是传统的两点斜率画线计算(X坐标Y坐标都不用),而是采用相邻两点只画竖线,较少代码计算量。
- 波形发生器种类:改变DAC的缓存Buff内的单周期数据,自动生成对应的波形输出。
- 波形发生器频率:改变DAC的Time触发周期,自动生成对应的波形周期。
- FFT测量开关:软件控制FFT频谱波形的显示。
- FFT频率测量:通过最大频率占比点进行频率计算,和实际频率略有差异。
- FFT频谱缩放倍数:通过需要显示高度150像素值进行最大值缩放。
- 显示坐标偏移:采样状态为触发位置值,暂停状态显示为手动偏移位置。
6.示波器扩展板的进阶
- 由于软件框架将ADC和DAC都采用的Time+Dma的方式进行处理,如果进阶采用外置ADC和DAC,需要将Time+Dma的数据交互切换为相应Gpio。
- 使用DMA+GPIO时需要注意使用连续的GPIO,并且需要防止同总线GPIO被误操作(8/16位)。
二、示波器扩展模块电路设计
1.AC/DC耦合电路
该电路使用的一个信号继电器并在耦合电容两端,继电器不工作时由耦合电容进行耦合,由于电容的通交流阻直流特性,只有交流变化的信号会被后面的电路检测到。继电器工作时,耦合电容短路直连耦合,所有信号均可被后面的电路检测到。
这里的耦合电容越小,高频信号通过越容易,低频信号通过失真越严重。而耦合电容越大则反之,低频信号正常,高频信号失真,并且信号会出现延迟。这里我们使用单片机内部ADC(2M采样),故使用100nF(1206封装)进行耦合。(结合后级分压电路的计算fp=1/2πRC)
这里选用的信号继电器驱动线圈有正负级之分,驱动电压需要正确,驱动电流也需要注意,详细资料请参考元器件对应文档。
2.分压电路
相比于传统的分压电路,这里的分压电阻都有点过大,主要为了满足1MΩ的信号阻抗匹配,而分压电阻旁的电容有起到交流信号耦合的重要作用。
最后下面的可调电容,则是为了调整示波器的信号补偿电容,对于实际电路存在的偏差造成的信号损耗不同,就需要使用可调电容对信号就行补偿,补偿过大和补偿不足都会导致波形失真。
3.程控运放信号处理电路
这里主要使用一个8通单选模拟开关芯片74HC405和双运算放大器芯片TLV2372组成。(在模拟电路的设计部分有深度学习借鉴安富莱的开源示波器模块电路,已经和安富莱那边沟通取得使用许可。)
双运算放大器分别作为信号的缩放和信号的平移处理,将前级分压的模拟信号进行处理,并且保证处理后的信号在0-2.5V区间以方便单片机ADC采集。
74HC405芯片等效为一个单选8通开关,但是需要注意其存在导通电阻,并且其阻值随信号的频率,电压不同而不同。。。(详情参考手册数据,想得到精确的信号电压,需要进行校准处理)
4.波形发生器输出电压跟随电路
这里使用的一个普通的单运算放大器作为电压跟随输出,主要是为了隔离单片机的DAC输出。 理论上也可以将DAC信号直接进行输出(DAC内部含有BUFF电路提高输出能力)。
5.负压电路
这里使用了一个电荷泵负压芯片,将5V的电压转换成-5V的电压,给运放放大器的VSS供电组成双电源运算放大电路,保证了运算放大器的线性度。
6.基准源电路
基准电压在电路中提供一个稳定的参考电压,这里选择了2.5V的电压基准给运放平移电路进行平移,还给ADC提供了参考基准,需要注意的是CJ431基准芯片的Ika默认一般为10mA工作效果较为稳定,故限流电阻选择为470Ω。
7.按键电路
示波器模块使用了两种按键,正面三个独立按键,背面两个拨码按键,拨轮按键有三种触发状态分别为上拨,按下,下拨。拨轮接线需要参考器件手册进行设计。
8.屏幕显示电路
这里使用了一块2.4寸SPI屏幕,屏幕大小(42.7260.26mm)和梁山派尺寸(4570mm)非常的搭配,并且SPI的屏幕驱动简单,引出的IO也能有效的和模拟电路进行分离。
三、屏幕驱动
本章节主要介绍一下示波器扩展板屏幕的DMA+SPI驱动及注意事项,具体屏幕的UI设计部分会在后面章节讲解。
1.屏幕的选型设计
在电路设计章节也有相应说明,示波器模块采用的2.4寸SPI屏,和开发板的尺寸也是非常的搭配的。而SPI的设计能减少IO设计,避免和模拟电路产生干扰(屏幕的高速刷屏及其容易产生干扰)。
2.屏幕的初始化点亮
一般我们拿到一个屏幕首先需要移植厂商提供的官方代码进行亮屏测试。这里我们就不使用DMA配置了,只用最基本Io模拟SPI或者硬件SPI的方法,先将屏幕点亮清屏或者整屏刷新颜色就行。
移植好代码后要仔细观察颜色刷新是否正确(色差),显示方法是否正确(横屏竖屏),颜色数据刷新是否正常(错,漏,闪烁)。
设计连线符合器件要求无误的情况下存在问题,就需要查看手册,通过手册发生GD32F450的SPI存在FIFO,所以发送数据的结尾需要等待数据发送完成再释放片选。否则容易导致屏幕显示出现问题(一般都是以缓冲区是否为空当作传输的判断标准,以此提高发送效率)。
#define Chip_Selection 0 //设置芯片初始化 0为ILI9341 1为ST7789
#define USE_HORIZONTAL 2 //设置横屏或者竖屏显示 0或1为竖屏 2或3为横屏
#if USE_HORIZONTAL==0||USE_HORIZONTAL==1
#define LCD_W 240
#define LCD_H 320
#else
#define LCD_W 320
#define LCD_H 240
#endif
2
3
4
5
6
7
8
9
屏幕的代码初始化需要根据屏幕厂商提供的初始化代码就行移植以减少开发难度。
3.屏幕的显示缓冲区(GRAM)分配
示波器屏幕本身是带gram的,不需要像rgb屏那样重复刷新,但是在这里我们同样给这个2.4寸320240屏幕配置显示缓冲区,是为了方便DMA的全屏更新,并且也是为了方便UI的绘制和运算。
一个全屏缓存需要320240*2字节=150K,这样我们就可以先将缓存内的数据修改为需要显示的内容,在统一更新在屏幕里面了,然而梁山派外置了SDRAM(32MB),这里我们使用外置SDRAM的空间,而不使用内部RAM的空间主要是因为内部RAM有限,需要留给其他需要高速访问的RAM变量使用。
为了方便后续的DMA传输,这里定义的SRAM地址一定要4字节对齐。
//__attribute__((at(0XC0000000)));
__align(32) uint16_t Show_GramA[LCD_RAM_NUMBER] __attribute__((at(SDRAM_DEVICE0_ADDR))); //双显示Buffr
__align(32) uint16_t Show_GramB[LCD_RAM_NUMBER]__attribute__((at(SDRAM_DEVICE0_ADDR+LCD_RAM_BYTE_NUMBER)));
2
3
4.使用DMA进行SPI刷屏
使用DMA的通用流程都是先配置外设对应的DMA及通道,再配置自动或者软件触发DMA搬运方向。这里使用的是RAM数据搬运到SPI_TX,由于SPI使用的8位数据发送,(可以修改为16位发送,但是同样超出了DMA的最大0xffff=65535数量)所以这里需要分4次将这3202402=153600字节数据搬运完成,这里使用的是DMA中断计数搬运完成机制。
void Spi2_Dma_Init(void)
{
dma_single_data_parameter_struct dma_init_struct;
/* enable DMA1 clock */
rcu_periph_clock_enable(RCU_DMA0);
/* initialize DMA0 channel5 */
dma_deinit(DMA0, DMA_CH5);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_init_struct.memory0_addr = (uint32_t)0;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.periph_memory_width = DMA_MEMORY_WIDTH_8BIT; //DMA_MEMORY_WIDTH_32BIT
dma_init_struct.number = (uint16_t)(38400);
dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(SPI2) ;
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(DMA0, DMA_CH5, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(DMA0, DMA_CH5);
dma_channel_subperipheral_select(DMA0, DMA_CH5, DMA_SUBPERI0);
// enable DMA1 transfer complete interrupt */
dma_interrupt_enable(DMA0, DMA_CH5, DMA_CHXCTL_FTFIE);
nvic_irq_enable(DMA0_Channel5_IRQn, 0, 0);
//dma_channel_disable(DMA0, DMA_CH5);
}
/////开启一次DMA刷屏
void LCD_Show_Gram(uint16_t \*dat)
{
//uint16_t i;
LCD_SHOW_RAM = dat;
Lcd_Show_Over=1;
//设置地址
LCD_Address_Set(0,0,LCD_W-1,LCD_H-1);//设置显示范围
LCD_CS_Clr();
/* enable DMA0 channel5 */
DMA_INTC1(DMA0) = 0xffff;
DMA_CHCNT(DMA0, DMA_CH5) = 38400;
DMA_CH5M0ADDR(DMA0) = (uint32_t)LCD_SHOW_RAM;
//DMA_CH5M1ADDR(DMA0) = (uint32_t)dat;
DMA_CHCTL(DMA0, DMA_CH5) |= DMA_CHXCTL_CHEN;
}
////////屏幕搬运完成
void DMA0_Channel5_IRQHandler(void)
{
static uint8_t Show_Number=0;
LED2_TOGGLE();
if(++Show_Number < 4)
{
DMA_INTC1(DMA0) = 0xffff;
DMA_CHCNT(DMA0, DMA_CH5) = 38400;
DMA_CH5M0ADDR(DMA0) = (uint32_t)LCD_SHOW_RAM + 38400*Show_Number;
DMA_CHCTL(DMA0, DMA_CH5) |= DMA_CHXCTL_CHEN;
}
else
{
dma_interrupt_flag_clear(DMA0, DMA_CH5, DMA_INT_FLAG_FTF);
while(SPI_STAT(SPI2) & SPI_STAT_TRANS);
LCD_CS_Set();
Show_Number=0;
Lcd_Show_Over=0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
附注:DMA初始化配置后,需要再次传输的快捷操作为:清除标志位,设置传输量,设置传输地址,开始传输。
5.使用定时固定周期20Hz查询并刷屏
理论上将SPI的刷屏交给DMA已经做到了有效的提高CPU利用率,但是在不同的循环里面进行刷屏会有不稳定刷屏撕裂现象(MCU屏带显存,数据传输给屏幕主控,屏幕主控也需要处理后再更新显示。本身和SPI的通信时长也有原因。RGB屏使用的单片机RAM作为显存,可以保证同步)。这种情况下我们使用定时器周期的查询是否更新屏幕,保证屏幕刷屏符合固定周期,减少不同时差刷新导致画面撕裂不同步现象。(不严重,也可以不使用定时器)
//50ms周期
void Lcd_Show_Time(void)
{
timer_parameter_struct timer_initpara;
nvic_irq_enable(TIMER3_IRQn, 0, 1); // 设置中断优先级
rcu_periph_clock_enable(RCU_TIMER3);
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
timer_deinit(TIMER3); // 定时器复位
/* TIMER2 configuration */
timer_initpara.prescaler = 200 - 1; // 预分频
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 对齐模式
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 计数方向
timer_initpara.period = 200000000 / 200 / 20 -1; // 周期
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 时钟分频
timer_initpara.repetitioncounter = 0; // 重复计数器
timer_init(TIMER3,&timer_initpara);
timer_master_output_trigger_source_select(TIMER3,TIMER_TRI_OUT_SRC_UPDATE);
/* TIMER2 interrupt enable */
timer_interrupt_enable(TIMER3,TIMER_INT_UP); // 中断使能
timer_enable(TIMER3);
}
void TIMER3_IRQHandler(void)
{
LED1_TOGGLE();
timer_interrupt_flag_clear(TIMER3, TIMER_INT_FLAG_UP);
if(Show_Star)
{
Show_Star=0;
if(Show_AB)LCD_Show_Gram(Show_GramA);
else LCD_Show_Gram(Show_GramB);
Show_AB =!Show_AB;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
在定时器的中断函数里面,需要保证刷新RAM和操作RAM分离,这样才能保证显示的画面和实际画面的帧同步。而这个读写标志Show_AB也是判断当前的操作的唯一指标。
6.屏幕刷新测试验证
这里只是简单的实现DMA的SPI刷新,具体UI等显示在之后的章节进行介绍。
int main(void)
{
uint32_t i;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config();
led_gpio_config(); // led初始化
key_gpio_config(); // key初始化
usart_gpio_config(9600U);
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//LCD初始化
LCD_Init();
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);//50ms
Spi2_Dma_Init();
Lcd_Gram_Fill(Show_GramA,GRAM_BLUE);
Lcd_Gram_Fill(Show_GramB,GRAM_RED);
LCD_Show_Gram(Show_GramA);
while(Lcd_Show_Over);
//开启定时器固定刷屏
Lcd_Show_Time();
while(1)
{
// //软件刷屏
// LCD_Fill(0,0,LCD_W,LCD_H,BLUE);//50ms
// LCD_Fill(0,0,LCD_W,LCD_H,RED);//50ms
// //循环DMA刷屏
// Lcd_Gram_Fill(Show_GramA,GRAM_BLUE);
// LCD_Show_Gram(Show_GramA);
// while(Lcd_Show_Over);
// Lcd_Gram_Fill(Show_GramB,GRAM_RED);
// LCD_Show_Gram(Show_GramB);
// while(Lcd_Show_Over);
//定时器循环固定数据刷屏
while(Lcd_Show_Over);
Show_Star=1;
while(Show_Star);
delay_1ms(200);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
四、波形发生功能
本章节主要介绍一下DAC的DMA驱动,以及使用TIME定时器进行触发DAC控制波形周期。通过这种机制能有效减少CPU的占用时间。初始化后只需要修改定时器的周期或者修改缓冲区内的数据即可修改波形发生器的频率和波形。
1.DAC的基本原理
DAC是一种数字量转模拟电压的外设,可以用来生成各种波形和信号,常用在音乐输出,波形发生器,电压控制,信号输出等地方。这里我们用来实现简单的波形发生器。
DAC就是将电压等比例进行模拟输出的,所以实际的输出电压上限就是Vref电压,在一般电路通常为3.3V,但是在示波器电路中,我们使用的2.5V作为单片机及模拟电路的基准电压,所以DAC的上限为2.5V,实际控制DAC的赋值以2.5V为准。
2.特定波形数组的生成
可以周到DAC可以生产模拟电压,而周期性的给DAC数据那么就会生产特定的波形,如常见的三角波,正弦波,方波等。。。
通常我们可以用excel表格生成固定的波形数组,也可以用代码生成可以控制幅值的数组。梁山派主控GD32F450的DAC为12bit,这里使用8bit主要是便于存储计算,对精细度有要求可以修改对应数据。使用的100个数据点为一个周期,数据点越多显示越精细可生成的波形周期越高,可以根据自己情况修改。
////方波//反锯齿波//正锯齿波//三角波//正弦波
const unsigned char wav_data[5][100]={
{
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
},
{
255,252,250,247,245,242,240,237,235,232,230,227,224,222,219,217,214,212,209,207,204,201,199,196,194,191,189,186,
184,181,179,176,173,171,168,166,163,161,158,156,153,150,148,145,143,140,138,135,133,130,128,125,122,120,117,
115,112,110,107,105,102,99,97,94,92,89,87,84,82,79,77,74,71,69,66,64,61,59,56,54,51,48,46,43,41,38,36,33,31,28,26,23,20,18,15,13,10,8,5,3
},
{
0,3,5,8,10,13,15,18,21,23,26,28,31,33,36,39,41,44,46,49,52,54,57,59,62,64,67,70,72,75,
77,80,82,85,88,90,93,95,98,100,103,106,108,111,113,116,118,121,124,126,129,131,
134,137,139,142,144,147,149,152,155,157,160,162,165,167,170,173,175,178,180,183,185,
188,191,193,196,198,201,203,206,209,211,214,216,219,222,224,227,229,232,234,237,240,242,245,247,250,252,255
},
{
0,5,10,15,20,26,31,36,41,46,51,56,61,66,71,77,82,87,92,97,102,107,112,117,122,128,
133,138,143,148,153,158,163,168,173,179,184,189,194,199,204,209,214,219,224,230,
235,240,245,250,255,250,245,240,235,230,224,219,214,209,204,199,194,189,184,179,
173,168,163,158,153,148,143,138,133,128,122,117,112,107,102,97,92,87,82,77,71,66,61,56,51,46,41,36,31,26,20,15,10,5
},
{
128,136,144,152,160,168,175,182,190,197,203,210,216,221,227,232,236,240,
244,247,250,252,254,254,255,255,255,254,254,252,250,247,244,240,236,
232,227,221,216,210,203,197,190,182,175,168,160,152,144,136,128,120,
112,104,96,88,81,74,66,59,53,46,40,35,29,24,20,16,12,9,6,4,2,1,0,0,0,
1,2,4,6,9,12,16,20,24,29,35,40,46,53,59,66,74,81,88,96,104,112,120
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
3.定时器的周期性触发DAC进行DMA搬运生成波形
通常DMA的机制为定时器触发DMA进行数据搬运,但是DAC为定时器触发DAC后,DAC再触发DMA进行数据搬运及转换。。。通过改变定时器的触发周期,可以改变DAC波形的周期,这也是使用定时器的重要原因,而DMA则是因为无需占用cpu处理,就可以稳定的生成波形。
uint8_t convertarr[100]; //__attribute__((at(0X20020000))); //内部地址高扇区RAM
void dac_config(void)
{
dma_single_data_parameter_struct dma_struct;
//GPIO使能
rcu_periph_clock_enable(RCU_GPIOA);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_5);
//DAC使能
rcu_periph_clock_enable(RCU_DAC);
dac_deinit();
dac_trigger_source_config(DAC1, DAC_TRIGGER_T6_TRGO);
dac_trigger_enable(DAC1);
dac_wave_mode_config(DAC1, DAC_WAVE_DISABLE);
dac_output_buffer_enable(DAC1);
/* enable DAC1 and DMA for DAC1 */
dac_enable(DAC1);
dac_dma_enable(DAC1);
//DMA使能
rcu_periph_clock_enable(RCU_DMA0);
dma_channel_subperipheral_select(DMA0, DMA_CH6, DMA_SUBPERI7);
dma_struct.periph_addr = (uint32_t)&DAC1_R8DH;
dma_struct.memory0_addr = (uint32_t)convertarr;
dma_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_struct.number = 100;
dma_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE;
dma_single_data_mode_init(DMA0, DMA_CH6, &dma_struct);
dma_channel_enable(DMA0, DMA_CH6);
//定时器使能
rcu_periph_clock_enable(RCU_TIMER6);
timer_prescaler_config(TIMER6, 9, TIMER_PSC_RELOAD_UPDATE);
timer_autoreload_value_config(TIMER6, 0xff);
timer_master_output_trigger_source_select(TIMER6, TIMER_TRI_OUT_SRC_UPDATE);
timer_enable(TIMER6);
}
//1-50K
void Dac_Time_Hz(uint16_t hz)
{
if(hz>max_wav_fps) hz=max_wav_fps;
if(hz>1000)
{
TIMER_PSC(TIMER6) = 0;
TIMER_CAR(TIMER6) = 200000000 / 100 / hz -1;
}
else if(hz>10)
{
TIMER_PSC(TIMER6) = 10-1;
TIMER_CAR(TIMER6) = 200000000 / 1000 / hz -1;
}
else
{
TIMER_PSC(TIMER6) = 50-1;
TIMER_CAR(TIMER6) = 200000000 / 5000 / hz -1;
}
}
//设置波形
void Dac_Show_Wav(uint8_t number)
{
uint8_t i;
if(number>max_wav_number-1) number=max_wav_number-1;
for(i=0;i<100;i++) convertarr[i]=wav_data[number][i];
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
4.整体功能
目前DAC的输出只有通过其他工具才可以观测,这里只进行简单的输出,以供后续ADC的验证和代码编辑,后续的功能设置在UI部分完成。
int main(void)
{
// uint32_t i;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config();
led_gpio_config(); // led初始化
key_gpio_config(); // key初始化
usart_gpio_config(9600U);
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//LCD初始化
LCD_Init();
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);//50ms
Spi2_Dma_Init();
Lcd_Gram_Fill(Show_GramA,GRAM_BLUE);
Lcd_Gram_Fill(Show_GramB,GRAM_RED);
LCD_Show_Gram(Show_GramA);
while(Lcd_Show_Over);
//开启定时器固定刷屏
Lcd_Show_Time();
//波形输出
dac_config();
Dac_Show_Wav(4);
Dac_Time_Hz(2000);
while(1)
{
// //定时器循环固定数据刷屏
// while(Lcd_Show_Over);
// Show_Star=1;
// while(Show_Star);
delay_1ms(200);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
简单进阶:可以在while(1)里面通过按键修改波形输出的频率。
五、模拟信号采集
本章节主要介绍一下ADC的DMA搬运机制以及使用定时器触发ADC进行DMA数据采集。这样只需要修改定时器的触发周期即可控制ADC的采样频率,能大大提高CPU的利用率。
1.ADC的基本原理
ADC是一种模拟电压转数字量的外设,和前面介绍的DAC功能相反,可以用来检测外部的模拟电压值,常用在滑动变阻器遥感,可调电阻,热敏光敏电阻的测量,电压测量,示波器模型采集等多个场景。
ADC就是将电压等比例进行数字量化的,所以实际的采集电压上限就是Vref电压,在一般电路通常为3.3V,但是在示波器电路中,我们使用的2.5V作为单片机及模拟电路的基准电压,所以ADC的上限电压为2.5V,实际运用中经过运放比例处理需要进行对应计算实际电压。
2.定时器的周期性触发ADC进行DMA搬运生成波形
使用定时器触发能保证ADC可以保证采样的稳定周期性,修改定时器的周期即可修改ADC的采样频率,通过DMA的数据搬运降低了CPU的占用率,再通过DMA传输过半和传输完成将缓冲区划分为两个部分分别处理,同样避免了数据的刷新不一致问题。
uint16_t adc_value[adc_buff_max]; //数据存储缓存
uint8_t adc_dma_AB; //记录数据片
uint8_t adc_dma_ok; //更新标志
uint16_t adc_buff[adc_buff_2x]; //数据临时中转
void adc_config(void)
{
/* ADC_DMA_channel configuration */
dma_single_data_parameter_struct dma_single_data_parameter;
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
/* enable GPIOC clock */
rcu_periph_clock_enable(RCU_GPIOA);
/* enable ADC clock */
rcu_periph_clock_enable(RCU_ADC0);
/* enable timer1 clock */
rcu_periph_clock_enable(RCU_TIMER1);
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA1);
/* config the GPIO as analog mode */
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1);
/* TIMER1 configuration */
timer_initpara.prescaler = 0;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 99;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER1,&timer_initpara);
/* CH2 configuration in PWM mode1 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
timer_channel_output_config(TIMER1,TIMER_CH_2,&timer_ocintpara);
timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_2,49);
timer_channel_output_mode_config(TIMER1,TIMER_CH_2,TIMER_OC_MODE_PWM1);
timer_channel_output_shadow_config(TIMER1,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);
timer_enable(TIMER1);
/* ADC DMA_channel configuration */
dma_deinit(DMA1, DMA_CH0);
/* initialize DMA single data mode */
dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_single_data_parameter.memory0_addr = (uint32_t)(adc_value);
dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;
dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY;
dma_single_data_parameter.number = adc_buff_max;
dma_single_data_parameter.priority = DMA_PRIORITY_HIGH;
dma_single_data_mode_init(DMA1, DMA_CH0, &dma_single_data_parameter);
dma_channel_subperipheral_select(DMA1, DMA_CH0, DMA_SUBPERI0);
/* enable DMA circulation mode */
dma_circulation_enable(DMA1, DMA_CH0);
/* enable DMA channel */
dma_channel_enable(DMA1, DMA_CH0);
dma_interrupt_enable(DMA1, DMA_CH0, DMA_CHXCTL_FTFIE|DMA_CHXCTL_HTFIE);//
nvic_irq_enable(DMA1_Channel0_IRQn, 0, 0);
adc_clock_config(ADC_ADCCK_HCLK_DIV5);//ADC_ADCCK_HCLK_DIV5
//规则
adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,1);
adc_regular_channel_config(ADC0,0,ADC_CHANNEL_1,ADC_SAMPLETIME_3);
adc_external_trigger_config(ADC0,ADC_REGULAR_CHANNEL,EXTERNAL_TRIGGER_FALLING);
adc_external_trigger_source_config(ADC0,ADC_REGULAR_CHANNEL,ADC_EXTTRIG_REGULAR_T1_CH2);
/* ADC data alignment config */
adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
adc_resolution_config(ADC0,ADC_RESOLUTION_12B);
/* ADC DMA function enable */
adc_dma_request_after_last_enable(ADC0);
adc_dma_mode_enable(ADC0);
/* enable ADC interface */
adc_enable(ADC0);
/* wait for ADC stability */
delay_1ms(1);
/* ADC calibration and reset calibration */
adc_calibration_enable(ADC0);
delay_1ms(10);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
3.模拟采样电路的控制部分
通过控制模拟电路的耦合继电器以及运算放大器模拟片选,可以实现交流/直流的耦合方式切换,放大倍数切换。(电路详情请看章节1电路设计部分说明)
//交流直流设置
void alternating_direct_set(unsigned char gather)
{
if(gather == alternating) gpio_bit_reset(GPIOF,GPIO_PIN_10);//交流
else gpio_bit_set(GPIOF,GPIO_PIN_10);//直流
}
//采样倍率设置
void gather_rate_set(unsigned char rate)
{
if(rate & 0x01) gpio_bit_set(GPIOB,GPIO_PIN_8);
else gpio_bit_reset(GPIOB,GPIO_PIN_8);
if(rate & 0x02) gpio_bit_set(GPIOB,GPIO_PIN_7);
else gpio_bit_reset(GPIOB,GPIO_PIN_7);
if(rate & 0x04) gpio_bit_set(GPIOB,GPIO_PIN_5);
else gpio_bit_reset(GPIOB,GPIO_PIN_5);
}
//采样速度设置
void adc_speed_set(unsigned char speed)
{
TIMER_PSC(TIMER1) = (uint32_t)((0x01<<speed) - 1);
}
//采样开始停止设置
void adc_power_set(unsigned char onoff)
{
if(onoff) timer_enable(TIMER1);
else timer_disable(TIMER1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
为保证采样周期的整数倍,故设置的分频系数达到2^N倍周期。
4.波形的简单显示-画直线
这里使用了画竖线的方式对应每个像素的的位置快速连接,形成波形。由于存在显示缓存,所以只需要在缓存中画好线条,更新到屏幕就可以显示了。
//画竖线
void Lcd_Show_Lin(uint16_t *dat,uint16_t x,uint8_t pointa,uint8_t pointb,uint16_t color)
{
uint8_t i;
if(pointa >= LCD_H) pointa=LCD_H-1;
if(pointb >= LCD_H) pointb=LCD_H-1;
if(pointa<pointb)
{
for(i=pointa;i<pointb;i++) *(dat+(239-i)*LCD_W+x) = color;
}
else if(pointa>pointb)
{
for(i=pointb;i<pointa;i++) *(dat+(239-i)*LCD_W+x) = color;
}
else *(dat+(239-pointa)*LCD_W+x) = color;
}
//简单显示
void Lcd_Show_LinA(uint16_t *dat)
{
uint16_t x,y;
uint16_t *buf=dat;
// //清除全部无线条
// for(y=0;y<240;y++)
// {
// for(x=0;x<320;x++) *buf++ = GRAM_BLACK;
// }
//绘制简单线条
for(x=0;x<320;x++) *buf++ = GRAM_BLACK;
for(y=1;y<240;y++)
{
*buf++ = GRAM_BLACK;
if(y%20 == 0)
{
for(x=1;x<320;x++) *buf++ = GRAM_GRAY;
}
else
{
for(x=1;x<320;x++)
{
if(x%25 == 0) *buf++ = GRAM_GRAY;
else *buf++ = GRAM_BLACK;
}
}
}
//绘制线段
for(x=0;x<319;x++)
{
Lcd_Show_Lin(dat,x,Show_Lin[x],Show_Lin[x+1],GRAM_GREEN);
}
Lcd_Show_Lin(dat,x,Show_Lin[319],Show_Lin[319],GRAM_GREEN);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
这里使用的简单的画线显示,实际后面的UI显示中,先将绘制好的栅格图片(单色)绘制在画框内,再开始显示波形图,这样可以自由的设计栅格大小和形状。
5.整体功能
在这里,我们只进行简单的波形采集和简单显示工作,如需特定采样率或者波形输出直接在代码里面修改看现象就行,由于没有做触发对齐,非整数倍频率的波形显示时会有滚动的现象,这是正常的(采样和波形发生器频率不整除,所以会有余数滚动)。具体解决办法在后续触发设计章节讲解(软件检测位置进行显示,很稳)。
实际的显示中,由于电路的缩放比例和像素的比例一一对应,所以直接将数值缩放后对应像素的值大小,由于显示方便居中,16bit缩放为8bit(255)超过显示的200(栅格画框大小,最大240屏幕像素).故下移了(255-200)/2=27个像素保证波形尽量居中显示。
//ADC采集数据指针
uint16_t *adc_tmp;
int main(void)
{
uint32_t i;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config();
led_gpio_config(); // led初始化
key_gpio_config(); // key初始化
usart_gpio_config(9600U);
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//LCD初始化
LCD_Init();
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);//50ms
Spi2_Dma_Init();
// Lcd_Gram_Fill(Show_GramA,GRAM_BLUE);
// Lcd_Gram_Fill(Show_GramB,GRAM_RED);
// LCD_Show_Gram(Show_GramA);
// while(Lcd_Show_Over);
//开启定时器固定刷屏
Lcd_Show_Time();
//波形输出
dac_config();
Dac_Show_Wav(4);
Dac_Time_Hz(10000);
//ADC采集
adc_config();
adc_setio_init();
alternating_direct_set(alternating);
gather_rate_set(gather_rate_1);
adc_speed_set(adc_speed_500K);
while(1)
{
//定时器循环固定数据刷屏
while(Lcd_Show_Over);
Show_Star=1;
while(Show_Star);
//等待采集完成
while(!adc_dma_ok);
adc_dma_ok=0;
//配置指针 添加计算缓冲 DMA采样速度过快
if(adc_dma_AB) adc_tmp = adc_value + adc_buff_2x;
else adc_tmp = adc_value ;
for(i=0;i<adc_buff_2x;i++) adc_buff[i] = adc_tmp[i];
//缓存导入
for(i=0;i<320;i++)
{
Show_Lin[i]=((adc_buff[i] + 0x08)>>4);
if(Show_Lin[i] > 227) Show_Lin[i]=227-8;
else if(Show_Lin[i] < 27) Show_Lin[i]=27-8;
else Show_Lin[i]=Show_Lin[i]-8;
}
//显示数据
if(Show_AB) Lcd_Show_LinA(Show_GramA);
else Lcd_Show_LinA(Show_GramB);
//delay_1ms(200);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
简单进阶:尝试用贴图制作好看的栅格效果,尝试让波形不左右移动。
六、基于DSP库的FFT使用及简单显示
本章节主要介绍梁山派GD32F450移植RAM公司的DSP库,加上GD32F450内置了FPU浮点运算加速,可以使FFT运算又快又准。
1.FFT的简单原理介绍
FFT名为快速傅里叶变换,可以将时域信号(波形-时间x数值)变为频域信号(频谱-频点x幅值)。具体算法可以网上自行检索学习,这里主要介绍DSP库的FFT算法快速是实现。
FFT如图所示,可以快速有效分析波形的组成和不同频率的占比,也常常用来作为音乐频谱显示,底噪滤除,数据调节等领域。
最大占比频率 = 最大幅值频点 * 采样周期 / FFT计算点1024;(存在略微误差)
(阶梯误差效应,最小误差=采样频率 / 1024,降低采样频率会略微提高计算精度)
2.DSP库的移植
详情可以参考GD官网的DSP库移植文档,也可以参考STM32F4的DSP库移植,因为它们都是使用的arm公司内核的DSP库。本文档教程仅供参考不同keil版本和库略有差异。
2.1. 添加RAM官方DSP库文件到工程
2.2. 打开工程添加编译宏和库文件
USE_HAL_DRIVER,GD32F450xx,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING
2.3. 在代码里面使用FFT
#include "arm_math.h"
//FFT变量
#define FFT_LENGTH 1024 //FFT长度,默认是1024点FFT
float fft_inputbuf[FFT_LENGTH*2]; //FFT输入数组
float fft_outputbuf[FFT_LENGTH]; //FFT输出数组
arm_cfft_radix4_instance_f32 scfft;
////FFT初始化一次 scfft结构体
arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);
//FFT运算
for(i=0;i<FFT_LENGTH;i++)//生成信号序列
{
fft_inputbuf[2*i]=adc_buff[i];//生成输入信号实部
fft_inputbuf[2*i+1]=0;//虚部全部为0
}
arm_cfft_radix4_f32(&scfft,fft_inputbuf); //FFT计算(基4)
arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH); //把运算结果复数求模得幅值
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
3.实际FFT功能的实际运用
在ADC采集到数据后,选取前1024个数据进行FFT处理,并且需要进行缩放到合适大小进行显示。
实测GD32F450进行浮点FFT1024点计算,耗时时常小于1mS。可以看出搭载FPU配合DSP库进行数据处理性能非常强悍。
//ADC采集数据指针
uint16_t *adc_tmp;
//FFT变量
#define FFT_LENGTH 1024 //FFT长度,默认是1024点FFT
float fft_inputbuf[FFT_LENGTH*2]; //FFT输入数组
float fft_outputbuf[FFT_LENGTH]; //FFT输出数组
arm_cfft_radix4_instance_f32 scfft;
//FFT显示处理
float max_fft;
uint16_t fft_n; //缩放比例
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
uint32_t i;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config();
led_gpio_config(); // led初始化
key_gpio_config(); // key初始化
usart_gpio_config(9600U);
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//LCD初始化
LCD_Init();
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);//50ms
Spi2_Dma_Init();
// Lcd_Gram_Fill(Show_GramA,GRAM_BLUE);
// Lcd_Gram_Fill(Show_GramB,GRAM_RED);
// LCD_Show_Gram(Show_GramA);
// while(Lcd_Show_Over);
//开启定时器固定刷屏
Lcd_Show_Time();
//波形输出
dac_config();
Dac_Show_Wav(4);
Dac_Time_Hz(10000);
//ADC采集
adc_config();
adc_setio_init();
alternating_direct_set(alternating);
gather_rate_set(gather_rate_1);
adc_speed_set(adc_speed_500K);
////FFT初始化 scfft结构体
arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);
while(1)
{
//定时器循环固定数据刷屏
while(Lcd_Show_Over);
Show_Star=1;
while(Show_Star);
//等待采集完成
while(!adc_dma_ok);
adc_dma_ok=0;
//配置指针 添加计算缓冲 DMA采样速度过快
if(adc_dma_AB) adc_tmp = adc_value + adc_buff_2x;
else adc_tmp = adc_value ;
for(i=0;i<adc_buff_2x;i++) adc_buff[i] = adc_tmp[i];
//波形导入
for(i=0;i<320;i++)
{
Show_LinA[i]=((adc_buff[i] + 0x08)>>4);
if(Show_LinA[i] > 227) Show_LinA[i]=227-8;
else if(Show_LinA[i] < 27) Show_LinA[i]=27-8;
else Show_LinA[i]=Show_LinA[i]-8;
}
//FFT运算 <1ms
for(i=0;i<FFT_LENGTH;i++)//生成信号序列
{
fft_inputbuf[2*i]=adc_buff[i];//生成输入信号实部
fft_inputbuf[2*i+1]=0;//虚部全部为0
}
arm_cfft_radix4_f32(&scfft,fft_inputbuf); //FFT计算(基4)
arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH); //把运算结果复数求模得幅值
fft_outputbuf[0] = 0;//第一个值非常大,这里舍弃
//测算最大FFT点
max_fft = 0;
for(i=0;i<320;i++)
{
if(fft_outputbuf[i] > max_fft) max_fft = fft_outputbuf[i];
}
//自动评估FFT缩放等级
fft_n = max_fft/150 + 1; //只显示150个刻度
for(i=0;i<320;i++)
{
Show_LinB[i]=(fft_outputbuf[i]/fft_n +27);
if(Show_LinB[i] > 227) Show_LinB[i]=227-8;
else if(Show_LinB[i] < 27) Show_LinB[i]=27-8;
else Show_LinB[i]=Show_LinB[i]-8;
}
//显示数据
if(Show_AB) Lcd_Show_LinA(Show_GramA);
else Lcd_Show_LinA(Show_GramB);
//delay_1ms(200);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
简单进阶:尝试通过前面公式计算最大占比的频率。(最大占比频率 = 最大幅值频点 * 采样周期 / FFT计算点1024;),尝试按键修改波形发生器DAC输出频率进行FFT计算。
七、简单的UI界面设计及显示
1.波形栅格图框的绘制和显示
前面ADC波形采集和简单显示使用的固定画线代码进行栅格的制作,虽说这样简单快捷,但是无法满足实际使用,并且不便于修改,所以,实际使用中大量使用了图片取模贴图代替实际画线。主要还有是因为梁山派芯片GD32F450的Flash够大,取模放个图片完全没问题,这里使用的单色取模显示,以减少实际图片的存储占用量。
【纵坐标栅格像素】实际显示图片的代码只需要将图片显示在特定的位置就好,但是栅格的点数还是有一点讲究的。因为ADCd的电压基准为2.5V,实际的Y轴格数对应ADC的转换8bit(255),使用220个像素对应220位ADC数值的话,每个像素的大约为10mV(9.8mV)的IO采集电压。。实际受到运放电路的缩放作用,考虑5倍分压1倍放大的情况,1V电压就为IO电压200mV=20格像素点。,故这里使用了20个像素作为纵刻度。(这种方式较为取巧,实际上前面的基础功能都是由DMA进行处理,这里就算先转换成电压值,在使用任意栅格刻度进行换算都是来的及的,有兴趣可以自由扩展)
【横坐标刻度】这里同样使用了刻意设置的栅格刻度,因为我们的划线使用的简单的两点竖直线,所以不支持一列数据多点数据显示,每列像素对应一个数据,故考虑单片机ADC的采样率为2M/2^n配置机制,所以单个像素为0.25uS的N次方时间刻度,为保证整除所以使用了25个像素作为横坐标栅格。(有兴趣可以自行扩展任意像素栅格)
////绘制黑白取模图片
void Lcd_Show_Bmp(uint16_t *dat,uint8_t *bmp,uint16_t x,uint16_t y,uint16_t w,uint16_t h)
{
uint16_t *buf;
uint16_t a,b;
uint8_t i,temp;
for(a=y;a<y+h;a++)
{
buf = dat + a*320 + x;
for(b=x;b<x+w;b+=8)
{
temp = *bmp++;
if(b+8 > x+w)
{
for(i=0;i<(x+w-b);i++)
{
if(temp & 0x80) *buf++ = POINT_COLOR;
else *buf++ = BACK_COLOR;
temp = temp<<1;
}
}
else
{
for(i=0;i<8;i++)
{
if(temp & 0x80) *buf++ = POINT_COLOR;
else *buf++ = BACK_COLOR;
temp = temp<<1;
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
将栅格进行取模之后,就可以这样写入到屏幕缓存内就行显示了。后续的功能图标也是使用这个刷图函数进行显示(进阶提示:也可以使用彩色图片取模进行栅格和图标显示,彩色2013012=121002字节,黑白201/8*301/8=7638字节)
2.栅格框内波形的显示
前面的简单显示已经可以将波形进行缩放并且整屏显示了,这里只需要保证波形只在栅格框中显示,目前只有采集数据波形和FFT波形。
//绘制FFT波形线段
for(x=0;x<300;x++)
{
Lcd_Show_Lin(dat,11+x,Show_LinB[x],Show_LinB[x+1],GRAM_YELLOW);
}
//绘制ADC波形线段
for(x=11;x<310;x++)
{
Lcd_Show_Lin(dat,x,Show_LinA[x],Show_LinA[x+1],GRAM_GREEN);
}
2
3
4
5
6
7
8
9
10
在波形的绘制中,后绘制的波形会覆盖之前显示的内容,这里FFT不那么重要,故放在前面。由于栅格大小位置确定,所以显示时去除前面和后面的十几个数据。
3.偏移线和触发线的绘制和功能制作
波形在实际显示中,由于采样周期和波形周期的不同步会导致波形周期滚动,这里使用软件触发的方式进行波形显示固定,保证显示时的稳定度。而偏移线则是上下移动数据波形的刻度。
//////////////////绘制部分
//绘制触发导线
tmp = dat + (120-Trigger)*320 + 10;
for(x=0;x<101;x++)
{
*tmp = GRAM_BLUE;
tmp += 3;
}
//绘制偏移导线
tmp = dat + (120-Level)*320 + 10;
for(x=0;x<61;x++)
{
*tmp = GRAM_WHITE;
tmp += 5;
}
///////////////采样数据处理部分
//////设定值判定 Trigger 下降沿
Trigger_number=0;
Trigger_data = 2048+(Trigger-Level)*16;
for(i=lin_stat_set;i<lin_over_set;i++)
{
if(adc_tmp[i] > Trigger_data+25)
{
for(;i<lin_over_set;i++)
{
if(adc_tmp[i] < Trigger_data)
{
Trigger_number=i-lin_stat_set;
break;
}
}
break;
}
}
//缓存导入
for(i=0;i<320;i++)
{
Show_LinA[i]=((adc_tmp[i+Trigger_number])>>4) + Level;
if(Show_LinA[i] > 227) Show_LinA[i]=227-8;
else if(Show_LinA[i] < 27) Show_LinA[i]=27-8;
else Show_LinA[i]=Show_LinA[i]-8;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
从实物显示图和代码中可以看出,采用下降沿触发后,波形的位置和(居中)触发位置一致,这样可以保证波形的稳定居中显示。
4.功能图标和字符的显示
功能图标的显示和栅格图片显示一样就行,字符的显示主要是显示频率等数字参数,某些特定字符串也可以提前设定好,按数组进行显示。
界面的显示分为每帧需要更新的部分和只有设置修改后需要更新的部分。如图4-1所示,画红框的部分为需要实时显示的内容,分别为:软件触发/暂停移动的位置,波形界面,FFT预估频率,FFT缩放倍数,软件触发/暂停移动的位置。因此代码UI分为两个部分显示,实时显示界面函数,设置界面函数+初始画显示。
////显示ADC和FFT波形 实时刷新
void Lcd_Show_Wav(uint16_t *dat)
{
uint16_t x;
uint16_t *tmp;
//位置框
POINT_COLOR=GRAM_WHITE; //画笔颜色
Lcd_Show_Bmp(dat,(uint8_t *)gImage_1,30,2,88,16); //缓冲区
//位置浮标
if(adc_power) POINT_COLOR=GRAM_WHITE; //画笔颜色
else POINT_COLOR=GRAM_LIGHTBLUE; //画笔颜色
Lcd_Show_Bmp(dat,(uint8_t *)gImage_2,38+(Trigger_number*62/1680),7,11,6);//进度条 74-11
LCD_ShowNum(dat,240,222,Trigger_number,4,16,0);
//FFT
POINT_COLOR=GRAM_WHITE; //画笔颜色
LCD_ShowNum(dat,140,222,fft_fps,5,16,0); //频率
LCD_ShowNum(dat,190,222,fft_n,5,16,0); //缩放倍数
//绘制黑白取模栅格
POINT_COLOR=GRAM_GRAYA; //画笔颜色
Lcd_Show_Bmp(dat,(uint8_t *)gImage_b,10,20,301,201);
//绘制触发导线
tmp = dat + (120-Trigger)*320 + 10;
for(x=0;x<101;x++)
{
*tmp = GRAM_BLUE;
tmp += 3;
}
//绘制偏移导线
tmp = dat + (120-Level)*320 + 10;
for(x=0;x<61;x++)
{
*tmp = GRAM_WHITE;
tmp += 5;
}
//绘制FFT波形线段
for(x=0;x<300;x++)
{
Lcd_Show_Lin(dat,11+x,Show_LinB[x],Show_LinB[x+1],GRAM_YELLOW);
}
//绘制ADC波形线段
for(x=11;x<310;x++)
{
Lcd_Show_Lin(dat,x,Show_LinA[x],Show_LinA[x+1],GRAM_GREEN);
}
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
////控制对应统一刷新函数 设置初始化刷新
void Lcd_Show_Data(uint8_t Number)
{
switch(Number)
{
case 0:
{ //开始暂停
if(!adc_power) POINT_COLOR=GRAM_LIGHTBLUE; //画笔颜色
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
POINT_COLOR=GRAM_WHITE; //画笔颜色
break;
}
case 1:
{ //下降沿上升沿
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
break;
}
case 2:
{ //ACDC
LCD_ShowString(Show_GramA,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
LCD_ShowString(Show_GramB,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
break;
}
case 3:
{ //增加减少放大倍数
LCD_ShowString(Show_GramA,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
LCD_ShowString(Show_GramB,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
break;
}
case 4:
{ //增加减少采样速度
LCD_ShowString(Show_GramA,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
LCD_ShowString(Show_GramB,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
break;
}
case 5:
{ //增加减少触发位置
//POINT_COLOR=GRAM_BLUE; //画笔颜色
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
break;
}
case 6:
{ //增加减少中心位置
//POINT_COLOR=GRAM_WHITE; //画笔颜色
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[0],0,117-Level,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[0],0,117-Level,8,8);
break;
}
case 7:
{ //增加减少波形种类
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[wav_number],10,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[wav_number],10,222,32,16);
break;
}
case 8:
{ //增加减少波形频率
LCD_ShowNum(Show_GramA,50,222,wav_Fps,5,16,0);
LCD_ShowNum(Show_GramB,50,222,wav_Fps,5,16,0);
break;
}
case 9:
{ //FFT功能
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
break;
}
default:
break;
}
}
////////////////////实际的初始化配置
//波形输出
dac_config();
Dac_Show_Wav(wav_number);
Dac_Time_Hz(wav_Fps);
//ADC采集
adc_config();
adc_setio_init();
alternating_direct_set(ac_dc);
gather_rate_set(gather_rate);
adc_speed_set(adc_speed);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
这里定义了界面的全局变量,将显示和初始化ADC和DAC进行结合,可以达到实际的显示和控制效果,具体的控制设置在后面的整体功能实现进行设计。
八、整体功能的实现
前面已经实现了基本的功能和显示,本章节主要是将按键功能和波轮功能整合,还有实际的操作逻辑进行制作。
1.按键功能的基本检测
将按键放在定时器中断中进行检测,即可以及时检测到按键的触发。这了为了保证定时器的利用率,直接在屏幕刷新的50mS周期内进行查询的处理。(也可以单独使用过一个定时器进行按键检测)
//屏幕刷新 50mS
void TIMER3_IRQHandler(void)
{
timer_interrupt_flag_clear(TIMER3, TIMER_INT_FLAG_UP);
if(Show_Star)
{
LED1_TOGGLE();
Show_Star=0;
if(Show_AB)LCD_Show_Gram(Show_GramA);
else LCD_Show_Gram(Show_GramB);
Show_AB =!Show_AB;
}
//按键检测
if(KeyUp) key[0] &= Key_Time;
else if(key[0] < Key_Time) key[0]++;
else;
if(KeyOn) key[1] &= Key_Time;
else if(key[1] < Key_Time) key[1]++;
else;
if(KeyDown) key[2] &= Key_Time;
else if(key[2] < Key_Time) key[2]++;
else;
//拨码A
if(KeyAUp) key[3] &= Key_Time;
else if(key[3] < Key_Time) key[3]++;
else;
if(KeyAOn) key[4] &= Key_Time;
else if(key[4] < Key_Time) key[4]++;
else;
if(KeyADown) key[5] &= Key_Time;
else if(key[5] < Key_Time) key[5]++;
else;
//拨码B
if(KeyBUp) key[6] &= Key_Time;
else if(key[6] < Key_Time) key[6]++;
else;
if(KeyBOn) key[7] &= Key_Time;
else if(key[7] < Key_Time) key[7]++;
else;
if(KeyBDown) key[8] &= Key_Time;
else if(key[8] < Key_Time) key[8]++;
else;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
这里为了区分长按触发和单次触发,做了一个简单检测处理机制。通过在功能代码处理区分释放和不释放来实现单次触发(单击)和多次触发(长按)效果。
//采集状态按键配置
if(key[0] == Key_Time)
{
key[0] = Key_No; //需要再次释放
//key[0]--; //不需要再次释放
}
if(key[1] == Key_Time)
{
key[1] = Key_No; //需要再次释放
//key[1]--; //不需要再次释放
}
if(key[2] == Key_Time)
{
key[2] = Key_No; //需要再次释放
//key[2]--; //不需要再次释放
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.按键功能的设置及显示
示波器采用了3个独立按键和2个波轮按键,这里是将独立按键固定为特定功能,波轮为可选择功能进行设置,所以需要将功能表进行制作。这里使用了查找法进行对应功能的变量控制和界面显示。
////按键对应统一控制函数-查表法
void Key_Make_Set(uint8_t Number)
{
switch(Number)
{
case 0:
{ //开始暂停
adc_power = !adc_power;
adc_power_set(adc_power);
if(!adc_power) POINT_COLOR=GRAM_LIGHTBLUE; //画笔颜色
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
POINT_COLOR=GRAM_WHITE; //画笔颜色
break;
}
case 1:
{ //暂停
adc_power = 0;
adc_power_set(adc_power);
POINT_COLOR=GRAM_LIGHTBLUE; //画笔颜色
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
POINT_COLOR=GRAM_WHITE; //画笔颜色
break;
}
case 2:
{ //开始
adc_power = 1;
adc_power_set(adc_power);
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[2+adc_power],10,2,16,16);//开始暂停
break;
}
case 3:
{ //下降沿上升沿
wav_trigger = !wav_trigger;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
break;
}
case 4:
{ //上升沿
wav_trigger = 0;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
break;
}
case 5:
{ //下降沿
wav_trigger = 1;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_4[0+wav_trigger],125,2,16,16);
break;
}
case 6:
{ //ACDC
ac_dc = !ac_dc;
alternating_direct_set(ac_dc);
LCD_ShowString(Show_GramA,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
LCD_ShowString(Show_GramB,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
break;
}
case 7:
{ //AC
ac_dc = 0;
alternating_direct_set(ac_dc);
LCD_ShowString(Show_GramA,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
LCD_ShowString(Show_GramB,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
break;
}
case 8:
{ //DC
ac_dc = 1;
alternating_direct_set(ac_dc);
LCD_ShowString(Show_GramA,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
LCD_ShowString(Show_GramB,150,2,320,20,16,0,(uint8_t *)ui_show[ac_dc]);
break;
}
case 9:
{ //增加减少放大倍数
if(++gather_rate > gather_rate_64) gather_rate=gather_rate_1_2;
gather_rate_set(gather_rate);
LCD_ShowString(Show_GramA,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
LCD_ShowString(Show_GramB,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
break;
}
case 10:
{ //减少放大倍数
if(gather_rate > gather_rate_1_2) gather_rate--;
gather_rate_set(gather_rate);
LCD_ShowString(Show_GramA,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
LCD_ShowString(Show_GramB,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
break;
}
case 11:
{ //增加放大倍数
if(gather_rate < gather_rate_64) gather_rate++;
gather_rate_set(gather_rate);
LCD_ShowString(Show_GramA,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
LCD_ShowString(Show_GramB,175,2,320,20,16,0,(uint8_t *)ui_show[2+gather_rate]);
break;
}
case 12:
{ //增加减少采样速度
if(++adc_speed > adc_speed_min) adc_speed=adc_speed_2M;
adc_speed_set(adc_speed);
LCD_ShowString(Show_GramA,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
LCD_ShowString(Show_GramB,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
break;
}
case 13:
{ //减少采样速度
if(adc_speed > adc_speed_2M) adc_speed--;
adc_speed_set(adc_speed);
LCD_ShowString(Show_GramA,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
LCD_ShowString(Show_GramB,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
break;
}
case 14:
{ //增加采样速度
if(adc_speed < adc_speed_min) adc_speed++;
adc_speed_set(adc_speed);
LCD_ShowString(Show_GramA,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
LCD_ShowString(Show_GramB,240,2,320,20,16,0,(uint8_t *)ui_show[10+adc_speed]);
break;
}
case 15:
{ //增加减少触发位置
if(Trigger<99) Trigger++;
else Trigger = -99;
//POINT_COLOR=GRAM_BLUE; //画笔颜色
if(last_Trigger != Trigger)
{
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[2],311,117-last_Trigger,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[2],311,117-last_Trigger,8,8);
last_Trigger = Trigger;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
}
break;
}
case 16:
{ //减少触发位置
if(Trigger > -99) Trigger--;
//POINT_COLOR=GRAM_BLUE; //画笔颜色
if(last_Trigger != Trigger)
{
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[2],311,117-last_Trigger,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[2],311,117-last_Trigger,8,8);
last_Trigger = Trigger;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
}
break;
}
case 17:
{ //增加触发位置
if(Trigger<99) Trigger++;
//POINT_COLOR=GRAM_BLUE; //画笔颜色
if(last_Trigger != Trigger)
{
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[2],311,117-last_Trigger,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[2],311,117-last_Trigger,8,8);
last_Trigger = Trigger;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[1],311,117-Trigger,8,8);
}
break;
}
case 18:
{ //增加减少中心位置
if(Level<99) Level++;
else Level = -99;
//POINT_COLOR=GRAM_WHITE; //画笔颜色
if(last_Level != Level)
{
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[2],0,117-last_Level,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[2],0,117-last_Level,8,8);
last_Level = Level;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[0],0,117-Level,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[0],0,117-Level,8,8);
}
break;
}
case 19:
{ //减少中心位置
if(Level > -99) Level--;
//POINT_COLOR=GRAM_WHITE; //画笔颜色
if(last_Level != Level)
{
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[2],0,117-last_Level,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[2],0,117-last_Level,8,8);
last_Level = Level;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[0],0,117-Level,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[0],0,117-Level,8,8);
}
break;
}
case 20:
{ //增加中心位置
if(Level<99) Level++;
//POINT_COLOR=GRAM_WHITE; //画笔颜色
if(last_Level != Level)
{
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[2],0,117-last_Level,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[2],0,117-last_Level,8,8);
last_Level = Level;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_t[0],0,117-Level,8,8);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_t[0],0,117-Level,8,8);
}
break;
}
case 21:
{ //增加减少波形种类
if(++wav_number >= max_wav_number) wav_number=0;
Dac_Show_Wav(wav_number);
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[wav_number],10,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[wav_number],10,222,32,16);
break;
}
case 22:
{ //减少波形种类
if(wav_number > 0) wav_number--;
Dac_Show_Wav(wav_number);
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[wav_number],10,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[wav_number],10,222,32,16);
break;
}
case 23:
{ //增加波形种类
if(wav_number < max_wav_number-1) wav_number++;
Dac_Show_Wav(wav_number);
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[wav_number],10,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[wav_number],10,222,32,16);
break;
}
case 24:
{ //增加减少波形频率
if(wav_Fps < max_wav_fps) wav_Fps += 1000;
else wav_Fps = 1000;
Dac_Time_Hz(wav_Fps);
LCD_ShowNum(Show_GramA,50,222,wav_Fps,5,16,0);
LCD_ShowNum(Show_GramB,50,222,wav_Fps,5,16,0);
break;
}
case 25:
{ //减少波形频率
if(wav_Fps > 1000) wav_Fps-=1000;
Dac_Time_Hz(wav_Fps);
LCD_ShowNum(Show_GramA,50,222,wav_Fps,5,16,0);
LCD_ShowNum(Show_GramB,50,222,wav_Fps,5,16,0);
break;
}
case 26:
{ //增加波形频率
if(wav_Fps < max_wav_fps) wav_Fps += 1000;
Dac_Time_Hz(wav_Fps);
LCD_ShowNum(Show_GramA,50,222,wav_Fps,5,16,0);
LCD_ShowNum(Show_GramB,50,222,wav_Fps,5,16,0);
break;
}
case 27:
{ //开启关闭FFT
fft_on = !fft_on;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
break;
}
case 28:
{ //关闭FFT
fft_on=0;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
break;
}
case 29:
{ //开启FFT
fft_on=1;
Lcd_Show_Bmp(Show_GramA,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
Lcd_Show_Bmp(Show_GramB,(uint8_t *)gImage_3[5+fft_on],100,222,32,16);
break;
}
default:
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
通过控制波轮的设置变量,既可以进行相应的功能切换了。
3.采集开始/暂停的功能制作
在ADC章节有提到过,开始暂停触发ADC的定时器,既可以控制采样的开始和暂停,在暂停后,还需要进行采集数据移动查看,并且显示相应的提示信息。
//////////////////////GUI实时刷新部分
//位置浮标
if(adc_power) POINT_COLOR=GRAM_WHITE; //画笔颜色
else POINT_COLOR=GRAM_LIGHTBLUE; //画笔颜色
Lcd_Show_Bmp(dat,(uint8_t *)gImage_2,38+(Trigger_number*62/1680),7,11,6);//进度条 74-11
LCD_ShowNum(dat,240,222,Trigger_number,4,16,0);
//暂停显示信息
if(!adc_power)//开始暂停标志
{
//产品归属
LCD_ShowString(dat,167,202,320,240,16,1,(uint8_t *)"https://lckfb.com");
//数据显示
LCD_ShowString(dat,10,22,320,240,16,1,(uint8_t *)"min: mV Max: mV Vp-p: mV");
volt = (float)min_data*2500/4096*5/(1<<gather_rate)*2;
LCD_ShowNum(dat,42,42,min_data,5,16,1);
LCD_ShowNum(dat,42,22,volt,5,16,1);
volt = (float)max_data*2500/4096*5/(1<<gather_rate)*2;
LCD_ShowNum(dat,136,42,max_data,5,16,1);
LCD_ShowNum(dat,136,22,volt,5,16,1);
volt = (float)(max_data-min_data)*2500/4096*5/(1<<gather_rate)*2;
LCD_ShowNum(dat,240,42,(max_data-min_data),5,16,1);
LCD_ShowNum(dat,240,22,volt,5,16,1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
在主函数中需要做明确的状态功能区分。以便于处理开始状态和暂停状态的功能实现。可以通过以下代码看出,开始状态采集数据变化一次就必须进行显示刷新,而暂停状态则是位置移动变化再进行刷新显示。
//////////////////////while(1)循环部分
if(adc_power)//开始暂停标志
{
//等待采集完成
while(!adc_dma_ok);
adc_dma_ok=0;
//配置指针 添加计算缓冲 DMA采样速度过快
if(adc_dma_AB) adc_tmp = adc_value + adc_buff_2x;
else adc_tmp = adc_value ;
for(i=0;i<adc_buff_2x;i++) adc_buff[i] = adc_tmp[i];
//FFT运算 <1ms
for(i=0;i<FFT_LENGTH;i++)//生成信号序列
{
fft_inputbuf[2*i]=adc_buff[i];//生成输入信号实部
fft_inputbuf[2*i+1]=0;//虚部全部为0
}
arm_cfft_radix4_f32(&scfft,fft_inputbuf); //FFT计算(基4)
arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH); //把运算结果复数求模得幅值
fft_outputbuf[0] = 0;//第一个值非常大,这里舍弃
//测算最大FFT点
max_fft = 0;
fft_number=0;
for(i=0;i<320;i++)
{
if(fft_outputbuf[i] > max_fft)
{
max_fft = fft_outputbuf[i];
fft_number = i;
}
}
//自动评估FFT缩放等级
fft_n = max_fft/150 + 1; //只显示150个刻度
for(i=0;i<320;i++)
{
Show_LinB[i]=(fft_outputbuf[i]/fft_n +27);
if(Show_LinB[i] > 227) Show_LinB[i]=227-8;
else if(Show_LinB[i] < 27) Show_LinB[i]=27-8;
else Show_LinB[i]=Show_LinB[i]-8;
}
//FFT频率估测
fft_fps = (2000000 / (0x01<<adc_speed)) * fft_number / 1024;
//////设定值判定 Trigger 下降沿
Trigger_number=0;
Trigger_data = 2048+(Trigger-Level)*16;
for(i=lin_stat_set;i<lin_over_set;i++)
{
if(adc_buff[i] > Trigger_data+25)
{
for(;i<lin_over_set;i++)
{
if(adc_buff[i] < Trigger_data)
{
Trigger_number=i-lin_stat_set;
break;
}
}
break;
}
}
//缓存导入
for(i=0;i<320;i++)
{
Show_LinA[i]=((adc_buff[i+Trigger_number] + 0x08)>>4) + Level;//
if(Show_LinA[i] > 227) Show_LinA[i]=227-8;
else if(Show_LinA[i] < 27) Show_LinA[i]=27-8;
else Show_LinA[i]=Show_LinA[i]-8;
}
//显示数据
if(Show_AB) Lcd_Show_Wav(Show_GramA);
else Lcd_Show_Wav(Show_GramB);
//按键配置
if(key[0] == Key_Time)
{
key[0] = Key_No; //需要再次释放
}
if(key[1] == Key_Time)
{
key[1] = Key_No; //需要再次释放
Lase_Trigger_number = Trigger_number+1;
Key_Make_Set(0); //开始暂停
}
if(key[2] == Key_Time)
{
key[2] = Key_No; //需要再次释放
}
//更新显示
show_updata=1;
}
else
{
//如果有数据位置修改再更新
if(Lase_Trigger_number != Trigger_number)
{
Lase_Trigger_number = Trigger_number;
//确定最大/小值
min_data=0xffff;
max_data=0;
for(i=0;i<300;i++)
{
if(adc_tmp[i+Trigger_number]< min_data) min_data=adc_tmp[i+Trigger_number];
if(adc_tmp[i+Trigger_number]> max_data) max_data=adc_tmp[i+Trigger_number];
}
//缓存导入
for(i=0;i<320;i++)
{
Show_LinA[i]=((adc_tmp[i+Trigger_number] + 0x08)>>4) + Level;
//Show_LinA[i]=((adc_tmp[i+Trigger_number] )>>4) + Level;
if(Show_LinA[i] > 227) Show_LinA[i]=227-8;
else if(Show_LinA[i] < 27) Show_LinA[i]=27-8;
else Show_LinA[i]=Show_LinA[i]-8;
}
//显示数据
if(Show_AB)
{
//绘制波形
Lcd_Show_Wav(Show_GramA);
}
else
{
//绘制波形
Lcd_Show_Wav(Show_GramB);
}
//更新显示
show_updata=1;
}
//按键配置
if(key[0] == Key_Time)
{
key[0]--; //不需要再次释放
if(Trigger_number < adc_buff_2x-320) Trigger_number+=5;
}
if(key[1] == Key_Time)
{
key[1] = Key_No; //需要再次释放
//Lase_Trigger_number = Trigger_number+1;
Key_Make_Set(0); //开始暂停
}
if(key[2] == Key_Time)
{
key[2]--; //不需要再次释放
if(Trigger_number > 5) Trigger_number-=5;
else if(Trigger_number) Trigger_number = 0;
else;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
4.三个独立按键的功能
这里只需要在波形采集开始函数内加入对应的功能操作代码即可。这里我们定义三个按键的功能分别为:直流交流耦合切换,开始暂停切换,FFT开关。
//按键配置
if(key[0] == Key_Time)
{
key[0] = Key_No; //需要再次释放
Key_Make_Set(6); //ACDC
}
if(key[1] == Key_Time)
{
key[1] = Key_No; //需要再次释放
Lase_Trigger_number = Trigger_number+1;
Key_Make_Set(0); //开始暂停
}
if(key[2] == Key_Time)
{
key[2] = Key_No; //需要再次释放
Key_Make_Set(27); //FFT
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在暂停后,ACDC和FFT都不需要切换,还是之前的左右移动数据查看就好。
5.波轮的功能切换
为了方便个人喜好的设置,波轮的功能是需要可以切换的,波轮的按键分为上拨动,按下,下拨动。这里定义按下为确定/切换对应功能,上拨动为该功能加一,下拨动为该功能减一。只有触发,位移,输出波形频率可以连续设置,其他设置和功能切换均为单次触发。
//拨轮A
if(key[3] == Key_Time)
{
if(keya_set)
{
key[3] = Key_No; //需要再次释放
Lcd_Show_Data(keya_number);
if(keya_number < 9) keya_number++;
POINT_COLOR=GRAM_YELLOW; //画笔颜色
Lcd_Show_Data(keya_number);
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
else
{
if(keya_number==5 || keya_number==6 || keya_number==8) key[3]--; //不需要释放
else key[3] = Key_No; //需要再次释放
POINT_COLOR=GRAM_YELLOW; //画笔颜色
Key_Make_Set(keya_number*3 + 2); //减1
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
}
if(key[4] == Key_Time)
{
key[4] = Key_No; //需要再次释放
keya_set = !keya_set;
}
if(key[5] == Key_Time)
{
if(keya_set)
{
key[5] = Key_No; //需要再次释放
Lcd_Show_Data(keya_number);
if(keya_number) keya_number--;
POINT_COLOR=GRAM_YELLOW; //画笔颜色
Lcd_Show_Data(keya_number);
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
else
{
if(keya_number==5 || keya_number==6 || keya_number==8) key[5]--; //不需要释放
else key[5] = Key_No; //需要再次释放
POINT_COLOR=GRAM_YELLOW; //画笔颜色
Key_Make_Set(keya_number*3 + 1); //减1
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
}
//拨轮B
if(key[6] == Key_Time)
{
if(keyb_set)
{
key[6] = Key_No; //需要再次释放
Lcd_Show_Data(keyb_number);
if(keyb_number < 9) keyb_number++;
POINT_COLOR=GRAM_GREEN; //画笔颜色
Lcd_Show_Data(keyb_number);
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
else
{
if(keyb_number==5 || keyb_number==6 || keyb_number==8) key[6]--; //不需要释放
else key[6] = Key_No; //需要再次释放
POINT_COLOR=GRAM_GREEN; //画笔颜色
Key_Make_Set(keyb_number*3 + 2); //减1
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
}
if(key[7] == Key_Time)
{
key[7] = Key_No; //需要再次释放
keyb_set = !keyb_set;
}
if(key[8] == Key_Time)
{
if(keyb_set)
{
key[8] = Key_No; //需要再次释放
Lcd_Show_Data(keyb_number);
if(keyb_number) keyb_number--;
POINT_COLOR=GRAM_GREEN; //画笔颜色
Lcd_Show_Data(keyb_number);
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
else
{
if(keyb_number==5 || keyb_number==6 || keyb_number==8) key[8]--; //不需要释放
else key[8] = Key_No; //需要再次释放
POINT_COLOR=GRAM_GREEN; //画笔颜色
Key_Make_Set(keyb_number*3 + 1); //减1
POINT_COLOR=GRAM_WHITE; //画笔颜色
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
在暂停状态,波轮的上拨和下拨分别为不同和程度的移动数据查看。
//拨轮A
if(key[3] == Key_Time)
{
key[3]--;
if(Trigger_number < adc_buff_2x-320) Trigger_number++;
}
if(key[4] == Key_Time)
{
key[4] = Key_No; //需要再次释放
}
if(key[5] == Key_Time)
{
key[5]--;
if(Trigger_number) Trigger_number--;
}
//拨轮B
if(key[6] == Key_Time)
{
key[6]--;
if(Trigger_number < adc_buff_2x-320) Trigger_number+=2;
}
if(key[7] == Key_Time)
{
key[7] = Key_No; //需要再次释放
}
if(key[8] == Key_Time)
{
key[8]--;
if(Trigger_number > 2) Trigger_number-=2;
else if(Trigger_number) Trigger_number = 0;
else;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
6.部分功能的完善
这里有一个触发的上升沿和下降沿触发,需要再进行完善。还有就是没触发的情况下需要进行处理,不然画面就是原始波形采集数据,会出现滚动的情况。
//////设定值判定 Trigger 上升下降沿
Trigger_number=0;
max_data = 2048+(Trigger-Level)*16;
if(wav_trigger)
{
for(i=lin_stat_set;i<lin_over_set;i++)
{
if(adc_tmp[i] < max_data-25)
{
for(;i<lin_over_set;i++)
{
if(adc_tmp[i] > max_data)
{
Trigger_number=i-lin_stat_set;
break;
}
}
break;
}
}
}
else
{
for(i=lin_stat_set;i<lin_over_set;i++)
{
if(adc_tmp[i] > max_data+25)
{
for(;i<lin_over_set;i++)
{
if(adc_tmp[i] < max_data)
{
Trigger_number=i-lin_stat_set;
break;
}
}
break;
}
}
}
//没有触发的情况
if(!Trigger_number)
{
//确定最大/小值
min_data=0xffff;
max_data=0;
for(i=lin_stat_set;i<lin_over_set;i++)
{
if(adc_tmp[i]< min_data) min_data=adc_tmp[i];
if(adc_tmp[i]> max_data) max_data=adc_tmp[i];
}
//最低确定位置
data_tmp = (max_data + min_data)/2;
for(i=lin_stat_set;i<lin_over_set;i++)
{
if(adc_tmp[i] > data_tmp)
{
for(;i<lin_over_set;i++)
{
if(adc_tmp[i] < min_data+25)
{
Trigger_number=i-lin_stat_set;
break;
}
}
break;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68