基于NIOS-II的示波器:PART3 初步功能实现

时间:2022-03-19 03:18:55

本文记录了在NIOS II上实现示波器的第三部分。

本文主要包括:硬件部分的BRAM记录波形,计算频率的模块,以及软件部分这两个模块的驱动。

本文所有的硬件以及工程参考来自魏坤示波仪,重新实现驱动并重构工程。

version 0.3 初步功能实现

关于示波器的两种Trigger Mode的

以下内容参考博客StrongPiLab

设置好的Trigger condition才会使得波形固定在屏幕上,不会左右乱飘。

触发就是,当波形穿过Trigger level的时候,就会产生触发信号,且该点为触发点。

如下图所示:

基于NIOS-II的示波器:PART3 初步功能实现

触发模式有以下几种

  • Auto Trigger

    若ADC输入的数据没有满足Trigger condition,则示波器不会发出Trigger信号。

    Auto Trigger就是在没有满足Trigger condition的时候,就内部自动发出Trigger信号画波形。

    基于NIOS-II的示波器:PART3 初步功能实现

    第一个直流波形因为毫无震幅变化,所以Trigger永远无法满足,因此Auto trigger会自行发出Trigger讯号画波形,红色的框框就是每次画出的波形内容。这也就是为何一个没有讯号输入的示波器,你还是能够看到0V(ground)能不断更新画面的原因。

    第二个含有脉冲的方波因为有部分波型满足上缘触发,因此前两格画面是Trigger条件满足下而画出来的,后面三格画面则是Auto trigger自己画出来的,以使用者观点来说,他会看到一个脉冲波突然出现,之后随即消失。

  • Normal Trigger

    Auto trigger平常很好用,但在Debug的时候可能就不见得这么好用。因为Debug时所面对的波形通常是在不确定时间出现的不正常波形,因此若採用Auto trigger的话,很容易错失观察波形的机会,这时Normal trigger就派上用场了。

    基于NIOS-II的示波器:PART3 初步功能实现

    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计时超时之前成功触发并保存触发地址。

    基于NIOS-II的示波器:PART3 初步功能实现

  • 内存读取测试:

    当触发成功后Counter2开始计数,并在计数到指定存储深度后结束计数,将Mem_done置为1

    基于NIOS-II的示波器:PART3 初步功能实现

    Mem_done置为1之后,通过RD控制正确读取MEM_Addr中的数据

    基于NIOS-II的示波器:PART3 初步功能实现

  • Normal模式测试

    Counter1结束之后,Trig_en置为1。由于选择是Noramal模式触发,故Timeout计数器Counter3不工作。

    Trig_en置为1之后,Trig_pulse上升沿代表成功触发。

    基于NIOS-II的示波器:PART3 初步功能实现

生成的RTL图如下:

基于NIOS-II的示波器:PART3 初步功能实现

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数据结束。

基于NIOS-II的示波器:PART3 初步功能实现

该模块的设计思路如下:

  • 设计的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

上面三个模块的连接方式如下所示

基于NIOS-II的示波器:PART3 初步功能实现

其中CUT_OFFFREQ_COUNTER_MODULE的连接说明如下:

CUT_OFF模块的连接

  • CUT_OFF接入由RDMEM_ADDR控制的内存读取的输出
  • 对数据进行裁剪之后,输出给NIOS II系统

FREQ_COUNTER_MODULE模块的连接

  • 输入接标准100MHZ时钟周期、CH1CH2的触发脉冲、来自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;
}
}

信号调理模块控制

基于NIOS-II的示波器:PART3 初步功能实现

74HC595用于将SPI总线串行输入的内容,并行输出,用于控制信号调理模块。

信号调理模块原理图如下:

基于NIOS-II的示波器:PART3 初步功能实现

该模块涉及以下信号

  • 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转换

基于NIOS-II的示波器:PART3 初步功能实现

示波器系统设计

这里仍然采用前端中断处理事件,后端循环维护数据的单片机开发思路。上面已经详细描述过按键中断实现的内容。下面则是时钟中断。

时钟中断

时钟中断在此类FPGA+ARMFPGA+NIOS2类似的架构中非常重要。这里虽然是和硬件电路打交道,但是时序仍然是其中非常重要的一环。始终定时器中断作为串行处理器中的最稳定可靠的时序根据。

该示波器的时钟中断服务主要有以下两个功能

  • 通过744051AD5320对上文所述的信号调理模块进行控制
  • 对显示屏显示内容及亮度进行控制
/*
* 函数名: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_counterFPGA部分通讯获取频率信息
    /**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为示波器主功能函数,主要包括获取触发状态,根据模式获取波形数据,显示波形数据,计算VPPRMS,在STOP状态下控制横轴分度值。

    • display_area是由于之前写的波形数据的CLR均是直接换成背景色,而没有考虑是否那个地方应该显示轴线。所以这里重新画一下轴线。

下面就详细说明scope这个函数

Scope

主要功能有以下几点

  • 根据触发模式和触发状态进行处理
  • 利用RDADDRFPGA中的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();