zoukankan      html  css  js  c++  java
  • 【小梅哥FPGA进阶教程】第十三章 四通道数字电压表

    十三、四通道数字电压表

    本文由山东大学研友袁卓贡献,特此感谢

    实验目的

    设计一个四通道的数字电压表

    实验平台

    芯航线FPGA核心板、AD/DA模块

    图片1

    图片2

    实验现象

    实现一个四通道的数字电压表,其中可以用按键切换测量通道并在4位数码管上显示对应的测量值。

    实验原理及步骤

    数字电压表的工作原理即为,被测信号接入ADC模块的输入引脚,FPGA控制ADC的转换进程以及原始数据的采集,并将其采集到的二进制数据转换为数码管的显示数据。其中按键可以选择ADC模块不同的通道。其系统工作原理图如图1所示。

    图片3

    图1 系统工作原理图

    由工作原理图可以暂时将本系统划分为ADC控制模块、码制转换模块、按键数据模块以及数码管驱动模块组成。

    ADC控制模块之TLV1544

    本系统采用的是TLV1544芯片,其为10位的ADC。因此其理论测量精度为图片3-1,且当其输出为’dx时,实际电压为图片3-2V。本模块的设计在基础课程已经讲解,此处不再详述。其模块接口示意图如下所示。

    图片4

    图2 ADC模块接口示意图

    数据预处理模块

    ADC模块输出还是一个十位二进制数,因此需要先将数据转换成实际电压值。

    图片5

    上式中3.42为满量程电压,data为输出的二进制数,1024为ADC总的阶梯数。之所以是3.42,是本模块基准电源TL341输出电压。

    这样得出的数据太小,因此先将其放大图片6倍。这里也可以放大其他倍数。

    图片7

    经过上式的转换,图片8还是一个小数,这里再放大1000倍以消除小数。即实际显示的数据为实际电压的1000倍。这样就完成了二进制数到实际电压的转换。

    图片9

    由于在上面TLV1544驱动设计中,数据更新速度为4000ns/次。这样已足够用做电压表显示,但是此处为了使数据稳定,加入均值滤波程序。本模块接口示意图如图3所示,其接口功能列表如表1所示。

    图片10

    图3 数据预处理模块接口示意图

    图片11

    表1 数据预处理模块接口功能描述

    先将原始数据进行累加1024次。

        reg [19:0]Hex_SUM;
        reg [9:0]Hex;
        
        reg [9:0]cnt;
        always@(posedge Clk or negedge Rst_n)
            if(!Rst_n)
                cnt <= 10'b0;
            else if(ADC_flag)
               cnt <= cnt + 1'b1;
            else 
                cnt <= cnt;    
    
        always@(posedge Clk or negedge Rst_n)
            if(!Rst_n)
                Hex_SUM <= 20'd0;
            else if(cnt == 1023 && ADC_flag)
               Hex_SUM <= 20'd0;
           else if(ADC_flag)
               Hex_SUM <= Hex_SUM + Hex_data;
           else
               Hex_SUM <= Hex_SUM;

    将累加后的数据除以1024,也就是右移10位,这里数据总位数为20直接取其高10位即可。

        always@(posedge Clk or negedge Rst_n)
            if(!Rst_n)
                Hex <= 10'b0;
            else if(cnt == 1023 && ADC_flag)
               Hex <= Hex_SUM[19:10];
            else
               Hex <= Hex;

    利用上面推导的公式即可输出最后的数据。

       assign Voltage = (219264 * Hex) >> 16;  

    码制转换模块

    由于ADC输出的为10位二进制数而数码管需要的是BCD码的格式,因此需要将其进行码制的转换。

    首先,先了解二进制与BCD码的位数对应关系。比如一个8位二进制码,可以表示的最大十进制数为255,转换成BCD码为 0010_0101_0101,共需12位,其中每4位组成一个BCD单元。n位二进制码转换成D个BCD码的n~D对应关系表见表2。

    图片15

    表2  n~D对应关系

    此处采用加3移位法进行转换,附件中列举了另一种方式来进行转换。以8位二进制转换为3位BCD码为例,转换步骤是:将待转换的二进制码从最高位开始左移BCD的寄存器(从高位到低位排列),每移一次,检查每一位BCD码是否大于4,是则加上3,否则不变。左移8次后,即完成了转换。需要注意的是第八次移位后不需要检查是否大于5。

    注意:为什么检查每一个BCD码是否大于4,因为如果大于 4(比如 5、6),下一步左移就要溢出了,所以加 3,等于左移后的加 6,起到十进制调节的作用。

    表3给出了一个二进制码11101011转换成8421BCD码的时序。

    图片16

    表3  B/BCD时序

    首先进行判断一个BCD码是否大于4,是则进行加3处理,否则输出原来数值。

    module bcd_single_modify(bcd_in,bcd_out);
    
        input [3:0] bcd_in;
        output [3:0] bcd_out;
    
        reg [3:0] bcd_out;
        
        always @ (bcd_in)
        begin
            if (bcd_in > 4)
                bcd_out = bcd_in + 2'd3;
            else
                bcd_out = bcd_in;
        end
    
    endmodule

    由以上原理可看出,这里需要定义一个10+12位的寄存器。同时从表3克拿出有几位二进制数就需移位几次。这里为了增加适用范围,将输入定位20位的二进制数,因此输出为7*4位BCD数。这样定义一个48位的移位寄存器。低20位为二进制数,高28位为BCD码。

    每移位一次就需验证高28位BCD码是否大于4因此,编写以下代码。

    module bcd_modify(data_in, data_out);
    
        input [47:0] data_in;
        output [47:0] data_out;
    
        bcd_single_modify bcd6(.bcd_in(data_in[47:44]), .bcd_out(data_out[47:44]));
        bcd_single_modify bcd5(.bcd_in(data_in[43:40]), .bcd_out(data_out[43:40]));
        bcd_single_modify bcd4(.bcd_in(data_in[39:36]), .bcd_out(data_out[39:36]));
        bcd_single_modify bcd3(.bcd_in(data_in[35:32]), .bcd_out(data_out[35:32]));
        bcd_single_modify bcd2(.bcd_in(data_in[31:28]), .bcd_out(data_out[31:28]));
        bcd_single_modify bcd1(.bcd_in(data_in[27:24]), .bcd_out(data_out[27:24]));
        bcd_single_modify bcd0(.bcd_in(data_in[23:20]), .bcd_out(data_out[23:20]));
        assign data_out[19:0] = data_in[19:0];
    
    endmodule

    现在编写顶层文件,其端口示意图及功能描述如下所示/

    图片19

    图4 码制转换模块接口示意图

    图片20

    表4 码制转换模块接口功能描述

        reg [47:0] shift_reg;   
        wire [47:0] shift_reg_out;
        wire [47:0]tmp;
        reg [47:0]bcd_tmp;
        
        reg [4:0] cnt = 5'b0;
        
        assign tmp = {28'b0,Bin};

    向左移位20次。

        always@(posedge Clk or negedge Rst_n)
        begin
            if(!Rst_n)
                begin
                    Done_Sig <= 1'b0;
                    cnt <= 5'd0;
                    shift_reg <= 48'd0;         
                end
            else
            begin
                case(cnt)
                    0:begin
                            Done_Sig <= 1'b0;
                            if(Do_Translate)
                                begin
                                    cnt <= cnt + 1'b1;
                                    shift_reg <= tmp<<1;
                                end
                            else
                                cnt <= 1'b0;
                        end
                        
                    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18:
                        begin
                            shift_reg <= (shift_reg_out<<1);
                            cnt <= cnt + 1'b1;
                        end
                    
                    19:
                        begin
                            bcd_tmp <= shift_reg_out<<1;
                            Done_Sig <= 1;
                            cnt <= 5'b0;
                        end
                    default :cnt <= 5'b0;
                endcase 
            end
        end

    校验以及输出最终输出数据。

        assign Bcd = bcd_tmp[47:20];
        
        bcd_modify bcd_modify
        (
            .data_in(shift_reg),
            .data_out(shift_reg_out)
        );

    按键输入模块

    本部分在基础课程中也有介绍,此处只给出其端口示意图。

    图片24

    图5 按键输入模块接口示意图

    通道选择模块

    通过按键进行ADC四通道的选择,本模块接口示意图以及功能描述如下所示。

    图片25

    图6 通道选择模块接口示意图

    图片26

    表5 通道选择模块接口功能描述

    内部除了例化还需产生通道选择信号,这里用的模块有四个通道但是为何按键一来就加’d2,是因为通道选择实际信号需为0000、0010、0100、1000,这样我们就需要加’d2。

        wire key_state;
        wire key_flag;
        
        always@(posedge Clk or negedge Rst_n)
        if(!Rst_n)
            ADC_CHSEL <= 0;
        else if(key_flag && !key_state)begin
            if(ADC_CHSEL == 4'b0110)
                ADC_CHSEL <= 4'd0;
            else
                ADC_CHSEL <= ADC_CHSEL + 2'd2;
        end
        else
            ADC_CHSEL <= ADC_CHSEL;

    数码管驱动模块

    本部分在基础课程中也有介绍,此处只给出其端口示意图。

    图片28

    图7 数码管模块接口示意图

    顶层设计

    此处只需例化各个模块即可,顶层模块接口示意图如下所示。

    图片29

    图8 顶层模块接口示意图

    综合后的RTL视图如图9所示。

    图片30

    图9 RTL Viewer视图

    分配好引脚下载后可以看到改变输入电压,数码管上均有正常的显示,且切换通道时数据可以随之更新。

    图片31

    至此一个四通道数字电压表设计完毕。

    附:基于查找表的数据电压换算

    前面指出了一种数据处理及码制的方式,这里再列举利用查找表的实现方式。

    这里因为是输入的10位二进制数,也就是说每一位变化对应的变化量为即为精度,这样就可以得出以下待转换数据与实际电压的对照表。这样当3.296v

    图片32-1图片32-2

    这样就可以建立一个查找表,来分别计算其对应位的电压值的BCD码,然后相加。

         reg [15:0] data1;
         reg [15:0] data2;
         reg [15:0] data3;
        case(ADC_DATA[3:0])
        4'b0000:data1[15:0] = 16'b0000_0000_0000_0000;
        4'b0001:data1[15:0] = 16'b0000_0000_0000_0011; //.003
        4'b0010:data1[15:0] = 16'b0000_0000_0000_0110; //.006
        4'b0011:data1[15:0] = 16'b0000_0000_0001_0000; //.010
        4'b0100:data1[15:0] = 16'b0000_0000_0001_0011; //.013
        4'b0101:data1[15:0] = 16'b0000_0000_0001_0110; //.016
        4'b0110:data1[15:0] = 16'b0000_0000_0001_1001; //.019
        4'b0111:data1[15:0] = 16'b0000_0000_0010_0011; //.023
        4'b1000:data1[15:0] = 16'b0000_0000_0010_0110; //.026
        4'b1001:data1[15:0] = 16'b0000_0000_0010_1001; //.029
        4'b1010:data1[15:0] = 16'b0000_0000_0011_0010; //.032
        4'b1011:data1[15:0] = 16'b0000_0010_0011_0101; //.035
        4'b1100:data1[15:0] = 16'b0000_0010_0011_1001; //.039
        4'b1101:data1[15:0] = 16'b0000_0010_0100_0010; //.042
        4'b1110:data1[15:0] = 16'b0000_0010_0100_0101; //.045
        4'b1111:data1[15:0] = 16'b0000_0010_0100_1000; //.048
        endcase  
        case(ADC_DATA[7:4])  
        4'b0000:data2[15:0] = 16'b0000_0000_0000_0000; 
        4'b0001:data2[15:0] = 16'b0000_0000_0101_0010; //.052
        4'b0010:data2[15:0] = 16'b0000_0001_0000_0011; //.103
        4'b0011:data2[15:0] = 16'b0000_0001_0101_0101; //.155
        4'b0100:data2[15:0] = 16'b0000_0010_0000_0110; //.206
        4'b0101:data2[15:0] = 16'b0000_0010_0110_1000; //.258
        4'b0110:data2[15:0] = 16'b0000_0011_0000_1001; //.309
        4'b0111:data2[15:0] = 16'b0000_0011_1000_0001; //.361
        4'b1000:data2[15:0] = 16'b0000_0100_0001_0011; //.413
        4'b1001:data2[15:0] = 16'b0000_0100_0110_0100; //.464
        4'b1010:data2[15:0] = 16'b0000_0101_0001_0110; //.516
        4'b1011:data2[15:0] = 16'b0000_0101_0011_0111; //.567
        4'b1100:data2[15:0] = 16'b0000_0110_0001_1000; //.618
        4'b1101:data2[15:0] = 16'b0000_0110_0111_0000; //.670
        4'b1110:data2[15:0] = 16'b0000_0111_0010_0001; //.722
        4'b1111:data2[15:0] = 16'b0000_0111_0111_0011; //.773
        endcase        
        case(ADC_DATA[9:8])  
        2'b00:  data3[15:0] = 16'b0000_0000_0000_0000; 
        2'b01:  data3[15:0] = 16'b0000_1000_0010_0101; //.825
        2'b10:  data3[15:0] = 16'b0001_0110_0101_0000; //1.650
        2'b11:  data3[15:0] = 16'b0010_0100_0101_0111; //2.457
        endcase

    如果0~3位相加大于9,则加6调整为BCD码,并产生进位信号。再进行4~7位相加加上进位信号判断,再判断8-9位。

    reg[3:0] c1; //低4位BCD进位信号
        reg[3:0] c2; //
        reg[3:0] c3;
    if(data1[3:0]+data2[3:0]+data3[3:0] < 5'b01010) begin   
        disp_data[3:0] = data1[3:0]+data2[3:0]+data3[3:0];
        c1 = 0000;
    end
    else  begin
        disp_data[3:0] = data1[3:0]+data2[3:0]+data3[3:0]-4'b1010;
        c1 = 0001;
    end 
    
    if(c1+data1[7:4]+data2[7:4]+data3[7:4] < 5'b01010) begin
        disp_data[7:4] = c1+data1[7:4]+data2[7:4]+data3[7:4];
        c2 = 0000;       
    end
    else begin
        disp_data[7:4] = c1+data1[7:4]+data2[7:4]+data3[7:4]-4'b1010;
        c2 = 0001;
    end
    
    if(c2+data1[11:8]+data2[11:8]+data3[11:8] < 5'b01010) begin
        disp_data[11:8] = c2+data1[11:8]+data2[11:8]+data3[11:8];
        c3 = 0000;          
    end
    else begin
        disp_data[11:8] = c2+data1[11:8]+data2[11:8]+data3[11:8]-4'b1010;
        c3 = 0001;
    end
    
    if(c3+data1[15:12]+data2[15:12]+data3[15:12] < 5'b01010)    begin
        disp_data[15:12]=c3+data1[15:12]+data2[15:12]+data3[15:12] ;
    end  
    else begin
        disp_data[15:0]=1'bz;
    end 

    这样再将以上两个部分放置到一个always块中即可。同样可以看到实际效果。将输入数据显示格式修改为十进制后,输入512时输出0001_0110_0101_0000。此时实际电压为512*3.296/1024=1.648,显示为1.650。输入256时实际电压0.824,显示为0.825。

    图片35

    图片36

    如有更多问题,欢迎加入芯航线 FPGA 技术支持群交流学习:472607506

    小梅哥

    芯航线电子工作室

    关于学习资料,小梅哥系列所有能够开放的资料和更新(包括视频教程,程序代码,教程文档,工具软件,开发板资料)都会发布在我的云分享。(记得订阅)链接:http://yun.baidu.com/share/home?uk=402885837&view=share#category/type=0

    赠送芯航线AC6102型开发板配套资料预览版下载链接:链接:http://pan.baidu.com/s/1slW2Ojj 密码:9fn3

    赠送SOPC公开课链接和FPGA进阶视频教程。链接:http://pan.baidu.com/s/1bEzaFW 密码:rsyh

  • 相关阅读:
    linux系统中将一列数据转换为若干列数据(列的顺序不变)
    linux系统中将矩形数据转换为一行、一列的形式
    linux系统中实现文本转置。
    linux shell 如何将多列数据变为一行数据
    linux系统中如何将一行数据变为一列
    bash: unlzma: command not found...
    linux系统中实现对行的批量替换
    linux系统中对指定行的字符串进行替换
    linux系统中对指定列的数据中的字符串进行替换
    linux系统中如何将每行特定数目字符后的字符替换为指定字符
  • 原文地址:https://www.cnblogs.com/xiaomeige/p/6437530.html
Copyright © 2011-2022 走看看