基于FPGA的简易频率计设计
基于FPGA的简易频率计设计
假期著
[TOC]
小班的大作业之一.
之前做这个是因为这个是国赛题,然后上网找了半天的资料都是等精度测量,然后我就,手撸了一个不太一样的...
测频原理
常用的数字频率测量方法有三种
直接测量法
在给定的闸门时间(常见为1s)内测量被测信号的脉冲个数,进行换算得出被测信号的频率。
周期测量法
通过测量被测信号一个周期时间计时信号的脉冲个数,然后换算出被测信号的频率。
这两种测量法的精度都与被测信号有关,因而它们属于非等精度测量法。
综合测量法
设实际闸门时间为t,被测信号周期数为Nx,则它通过测量被测信号数个周期的时间,然后换算得出被测信号的频率,克服了测量精度对被测信号的依赖性。算法核心思想是通过闸门信号与被测信号同步,将闸门时间t控制为被测信号周期长度的整数倍。测量时,先打开预置闸门,当检测到被测闸门关闭时,标准信号并不立即停止计数,而是等检测到的被测信号脉冲到达是才停止,完成被测信号的整数个周期的测量。测量的实际闸门时间与预置闸门时间可能不完全相同,但最大差值不超过被测信号的一个周期。
测频过程
由于是小班的作业讲解之一(甚者他们数电还是虚空基础),所以这里实现前两种方法.(第三种可以联系我拿源码参考.
顺便也写写我用来跑2015国赛F题的流程
整形电路
就是将待测信号整形变成计数器可以识别到的脉冲信号(方波).里面有点讲究,很多人以为里面只需要实现一个高速比较器,但实际上为了测量各个幅值的信号,必须对信号进行限幅,AGC(自动增益控制),抬升再比较(听朱老师说是为了降低噪声).等各种操作啦,但是实际上硬件是由另一位小伙伴@渤儿来写的,所以没有太操心...好像也是有一块芯片就可以直接实现上面的处理了.
FPGA整体框架
image事先声明:
- 本设计用的串口模块从黑金大兄弟哪里改过来的...不是卖广告,就是声明一下...
- 放的代码版本是v0.1,可粗略实现但未优化未删调试信号版,优化会作为小班接下来的作业...
- 第一遍阅读不建议阅读源码
端口说明
- ensig是外部按键控制脚,用来开启闸门(调试用)
- sig是信号输入脚
- rx是32串口返回的控制信号
- tx是输出的频率值
1s定时器
写得有点冗余,主要是比外头的做多了两个操作:
- 没有用异步来触发这个定时器(不然用always @ensig 差不多就写完了,选择用三段式同步触发信号
- 隔离了两条控制线(信号),start_up和op_reg.
module timer_1s(
clk,rst_n,en_sig,
gate_control //控制信号输出
);
input clk;
input rst_n;
input en_sig;
output gate_control;
reg [1:0] en_sig_edge;
reg [1:0] en_sig_edge_n;
reg start_up;
reg [31:0] count;
reg op_reg;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
en_sig_edge <= 2'b0;
end
else
begin
en_sig_edge <= en_sig_edge_n;
end
end
always @(*)
begin
if(!rst_n)
begin
en_sig_edge_n = 2'b0;
end
else
begin
en_sig_edge_n = {en_sig_edge[0],en_sig};
end
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
count <= 31'b0;
op_reg <= 1'b0;
end
else
begin
if(start_up && count!= 31'd49999999)
begin
count <= count + 1'b1;
op_reg <= 1'b1;
end
else
begin
count <= 31'b0;
op_reg <= 1'b0;
end
end
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
start_up <= 1'b0;
end
else if(count == 31'd49999999)
begin
start_up <= 1'b0;
end
else if(start_up == 1'b1)
begin
start_up <= start_up;
end
else if(count == 31'b0)
begin
start_up <= en_sig_edge[0]^en_sig_edge[1];
end
else
begin
start_up <= start_up;
end
end
assign gate_control = op_reg;
endmodule
测频模块
image直接测量法过于简单,就不说了,就是一个计数器...
用双边沿其实就是想让大家多体会体会双边沿移位寄存器的写法这样子,实际上算单边沿就够了.
可以看到其实我的测频模块和闸门信号是可以不相关的,但是由于题目有要求,我就不断输出这些周期数,再给stm32做数据处理了,因为这样测频会多出很多结果用于处理.
主要做的工作有:
- 用PLL例化了一个200M的时钟,用于计数和检测边沿
- 写了个双边沿触发的移位寄存器
- 用了二级流水线算周期(就是上面那条式子)
- 写了个占空比测量的demo(duty_count)
module FRE_SINE_CHECK_MODULE(
clk,rst_n,gate,sig,
fre_count,duty_count
);
input clk;
input rst_n;
input gate;
input sig;
output [31:0] fre_count;
output [31:0] duty_count;
wire clk_200;
wire locked;
pll_200 pll_module(
.areset(!rst_n),
.inclk0(clk),
.pfdena(1'b1),
.c0(clk_200)
);
reg [31:0] counter; //32位计数器
always @(posedge clk_200 or negedge rst_n)
begin
if(!rst_n)
begin
counter <= 1'b0;
end
else
begin
if(gate == 1'b1)
begin
counter <= counter + 1'b1;
end
else
begin
counter <= 32'b0;
end
end
end
reg [1:0] en_sig_edge;
reg [1:0] en_sig_edge_n;
always @(posedge clk_200 or negedge rst_n)
begin
if(!rst_n)
begin
en_sig_edge <= 2'b0;
end
else
begin
en_sig_edge <= en_sig_edge_n;
end
end
always @(*)
begin
if(!rst_n)
begin
en_sig_edge_n = 2'b0;
end
else
begin
en_sig_edge_n = {en_sig_edge[0],sig};
end
end
wire edge_turn;
assign edge_turn = en_sig_edge[0] ^ en_sig_edge[1]; //检测双边沿
reg [31:0] time_record[3:0];
always @(posedge clk_200 or negedge rst_n)
begin
if(!rst_n)
begin
time_record[0] <= 32'b0;
time_record[1] <= 32'b0;
time_record[2] <= 32'b0;
time_record[3] <= 32'b0;
end
else if(gate == 1'b0 )
begin
time_record[0] <= 32'b0;
time_record[1] <= 32'b0;
time_record[2] <= 32'b0;
time_record[3] <= 32'b0;
end
else
begin
if(edge_turn)
begin
time_record[0] <= counter;
time_record[1] <= time_record[0];
time_record[2] <= time_record[1];
time_record[3] <= time_record[2];
end
else
begin
time_record[0] <= time_record[0];
time_record[1] <= time_record[1];
time_record[2] <= time_record[2];
time_record[3] <= time_record[3];
end
end
end
reg [31:0] pipe_add_1;
reg [31:0] pipe_add_2;
reg [31:0] pipe_result;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
pipe_add_1 <= 32'b0;
pipe_add_2 <= 32'b0;
end
else
begin
pipe_add_1 <= time_record[0] + time_record[1];
pipe_add_2 <= time_record[2] + time_record[3];
end
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
pipe_result <= 32'b0 ;
end
else
begin
pipe_result <= pipe_add_1 - pipe_add_2;
end
end
reg [31:0] duty_count_reg;
always @(posedge sig or negedge rst_n)
begin
if(!rst_n)
begin
duty_count_reg <= 32'b0 ;
end
else
begin
if(gate == 1'b1)
duty_count_reg <= time_record[0] - time_record[1];
else
duty_count_reg <= 32'b0;
end
end
assign duty_count = duty_count_reg;
assign fre_count = pipe_result;
endmodule
串口 + stm32部分
负责显示屏显示和将fpga输出的周期转换成频率,由于串口使用黑金的,stm32不是我负责的,所以这里就不详述了.
结语
如您是不小心看了源码,然后心烦意燥滑到最后的,不妨不看源码再看看字和图...毕竟是初代的源码,没精简过(甚至人为设计了语法错误)
可以看到,其实他的原理极其简单,适合新手入门,和熟悉verilog语法,小班的同学和大三的老狗们好像都要开始学这门语言了,不妨结合夏宇闻教授的书来尝试一下
小班进阶作业:理解源码(已贴心地为您删掉大部分源码),删去冗余逻辑和错误部分
新年第一更,不过下年要考研,就可能要开始断更或者只发一些课外拓展咯.(无奈