FPGA_WebLab/public/doc/13/doc.md

11 KiB
Raw Blame History

进阶-3-频率计

3.1 章节导读

本实验将基于实验平台设计并实现一个简易频率计用于测量输入信号的频率值并通过数码管进行实时显示。实验核心是掌握ADC模块的使用方法被测信号频率的获取方法及其在数字系统中的处理流程。

3.2 理论学习

3.2.1 ADC模块

实验平台有一块8bit高速ADDA模块其中ADC模块使用AD9280芯片支持最高32MSPS的速率模拟电压输入范围为-5~+5VADC模块可以根据输入电压的大小将其转换为0~2552的8次方的数值。模块有一个clk管脚和8个data管脚data的输入速率和驱动时钟有关给clk管脚的驱动时钟越快采样率越高data的输入速率越高。

无法显示图片时显示的文字
图1.ADDA模块示意图

3.2.2 数码管模块

数码管模块在前面基础实验3已经介绍过这里不再赘述。

3.3 实战演练

3.3.1实验目标

能够驱动板载ADC模块对ADC模块的输入数据进行测试计算输入信号的频率值并在数码管模块中显示。

3.3.2硬件资源

实验所需的信号源来自我们的实验平台实验平台集成一个以FPGA为基础的dds信号发生器该dds信号发生器可以输出频率可调的方波正弦波三角波锯齿波等用户可以在web平台使用并且改变输出波形和频率。

无法显示图片时显示的文字
图2.ADDA实验示意图

用户接收到信号源之后需自行设计逻辑处理数据并显示。

3.3.3程序设计

pulse_gen.v

首先用户接收到的是8bit波形数据要直接利用波形数据计算频率不是很方便计算频率数据我们只需要计算其脉冲的个数即可所以我们设计一个模块通过设计一个脉冲阈值trig_level高于阈值的就计算为一次脉冲输出一个周期的高电平方便后续模块计数模块代码如下

module pulse_gen(
    input           rstn,      //系统复位,低电平有效
    
    input  [7:0]    trig_level,
    input           ad_clk,     //AD9280驱动时钟
    input  [7:0]    ad_data,    //AD输入数据
    
    output          ad_pulse    //输出的脉冲信号
);
//因为可能会有抖动,设置一个范围值避免反复触发
parameter THR_DATA = 3;

//reg define
reg          pulse;
reg          pulse_delay;

//*****************************************************
//**                    main code
//*****************************************************

assign ad_pulse = pulse & pulse_delay;

//根据触发电平将输入的AD采样值转换成高低电平
always @ (posedge ad_clk or negedge rstn)begin
    if(!rstn)
        pulse <= 1'b0;
    else begin
        if((trig_level >= THR_DATA) && (ad_data < trig_level - THR_DATA))
            pulse <= 1'b0;
        else if(ad_data > trig_level + THR_DATA)
            pulse <= 1'b1;
    end    
end

//延时一个时钟周期,用于消除抖动
always @ (posedge ad_clk or negedge rstn)begin
    if(!rstn)
        pulse_delay <= 1'b0;
    else
        pulse_delay <= pulse;
end

endmodule 

cymometer.v

下面根据pulse_gen信号生成的脉冲数据进行计数计算其频率。我们这里采用门控时钟法clk_fs(参考时钟)作为时间基准,测量 clk_fx(被测信号)的频率。

门控时钟法的原理很简单,也就是在一个固定时间窗内(即门控时间 GATE_TIME计数被测时钟 clk_fx 的上升沿次数,再结合参考时钟 clk_fs 的计数值,就可以算出频率:


\text{频率} = \frac{\text{被测脉冲数量}}{\text{门控时间(秒)}} = \frac{fx\_cnt}{fs\_cnt / \text{CLK\_FS}} = \frac{\text{CLK\_FS} \times fx\_cnt}{fs\_cnt}
步骤 描述
1 使用 clk_fx 作为计数时钟,控制一个门控时间 gate 信号
2 gate 为高电平时,fx_cnt_temp 开始统计 clk_fx 的脉冲个数
3 同时将 gate 同步到参考时钟 clk_fs,并计数 fs_cnt_temp,记录 gate 高电平持续期间 clk_fs 的个数
4 一旦 gate 下降沿到来(通过打拍检测),将计数值冻结到 fx_cntfs_cnt
5 最后用上述表达式计算频率输出。

代码设计如下:

module cymometer
   #(parameter    CLK_FS = 26'd50_000_000) // 基准时钟频率值
    (   //system clock
        input                 clk_fs ,     // 基准时钟信号
        input                 rstn  ,     // 复位信号

        //cymometer interface
        input                 clk_fx ,     // 被测时钟信号
        output   reg [19:0]   data_fx      // 被测时钟频率输出
);

//parameter define
localparam   MAX       =  30;              // 定义fs_cnt、fx_cnt的最大位宽
localparam   GATE_TIME = 16'd2_000;        // 门控时间设置

//reg define
reg                gate        ;           // 门控信号
reg                gate_fs     ;           // 同步到基准时钟的门控信号
reg                gate_fs_r   ;           // 用于同步gate信号的寄存器
reg                gate_fs_d0  ;           // 用于采集基准时钟下gate下降沿
reg                gate_fs_d1  ;           // 
reg                gate_fx_d0  ;           // 用于采集被测时钟下gate下降沿
reg                gate_fx_d1  ;           // 
reg    [   58:0]   data_fx_t    ;          // 
reg    [   15:0]   gate_cnt    ;           // 门控计数
reg    [MAX-1:0]   fs_cnt      ;           // 门控时间内基准时钟的计数值
reg    [MAX-1:0]   fs_cnt_temp ;           // fs_cnt 临时值
reg    [MAX-1:0]   fx_cnt      ;           // 门控时间内被测时钟的计数值
reg    [MAX-1:0]   fx_cnt_temp ;           // fx_cnt 临时值

//wire define
wire               neg_gate_fs;            // 基准时钟下门控信号下降沿
wire               neg_gate_fx;            // 被测时钟下门控信号下降沿

//*****************************************************
//**                    main code
//*****************************************************

//边沿检测,捕获信号下降沿
assign neg_gate_fs = gate_fs_d1 & (~gate_fs_d0);
assign neg_gate_fx = gate_fx_d1 & (~gate_fx_d0);

//门控信号计数器,使用被测时钟计数
always @(posedge clk_fx or negedge rstn) begin
    if(!rstn)
        gate_cnt <= 16'd0; 
    else if(gate_cnt == GATE_TIME + 5'd20)
        gate_cnt <= 16'd0;
    else
        gate_cnt <= gate_cnt + 1'b1;
end

//门控信号拉高时间为GATE_TIME个实测时钟周期
always @(posedge clk_fx or negedge rstn) begin
    if(!rstn)
        gate <= 1'b0;
    else if(gate_cnt < 4'd10)
        gate <= 1'b0;     
    else if(gate_cnt < GATE_TIME + 4'd10)
        gate <= 1'b1;
    else if(gate_cnt <= GATE_TIME + 5'd20)
        gate <= 1'b0;
    else 
        gate <= 1'b0;
end

//将门控信号同步到基准时钟下
always @(posedge clk_fs or negedge rstn) begin
    if(!rstn) begin
        gate_fs_r <= 1'b0;
        gate_fs   <= 1'b0;
    end
    else begin
        gate_fs_r <= gate;
        gate_fs   <= gate_fs_r;
    end
end

//打拍采门控信号的下降沿(被测时钟下)
always @(posedge clk_fx or negedge rstn) begin
    if(!rstn) begin
        gate_fx_d0 <= 1'b0;
        gate_fx_d1 <= 1'b0;
    end
    else begin
        gate_fx_d0 <= gate;
        gate_fx_d1 <= gate_fx_d0;
    end
end

//打拍采门控信号的下降沿(基准时钟下)
always @(posedge clk_fs or negedge rstn) begin
    if(!rstn) begin
        gate_fs_d0 <= 1'b0;
        gate_fs_d1 <= 1'b0;
    end
    else begin
        gate_fs_d0 <= gate_fs;
        gate_fs_d1 <= gate_fs_d0;
    end
end

 //门控时间内对被测时钟计数
always @(posedge clk_fx or negedge rstn) begin
    if(!rstn) begin
        fx_cnt_temp <= 32'd0;
        fx_cnt <= 32'd0;
    end
    else if(gate)
        fx_cnt_temp <= fx_cnt_temp + 1'b1;
    else if(neg_gate_fx) begin
        fx_cnt_temp <= 32'd0;
        fx_cnt   <= fx_cnt_temp;
    end
end 

//门控时间内对基准时钟计数
always @(posedge clk_fs or negedge rstn) begin
    if(!rstn) begin
        fs_cnt_temp <= 32'd0;
        fs_cnt <= 32'd0;
    end
    else if(gate_fs)
        fs_cnt_temp <= fs_cnt_temp + 1'b1;
    else if(neg_gate_fs) begin
        fs_cnt_temp <= 32'd0;
        fs_cnt <= fs_cnt_temp;
    end
end

//计算被测信号频率
always @(posedge clk_fs or negedge rstn) begin
    if(!rstn) begin
        data_fx_t <= 1'b0;
    end
    else if(gate_fs == 1'b0)
        data_fx_t <= CLK_FS * fx_cnt ;
end

always @(posedge clk_fs or negedge rstn) begin
    if(!rstn) begin
        data_fx <= 20'd0;
    end
    else if(gate_fs == 1'b0)
        data_fx <= data_fx_t / fs_cnt ;
end

endmodule 

frequency_meter.v

由于之前基础实验设计过数码管显示模块本次实验不在赘述但因为数码管模块是输入ascii码进行显示的而现在输出频率数据是一个20bit的二进制数所以我们应该先想办法将二进制转成ascii码再连接数码管模块进行显示。BCD转ascii码通过查表的方式即可完成。但二进制转BCD码的算法不是特别简单之后会在基础实验部分讲解。

顶层模块代码如下:

module frequency_meter(
    input         clk,
    input         rstn,       // 复位信号
    output        ad_clk,     // AD时钟
    input  [7:0]  ad_data,    // AD输入数据
    output [7:0]  led_display_seg,
    output wire [7:0] led_display_sel
);
wire ad_pulse;
wire [19:0] data_fx;
wire [25:0] bcd;
wire [31:0] data_bcd;
wire [63:0] asciidata;
assign data_bcd = {6'b00,bcd};
//生成ad驱动时钟由于使用杜邦线连接ad_clk不要超过10M
PLL PLLinst(
    .clkout0(ad_clk),    // output 10M
    .lock(),
    .clkin1(clk)       // input
);

pulse_gen  pulse_gen_inst (
    .rstn(rstn),
    .trig_level(8'd128),
    .ad_clk(ad_clk),
    .ad_data(ad_data),
    .ad_pulse(ad_pulse)
  );

cymometer # (
    .CLK_FS(32'd27_000_000)
  )
  cymometer_inst (
    .clk_fs(clk),
    .rstn(rstn),
    .clk_fx(ad_pulse),
    .data_fx(data_fx)
  );
//二进制转bcd码模块
bin2bcd # (
    .W(20)
  )
  bin2bcd_inst (
    .bin(data_fx),
    .bcd(bcd)
  );
//4位BCD码转ascii模块例化8次使8个bcd同时输出ascii
genvar i;
generate
  for (i = 0; i < 8; i = i + 1) begin : generate_module
    bcd2ascii  bcd2ascii_inst (
        .bcd(data_bcd[i*4 +:4]),
        .asciidata(asciidata[i*8 +: 8])
      );
  end
endgenerate
//数码管显示模块
led_display_driver  led_display_driver_inst (
    .clk(clk),
    .rstn(rstn),
    .assic_seg(asciidata),
    .seg_point(8'b00000000),
    .led_display_seg(led_display_seg),
    .led_display_sel(led_display_sel)
  );
endmodule

3.3.4仿真验证

3.3.5上板验证

3.4 章末总结