本文记录了在NIOS II上实现示波器的第三部分。
本文主要包括:硬件部分的BRAM记录波形,计算频率的模块,以及软件部分这两个模块的驱动。
本文所有的硬件以及工程参考来自魏坤示波仪,重新实现驱动并重构工程。
version 0.3 初步功能实现
关于示波器的两种Trigger Mode的
以下内容参考博客StrongPiLab
设置好的Trigger condition
才会使得波形固定在屏幕上,不会左右乱飘。
触发就是,当波形穿过Trigger level
的时候,就会产生触发信号,且该点为触发点。
如下图所示:
触发模式有以下几种
-
Auto Trigger
若ADC输入的数据没有满足
Trigger condition
,则示波器不会发出Trigger
信号。Auto Trigger
就是在没有满足Trigger condition
的时候,就内部自动发出Trigger
信号画波形。第一个直流波形因为毫无震幅变化,所以Trigger永远无法满足,因此
Auto trigger
会自行发出Trigger讯号画波形,红色的框框就是每次画出的波形内容。这也就是为何一个没有讯号输入的示波器,你还是能够看到0V(ground)能不断更新画面的原因。第二个含有脉冲的方波因为有部分波型满足上缘触发,因此前两格画面是Trigger条件满足下而画出来的,后面三格画面则是Auto trigger自己画出来的,以使用者观点来说,他会看到一个脉冲波突然出现,之后随即消失。
-
Normal Trigger
Auto trigger平常很好用,但在Debug的时候可能就不见得这么好用。因为Debug时所面对的波形通常是在不确定时间出现的不正常波形,因此若採用Auto trigger的话,很容易错失观察波形的机会,这时Normal trigger就派上用场了。
Normal trigger
只在波形符合trigger条件时, 才会更新屏幕上的波形,否则屏幕就继续维持著上次的波形。也就是屏幕上永远都会有一个上次触发过的波形固定在那里。
这里设计的MEM_CONTROL
利用TRIG_AN
在自动触发以及Normal Trigger
中选择。
利用三个计数器来实现Timeout
的功能。
- 若选择
Auto
触发模式,在COUNTER3
计数结束之后便自动开始触发 - 若选择
Noramal
模式,则只有在满足了Triger Condition
的情况下才触发 - 触发开始后
counter2
开始计数,增长一个存储深度后便停止增长,并停止向内存中写入
if(COUNTER1>=MEM_LEN)
TRIG_EN<=1;
else
COUNTER1<=COUNTER1+1;
//Auto模式COUNTER3用来记录Timeout
if(TRIG_EN && COUNTER3<MEM_LEN && TRIG_AN == 0)
COUNTER3<=COUNTER3+1;
//触发结束或者自动触发TO时
if(TRIG_DONE||COUNTER3>=MEM_LEN)
begin
if(COUNTER2>=MEM_LEN)
MEM_DONE<=1;
else
COUNTER2<=COUNTER2+1;
end
触发成功模块如下,其中TRIG_PULSE
为触发脉冲
//有数据超过了Trigger condition 触发成功
always @(posedge TRIG_PULSE or negedge RESET)
begin
if(!RESET)
TRIG_DONE<=0;
else
TRIG_DONE<=TRIG_EN;
end
触发成功的同时记录触发地址
//这里记录触发的起始地址
always @(posedge TRIG_DONE or negedge RESET)
begin
if(!RESET)
TRIG_ADDR<=0;
else
TRIG_ADDR<=RAM_ADDR;
end
MEM_CONTROL
这个模块为整个硬件部分最为重要的一部分,主要承当了以下作用
读取ADC传入的信息并将其存入MEM中。
根据选择的触发法相输出脉冲给后续
FREQ_COUNTER_MODULE
计算频率。- 确定存储深度
MEM_LEN
后,先采集一个深度的数据,然后根据是否有触发脉冲确定是否有有效数据。
module MEM_control_H(CLK,RESET,RD,ADC_DATA_CH1,ADC_DATA_CH2,
MEM_DATA_CH1,MEM_DATA_CH2,MEM_ADDR,MEM_DONE,
TRIG_ADDR,TRIG_DATA,TRIG_DONE,
TRIG_PULSE_CH1,TRIG_PULSE_CH2,
TRIG_EDGE_SEL,TRIG_SEL,
MEM_LEN,TRIG_AN);
//输入输出端口声明
input CLK;
input RD;
input [12:0] MEM_LEN;
input RESET;
input TRIG_EDGE_SEL;
input TRIG_SEL;
input [7:0] TRIG_DATA;
input [7:0] ADC_DATA_CH1;
input [7:0] ADC_DATA_CH2;
input [12:0] MEM_ADDR;
input TRIG_AN; //TRIG_AUTO/NORMAL选择
output reg [12:0]TRIG_ADDR; //用来表示触发内存地址
output reg [7:0] MEM_DATA_CH1; //MEM_DONE为1时 利用RD读取MEM_ADDR的CH1的数据
output reg [7:0] MEM_DATA_CH2; //MEM_DONE为1时 利用RD读取MEM_ADDR的CH2的数据
output reg MEM_DONE; //用来表示内存存储已经完成,可以利用RD进行读取
output reg TRIG_DONE; //用来表示已经被触发
output reg TRIG_PULSE_CH1; //CH1的触发波形 用来计算CH1的周期
output reg TRIG_PULSE_CH2; //CH2的触发波形 用来计算CH2的周期
//临时变量
reg [12:0] RAM_ADDR;
reg [7:0] MEM_CH1[8192]; //B_RAM
reg [7:0] MEM_CH2[8192];
reg TRIG_EN;
reg [12:0] COUNTER1;
reg [12:0] COUNTER2;
reg [12:0] COUNTER3;
reg TRIG_PULSE;
always @(posedge CLK or negedge RESET)
begin
if(!RESET)
//RESET 重置
begin
TRIG_EN<=0;
COUNTER1<=0;
COUNTER2<=0;
COUNTER3<=0;
MEM_DONE<=0;
RAM_ADDR<=0;
end
else if(MEM_DONE==0)
begin
//将ADC的输入写入内存
MEM_CH1[RAM_ADDR]<=ADC_DATA_CH1;
MEM_CH2[RAM_ADDR]<=ADC_DATA_CH2;
RAM_ADDR=RAM_ADDR+1;
//若COUNTER大于存储深度 则开始触发用于计算周期
if(COUNTER1>=MEM_LEN)
TRIG_EN<=1;
else
COUNTER1<=COUNTER1+1;
if(TRIG_EN && COUNTER3<MEM_LEN && TRIG_AN == 0)
COUNTER3<=COUNTER3+1;
if(TRIG_DONE||COUNTER3>=MEM_LEN)
begin
if(COUNTER2>=MEM_LEN)
MEM_DONE<=1;
else
COUNTER2<=COUNTER2+1;
end
end
end
//RD的上升沿读取MEM_ADDR指向的地址
always @(posedge RD)
begin
if(MEM_DONE)
begin
MEM_DATA_CH1<=MEM_CH1[MEM_ADDR];
MEM_DATA_CH2<=MEM_CH2[MEM_ADDR];
end
end
//CH1实现边缘触发
//若TRIG_EDGE_SEL = 1 则为上升触发
//若TRIG_EDGE_SEL = 0 则为下降触发
//实现触发的思路均为当从不同的方向超过触发线
//则将TRIG_PULSE_CH1置为1,表示有一个CH1脉冲
//后还利用TRIG_PULSE判断一个周期的时间 计算频率
always @(posedge CLK)
begin
if(TRIG_EDGE_SEL)
begin
if(ADC_DATA_CH1>TRIG_DATA)
TRIG_PULSE_CH1<=1;
else
TRIG_PULSE_CH1<=0;
end
else
begin
if(ADC_DATA_CH1<TRIG_DATA)
TRIG_PULSE_CH1<=1;
else
TRIG_PULSE_CH1<=0;
end
end
//CH2实现边缘触发
//若TRIG_EDGE_SEL = 1 则为上升触发
//若TRIG_EDGE_SEL = 0 则为下降触发
//实现触发的思路均为当从不同的方向超过触发线
//则将TRIG_PULSE_CH2置为1,表示有一个CH1脉冲
//后还利用TRIG_PULSE判断一个周期的时间 计算频率
always @(posedge CLK)
begin
if(TRIG_EDGE_SEL)
begin
if(ADC_DATA_CH2>TRIG_DATA)
TRIG_PULSE_CH2<=1;
else
TRIG_PULSE_CH2<=0;
end
else
begin
if(ADC_DATA_CH1<TRIG_DATA)
TRIG_PULSE_CH2<=1;
else
TRIG_PULSE_CH2<=0;
end
end
//若TRIG_SEL为1 则TRIG_PULSE为CH1的脉冲记录
//若TRIG_SEL为0 则TRIG_PULSE为CH2的脉冲记录
//实现思路同上两个模块,根据TRIG_EDGE_SEL来选择触发方向
//用于表示触发信号
always @(posedge CLK)
begin
if(TRIG_SEL)
begin
if(TRIG_EDGE_SEL)
begin
if(ADC_DATA_CH1>TRIG_DATA)
TRIG_PULSE<=1;
else
TRIG_PULSE<=0;
end
else
begin
if(ADC_DATA_CH1<TRIG_DATA)
TRIG_PULSE<=1;
else
TRIG_PULSE<=0;
end
end
else
begin
if(TRIG_EDGE_SEL)
begin
if(ADC_DATA_CH2>TRIG_DATA)
TRIG_PULSE<=1;
else
TRIG_PULSE<=0;
end
else
begin
if(ADC_DATA_CH2<TRIG_DATA)
TRIG_PULSE<=1;
else
TRIG_PULSE<=0;
end
end
end
//有脉冲了后才将TRIG_DONE赋值
always @(posedge TRIG_PULSE or negedge RESET)
begin
if(!RESET)
TRIG_DONE<=0;
else
TRIG_DONE<=TRIG_EN;
end
//这里记录触发的起始地址
always @(posedge TRIG_DONE or negedge RESET)
begin
if(!RESET)
TRIG_ADDR<=0;
else
TRIG_ADDR<=RAM_ADDR;
end
endmodule
MEM_CONTROL测试模块
书写测试模块,用来测试该模块所有的功能。下面是自动触发模式
`timescale 1ns/1ns
module test_mem_ctl();
reg CLK;
reg RD;
reg [12:0] MEM_LEN;
reg RESET;
reg TRIG_EDGE_SEL;
reg TRIG_SEL;
reg [7:0] TRIG_DATA;
reg [7:0] ADC_DATA_CH1;
reg [7:0] ADC_DATA_CH2;
reg [12:0] MEM_ADDR;
reg TRIG_AN;
wire [12:0]TRIG_ADDR;
wire [7:0] MEM_DATA_CH1;
wire [7:0] MEM_DATA_CH2;
wire MEM_DONE;
wire TRIG_DONE;
wire TRIG_PULSE_CH1;
wire TRIG_PULSE_CH2;
MEM_control_H my_men(CLK,RESET,RD,ADC_DATA_CH1,ADC_DATA_CH2,
MEM_DATA_CH1,MEM_DATA_CH2,MEM_ADDR,MEM_DONE,
TRIG_ADDR,TRIG_DATA,TRIG_DONE,
TRIG_PULSE_CH1,TRIG_PULSE_CH2,
TRIG_EDGE_SEL,TRIG_SEL,
MEM_LEN,TRIG_AN);
initial
begin:b1
integer i;
CLK = 0;
for(i = 0; i< 1000;i++)
begin
#1 CLK = ~CLK;
end
end
initial
begin
RD = 0;MEM_LEN = 32;RESET = 0;
//测试上升触发
TRIG_EDGE_SEL = 1;TRIG_SEL = 0;TRIG_DATA = 20;
MEM_ADDR = 10;TRIG_AN = 0;
#1 RESET = 1;
//测试读取
#300 RD = 1;TRIG_SEL = 0;
#1000 $finish();
end
//模仿波形输入
initial
begin:b2
integer i;
integer j;
ADC_DATA_CH1 = 20;
ADC_DATA_CH2 = 20;
for(i = 0; i<100;i++)
begin
for(j = 0; j<10 ;j++)
begin
#1 ADC_DATA_CH1 = ADC_DATA_CH1+1;
ADC_DATA_CH2 = ADC_DATA_CH2-1;
end
for(j = 0; j<20 ;j++)
begin
#1 ADC_DATA_CH1 = ADC_DATA_CH1-1;
ADC_DATA_CH2 = ADC_DATA_CH2+1;
end
for(j = 0; j<10 ;j++)
begin
#1 ADC_DATA_CH1 = ADC_DATA_CH1+1;
ADC_DATA_CH2 = ADC_DATA_CH2-1;
end
end
end
initial
begin
$dumpfile("memctl.vcd");
$dumpvars();
end
endmodule
注意这里利用iverilog
进行编译仿真时要带上-g2005-sv
参数
$ iverilog -g2005-sv -o test test_mem_ctl.v Mem_control.v
下面是利用GTKWave
查看仿真结果的内容:
自动触发模式
-
自动触发计时器测试:
从波形仿真图中可以看到触发波形正常工作。由于设置
CH1
为一个模拟的锯齿波,触发点为中值。所以规则产生出发波形。在
Counter1
结束之后,Trig_en
置为1。由于选择是自动触发,故Timeout
计数器Counter3
开始工作。随后在Counter3
计时超时之前成功触发并保存触发地址。 -
内存读取测试:
当触发成功后
Counter2
开始计数,并在计数到指定存储深度后结束计数,将Mem_done
置为1在
Mem_done
置为1之后,通过RD
控制正确读取MEM_Addr
中的数据 -
Normal
模式测试在
Counter1
结束之后,Trig_en
置为1。由于选择是Noramal
模式触发,故Timeout
计数器Counter3
不工作。在
Trig_en
置为1之后,Trig_pulse
上升沿代表成功触发。
生成的RTL
图如下:
FREQ_COUNTER_MODULE
这个模块为用来计算频率。其输入有
- COUNTER_IN_CH1 连接CH1的脉冲信号
- COUNTER_IN_CH2 连接CH2的脉冲信号
- CLK_IN_100MHZ 连接100MHz的CLK的信号
- FREQ_COUNTER_START 用来控制是否开启频率控制模块
其输出有:
- FREQ_DATA_CH1 输出统计的CH1频率
- FREQ_DATA_CH2 输出统计的CH2频率
- FREQ_COUNTER_DONE 1s输出一次0用来表示记录的1s数据结束。
该模块的设计思路如下:
- 设计的DFF用来在每次
START
信号从1变为0的时候,在下一个1s
的周期到来时令FREQ_COUNTER
开始计数。 - 当
START
信号为1的时候,同时清零FREQ_COUNTER
的计数器 - 当
START
从1变为0时,计数器开始计数 - 下一个时钟周期来临时,输出计算结果
其中clk_1S_module
是将100MHZ
的时钟信号转变为1HZ
时钟信号的模块,具体实现代码如下:
module clk_1S_module(clk,reset,clk_out);
//当计数器达到cnt_top参数,立即将输出反转
parameter cnt_top=27'd100000000;
input clk;
input reset;
output clk_out;
reg clk_out;
reg [26:0] clk_cnt;
always @(posedge clk or negedge reset)
begin
if(!reset)
begin
clk_out <= 1'b0;
clk_cnt <= 0;
end
else
begin
if(clk_cnt==cnt_top-1)
begin
clk_out <= ~clk_out;
clk_cnt <= 0;
end
else
clk_cnt <= clk_cnt+1'b1;
end
end
endmodule
CUT_OFF
这个模块是用来裁剪数据的,确保数据在9~247之间
module CUT_OFF(data_in,data_out);
input [7:0] data_in;
output reg [7:0] data_out;
//对数据进行裁剪,去掉大于247或小于9的
always @(data_in)
begin
if(data_in>247)
data_out<=247;
else if(data_in<9)
data_out<=9;
else
data_out<=data_in;
end
endmodule
上面三个模块的连接方式如下所示
其中CUT_OFF
与FREQ_COUNTER_MODULE
的连接说明如下:
CUT_OFF
模块的连接
-
CUT_OFF
接入由RD
和MEM_ADDR
控制的内存读取的输出 - 对数据进行裁剪之后,输出给
NIOS II
系统
FREQ_COUNTER_MODULE
模块的连接
- 输入接标准
100MHZ
时钟周期、CH1
和CH2
的触发脉冲、来自NIOS II
的控制信号 - 输出包括两个频率计算结果的输出和用来表示计算结束的标志位,接入到
NIOS II
系统中
CLK_MODULE
这个模块是选择ADC采样模块的的时钟频率和存储模块的时钟频率的。
对于存储模块来说,选择信号对应的输出频率为
SEL | 输出频率 |
---|---|
00000 | 125MHz |
00001 | 50MHz |
00010 | 25MHz |
00011 | 12.5MHz |
00100 | 5MHz |
00101 | 2.5MHz |
… | … |
10010 | 5Hz |
对于ADC模块来说,输出频率为
SEL | 输出频率 |
---|---|
00000 | 125MHz |
00001 | 50MHz |
00010 | 25MHz |
00011 | 12.5MHz |
00100 | 5MHz |
00101 | 2.5MHz |
软件设计
工程结构如下
│
├─driver
│ lcd.h #lcd驱动
│ osc.h #示波器驱动&计算各种参数
│ tools.h #工具
│
├─main
│ display.h #显示内容函数
│ freq.h #计算频率函数
│ init.h #初始化函数
│ irs.h #中断处理函数
│ main.c #主函数
│ syscon.h #响应按键操作
│
└─src #各种图像和字库
ansii_lib.h
cn_lib.h
color.h
hz_lib.h
values.h
welcome.h
按键中断处理&系统控制
在version0.1
版本的按键中断处理KeyListener
之后加上如下内容:
(在中断处理程序已经注册了该函数为中断处理函数)
if (KEY_DATA == 9) {
//等待按键抬起
while (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) != 0x03);
//切换暂停和启动模式
if (RUN_STOP_FLAG == 0)
RUN_STOP_FLAG = 1;
else
RUN_STOP_FLAG = 0;
} else if (KEY_DATA == 8) {
//等待按键抬起
while (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) != 0x03);
if (MENU2_FLAG >= 2) {
CON_FLAG = 1;
CON_DATA = KEY_DATA;
KEY_DATA = 0xff;
}
} else {
//正常情况
if (RUN_STOP_FLAG == 0) {
if (KEY_DATA != 0xff) {
//将KEY_DATA传给CON_DATA
CON_DATA = KEY_DATA;
CON_FLAG = 1;
KEY_DATA = 0xff;
}
if (!((CON_DATA >= 10 && CON_DATA <= 15) || CON_DATA == 8
|| CON_DATA == 9)) {
//等待按键抬起
while (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) != 0x03);
}
}
}
设置类似中断处理函数SYS_CONTROL
来处理按键事件。
该函数在主循环中调用,用来响应按键切换系统的功能
/*
* 函数名:SYS_CONTROL
* 功能:系统控制函数
* 说明:根据IRS改变的CON_DATA参执行相对应功能
* 日期:2017-03-19
*/
void SYS_CONTROL() {
switch (CON_DATA) {
case 0:
MENU0();
break;
case 1:
MENU1();
break;
case 2:
MENU2();
break;
case 3:
MENU3();
break;
//...
}
CON_FLAG = 0;
}
并在对应的函数MENU0
等进行处理。
例如MENU1
对应更改输出通道:
/*
* 函数名:MENU1
* 功能:按键响应函数
* 说明:按下第一个MENU键后的调用内容,用来在显示CH1和显示CH2之间切换
* 日期:2017-03-19
*/
void MENU1() {
if (MENU1_FLAG >= 1)
MENU1_FLAG = 0;
else
MENU1_FLAG++;
switch (MENU1_FLAG) {
case 0:
sprintf((char *) lcd_buffer, " CH1 ");
display_ascii(92, 16, 0x0000, MENU_FULL_COLOR);
//选择触发源为CH1
IOWR_ALTERA_AVALON_PIO_DATA(TRIG_SEL_BASE, 1);
sprintf((char *) lcd_buffer, " CH1 ");
display_ascii(252, 16, 0x0000, MENU_FULL_COLOR);
SCOPE_CHANNEL_FLAG = 0;
//清除显示的CH2的内容
CLR_WAVE_CH2();
CLR_AMP_CH2();
break;
case 1:
sprintf((char *) lcd_buffer, " CH2 ");
display_ascii(92, 16, 0x0000, MENU_FULL_COLOR);
//选择触发源为CH2
IOWR_ALTERA_AVALON_PIO_DATA(TRIG_SEL_BASE, 0);
sprintf((char *) lcd_buffer, " CH2 ");
display_ascii(252, 16, 0x0000, MENU_FULL_COLOR);
SCOPE_CHANNEL_FLAG = 1;
//清除显示的CH1的内容
CLR_WAVE_CH1();
CLR_AMP_CH1();
break;
}
}
信号调理模块控制
74HC595
用于将SPI
总线串行输入的内容,并行输出,用于控制信号调理模块。
信号调理模块原理图如下:
该模块涉及以下信号
-
ATT
衰减信号,AMP
放大信号利用
ATT_CON
函数对灵敏度进行控制/*
* 函数名:ATT_CON_CH1
* 说明:修改CH2的分度值(灵敏度) 传入参数为1则增加,传入参数为0则减少
* 日期:2017-03-19
*/
void ATT_CON_CH1(unsigned char flag) {
if (flag) {
if (ATT_FLAG_CH1 < 8)
ATT_FLAG_CH1++;
} else {
if (ATT_FLAG_CH1 > 0)
ATT_FLAG_CH1--;
}
switch (ATT_FLAG_CH1) {
case 0:
sprintf((char *) lcd_buffer, "CH1=^10mV/");
//CH1_ATT置0
(ATT_CON_VAR &= 0XF7);
//利用AD5230搭配744051对VAMP进行精准控制
CH1_VAMP_DATA = 1611;
break;
...
}
//查表获取CH1_VPOS_DATA 通过AD5230形成指定电压
CH1_VPOS_DATA = CH1_VPOS_VAR[ATT_FLAG_CH1] + ((unsigned int) ((LEVEL_FLAG_CH1 - 128) * CH1_MULVAR));
display_ascii(180, 313, 0x0000, MENU_FULL_COLOR);
while (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) != 0x03);
} -
ACDC
直流交流耦合选择利用
ACDC_CON
模块对交流直流耦合控制/*
* 函数名:ACDC_CON
* 直流交流耦合
* 日期:2017-03-27
*/
void ACDC_CON() {
if (SCOPE_CHANNEL_FLAG == 0) {
if (ACDC_FLAG_CH1) {
ACDC_FLAG_CH1 = 0;
//利用74595串转并
//将第五位置0
ATT_CON_VAR &= 0XEF;
sprintf((char *) lcd_buffer, "AC");
display_ascii(412, 16, 0x0000, MENU_FULL_COLOR);
} else {
ACDC_FLAG_CH1 = 1;
//利用74595串转并
ATT_CON_VAR |= 0X10;
sprintf((char *) lcd_buffer, "DC");
display_ascii(412, 16, 0x0000, MENU_FULL_COLOR);
}
} else if (SCOPE_CHANNEL_FLAG == 1) {
if (ACDC_FLAG_CH2) {
ACDC_FLAG_CH2 = 0;
//利用74595串转并
ATT_CON_VAR &= 0XDF;
sprintf((char *) lcd_buffer, "AC");
display_ascii(452, 16, 0x0000, MENU_FULL_COLOR);
} else {
ACDC_FLAG_CH2 = 1;
//利用74595串转并
ATT_CON_VAR |= 0X20;
sprintf((char *) lcd_buffer, "DC");
display_ascii(452, 16, 0x0000, MENU_FULL_COLOR);
}
}
} AD_S
AD选择信号
最后输入到AD9288
进行AD
转换
示波器系统设计
这里仍然采用前端中断处理事件,后端循环维护数据的单片机开发思路。上面已经详细描述过按键中断实现的内容。下面则是时钟中断。
时钟中断
时钟中断在此类FPGA+ARM
或FPGA+NIOS2
类似的架构中非常重要。这里虽然是和硬件电路打交道,但是时序仍然是其中非常重要的一环。始终定时器中断作为串行处理器中的最稳定可靠的时序根据。
该示波器的时钟中断服务主要有以下两个功能
- 通过
744051
和AD5320
对上文所述的信号调理模块进行控制 - 对显示屏显示内容及亮度进行控制
/*
* 函数名:timer
* 功能:计时器中断处理程序
* 日期:2016-9-21
*/
void timer(void* context) {
IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_BASE, 0);
IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_BASE, 0x0b);
//对AD模块进行控制
if (TIMER_FLAG == 0) {
//Disable M74HC4051M1R
SEND_595M(0x04 | (ATT_CON_VAR & 0xf8));
SEND_AD5320(CH2_VAMP_DATA);
delay_ms(1);
//Enable M74HC4051M1R
//通过744051将数据送到对应的端口
SEND_595M(0x00 | (ATT_CON_VAR & 0xf8));
TIMER_FLAG = 1;
} else if (TIMER_FLAG == 1) {
//Disable M74HC4051M1R
SEND_595M(0x04 | (ATT_CON_VAR & 0xf8));
SEND_AD5320(CH2_VPOS_DATA);
delay_ms(1);
//Enable M74HC4051M1R
SEND_595M(0x01 | (ATT_CON_VAR & 0xf8));
TIMER_FLAG = 2;
} else if (TIMER_FLAG == 2) {
//Disable M74HC4051M1R
SEND_595M(0x04 | (ATT_CON_VAR & 0xf8));
SEND_AD5320(CH1_VPOS_DATA);
delay_ms(1);
//Enable M74HC4051M1R
SEND_595M(0x02 | (ATT_CON_VAR & 0xf8));
TIMER_FLAG = 3;
} else if (TIMER_FLAG == 3) {
//Disable M74HC4051M1R
SEND_595M(0x04 | (ATT_CON_VAR & 0xf8));
SEND_AD5320(CH1_VAMP_DATA);
delay_ms(1);
//Enable M74HC4051M1R
SEND_595M(0x03 | (ATT_CON_VAR & 0xf8));
TIMER_FLAG = 0;
}
if (TL_LOOP <= 100) {
TL_DISP_FLAG = 1;
TL_LOOP++;
} else if (TL_DISP_FLAG == 1) {
TL_DISP_FLAG = 0;
CLR_LT_FLAG = 1;
}
if (LED_PWM_DATA <= 13000) {
LED_PWM_DATA += 100;
IOWR_ALTERA_AVALON_PIO_DATA(PWM_LED_BASE, LED_PWM_DATA);
}
IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_BASE, 0x07);
}
其中显示屏显示内容控制在后端循环中实现,详细见下。
后端主函数循环
while (1) {
if (CON_FLAG) {
//主循环,处理中断,更新界面
SYS_CONTROL();
}
//在时钟中断中控制
if (TL_DISP_FLAG) {
//显示内容
DISP_LEVEL_CH1(LEVEL_FLAG_CH1);
DISP_LEVEL_CH2(LEVEL_FLAG_CH2);
DISP_TRIGY(TRIG_Y_DATA);
DISP_TRIGX(TRIG_X_DATA);
} else if (CLR_LT_FLAG) {
//清空显示屏内容
CLR_LT();
CLR_LT_FLAG = 0;
}
freq_counter();
Scope();
display_area();
}
其中主要有以下几个部分
-
中断响应部分
- 若有按键中断改变了系统中断,调用
SYS_CONTOL
更改系统状态 - 根据时钟中断控制的显示\清空标记来更新显示屏
- 若有按键中断改变了系统中断,调用
-
处理部分
- 利用
freq_counter
与FPGA
部分通讯获取频率信息
/**freq_counter
* 频率计数器
* 从freq_counter模块读取数据
* 并在LCD屏幕上进行显示
*/
void freq_counter() {
if (IORD_ALTERA_AVALON_PIO_DATA(FREQ_DONE_BASE) == 0) {
FREQ_CH1 = IORD_ALTERA_AVALON_PIO_DATA(FREQ_DATA_CH1_BASE);
FREQ_CH2 = IORD_ALTERA_AVALON_PIO_DATA(FREQ_DATA_CH2_BASE);
switch (SCOPE_CHANNEL_FLAG) {
case 0:
DISP_FREQ_CH1();
sprintf((char *) lcd_buffer, " ");
display_ascii(264, 55, 0x0000, MENU_FULL_COLOR);
break;
case 1:
DISP_FREQ_CH2();
sprintf((char *) lcd_buffer, " ");
display_ascii(24, 55, 0x0000, MENU_FULL_COLOR);
break;
}
IOWR_ALTERA_AVALON_PIO_DATA(FREQ_MEAS_START_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(FREQ_MEAS_START_BASE, 1);
}
}scope
为示波器主功能函数,主要包括获取触发状态,根据模式获取波形数据,显示波形数据,计算VPP
和RMS
,在STOP
状态下控制横轴分度值。display_area
是由于之前写的波形数据的CLR
均是直接换成背景色,而没有考虑是否那个地方应该显示轴线。所以这里重新画一下轴线。
- 利用
下面就详细说明scope
这个函数
Scope
主要功能有以下几点
- 根据触发模式和触发状态进行处理
- 利用
RD
和ADDR
将FPGA
中的BRAM
数据读入缓冲数组 - 对缓冲数组进行插值写入显示缓冲区
- 在停止状态下响应更改横轴分度值的功能
/**Scope
* 示波器功能主函数
*/
void Scope() {
unsigned int i = 0;
unsigned int trig_addr;
unsigned int addr_offset;
unsigned int dso_addr_offset;
unsigned int dso_offset_stop_old = 0;
//等待触发成功 MEM_DONE完成
while (!IORD_ALTERA_AVALON_PIO_DATA(MEM_DONE_BASE)) {
//若有按键按下
if (CON_FLAG) break;
}
//如果触发成功
if (IORD_ALTERA_AVALON_PIO_DATA (TRIG_DONE_BASE)) {
//获取触发地址
trig_addr = IORD_ALTERA_AVALON_PIO_DATA(TRIG_ADDR_IN_BASE);
if (trig_addr < TRIG_POINT)
addr_offset = (trig_addr + 8192) - TRIG_POINT;
else
addr_offset = trig_addr - TRIG_POINT;
//计算插值offset
dso_addr_offset = TRIG_POINT - TRIG_X_DATA;
}
//如果触发失败 且有按键按下更新系统状态
else {
trig_addr = 0;
addr_offset = 0;
dso_addr_offset = 0;
}
if (SINGLE_FLAG) {
switch (MENU4_FLAG) {
case 0:
dso_offset_stop = 300;
break;
case 1:
dso_offset_stop = 800;
break;
case 2:
dso_offset_stop = 1800;
break;
case 3:
dso_offset_stop = 3800;
break;
}
TRIG_X_DATA = 200;
CLR_WAVE_DUAL();
//如果没有触发成功
if (IORD_ALTERA_AVALON_PIO_DATA(TRIG_DONE_BASE) == 0) {
//设置触发 重新开始
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RESET_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RESET_BASE, 1);
for (i = 0; i < 8192; i++) {
ADC_DATA_CH1[i] = 127;
ADC_DATA_CH2[i] = 127;
}
return ;
}
//如果触发成功 则STOP
else {
sprintf((char *) lcd_buffer, "STOP");
display_ascii(420, 250, 0xf800, 0xffff);
RUN_STOP_FLAG = 1;
SINGLE_FLAG = 0;
}
}
for (i = addr_offset; i < addr_offset + (2 * TRIG_POINT); i++) {
//利用RD和MEM_ADDR将BRAM中的内容读取到数组ADC_DATA中
IOWR_ALTERA_AVALON_PIO_DATA(MEM_ADDR_BASE, i);
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RD_BASE, 1);
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RD_BASE, 0);
ADC_DATA_CH1[i - addr_offset] = IORD_ALTERA_AVALON_PIO_DATA(MEM_DATA_CH1_BASE);
ADC_DATA_CH2[i - addr_offset] = IORD_ALTERA_AVALON_PIO_DATA(MEM_DATA_CH2_BASE);
}
//重新开始采集
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RESET_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RESET_BASE, 1);
//进行插值
if (freq_div_data == 0) {
Wave_Interpolation(TRIG_POINT - (TRIG_X_DATA >> 1) - 1);
for (i = 0; i < 500; i++) {
MEAS_DATA_CH1[i] = ADC_DATA_CH1[i + dso_addr_offset];
MEAS_DATA_CH2[i] = ADC_DATA_CH2[i + dso_addr_offset];
}
} else {
for (i = 0; i < 500; i++) {
DISP_DATA_CH1[i] = ADC_DATA_CH1[i + dso_addr_offset] + 52;
DISP_DATA_CH2[i] = ADC_DATA_CH2[i + dso_addr_offset] + 52;
MEAS_DATA_CH1[i] = ADC_DATA_CH1[i + dso_addr_offset];
MEAS_DATA_CH2[i] = ADC_DATA_CH2[i + dso_addr_offset];
}
}
//计算数据并显示
Signal_Meas();
if (RUN_STOP_FLAG) {
sprintf((char *) lcd_buffer, "STOP");
display_ascii(420, 250, 0xf800, 0xffff);
sprintf((char *) lcd_buffer, " ");
display_ascii(420, 270, 0x0000, 0xffff);
while (RUN_STOP_FLAG) {
//重新读取按键输入
K_DATA = 0xff;
READ_KEY();
//修改横轴分度值
if (K_DATA == 15) {
delay_ms(50);
if (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) != 0x03) {
if (dso_offset_stop < dso_offset_stop_max)
dso_offset_stop += 5;
}
}
else if (K_DATA == 14) {
delay_ms(50);
if (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) != 0x03) {
if (dso_offset_stop > 0)
dso_offset_stop -= 5;
}
}
K_DATA = 0xff;
if (dso_offset_stop_old != dso_offset_stop) {
//重新插值
if (freq_div_data == 0) {
Wave_Interpolation(dso_offset_stop + 99);
} else {
for (i = 0; i < 400; i++) {
DISP_DATA_CH1[i] = ADC_DATA_CH1[i + dso_offset_stop]
+ 52;
DISP_DATA_CH2[i] = ADC_DATA_CH2[i + dso_offset_stop]
+ 52;
}
}
display_area();
switch (SCOPE_CHANNEL_FLAG) {
case 0:
DISP_WAVE_CH1();
break;
case 1:
DISP_WAVE_CH2();
break;
case 2:
DISP_WAVE_DUAL();
break;
case 3:
DISP_XY();
break;
}
dso_offset_stop_old = dso_offset_stop;
}
}
sprintf((char *) lcd_buffer, "RUN ");
display_ascii(420, 250, 0x07e0, 0xffff);
}
//否则继续显示波形
else{
switch (SCOPE_CHANNEL_FLAG) {
case 0:
DISP_WAVE_CH1();
break;
case 1:
DISP_WAVE_CH2();
break;
case 2:
DISP_WAVE_DUAL();
break;
case 3:
DISP_XY();
break;
}
}
for (i = 0; i < 8192; i++) {
ADC_DATA_CH1[i] = 127;
ADC_DATA_CH2[i] = 127;
}
}
至此所以示波器的功能函数均说明完成。
但是相较与第一版,在初始化的时候需要
- 初始化
FPGA
模块 - 初始化缓存数据
在sysinit
中添加相应语句即可
//初始化FPGA模块
IOWR_ALTERA_AVALON_PIO_DATA(MEM_LEN_BASE, 511);
IOWR_ALTERA_AVALON_PIO_DATA(TRIG_AN_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(TRIG_EDGE_SEL_BASE, 1);
IOWR_ALTERA_AVALON_PIO_DATA(TRIG_SEL_BASE, 1);
IOWR_ALTERA_AVALON_PIO_DATA(MUL_EN_BASE, 1);
IOWR_ALTERA_AVALON_PIO_DATA(TRIG_DATA_BASE, TRIG_Y_DATA);
IOWR_ALTERA_AVALON_PIO_DATA(FREQ_DIV_DATA_BASE, freq_div_data);
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RESET_BASE, 1);
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RESET_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(MEM_RESET_BASE, 1);
IOWR_ALTERA_AVALON_PIO_DATA(FREQ_MEAS_START_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(FREQ_MEAS_START_BASE, 1);
//初始化缓存数据
Init_Scope();