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

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

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

version 0.3 初步功能实现

关于示波器的两种Trigger Mode的

以下内容参考博客StrongPiLab

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

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

如下图所示:

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

触发模式有以下几种

  • Auto Trigger

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

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

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

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

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

  • Normal Trigger

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

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

    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 初步功能实现-LMLPHP

  • 内存读取测试:

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

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

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

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

  • Normal模式测试

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

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

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

生成的RTL图如下:

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

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 初步功能实现-LMLPHP

该模块的设计思路如下:

  • 设计的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 初步功能实现-LMLPHP

其中CUT_OFFFREQ_COUNTER_MODULE的连接说明如下:

CUT_OFF模块的连接

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

FREQ_COUNTER_MODULE模块的连接

  • 输入接标准100MHZ时钟周期、CH1CH2的触发脉冲、来自NIOS II的控制信号
  • 输出包括两个频率计算结果的输出和用来表示计算结束的标志位,接入到NIOS II系统中

CLK_MODULE

这个模块是选择ADC采样模块的的时钟频率和存储模块的时钟频率的。

对于存储模块来说,选择信号对应的输出频率为

00000125MHz
0000150MHz
0001025MHz
0001112.5MHz
001005MHz
001012.5MHz
100105Hz

对于ADC模块来说,输出频率为

00000125MHz
0000150MHz
0001025MHz
0001112.5MHz
001005MHz
001012.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 初步功能实现-LMLPHP

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

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

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

该模块涉及以下信号

  • 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 初步功能实现-LMLPHP

示波器系统设计

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

时钟中断

时钟中断在此类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();
05-04 04:56