FPGA学习——简易电压表的设计和验证

1 理论学习

   模拟信号与数字信号的转换过程一般分为四个步骤:采样、保持、量化、编码。前两个步骤在采样-保持电路中完成,后两部在ADC芯片中完成。
   常用的ADC可分为积分型、逐次逼近型、并行比较型等等
   积分型ADC工作原理是将输入电压转换成时间和频率,然后由定时器计数器获得数字值。其优点是使用简单电路就能获得高分辨率。缺点是由于转换精度依赖于积分时间,因此转换速率极低。双积分是一种常用的AD转换技术,具有精度高,抗干扰能力强等优点。但高精度的双积分AD芯片,价格昂贵,设计成本较高。
   逐次逼近型ADC由一个比较器和DA转换器通过逐次比较逻辑构成,从MSB开始,顺序地对每一位输入电压与内置DA转换器输出进行比较,经n次比较而输出数字值。其电路规模属于中等,优点是速度较高、功耗低,在低分辨率(小于12位)时价格便宜,但高精度(大于12位)价格昂贵。
    ADC的主要技术指标包括:分辨率、转换速率、量化误差、满刻度误差、线性度。分辨率指输出数字量变化一个最低有效位(LSB)所需的输入模拟电压的变化量。转换速率是指完成一次从模拟转换到数字的AD转换所需要的时间的倒数。积分型AD的转换时间是毫秒级属于低速AD,逐次比较型AD是微秒级属中速AD,全并行/串 并行型AD可达到纳秒级。采样时间则是另一个概念,是指两次转换的时间间隔。为了保证转换的正确完成,采样速率必须小于或等于转换速率。
   量化误差是由于AD的有限分辨率引起的误差,即有限分辨率AD的阶梯转移特性曲线与无限分辨率AD(理想AD)的转移特性曲线(直线)之间的最大偏差。通常是1个或半个最小数字量的模拟变化量,表示为1LSB、1/2 LSB。
   满刻度误差是满刻度输出时对应的输入信号与理想输入信号值之差。
   线性度指实际转换器的转移函数与理想直线的直达偏移

2 实验目标

外部挂载的高速AD DA办卡的AD部分将输入其中的模拟信号转换成数字量,将数字量传入FPGA,FPGA将传入的数字量通过计数转化为电压数值,通过ila在线抓取转化后的电压值,实现模拟信号的电压测量。

3 程序设计

1 模块框图

FPGA学习——简易电压表的设计和验证_第1张图片
将待测量的模拟信号接入外部挂载的高速AD / DA 板卡的模拟信号输入端,板卡会将输入的模拟信号进行采样、量化、编码后,将模拟电压转换成数字值传给FPGA;内部dig_volt 模块接收到传入的电压数字值,经过运算,转换为可用于显示的电压值输出。
由dig volt模块框图可知,模块有3路输入信号和1路输出信号。输入信号有时钟、复位和外载板卡的时钟信号,频率为12.5Mhz,由系统时钟信号4分频得到,ADC芯片在此时钟同步下进行模拟信号的采样、量化和编码。

2 波形图绘制

本实验使用的ADC芯片位宽为8位,板卡模拟电压输入范围为-5到+5,即电压表测量范围,最大值和最小值降压为10v,分辨率为10/2^8
当ADC芯片采集后的电压数值ad_data 位于0-127范围内,表示测量电压位于-5v到0v范围内,换算为电压值:
在这里插入图片描述

当ADC芯片采集后的电压数据ad_data 位于128-255范围内,表示测量电压位于0-5v范围内,换算成电压值
在这里插入图片描述
在电压表上电后未接入测量电压时,取ADC芯片采集的最初的若干测量值,取平均,作为测量中值 data median,与实际测量值0v对应。
使用定义值得测量方法时,当ADC芯片采集后的电压数值 ad data 位于0-data median范围内,表示测量电压位于-5v到0v范围内,分辨率为10/((data median + 1)* 2),换算成电压值
在这里插入图片描述
当ADC芯片采集后的电压数值ad data 位于data median - 255 范围内,表示测量电压位于0-5v范围内,分辨率为 在这里插入图片描述
换算成电压值:
在这里插入图片描述
了解了具体实现方法,我们开始绘制框图
FPGA学习——简易电压表的设计和验证_第2张图片
FPGA学习——简易电压表的设计和验证_第3张图片
对于模块的输入信号不再说明,输出至外载板块的时钟信号 ad_clk ,频率为12.5Mhz,使用系统时钟4分频得来,所以声明了分频计数器cnt sys clk,初值为0,在系统时钟同步下,再0和1 之间循环计数;声明时钟信号clk sample,在 计数器cnt sys clk 计数值为1时,对自身取反,就得到了时钟频率为12.5Mhz的分频时钟信号 clk_sample,也作为本模块工作时钟信号;因为外载板卡与本模块均使用时钟上升沿对数据采样,为保证模块内工作时钟上升沿能够采集到板块传入的稳定数据,我们对clk sample 时钟信号取反作为输入板卡的时钟信号adc clk,adc clk 的上升沿刚好采集到数据的稳定状态
声明中值信号使能信号median en,方便计算中值,当median en 信号为低电平时,进行中值的计算;当median en 信号为高电平时,对ADC测量值进行累加求平均的计算。
对中值的计算我们也是用累加求平均的方法,在无测量电压输入电压表时,对前1024个数据进行累加求平均,所以声明计数器 cnt median 对累加值个数进行计数,计算范围0-1023,旨在median en 为低电平时进行计数,median en为高电平时,保持计数最大值;同时,计数最大值作为条件,拉高 median en 使能信号,1024个测量值总和保存在变量data_sum_m 中,当cnt median 计数到最大值,将平均值赋给变量 data median
FPGA学习——简易电压表的设计和验证_第4张图片
FPGA学习——简易电压表的设计和验证_第5张图片

3 代码编写

///
// Company: 
//
// File: dig_volt.v
// File history:
//      : : 
//      : : 
//      : : 
//
// Description: 
//
// 
//
// Targeted device:   
// Author: 
//
/// 

//`timescale  / 

module dig_volt
(
    input wire clk,
    input wire rstn,
    input wire [7:0] ad_data,           // AD 输入数据

    output wire ad_clk                  // AD 驱动时钟,最大支持20Mhz时钟                  
); 

// 数据累加次数
parameter CNT_DATA_MAX = 11'd1024;

// wire define
wire    [27:0] data_p;      // 根据中值计算出的正向电压AD分辨率
wire    [27:0] data_n;      // 根据中值计算出的负向电压AD分辨率
wire    sign;               // 正负符号位
wire    [15:0] volt;        // 数据转换后的电压值

// reg define
reg     median_en;          // 中值数据使能
reg     [10:0] cnt_median;  // 中值数据累加计数器
reg     [18:0] data_sum_m;  //  1024次中值数据累加总和
reg     [7:0]  data_median; // 中值数据
reg     [1:0]  cnt_sys_clk; // 时钟分频计数器
reg     clk_sample;         // 采样数据时钟
reg     [27:0] volt_reg;    // 电压值寄存


// main code
// 数据ad data 是在ad sys clk 的上升沿更新
// 所以在 ad sys clk 的下降沿采集数据是数据稳定的时刻
// FPGA 内部一般使用上升沿锁存数据,所以时钟取反
// 这样 ad sys clk 的下降沿相当于 sample sys clk 的上升沿
assign ad_clk = ~clk_sample;

// sign 正负符号位
assign sign = (ad_data < data_median) ? 1'b1: 1'b0;

// 时钟分频(4分频,时钟频率为12.5Mhz),产生采样AD数据时钟
always @(posedge clk or negedge rstn) begin
    if(rstn == 1'b0) begin
        cnt_sys_clk <= 2'd0;
        clk_sample <= 1'b1;
    end
    else begin
        cnt_sys_clk <= cnt_sys_clk + 2'd1;
        if(cnt_sys_clk == 2'd1) begin
            cnt_sys_clk <= 2'd0;
            clk_sample <= ~clk_sample;
        end
    
    end
end

//中值信号使能
always @(posedge clk_sample or negedge rstn) begin
    if(~rstn) begin
        median_en <= 1'b0;
    end
    else if(cnt_median == CNT_DATA_MAX) begin
        median_en <= 1'b1;
    end
    else begin
        median_en <= median_en;
    end
end

// cnt_median: 中值数据累加计数器
always @(posedge clk_sample or negedge rstn) begin
    if(~rstn) begin
        cnt_median <= 11'd0;
    end
    else if(median_en == 1'b0) begin
        cnt_median <= cnt_median + 1'b1;
    end
    else
        cnt_median <= cnt_median;
end

// data_sum_m : 1024次中值数据累加总和
always @(posedge clk_sample or negedge rstn) begin
    if(~rstn) begin
        data_sum_m <= 19'd0;
    end
    else if(cnt_median == CNT_DATA_MAX + 1) begin
        data_sum_m <= 19'd0;
    end
    else begin
        data_sum_m <= data_sum_m + ad_data;
    end
end

//  data_median: 中值数据
always @(posedge clk_sample or negedge rstn) begin
    if(~rstn) begin
        data_median <= 8'd0;
    end
    else if (cnt_median == CNT_DATA_MAX) begin
        data_median <= data_sum_m / CNT_DATA_MAX;
    end
    else begin
        data_median <= data_median;
    end
end

// data_p: 根据中值计算出的正向电压AD分辨率
// data_n: 根据中值计算出的负向电压AD分辨率
assign data_p = (median_en == 1'b1)? 8192_0000 / ((255- data_median) * 2) : 0;
assign data_n = (median_en == 1'b1)? 8192_0000 / ((data_median + 1) * 2) : 0;

// volt_reg 处理后的稳定数据
always @(posedge clk_sample or negedge rstn) begin
    if(~rstn) begin
        volt_reg <= 1'b0;
    end
    else if (median_en) begin
        if((ad_data > (data_median - 3)) && (ad_data < (data_median + 3))) begin
            volt_reg <= 'd0;
        end
        else if (ad_data < data_median) begin
            volt_reg <= (data_n *(data_median - ad_data)) >> 13;
        end        
        else if (ad_data > data_median) begin
            volt_reg <= (data_p * (ad_data - data_median)) >> 13;    
        end
    end
    else begin
        volt_reg <= 'd0;
    end
end

assign volt = volt_reg;

endmodule


tb 文件

//
// Created by Microsemi SmartDesign Tue Mar 14 09:28:33 2023
// Testbench Template
// This is a basic testbench that instantiates your design with basic 
// clock and reset pins connected.  If your design has special
// clock/reset or testbench driver requirements then you should 
// copy this file and modify it. 
//

///
// Company: 
//
// File: dig_volt_tb.v
// File history:
//      : : 
//      : : 
//      : : 
//
// Description: 
//
// 
//
// Targeted device:   
// Author: 
//
/// 

`timescale 1ns/100ps

module dig_volt_tb();

wire ad_clk;

reg sys_clk;
reg  clk_sample;
reg sys_rst_n;
reg data_en;
reg [7:0] ad_data_reg;
reg [7:0] ad_data;


initial begin
    sys_clk =   1'b1;
    clk_sample  =   1'b1;
    sys_rst_n   =   1'b0;
    #200;
    sys_rst_n   =   1'b1;
    data_en     =   1'b0;
    #499990;
    data_en     =   1'b1;
end

always #10 sys_clk = ~sys_clk;
always #40 clk_sample = ~clk_sample;

always @(posedge clk_sample or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        ad_data_reg <= 8'd0;
    else if (data_en == 1'b1) begin
        ad_data_reg <= ad_data_reg + 1'b1;
    end
    else begin
        ad_data_reg <= 8'd0;
    end
end

always @(posedge clk_sample or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        ad_data <= 8'd0;
    else if (data_en == 1'b0) begin
        ad_data <= 8'd125;
    end
    else if (data_en == 1'b1) begin
        ad_data <= ad_data_reg;
    end
    else begin
        ad_data <= ad_data;
    end
end


dig_volt dig_volt_inst(
    .clk(sys_clk),
    .rstn(sys_rst_n),
    .ad_data(ad_data),

    .ad_clk(ad_clk)
); 

endmodule


波形仿真:
FPGA学习——简易电压表的设计和验证_第6张图片
FPGA学习——简易电压表的设计和验证_第7张图片

你可能感兴趣的:(FPGA学习,fpga开发,学习)