基于NIOS-II的示波器:PART3 初步功能实现
本文记录了在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_SAD选择信号
最后输入到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();
基于NIOS-II的示波器:PART3 初步功能实现的更多相关文章
- 基于NIOS II的双端口CAN通信回环测试
基于NIOS II的双端口CAN通信回环测试 小梅哥编写,未经授权,严禁用于任何商业用途 说明:本稿件为初稿,如果大家在使用的过程中有什么疑问或者补充,或者需要本文中所述工程源文件,欢迎以邮件形式发送 ...
- 【推荐图书】+ 基于Nios II的嵌入式SoPC系统设计与Verilog开发实例+C#入门经典等
[推荐图书]+ 基于Nios II的嵌入式SoPC系统设计与Verilog开发实例+C#入门经典等 3赞 发表于 2016/7/4 21:14:12 阅读(1921) 评论(3) 初次接触FPGA,到 ...
- FPGA回忆记事(一):基于Nios II的LED实验
实验一:基于Nios II的LED实验 一. 创建Quartus II工程 1.打开Quartus II环境.开始->程序->Altera->Quartus II 9.1. 2 ...
- [置顶]
基于FPGA的VGA简易显存设计&NIOS ii软核接入
项目简介 本项目基于Altera公司的Cyclone IV型芯片,利用NIOS II软核,2-port RAM与时序控制模块,实现64*48分辨率的显存(再大的显存板载资源m9k不够用) 实现效果如下 ...
- NIOS ii 流水灯
为了做项目的前期验证工作,实验室购买了某开发板,下面是基于该板子的实现过程.作为笔记记录,供入门者参考. 1:创建一个Quartus II的工程 next选择器件,然后finish.我的器件是cycl ...
- sof文件和NIOS II的软件(elf)合并为jic文件以使用Quartus Programmer烧写
将Altera FPGA的sof文件和NIOS II的elf固件合并为一个jic文件以使用Quartus Programmer烧写 我们在学习和调试NIOS II工程的时候,一般都是先使用Quar ...
- 给NIOS II CPU增加看门狗定时器并使用
给NIOS II CPU增加看门狗定时器并使用 配置看门狗定时器: 设置计时溢出时间为1秒 计数器位宽为32位 勾选No Start/Stop control bits 勾选Fixed perio ...
- 给NIOS II CPU添加一颗澎湃的心——sysclk的使用
给NIOS II CPU添加一颗澎湃的心——系统时钟的使用 本实验介绍如何在Qsys中添加一个定时器作为NIOS II的心跳定时器,并在NIOS II中软件编程使用该定时器. 将上一个实验watchd ...
- NIOS II开发备忘录
大概有一年没做NIOS II的开发了,回想上一次做NIOS II还是去年做毕业设计的时候.那时候做的是基于SOPC的频率特性测试仪,我大约花了2个月的时间,从无到有的学习了NIOS II开发.学习过N ...
随机推荐
- 24. leetcode 409. Longest Palindrome
409. Longest Palindrome Given a string which consists of lowercase or uppercase letters, find the le ...
- Git时光机穿梭之工作区和暂存区
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 先来看名词解释. 工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工 ...
- [转载]无旋treap:从单点到区间(例题 BZOJ1500&NOI2005 维护数列 )
转自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182631.html 1500: [NOI2005]维修数列 Time Limit: 10 Sec Mem ...
- 【Spring】浅谈ContextLoaderListener及其上下文与DispatcherServlet的区别
一般在使用SpingMVC开发的项目中,一般都会在web.xml文件中配置ContextLoaderListener监听器,如下: <listener> <listener-clas ...
- [STM32F429-DISCO-HAL]4.Uart 的使用
今天来学习一下最常用的外设之一USART. 首先是硬件的连接,我们需要至少三根线,GND,TX,RX.参阅datasheet.默认的USART1_TX和USART1_RX的引脚如下图 关于HAL dr ...
- Python requests 安装与开发
Requests 是用Python语言编写HTTP客户端库,跟urllib.urllib2类似,基于 urllib,但比 urllib 更加方便,可以节约我们大量的工作,完全满足 HTTP 测试需求, ...
- 马的遍历 洛谷 p1443
题目描述 有一个n*m的棋盘(1<n,m<=400),在某个点上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步 输入输出格式 输入格式: 一行四个数据,棋盘的大小和马的坐标 输出 ...
- 使用sed命令向文件中追加可变字符串
1.如何向文件追加可变字符串,有如下两种方法 sed -i '1a '$s'' filename sed -i "1a $s" filename 注意: 以上命令是假定向文件fil ...
- python学习之核心数据类型
python核心数据类型 对象类型 例子 数字 1234,-345 字符串 'spam' 列表 [1,3,'ds'] 元组 (1,'spam',6) 字典 {'name':'lili','age':1 ...
- JS网页特效操作流程——下拉菜单列表与登录注册弹窗效果
下拉菜单列表 <style> *{ margin: 0px; padding: 0px; } .men ...