最近自己拿FPGA弄了一个简易频率计(verilog):精度1HZ,可以测试占空比,但是精度只有10%。下面讲解一下,希望对想弄这个的人有一点帮助:
我的硬件说明:EP1C3T144C8N核心板、8位数码管、按键、8位LED、20M晶振
首先说一下测量方波信号频率的原理:
首先一个几位(比如4位)的计数器在方波信号的跳变沿(比如下降沿激励)激励下,来一个激励脉冲(比如下降沿)计数器将加一,但是问题出现了,这个计数器一直加的话不是溢出了?你怎么样通过这个加的数值来确定你的方波频率?
先卖个关子,先来说说这个计数器!计数器肯定是一直在加的(前提是只要你的IO口有跳变沿),那么你的计数的最高数值就是由你的计数器的位数决定的,比如这里是2的4次方,又回到原来的问题,你的计数器不是有跳变沿就加一吗?那岂不是不同的方波信号频率都是加到了最大值?但是你想到没有,不同的方波信号频率输入的话,你的计数器加到最大值的时间是不同的,这里才是设计理解的关键!那么我们就是通过这个不同的时间来判断的。下面讲解原理:
我们让这个计数器在规定的时间内加,然后设定的时间一到我们把这个加的数值存起来,同时将这个计数器清零(很多人这里或许还是不理解为什么要这样,继续往下看)。那么我们先假设这个待测输入的方波信号的频率是固定不变的,也就是说在这个设定的时间内,计数器加的数值,也就是检测到的跳变沿的个数,具体假设是下降沿,在理论上是一个固定的值!(这里可以仔细想一下),因为每次时间一到这个计数器将被清零了。我们为了后面计算的方便,假设我们设定的时间是1秒钟,也就是1HZ,在这个1HZ时钟的控制下面,每秒钟将会把这个计数器清零,那么很显然,这个计数器在这个时间1秒钟内加的数值N就是你测的的方波的频率值!
这个就是一个很简单的数学的问题,在相同的1秒钟时间内,clk信号相当于加了一次,signal信号加了N次,那么你的signal信号的频率肯定是这个clk频率的N倍,你的测量的精度显然就是你的设定比较的时钟1HZ咯。这里有人会提出疑问了,那么我把这个清零的控制时钟改为0.5HZ,那你的频率的测量精度会不会是0.5HZ呢?懂了上面的原理后,这个问题留给你自己思考吧。。。
好了,测量频率基本原理我们讲了,上面举的例子是4位的计数器,那么最大的计数频率就是2的4次方,很显然这个范围太小了,我们需要很多位的计数器!这里就不得不用模块化的设计思想了,把4位的计数器串接起来不就实现多位了!我在实验中做的是一个32位的,也就是2的32次方,大家可以算一下最大测量的理论频率有多大,但是我的只有8位的数码管,最大也就是99999999,姑且算为100MHZ吧,相比较的基准频率设定为1HZ,那么我在实验中就要将这个20M的主晶振20M分频!听起来很恐怖是吧,用FPGA简单的不可思议啦!
频率测量好了,现在还需要测量这个signal方波信号的占空比 。测量占空比我用的是除法器,但是数字电路实现除法器实在是太耗资源了,大家看下编译完成后的报告就知道了!下面讲测量方法的原理:
思路可以采用时基法,和前面测量方波占空比是一样的道理,
在clk基准时钟下,对每个signal信号的高电平持续时间和低电平持续时间进行测量,这时你如果检测到待测信号为高电平->低电平,这时你开始对你的1G的基准频率开始计数(就是来一个1G的上升沿加1),存成一个整数比如a。什么时候停呢?那就是待测信号由低电平->高电平的时候,这时a不加了,改成对另外一个整数b开1g的上升沿加1,在待测信号为高电平->低电平时候b停止加一,这时你得到a,b。
a就是待测信号低电平的时长(待测信号低电平时来了多少个1G的上升沿),b就是待测信号高电平的时长(待测信号高电平时来了多少个1G的上升沿)。这时你的占空比 = (b/(a+b))*100%。
下面我把每个模块的代码和综合后的图像贴在下面:
4位计数器代码:
module cnt_10(out,cout,en,clr,clk);
input en,clr,clk;
output [3:0] out;
output cout;
reg [3:0] out;
always @(posedge clk or posedge clr)
begin
if(clr)
out <=4'b0;
else if(en) begin
out <= out + 1'b1;
if(out == 4'd9) begin
out <= 4'b0;
end
end
end
assign cout = ((out == 4'd9) & en) ? 1'b1 : 1'b0 ;
endmodule
注释:en为使能端,clr为异步清零端,也就是控制时钟1HZ的清零操作接口,这里可能有人会有疑问,为什么是计数到了10就进了一位呢?4位的计数器不是最大可以计数到2的4次方吗?其实这里有一个小技巧,为了数码管显示的方便,这里我们将8个数码管每一个对应一个计数器的数值,那么10进制显示的话就是每一个数码管对应数据的每一个位。
下面是20M的分频电路:
module f_div_1(clk,clk0,rst_n);
input clk;//输入的20MHZ基准时钟
input rst_n;
output clk0;//输出1HZ信号
reg clk0;
reg clk1;
reg [3:0] cnt1;
reg [22:0] cnt2;
//---------------------------------------------------//
//20分频电路,主晶振20M,分频为1M
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt1 <= 4'b0;
else if(cnt1 == 4'd9) begin
cnt1 <= 4'b0;
clk1 <= ~ clk1;
end
else cnt1 <= cnt1 + 1'b1;
//---------------------------------------------------//
//1M分频电路,输出为1HZ时钟
always @(posedge clk1 or negedge rst_n)
if(!rst_n)
cnt2 <= 23'b0;
else if(cnt2 == 23'd499999) begin
cnt2 <= 23'b0;
clk0 <= ~ clk0;
end
else cnt2 <= cnt2 + 1'b1;
endmodule
下面是测量占空比的两个参数a,b:
module duty_cycle(
clk,
rst_n,
signal,
a2,
b2
);
input clk;//输入的基准时钟20M
input rst_n;
input signal;//signal信号输入端
output [17:0] a2,b2;
reg [15:0] a,b,a1,b1;//这里我设置了16位的累加器,也就是说能测量占空//率的范围是300HZ到20M(这里最大的计数范围就是2的16次方)
always @(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
a <= 16'b0;
a1 <= 16'b0;
b <= 16'b0;
b1 <= 16'b0;
end
else if(signal == 1'b1) begin
if(a != 16'b0)//这里要把相应的寄存器清零的同时把值取出
a1 <= a;
a <= 16'b0;
b <= b + 1'b1;
end
else if(signal == 1'b0) begin
if(b != 16'b0)
b1 <= b;
b <= 16'b0;
a <= a + 1'b1;
end
end
assign b2 = (b1 * 7'd100);//这里我将这个高电平等效时间的b1乘以100
//再送到除法器模块
assign a2 = a1 + b1; //这里的a2输出的就是整个周期的时间
//那么b2除以a2的数值就是占空比啦
Endmodule
注释:这里可能有同学又会有疑问了,这个为什么要将这个计数器a,b清零呢?很简单嘛,你在这个高电平期间累加的值不可能在下一个高电平期间又是接着加吧,这里就是清零的同时将数值存到别的寄存器。
下面是VHDL语言的18位除法器:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
use ieee.numeric_std.all;
entity divider_prj is
port(
b : in std_logic_vector(17 downto 0);
a : in std_logic_vector(17 downto 0);
shang : out std_logic_vector(6 downto 0)
);
end;
architecture one of divider_prj is
signal tempa,tempb:std_logic_vector(17 downto 0);
begin
process(a,b)
begin
tempa <= a ;
tempb <= b ;
end process;
process(tempa,tempb)
variable n:integer;
variable temp_a,temp_b : std_logic_vector(35 downto 0);
begin
temp_a := "000000000000000000" &
tempa;
temp_b := tempb & "000000000000000000" ;
n := 0;
while(n<18) loop
temp_a := temp_a(34 downto 0) & "0";
n := n+1;
if temp_a(35 downto 18) >= tempb then
temp_a := temp_a - temp_b + 1;
end if;
end loop;
shang <= temp_a(6 downto 0) ;
end process;
end;
除法器模块是整个系统中最占资源的部分!为了简便这里我将这个余数部分舍掉了。
好了,具体就是这么多了,说下这个工程的缺点还有我的一些疑问和建议吧:
精度为1HZ,除法器设计不是很好,比较慢,但是这个简易的就够用了。
建议:1、可以同时做几个计数器来捕捉这个signal信号,最后求其平均值,这样精度可以提高;2、同时捕捉上升沿和下降沿的方法,可以同时满足你测频率和占空比的需要。
疑问:我在显示的时候遇到了一个问题,是10进制的显示,但是不知道为什么每次加到了9前面的高位就向前进了一位,也就是从8到9时候,结果9进了一位,显示的是19了!看了显示译码电路和以前做的John计数器一样的,John计数器显示的没有问题,但是这里我硬是没看懂怎么回事,觉得可能是送显示时的时序问题,希望高人看后能指点一下。
zenghao616_997442595 2011-11-12 10:40
用户1612008 2011-7-31 10:05