测频在fpga中是非常常见也是非常经典的问题,原因在于利用EDA技术可以测出毫赫兹到百兆赫兹范围的频率。

通常常见的测频方法有:直接测,等周期,等精度。

直接测频法原理:在一定时间内,对信号的脉冲计数,然后算出1s内有几个脉冲就是几赫兹,可以想到的是,如果开始数的时候不是从方波的上升沿或者下降沿开始数,而是在高电平持续时间或者低电平持续时间开始数,理论分析就知道一定存在误差。

等周期法:测出一个脉冲的时间,周期分之一即为频率。

等精度法:等精度测频法就是通过控制闸门信号与被测信号Fx同步从而消除了计数误差,其原理电路如图1所示,将闸门信号G作为边沿D触发器的输入,被测信号Fx作为D触发器的时钟脉冲时,D触发器输出的新闸门信号SG与Fx同步

python测赫兹 检测赫兹_fpga开发

图一

等精度测频法的工作原理是:当闸门信号G跳变为高电平时,只有当被测信号Fx的上升沿到来时D触发器输出的新闸门信号SG才能跳变为高电平;当闸门信号G跳变为低电平时,同样只有当被测信号Fx的上升沿到来时D触发器输出的新闸门信号SG才能跳变为低电平。因此,D触发器输出的新闸门信号SG与被测信号Fx严格同步,所以用新闸门信号控制计数器时,能够消除计数误差。但是,由于新闸门信号SG的作用时间受被测信号Fx的控制,虽然取原闸门信号G的作用时间为1秒,但新闸门信号的作用时间不一定为1秒,因而计数器N中的计数值并不能代表被测信号的频率值。改进的方法是,再添加一套与门和计数器,在新闸门信号SG的作用时间内同时对被测信号Fx和一个标准频率信号Fs进行计数,应用两个计数器的计数时间完全相同的关系推算出被测信号的频率值。

等精度测频法被测信号频率的计算原理如图2所示。若将新闸门信号SG的作用时间记为TD,标准频率信号Fs的周期记为Ts,被测信号Fx的周期记为Tx,在TD时间内对标准频率信号和被测信号的计数值分别记为N,和N;,则闸门信号SG的作用时间TD可以精确地表示为

TD=Nx×Tx

python测赫兹 检测赫兹_sed_02

图二

等精度法更适合测量高频信号,但题目要求10HZ-2MHZ,跨度比较大,所以我选择在低频时选择用等周期,高频选择用等精度,利用按键做切换显示。

等周期代码如下:

always @ (posedge clk or negedge rst_n)
     begin
       if (!rst_n)
         begin
         low_cnt <= 25'd0;
         high_cnt <= 25'd0;
         low_time <= 25'd0;
         high_time <= 25'd0;
         state <= high_state;
         end
       else
       begin
         case (state)
           high_state : begin
               if (wave == 1'b1)       //判断输入为高电平
                 begin
                   high_cnt <= high_cnt + 1'b1;
                   state <= high_state;
                 end
               else
                 begin
                   high_cnt <= 25'd0;
                   high_time <= high_cnt;
                   state <= low_state;
                 end
             end
             
           low_state : begin
               if (wave == 1'b0)  //判断输入为低电平
                 begin
                   low_cnt <= low_cnt + 1'b1;
                   state <= low_state;
                 end
               else
                 begin
                   low_cnt <= 25'd0;
                   low_time <= low_cnt;
                   state <= high_state;
                 end
             end
           default : state <= low_state;
           endcase
       end
     end

频率:assign freq = 1_000_000_000/(low_time * 20 + high_time * 20);  

解释一下这个公式,因为板载晶振是50MHZ,周期就是20ns,分母为ns,分子也为ns,算出来的频率单位才是HZ,所以分子1s=1_000_000_000ns。

同时也很好求出占空比:assign duty_cycle = (high_time * 100)/(high_time + low_time);

等精度需要详细解释,可以去看正点原子代码或者参考其他大佬

module cymometer
    #(parameter    CLK_FS = 32'd50_000_000) // 基准时钟频率值
     (   
         input                 clk_fs ,     // 基准时钟信号
         input                 rst_n  ,     // 复位信号         input                 clk_fx ,     // 被测时钟信号
         output   reg [20:0]   data_fx      // 被测时钟频率输出
 ); localparam   MAX       =  10'd64;           // 定义fs_cnt、fx_cnt的最大位宽
 localparam   GATE_TIME = 16'd5_000;        // 门控时间设置 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    [   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               neg_gate_fs;            // 基准时钟下门控信号下降沿
 wire               neg_gate_fx;            // 被测时钟下门控信号下降沿//边沿检测,捕获信号下降沿
 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 rst_n) begin
     if(!rst_n)
         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 rst_n) begin
     if(!rst_n)
         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 rst_n) begin
     if(!rst_n) 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 rst_n) begin
     if(!rst_n) 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 rst_n) begin
     if(!rst_n) 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 rst_n) begin
     if(!rst_n) 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 rst_n) begin
     if(!rst_n) 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 rst_n) begin
     if(!rst_n) begin
         data_fx <= 32'd0;
     end
     else if(gate_fs == 1'b0)
         data_fx <= (CLK_FS* fx_cnt ) / fs_cnt ;
 endendmodule

至此第一问就做完了。