1997年B题 - 简易数字频率计设计

1 任务 - 设计并制作一台数字显示的简易频率计
2 要求

2.1 基本要求

  1. 频率测量
    1. 测量范围
      1. 信号:方波、正弦波
      2. 幅度:0.5-5V
      3. 频率:1Hz - 1MHz
    2. 测量误差:《0.1%
  2. 周期测量
    1. 测量范围
      • 信号:方波、正弦波
      • 幅度:0.5-5V
      • 频率:1Hz - 1MHz
    2. 测量误差:《0.1%
  3. 脉冲宽度测量
    1. 测量范围:
      1. 信号:脉冲波
      2. 幅度:0.5-5V
      3. 脉冲宽度:> 100us
    2. 测量误差:《1%
  4. 显示器:10进制数字显示,显示刷新时间1-10s连续可调,对上述三种测量功能分别用不同颜色的发光二极管指示
  5. 具有自校功能,时标信号频率为1MHz
  6. 自行设计并制作满足本设计任务要求的稳压电源

2.2 发挥部分

  1. 扩展频率测量范围为0.1Hz - 10MHz(信号幅度0.5-5V), 测试误差降低为0.01%(最大闸门时间《10s)
  2. 测量并显示周期脉冲信号(幅度0.5-5V,频率1Hz - 1KHz)的占空比。占空比变化范围为10%-90%,测试误差《1%
  3. 在1Hz-1MHz范围内及测试误差《0.1%的条件下,进行小信号的频率测量,提出并实现抗干扰的措施

2015年F题 - 数字频率计

1 任务

设计并制作一台闸门时间为1s的数字频率计

2 要求

2.1 基本要求

  1. 频率和周期测量功能
    1. 被测信号为正弦波,频率范围为1Hz ~ 10MHz
    2. 被测信号有效值电压范围为50mV - 1V
    3. 测量相对误差的绝对值不大于10-4
  2. 时间间隔测量功能
    1. 被测信号为方波,频率范围为100Hz-1MHz
    2. 被测信号峰峰值电压为50mV - 1V
    3. 被测时间间隔的范围为0.1us - 100ms
    4. 测量相对误差的绝对值不小于10-2
  3. 测量数据刷新时间不大于2s,测量结果稳定,并能够自动显示单位

2.2 发挥部分

  1. 频率和周期测量的正弦波信号频率范围为1H ~ 100MHz,其它要求同基本要求(1)和(3)
  2. 频率和周期测量时被测正弦信号的最小有效值电压为10mV, 其它要求同基本要求(1)和(3)
  3. 增加脉冲信号占空比的测量功能,要求:
    1. 被测信号为矩形波,频率范围为1Hz ~ 5MHz
    2. 被测信号峰峰值电压范围为50mV ~ 1V
    3. 被测脉冲信号占空比范围为10% ~ 90%
    4. 显示的分辨率为0.1%, 测量相对误差的绝对值不大于10-2
  4. 其它(例如,进一步降低被测信号电压电压的幅度等)

2015年全国大学生电子设计竞赛 - F题 - 数字频率计设计

本设计基于 step-max0-08sam核心板 和 training功能板 平台,测量模拟信号调理后的脉冲信号 fxa 的频率、周期、占空比、以及 fxa 和 fxb 信号的相位差。整体RTL图如下所示

RTL框图

设计只包含数字部分,系统主要由4个模块组成:

  1. 频率采样测量
  2. 频率数据运算
  3. BCD转码实现
  4. OLED显示实现

1. 频率采样测量

频率采样测量主要分为三种方法,直接测频法、周期测频法、等精度测频法。

  • 直接测频法:是给定一个闸门时间 Tgate 在此时间内对被测信号Tx计数Nx,Nx量化误差| dNx |⇐1。因此有Tgate = NxTx 即:Fx = Nx / Tgate,Nx越大相对误差越小,也就是说对同一被测信号,闸门时间越长,相对误差越小。
  • 周期测频法:是在被测信号的一个周期时间Tx内,对标准信号Ts 脉冲进行计数,计数结果Ns,Ns 量化误差| dNs | ⇐1。 因此有Tx =NsT s 即:Fx = Fs / Ns,其中 Fx 与 Fs 分别对应被测信号和标准信号的频率。在实际计算测量中一般不考虑标准信号 Fs 的误差。Ns 越大相对误差越小,也就是说对同一被测信号选择频率越高的标准信号相对误差越小
  • 等精度测频法:闸门时间不是固定的值,而是被测信号周期的整数倍,即与被测信号同步,误差仅存在于时钟计数的±1误差,对于输入信号计数不存在误差。前两种方案在其测量范围内可以达到精度要求,但等精度测频法与前两种的不同之处在于其在测量范围内的精度是固定的,精度足够,而且可以满足测量范围,不用切换。闸门时间内,对 被测信号 和 标准信号 同时计数,标准信号频率为 Fs,计数为 Ns,被测信号计数 Nx,频率为 Fx,所以 Ns / Fs = Nx / Fx,因此 Fx = Fs * Nx / Ns。

通过对比,最终选择等精度测频法。

占空比测量在等精度测量的基础上,测量闸门时间内,使用标准信号对被测信号为高电平状态时间计数,占空比结果等于 Nduty * 100% / Ns


Verilog代码

//==========================================================================
//
// Author: STEP
// Module: freq_measure
// Description: 
// Web: www.stepfapga.com
//--------------------------------------------------------------------------
// Info:
//   V1.0---yyyy/mm/dd---Initial version
//
//==========================================================================
 
//timescale
`timescale 1ns / 1ns
module freq_measure (
    input clk_200m, //采用200MHz时钟信号
    input rst_n,
    input fxa,
    input fxb,
    input start,
 
    output reg done,
    output reg done1,
    output reg [WIDTH-1:0] FxCnt,
    output reg [WIDTH-1:0] FsCnt,
    output reg [WIDTH-1:0] DutyCnt,
    output reg [WIDTH-1:0] DelayDutyCnt,
    output reg [WIDTH-1:0] DelayCnt
);
 
//The defination of parameters
parameter WIDTH = 32; //
parameter CNT_1S = 'd199_999_999; //CLK=200M,then time=1S
 
reg t1s_gate;
reg fDelay_gate;
reg fxa_r1, fxa_r2, fxa_r3, fxa_pos;
reg fxb_r1, fxb_r2, fxb_r3, fxb_pos;
reg start_r, start_pos;
reg freq_real_gate;
reg delay_real_gate;
reg gate_done_r;
reg gate_done1_r;
reg [WIDTH-1:0] cnt_1s;
reg [WIDTH-1:0] fsCntTemp;
reg [WIDTH-1:0] fxCntTemp;
reg [WIDTH-1:0] DutyCntTemp;
reg [WIDTH-1:0] DelayDutyCntTemp;
reg [WIDTH-1:0] DelayCntTemp;
 
//检测两路信号的上升沿
////////////////////////////////////////////////////////////////////
//logic for fxa_r1, fxa_r2, fxa_r3
always @(posedge clk_200m) begin
    fxa_r1 <= fxa;
    fxa_r2 <= fxa_r1;
    fxa_r3 <= fxa_r2;
end
 
always @(posedge clk_200m) fxa_pos <= (!fxa_r3) && fxa_r2; 
 
//logic for fxb_r1, fxb_r2, fxb_r3
always @(posedge clk_200m) begin
    fxb_r1 <= fxb;
    fxb_r2 <= fxb_r1;
    fxb_r3 <= fxb_r2;
end
 
always @(posedge clk_200m) fxb_pos <= (!fxb_r3) && fxb_r2; 
 
//检测测试的start信号
////////////////////////////////////////////////////////////////////
//start signal detect
always @(posedge clk_200m) start_r <= start;
always @(posedge clk_200m) start_pos <= (!start_r) && start; 
 
//产生1秒时间的闸门参考信号
////////////////////////////////////////////////////////////////////
always @(posedge clk_200m or negedge rst_n) begin
    if(!rst_n) cnt_1s <= 1'b0;
    else if(cnt_1s >= CNT_1S) cnt_1s <= 1'b0;
    else if(t1s_gate) cnt_1s <= cnt_1s + 1'b1;
    else cnt_1s <= 1'b0;
end
 
always @(posedge clk_200m or negedge rst_n) begin
    if(!rst_n) t1s_gate <= 1'b0;
    else if(start_pos) t1s_gate <= 1'b1;
    else if(cnt_1s >= CNT_1S) t1s_gate <= 1'b0;
    else t1s_gate <= t1s_gate;
end
 
//根据被测信号的上升沿,产生被测信号周期整数倍的闸门信号
////////////////////////////////////////////////////////////////////
//synchronous gate of frequency count of signal fx
always @(posedge fxa_r2) begin
    if(t1s_gate) freq_real_gate <= 1'b1;
    else freq_real_gate <= 1'b0;
end
 
//根据闸门信号对 标准信号 和 被测信号 计数
always @(posedge clk_200m) fsCntTemp <= (freq_real_gate)? (fsCntTemp + 1'b1) : 1'b0;
always @(posedge fxa_r2)   fxCntTemp <= (freq_real_gate)? (fxCntTemp + 1'b1) : 1'b0;
 
//对闸门有效期间中,被测信号为高电平的时间计数
////////////////////////////////////////////////////////////////////
//Count input signal's duty
always @(posedge clk_200m) begin
    if(start_pos) DutyCntTemp <= 1'b0;
    else if(freq_real_gate && fxa_r2) DutyCntTemp <= DutyCntTemp + 1; //Count the high time
    else DutyCntTemp <= DutyCntTemp;
end 
 
//根据两路信号的上升沿,得到两路信号的相位差信号
////////////////////////////////////////////////////////////////////
//Produce time delay count gate
always @(posedge clk_200m or negedge rst_n) begin
    if(!rst_n) fDelay_gate <= 1'b0;
    else if(fxa_pos) fDelay_gate <= 1'b1;
    else if(fxb_pos) fDelay_gate <= 1'b0;
    else fDelay_gate <= fDelay_gate;
end 
 
//对相位差信号进行等精度测量
//synchronous gate of delay time count
always @(posedge fDelay_gate) begin
    if(t1s_gate) delay_real_gate <= 1'b1;
    else delay_real_gate <= 1'b0;
end
 
always @(posedge clk_200m) begin
    if(start_pos) DelayDutyCntTemp <= 1'b0;
    else if(delay_real_gate && fDelay_gate) DelayDutyCntTemp <= DelayDutyCntTemp + 1; //Count the high time
    else DelayDutyCntTemp <= DelayDutyCntTemp;
end 
 
always @(posedge clk_200m) DelayCntTemp <= (delay_real_gate)? (DelayCntTemp + 1'b1) : 1'b0;
 
//闸门信号结束,返回测量结果
////////////////////////////////////////////////////////////////////
//Output count data when mesure done
always @(negedge freq_real_gate) begin
    FxCnt <= fxCntTemp;
    FsCnt <= fsCntTemp;
    DutyCnt <= DutyCntTemp;
end
 
always @(negedge delay_real_gate) begin
    DelayDutyCnt <= DelayDutyCntTemp;
    DelayCnt <= DelayCntTemp;	
end
 
//产生测量结束信号
////////////////////////////////////////////////////////////////////
wire gate_done = /*delay_real_gate || */freq_real_gate;
 
always @(posedge clk_200m) gate_done_r <= gate_done;
always @(posedge clk_200m) done <= (!gate_done) && gate_done_r; 
 
wire gate_done1 = delay_real_gate/* || freq_real_gate*/;
 
always @(posedge clk_200m) gate_done1_r <= gate_done1;
always @(posedge clk_200m) done1 <= (!gate_done1) && gate_done1_r; 
 
endmodule
  1. 频率数据运算

在频率采样测量环节我们提到等精度测量,最后通过公式 Fx = Fs * Nx / Ns 运算出 被测信号 的频率,通过 Nduty * 100% / Ns 运算出 被测信号 的占空比

这里涉及乘法运算和除法运算,我们的FPGA器件MAX10系列 10M08SAM153C8G 集成了48个9位的乘法器,可以直接调用乘法器功能块。MAX10系列没有集成除法器功能块,直接使用除法逻辑会占用大量逻辑资源,性能也不能保证,所以我们使用流水的算法实现,稳定可靠。

对于64位的无符号数除法,被除数 numer 除以除数 denom,他们的商和余数一定不会超过64位,首先将 numer 转换成高64位为0,低64位为 numer 的tempa,再将 denom 转换成高64位为 denom,低64位为0的tempb。在每个周期开始前,先将tempa左移一位,末尾补0,然后与tempb相比较看是否大于tempb,若大于tempb,则tempa = tempa - tempb + 1,否则继续往下执行。上面的移位操作、比较和减法要执行64次,执行完成后得到的 tempa 的高64位为两数 numer 和 denom 相除的余数,低64位表示商。具体的算法流程可从下图的例子中得到体现

除法器算法原理


任意位宽除法器设计,Verilog代码

//==========================================================================
//
// Author: STEP
// Module: divider 
// Description: 
// Web: www.stepfapga.com
//--------------------------------------------------------------------------
// Info:
//   V1.0---yyyy/mm/dd---Initial version
//
//==========================================================================
 
//timescale
`timescale 1ns / 1ns
module divider #(
    parameter DATA_WIDTH = 64
)
(
    input clk,
    input rst_n,
 
    input start,
    input [DATA_WIDTH-1:0] numer, 
    input [DATA_WIDTH-1:0] denom,
 
    output done,
    output reg [DATA_WIDTH-1:0] quotient,
    output reg [DATA_WIDTH-1:0] remain
); 
 
reg [7:0] cnt;
reg [2*DATA_WIDTH-1:0] temp_a;
reg [2*DATA_WIDTH-1:0] temp_b;
reg done_r;
//------------------------------------------------
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) cnt <= 1'b0;
    else if(start && (cnt < (DATA_WIDTH + 1))) cnt <= cnt + 1'b1; 
    else cnt <= 1'b0;
end
//------------------------------------------------
always @(posedge clk or negedge rst_n)
    if(!rst_n) done_r <= 1'b0;
    else if(cnt == DATA_WIDTH) done_r <= 1'b1;
    else if(cnt == DATA_WIDTH + 1) done_r <= 1'b0;
assign done = done_r;
//------------------------------------------------
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) begin
        temp_a <= 64'h0;
        temp_b <= 64'h0;
    end
    else if(start) begin
        if(cnt == 1'b0) begin
            temp_a = {{DATA_WIDTH{1'b0}},numer};
            temp_b = {denom,{DATA_WIDTH{1'b0}}}; 
        end
        else begin
            temp_a = temp_a << 1;
      if(temp_a >= temp_b) temp_a = temp_a - temp_b + 1'b1;
      else temp_a = temp_a;
        end
     end
end
 
always @(posedge clk) quotient <= (cnt == DATA_WIDTH + 1)? temp_a[31: 0] : quotient;
always @(posedge clk) remain   <= (cnt == DATA_WIDTH + 1)? temp_a[63:32] : remain;
 
endmodule

我们最终计算频率、周期、占空比、以及相位差,将四路运算例化,得到整体的运算模块,Verilog代码

//==========================================================================
//
// Author: Step
// Module: arithmetic
// Description: 
// Web: www.stepfapga.com
//--------------------------------------------------------------------------
// Info:
//   V1.0---2021/01/14---Initial version
//
//==========================================================================
 
//timescale
`timescale 1ns / 1ns
 
//module
module arithmetic #
(
    parameter WIDTH = 32
)
(
    input clk,
    input rst_n,
    input start,
    input [WIDTH-1:0] FxCnt,
    input [WIDTH-1:0] FsCnt,
    input [WIDTH-1:0] DutyCnt,
    input [WIDTH-1:0] DelayDutyCnt,
    input [WIDTH-1:0] DelayCnt,
 
    output [WIDTH-1:0] Fxcnt_quotient,
    output [WIDTH-1:0] FxPeriod_quotient,
    output [WIDTH-1:0] DutyCnt_quotient,
    output [WIDTH-1:0] DelayDutyCnt_quotient
);
 
reg [2*WIDTH-1:0] Fxcnt_numer;
always @(posedge clk) 
    if(start) Fxcnt_numer <= FxCnt * 200_000_000;
    else Fxcnt_numer = Fxcnt_numer;
 
reg [2*WIDTH-1:0] DutyCnt_numer;
always @(posedge clk) 
    if(start) DutyCnt_numer <= DutyCnt * 'd1000;
    else DutyCnt_numer = DutyCnt_numer;
 
reg [2*WIDTH-1:0] DelayDutyCnt_numer;
always @(posedge clk) 
    if(start) DelayDutyCnt_numer <= DelayDutyCnt * 'd1000;
    else DelayDutyCnt_numer = DelayDutyCnt_numer;
 
wire [WIDTH-1:0] Fxcnt_denom = FsCnt;
wire [WIDTH-1:0] FxPeriod_denom = Fxcnt_quotient;
wire [WIDTH-1:0] DutyCnt_denom = FsCnt;
wire [WIDTH-1:0] DelayDutyCnt_denom = DelayCnt;
 
divider Fxcnt_u_div (
    .clk                     ( clk                        ),
    .rst_n                   ( rst_n                      ),
    .start                   ( 1'b1                       ),
    .numer                   ( Fxcnt_numer                ),
    .denom                   ( Fxcnt_denom                ),
    .quotient                ( Fxcnt_quotient             )
);
 
divider FxPeriod_u_div (
    .clk                     ( clk                       ),
    .rst_n                   ( rst_n                     ),
    .start                   ( 1'b1                      ),
    .numer                   ( 1_000_000_000             ),
    .denom                   ( FxPeriod_denom            ),
    .quotient                ( FxPeriod_quotient         ) 
);
 
divider DutyCnt_u_div (
    .clk                     ( clk                       ),
    .rst_n                   ( rst_n                     ),
    .start                   ( 1'b1                      ),
    .numer                   ( DutyCnt_numer             ),
    .denom                   ( DutyCnt_denom             ),
    .quotient                ( DutyCnt_quotient          )
);
 
divider DelayDutyCnt_u_div (
    .clk                     ( clk                       ),
    .rst_n                   ( rst_n                     ),
    .start                   ( 1'b1                      ),
    .numer                   ( DelayDutyCnt_numer        ),
    .denom                   ( DelayDutyCnt_denom        ),
    .quotient                ( DelayDutyCnt_quotient     )
);
 
endmodule //arithmetic
  1. BCD转码实现

通过运算得到的二进制数据,想要显示在OLED屏幕,要以十进制形式显示,需要对二进制数据进行BCD转码。

BCD转码实现原理


Verilog代码

//==========================================================================
//
// Author: STEP
// Module: bin2bcd
// Description: 
// Web: www.stepfapga.com
//--------------------------------------------------------------------------
// Info:
//   V1.0---yyyy/mm/dd---Initial version
//
//==========================================================================
 
//timescale
`timescale 1ns / 1ns
 
module bin2bcd #(
    parameter BIN_WIDTH = 32,
    parameter BCD_WIDTH = 40
)
(
    input clk,
    input rst_n,
    input start,
    input [BIN_WIDTH-1:0] bin_code,
    output reg done,
    output reg [BCD_WIDTH-1:0] bcd_code
);
//-------------------------------------------------------
localparam  IDLE    =   3'b001;
localparam   SHIFT   =   3'b010;
localparam   DONE    =   3'b100;
 
//-------------------------------------------------------
reg [2:0] state_c;
reg [2:0] state_n;
reg [BIN_WIDTH-1:0] shift_cnt;
reg [BIN_WIDTH-1:0] bin_reg;
reg [BCD_WIDTH-1:0] bcd_reg;
wire [BCD_WIDTH-1:0] bcd_temp;
 
//-------------------------------------------------------
//FSM step1
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) state_c <= IDLE;
    else state_c <= state_n;
end
 
//FSM step2
always @(*) begin
    case(state_c)
        IDLE: state_n = (start)? SHIFT : IDLE;
        SHIFT: state_n = (shift_cnt==BIN_WIDTH-1)? DONE : SHIFT;
        DONE: state_n = IDLE;
        default:state_n = IDLE;
    endcase
end
 
//FSM step3
always @(posedge clk) begin
    case(state_c)
        IDLE:begin
            shift_cnt <= 1'b0; 
            bin_reg <= bin_code;
            bcd_reg <= 1'b0;
        end
        SHIFT:begin
            shift_cnt <= shift_cnt + 1'b1;
            bin_reg <= bin_reg << 1;
            bcd_reg <= {bcd_temp[BCD_WIDTH-2:0], bin_reg[BIN_WIDTH-1]};
        end
        default:begin
            shift_cnt <= 1'b0;
            bin_reg <= 1'b0;
            bcd_reg <= bcd_reg; 
        end
    endcase
end
 
//-------------------------------------------------------
assign bcd_temp[ 0+:4] = (bcd_reg[ 0+:4] > 4'd4)? (bcd_reg[ 0+:4] + 4'd3) : bcd_reg[ 0+:4];
assign bcd_temp[ 4+:4] = (bcd_reg[ 4+:4] > 4'd4)? (bcd_reg[ 4+:4] + 4'd3) : bcd_reg[ 4+:4];
assign bcd_temp[ 8+:4] = (bcd_reg[ 8+:4] > 4'd4)? (bcd_reg[ 8+:4] + 4'd3) : bcd_reg[ 8+:4]; 
assign bcd_temp[12+:4] = (bcd_reg[12+:4] > 4'd4)? (bcd_reg[12+:4] + 4'd3) : bcd_reg[12+:4]; 
assign bcd_temp[16+:4] = (bcd_reg[16+:4] > 4'd4)? (bcd_reg[16+:4] + 4'd3) : bcd_reg[16+:4];
assign bcd_temp[20+:4] = (bcd_reg[20+:4] > 4'd4)? (bcd_reg[20+:4] + 4'd3) : bcd_reg[20+:4];
assign bcd_temp[24+:4] = (bcd_reg[24+:4] > 4'd4)? (bcd_reg[24+:4] + 4'd3) : bcd_reg[24+:4]; 
assign bcd_temp[28+:4] = (bcd_reg[28+:4] > 4'd4)? (bcd_reg[28+:4] + 4'd3) : bcd_reg[28+:4]; 
assign bcd_temp[32+:4] = (bcd_reg[32+:4] > 4'd4)? (bcd_reg[32+:4] + 4'd3) : bcd_reg[32+:4]; 
assign bcd_temp[36+:4] = (bcd_reg[36+:4] > 4'd4)? (bcd_reg[36+:4] + 4'd3) : bcd_reg[36+:4];
 
always @(posedge clk) begin
    case(state_c)
        DONE: begin
            done <= 1'b1;
            bcd_code <= bcd_reg;
        end
        default: begin
            done <= 1'b0;
            bcd_code <= bcd_code;
        end
    endcase
end
 
endmodule 

Verilog代码