作者:FPGA入门到精通
频率测量是电子设计和信号处理中的一个基础而关键的环节。FPGA,作为可编程的硬件平台,提供了实现频率计的灵活性和高效性。本文将带领大家一步步了解并实现一个基于FPGA的简易频率计。
一、频率测量原理
测频的原理,简单理解就是“在单位时间内对被测信号进行计数”。
1、频率测量法(直接测量法)
(1)原理
通过计算在固定时间窗口内被测信号的周期数量来确定频率,计算出单位时间内被测信号的时钟周期数。这种方法简单直观,适用于高频信号的测量。
(2)误差分析
由于测量时间可能不是被测信号周期的整数倍,因此在周期计数上可能存在±1的误差,这会导致测量结果有偏差。
2、周期测量法
(1)原理
首先测量被测信号的一个完整周期内包含的基准时钟脉冲数量,然后利用这个信息计算出被测信号的周期,进而求得频率。
(2)误差分析
这种方法在测量低频信号时较为有效,因为基准时钟的周期相对于被测信号周期的占比较小,误差较小。但是,如果基准时钟频率选择不当,测量误差会增大。
3、等精度测量法
(1)原理
等精度测量法的核心思想是使实际的门控时间成为被测信号周期的整数倍。通过设定一个与被测信号同步的门控信号,可以在门控时间内同时对被测信号和基准时钟进行计数。
(2)误差分析
由于门控时间是被测信号周期的整数倍,这种方法消除了直接测量法中±1周期的误差。
不过,门控时间内基准时钟的计数可能存在±1的误差,但这个误差通常可以通过提高基准时钟频率或增加门控时间来减小。
二、FPGA简易频率计设计
1、系统时钟
通常使用50MHz或更高频率的时钟信号作为基准时钟 。
2、门控信号
生成周期为被测信号周期整数倍的门控信号,确保测量精度 。
这个门控信号在一个门控信号周期内对被测信号和基准时钟进行采样。
3、计数器模块
在门控信号的控制下,捕获基准时钟和被测信号的上升沿或下降沿,记录门控时间内基准时钟的周期数(Y)和被测信号的周期数Y(X)。
4、频率计算
根据计数结果和基准时钟频率,计算出被测信号的频率值 。
设定被测信号的频率 f_x,基准时钟的频率f_s。
则计算公式为:f_x = X*f_s/Y。
5、考虑误差
由于实际门控可能存在±1基准时钟周期的误差,可以通过增加门控时间或提高基准时钟频率来减小相对误差。
6、优化和调整
根据实际测量结果和预期精度要求,可能需要对门控时间、基准时钟频率等参数进行调整和优化。
三、Verilog代码示例
module freq_meter_calc(
input wire clk,
input wire rst_n,
input wire clk_test,
output reg [33:0] freq
);
// 参数定义
// 软件闸门计数器计数最大值
parameter CNT_GATE_S_MAX = 28'd37_499_999;
// 软件闸门拉高计数
parameter CNT_RISE_MAX = 28'd6_250_000;
// 标准时钟频率100MHz
parameter CLK_STAND_FREQ = 28'd100_000_000;
// 内部信号定义
wire clk_stand; // 标准时钟,频率100MHz
wire gate_a_fall_s; // 实际闸门下降沿(标准时钟下)
wire gate_a_fall_t; // 实际闸门下降沿(待检测时钟下)
// 寄存器定义
reg [27:0] cnt_gate_s; // 软件闸门计数器
reg gate_s; // 软件闸门
reg gate_a; // 实际闸门
reg gate_a_stand; // 实际闸门打一拍(标准时钟下)
reg gate_a_test; // 实际闸门打一拍(待检测时钟下)
reg [47:0] cnt_clk_stand; // 标准时钟周期计数器
reg [47:0] cnt_clk_stand_r; // 实际闸门下标准时钟周期数
reg [47:0] cnt_clk_test; // 待检测时钟周期计数器
reg [47:0] cnt_clk_test_r; // 实际闸门下待检测时钟周期数
reg calc_flag; // 待检测时钟频率计算标志信号
// 软件闸门计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt_gate_s <= 28'd0;
end else if (cnt_gate_s == CNT_GATE_S_MAX) begin
cnt_gate_s <= 28'd0;
end else begin
cnt_gate_s <= cnt_gate_s + 1'b1;
end
end
// 软件闸门,高电平有效时间
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
gate_s <= 1'b0;
end else if (cnt_gate_s >= CNT_RISE_MAX && cnt_gate_s <= (CNT_GATE_S_MAX - CNT_RISE_MAX)) begin
gate_s <= 1'b1;
end else begin
gate_s <= 1'b0;
end
end
// 待测时钟的,实际闸门
always @(posedge clk_test or negedge rst_n) begin
if (rst_n == 1'b0) begin
gate_a <= 1'b0;
end else begin
gate_a <= gate_s;
end
end
// 标准时钟周期计数器
always @(posedge clk_stand or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt_clk_stand <= 48'd0;
end else if (gate_a == 1'b0) begin
cnt_clk_stand <= 48'd0;
end else if (gate_a) begin
cnt_clk_stand <= cnt_clk_stand + 1'b1;
end
end
// 待检测时钟周期计数器
always @(posedge clk_test or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt_clk_test <= 48'd0;
end else if (gate_a == 1'b0) begin
cnt_clk_test <= 48'd0;
end else if (gate_a) begin
cnt_clk_test <= cnt_clk_test + 1'b1;
end
end
// 实际闸门打一拍(标准时钟下)
always @(posedge clk_stand or negedge rst_n) begin
if (rst_n == 1'b0) begin
gate_a_stand <= 1'b0;
end else begin
gate_a_stand <= gate_a;
end
end
// 实际闸门下降沿(标准时钟下)
assign gate_a_fall_s = ((gate_a_stand == 1'b1) && (gate_a == 1'b0)) ? 1'b1 : 1'b0;
// 实际闸门下标准时钟周期数
always @(posedge clk_stand or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt_clk_stand_r <= 32'd0;
end else if (gate_a_fall_s) begin
cnt_clk_stand_r <= cnt_clk_stand;
end
end
// 实际闸门打一拍(待检测时钟下)
always @(posedge clk_test or negedge rst_n) begin
if (rst_n == 1'b0) begin
gate_a_test <= 1'b0;
end else begin
gate_a_test <= gate_a;
end
end
// 实际闸门下降沿(待检测时钟下)
assign gate_a_fall_t = (gate_a_test&& (gate_a == 1'b0)) ? 1'b1 : 1'b0;
// 实际闸门下待检测时钟周期数
always @(posedge clk_test or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt_clk_test_r <= 32'd0;
end else if (gate_a_fall_t) begin
cnt_clk_test_r <= cnt_clk_test;
end
end
// 计算标志信号
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
calc_flag <= 1'b0;
end else if (cnt_gate_s == (CNT_GATE_S_MAX - 1'b1)) begin
calc_flag <= 1'b1;
end else begin
calc_flag <= 1'b0;
end
end
// 待检测时钟信号时钟频率
always @(posedge clk or negedge srst_n) begin
if (rst_n == 1'b0) begin
freq <= 34'd0;
end else if (calc_flag) begin
freq <= (CLK_STAND_FREQ / cnt_clk_stand_r) * cnt_clk_test_r;
end
end
// 时钟生成模块实例化
clk_gen clk_gen_inst(
.areset(~rst_n),
.inclk0(clk),
.c0(clk_stand)
);
endmodule