基于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;
            //触发结束或者自动触发TOif(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_OFFFREQ_COUNTER_MODULE的连接说明如下:

CUT_OFF模块的连接

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

FREQ_COUNTER_MODULE模块的连接

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

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

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

SEL输出频率
00000125MHz
0000150MHz
0001025MHz
0001112.5MHz
001005MHz
001012.5MHz
100105Hz

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

SEL输出频率
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;
    }
}
信号调理模块控制

这里写图片描述

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+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();

转载于:https://www.cnblogs.com/he11o-liu/p/7503238.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值