基于FPGA和STM32的频率计设计(100Mhz-1hz精确到0.1hz)

1. 前言

2019年8月20日:本人一名FPGA的菜鸟,为了准备电赛,几天前完成了FPGA频率计和相位差测量的FPGA工程,这算我独立完成的FPGA工程代码,踩了很多坑,不过基本实现了入门,这个题目很适合菜鸟入门练手。现在特来分享心得用于交流。如有错误欢迎大家批评指正!。

2021年6月30日:博主目前已经参加工作,正从事FPGA的开发,会看之前的博客,有了比较多新的理解。之前很多地方不够严谨,实在抱歉。

平台:

  1. 黑金的Artix7开发板
  2. 正点原子战舰精英板
  3. Vivado2017.4
  4. Keil 5

2. 设计

2.1 要求

能够使用FPGA对由信号发生器(电平设置不能超过3.3V,与XDC文件中设置保持一致)产生100Mhz-1hz的频率的信号进行实时测量,对于低于1000hz的信号可以精确到0.1hz,将计算结果通过串口发送给stm32,显示在LCD屏幕上。上电开始测量可以自动换挡。

分为低频高频两档,高频档使用测频法,低频档使用测周法


2.2 说明

2021年6月30日:这个实验的信号输入未经过ADC,是通过FPGA的低速bank直接输入的,不过过程中加了一个IBUF。可以类似于测量低速bank输入的信号的频率。实用价值较低。


2.3 设计方案

  1. 首先对信号进行10ms的预采样,当频率高于1000hz时使用计数上升沿的方法,当频率低于1000hz时,计数两个上升沿之间的时间差,求得周期再求得倒数。

  2. 因为FPGA外部时钟只有50Mhz所以我们需要使用pll进行倍频。产生100Mhz和200Mhz的时钟,并且我们需要使用到串口,pll和串口代码由黑金附带教程的源代码稍作修改得到。stm32的代码由正点原子串口实验和LCD显示实验稍作修改得到。
    注意:

    其中还要注意一点verilog编程不同于c语言编程,verilog描述的是硬件结构,所有的硬件结构从供电起就是并行执行的,所以在预采样的处理上,我使用的方法是:将信号同时输入低频,高频,预采样模块,再根据预采样模块的输出决定顶层模块的最终输出。


3. 实现

3.1 顶层模块

  • 输入信号in_signal 我使用的是信号发生器直接发出的方波信号,黑金开发板基础时钟clk 50Mhz。
  • 顶层包含低频采样模块,高频采样模块,预采样模块,pll模块,串口模块

代码架构图如下:
工程结构


RTL图如下:

原理图


代码如下:

module fre_top2(
    input clk,
    input rst_n,
    input in_signal,
    input uart_rx,
    //output clk_100mhz_out,         //TB调试用
    //output [1:0]state_out,
    //output flag_10ms_out,
    //output [31:0]fre_cnt_out,
    //output [15:0]pre_cnt_num,
    output uart_tx
    );
wire [1:0]state_wire;
wire [31:0]fre_cnt1;        //LOW模块的最终结果
wire [31:0]fre_cnt2;        //HIGH模块的最终结果 
wire [31:0]fre_cnt;         //用于输出最终结果,作为串口的输出
reg  [1:0]state;            //用于状态分级,01为LOW,10为高


wire clk_100mhz;

localparam LOW =  2'b01;
localparam HIGH = 2'b10;

//调用pll产生100mHz时钟
pll_test pll_100mHz(
    .sys_clk(clk),              //system clock 50Mhz on board
    .rst_n(rst_n),              //reset ,low active
    .clk_out(clk_100mhz)  
);

//assign state_out=state_wire;     //tb调试用
//assign fre_cnt_out=fre_cnt;
//assign flag_10ms_out=flag_10ms;

//预采样,确定频率计算方式 

//定时10ms如果期间没有见得到两次上升沿就说明频率低于1Khz
Pre_sampling Pre_sampling(
    .clk(clk_100mhz),
    .rst_n(rst_n),
    .in_signal(in_signal),
    // .flag_10ms_out(flag_10ms),
    // .pre_cnt_num(pre_cnt_num),
    .state(state_wire)
);


always @(*)
    begin
        state <=state_wire;
    end
//


//LOW频率计模块
//小于1Khz的使用测量两个上升沿时间差
LOW_fre_measure low_fre(
    .clk(clk_100mhz),
    .rst_n(rst_n),
    .in_signal(in_signal),
    .fre_cnt(fre_cnt1)

);


// HIGH频率计模块
//大于1Khz使用1s内计数上升沿个数的方法
HIGH_fre_measure high_fre(
    .clk(clk_100mhz),
    .rst_n(rst_n),
    .in_signal(in_signal),
    .fre_cnt(fre_cnt2)
);

assign fre_cnt= (state==LOW)? fre_cnt1:fre_cnt2; 
   
//使用串口发送结果
uart_test uart_test1(
    .clk(clk),
    .rst_n(rst_n),
    .uart_rx(uart_rx),
    .fre_cnt(fre_cnt),
    .uart_tx(uart_tx)
);

endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

3.2 预采样模块

输出信号state,state=2’b01时为小于1000hz信号,state=2’b10时为大于1000hz信号。定时时间为10ms

module Pre_sampling(
    input clk,
    input rst_n,
    input in_signal,
    // output flag_10ms_out,             //tb调试用
    // output [15:0]pre_cnt_num,
    output reg [1:0] state

);
reg [15:0] fre_cnt_pre1;
reg [15:0] fre_cnt_pre2;
//reg low_state;

wire flag_10ms;

// assign flag_10ms_out=flag_10ms;      //tb调试用
// assign pre_cnt_num=fre_cnt_pre2;

localparam LOW =  2'b01;
localparam HIGH = 2'b10;
localparam nodefine = 2'b00;

//对上升沿进行计数
always @(posedge in_signal or negedge rst_n)
    begin
        if(!rst_n)
            fre_cnt_pre1 <=16'b0;
        else if(flag_10ms)
            fre_cnt_pre1 <= fre_cnt_pre1+1'b1;
        else //flag_1ms等于0
            fre_cnt_pre1 <=16'b0;
    end
//


always @(negedge flag_10ms)
    begin
        fre_cnt_pre2<=fre_cnt_pre1;
    end

//判断上升沿个数小于10个,则说明频率为1Khz一下    
//assign state=(fre_cnt_pre2 <=32'd1)? LOW:HIGH;
always @(negedge flag_10ms) 
    begin
    if(fre_cnt_pre2 < 32'd10)
        state<=LOW;
    else if(fre_cnt_pre2>=32'd10)
        state<=HIGH;
    else
        state<=nodefine;  
    end

//


//调用定时器模块定时10ms
Time_10ms#(
    .CLK_times(28'd99_999)
)timer_10ms
(
    .clk(clk),
    .rst_n(rst_n),
    .flag_10ms(flag_10ms)
);    

endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

3.3高频测量模块

引入一个match模块将输入信号与时钟匹配上升沿。减少误差,定时时间1s为了方便。

注:在这里,我将输出信号的最高位设置成了1‘b1.因为我在单片机中需要判断接受到的数据是低频测量模块还是高频测量模块的输出,然后再采取不同的算法结算频率值。

//高于1khz使用此模块进行频率测量
//精确度要求
//定时时间1s
module HIGH_fre_measure(
    input clk,
    input rst_n,
    input in_signal,
    output [31:0] fre_cnt
    );

reg[31:0] fre_cnt_r1 = 32'b0;
reg[31:0] fre_cnt_r2 = 32'b0;
wire clk_match;
wire flag_1s;

always@(negedge clk_match)
begin
fre_cnt_r2 <= fre_cnt_r1;
end

always@(posedge in_signal or negedge rst_n)
begin
if(!rst_n)
fre_cnt_r1 <= 32'b0;
else if(clk_match)
fre_cnt_r1 <= fre_cnt_r1 + 32'b1;
else
fre_cnt_r1 <= 32'b0;
end

assign fre_cnt = {1'b1,fre_cnt_r2[30:0]};
//调用定时模块1s
Time_1s#(
.CLK_times(28'd99_999_999)
)timer_1s(
.clk(clk),
.rst_n(rst_n),
.flag_1s(flag_1s)
);

Match match1(
    .in_signal(in_signal),
    .flag_xs(flag_1s),
    .clk_match(clk_match)
);

endmodule //

//在高频信号中使用同频率测量
//匹配上升沿
module Match(
input flag_xs,
input in_signal,
output reg clk_match
);

always @(posedge in_signal)
begin
clk_match <= flag_xs;
end

endmodule

//定时一秒

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

3.4 低频测量模块

在这个模块中,因为时钟频率足够高,我就直接使用记录两个上升沿之间时钟脉冲个数的方法,然后利用公式计算出频率。等于时钟频率➗计数值 (clk / cnt).

注:为达到精确到0.1hz的目的,我讲clk的实际值扩大了10倍然后再进行计算,在硬件除法器中,我们只能实现整除,余数将被抛弃,所以,我扩大10倍,使有效数字增加了一位。就达到了提高精度的方法。这个还需各位自行理解。

除法器IP核的使用请各位自行查找,csdn和其他博客中都有很详细的讲解

//低于1KHz使用此模块进行频率测量
//精确度可以达到0.1hz
module LOW_fre_measure(
    input clk,
    input rst_n,
    input in_signal,
    output [31:0] fre_cnt
    );
reg rasing=1'b0; //捕捉上升沿
reg[31:0]  fre_cnt_r1=32'd0;
reg[31:0]  fre_cnt_r2=32'd0;

reg[31:0]  clk_bigger_10times=32'd1_000_000_000; //时钟频率100Mh *10
wire [63:0]fre_cnt_wire;                       //div输出

always @(posedge in_signal)
    begin
        rasing &lt;=~rasing;   
    end

always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
fre_cnt_r1 <= 32'b0;
else if(rasing)
fre_cnt_r1 <= fre_cnt_r1 + 32'b1;
else
fre_cnt_r1 <= 32'b0;
end

always @(negedge rasing)
    begin
        fre_cnt_r2&lt;=fre_cnt_r1;    
    end

//输出结果


assign fre_cnt= fre_cnt_wire[63:32];

// //除法器输出结果
// div_ip div_ip1(
//     .a(clk_bigger_10times_wire),        //输入50Mhz *10 最后一位为小数位
//     //.a(32'd1_000_000_000),    //输入100Mhz * 10 得到的除出来结果为实际值10倍
//     .b(fre_cnt_r2_wire),
//     .yshang(fre_cnt_wire)

// );
  

div_gen_0 div_gen_0(
 .aclk                    (clk),
 .s_axis_divisor_tvalid   (1'b1),
 .s_axis_divisor_tdata    (fre_cnt_r2),
 .s_axis_dividend_tvalid  (1'b1),
 .s_axis_dividend_tdata   (clk_bigger_10times),
 .m_axis_dout_tvalid      (),
 .m_axis_dout_tdata       (fre_cnt_wire)
);   

endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

3.5 定时器模块

定时器当时被bug弄烦了,弄成了两个模块,各位可以自行优化修改成一个module.

//定时一秒
module Time_1s#(
 parameter CLK_times = 28'd99_999_999
    )
    (
 input       clk,
 input       rst_n,
 output reg  flag_1s=1'd0
    );

reg[27:0] timer;

always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
timer <= 28'd0;
else if(timer >= CLK_times)
begin
flag_1s<=~flag_1s;
timer <= 28'd0;
end
else
timer <= timer + 28'd1;
end

endmodule

//定时1毫秒
module Time_10ms#(
parameter CLK_times = 28'd999_999
)
(
input clk,
input rst_n,
output reg flag_10ms=1'd0
);

reg[27:0] timer;

always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
timer <= 28'd0;
else if(timer >= CLK_times)
begin
flag_10ms<=~flag_10ms;
timer <= 28'd0;
end
else
timer <= timer + 28'd1;
end

endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

3.6 TestBench代码

  • tb仿真文件编写十分简单,只要设置好顶层模块的输入输出就好,输入使用reg类型,输出使用wire类型,然后再对顶层模块进行例化,然后在initial模块中进行初始化。这个工程代码我都在板子上已经实验成功了,tb文件就是上板前进行逻辑的验证。
module fre_tb(
    );
reg clk;
reg rst_n;
reg in_signal;
// reg in_signal1;
// reg in_signal2;
// reg enable;

wire uart_tx;
// wire clk_100mhz_out;
wire [31:0]fre_cnt_out;
//wire [1:0] state; 
// wire flag_10ms_out;
// wire [15:0]pre_cnt_num;

fre_top2 fre_top2_tb(
    .clk(clk),
    .rst_n(rst_n),
    .in_signal(in_signal),
    .uart_rx(8'b0),
    //.clk_100mhz_out(clk_100mhz_out),
    //.state_out(state),
    .fre_cnt_out(fre_cnt_out),
    //.flag_10ms_out(flag_10ms_out),
    //.pre_cnt_num(pre_cnt_num),
    .uart_tx(uart_tx)

);
initial
begin
clk=0;
rst_n=0;
in_signal=0;

// in_signal1=0;
// in_signal2=0;

// enable=0;

#100
rst_n=  1;
end

always #10 clk=~clk;
// always #1000 in_signal1=~in_signal1;
// always #600000 in_signal2=~in_signal2;
// always #50000000 enable=~enable;

// always @(posedge clk)
//     begin
//         if(enable)
//         in_signal&lt;=in_signal1;
//         else
//         in_signal&lt;=in_signal2;
//     end
always #600000 in_signal=~in_signal;

endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

3.7 约束xdc文件

  • 最好在我的xdc文件上修改,当初上板是出现了一些问题,查百度,后来听博主的就加入了几条仅配置IO part没有的代码。
############## NET - IOSTANDARD ##################
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
#############SPI Configurate Setting##################
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] 
set_property CONFIG_MODE SPIx4 [current_design] 
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] 

create_clock -period 20 [get_ports sys_clk]

set_property PACKAGE_PIN Y18 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN D16 [get_ports in_signal]
set_property PACKAGE_PIN F20 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports in_signal]

set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets in_signal_IBUF]

set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property PACKAGE_PIN F14 [get_ports uart_rx]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rx]
set_property PACKAGE_PIN F13 [get_ports uart_tx]
set_property IOSTANDARD LVCMOS33 [get_ports uart_tx]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

4. 后言

需要源码的朋友们点赞,评论区留言,我私发给大家

频率计是一个很好的练手项目,大家可以了解清楚原理后可以自己试试。代码的直接移植很大概率会出问题,场景不同,环境不同,需求不同。

欢迎大家的批评指正!

参考文献
基于FPGA的全同步数字频率计的设计 --包本刚

posted @ 2023-07-21 11:36  SymPny  阅读(161)  评论(0编辑  收藏  举报