本文记录了在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 则为下降触发
//实现触发的思路均为当从不同的方向超过触发线