基于FPGA的简易频率计设计

2019-07-22  本文已影响0人  今日你学左米啊

基于FPGA的简易频率计设计

假期著

[TOC]

小班的大作业之一.

之前做这个是因为这个是国赛题,然后上网找了半天的资料都是等精度测量,然后我就,手撸了一个不太一样的...

测频原理

常用的数字频率测量方法有三种

直接测量法

在给定的闸门时间(常见为1s)内测量被测信号的脉冲个数,进行换算得出被测信号的频率。

周期测量法

通过测量被测信号一个周期时间计时信号的脉冲个数,然后换算出被测信号的频率。

这两种测量法的精度都与被测信号有关,因而它们属于非等精度测量法。

综合测量法

设实际闸门时间为t,被测信号周期数为Nx,则它通过测量被测信号数个周期的时间,然后换算得出被测信号的频率,克服了测量精度对被测信号的依赖性。算法核心思想是通过闸门信号与被测信号同步,将闸门时间t控制为被测信号周期长度的整数倍。测量时,先打开预置闸门,当检测到被测闸门关闭时,标准信号并不立即停止计数,而是等检测到的被测信号脉冲到达是才停止,完成被测信号的整数个周期的测量。测量的实际闸门时间与预置闸门时间可能不完全相同,但最大差值不超过被测信号的一个周期。

测频过程

由于是小班的作业讲解之一(甚者他们数电还是虚空基础),所以这里实现前两种方法.(第三种可以联系我拿源码参考.

顺便也写写我用来跑2015国赛F题的流程

整形电路

就是将待测信号整形变成计数器可以识别到的脉冲信号(方波).里面有点讲究,很多人以为里面只需要实现一个高速比较器,但实际上为了测量各个幅值的信号,必须对信号进行限幅,AGC(自动增益控制),抬升再比较(听朱老师说是为了降低噪声).等各种操作啦,但是实际上硬件是由另一位小伙伴@渤儿来写的,所以没有太操心...好像也是有一块芯片就可以直接实现上面的处理了.

FPGA整体框架

image

事先声明:

  1. 本设计用的串口模块从黑金大兄弟哪里改过来的...不是卖广告,就是声明一下...
  2. 放的代码版本是v0.1,可粗略实现但未优化未删调试信号版,优化会作为小班接下来的作业...
  3. 第一遍阅读不建议阅读源码

端口说明

  1. ensig是外部按键控制脚,用来开启闸门(调试用)
  2. sig是信号输入脚
  3. rx是32串口返回的控制信号
  4. tx是输出的频率值

1s定时器

写得有点冗余,主要是比外头的做多了两个操作:

  1. 没有用异步来触发这个定时器(不然用always @ensig 差不多就写完了,选择用三段式同步触发信号
  2. 隔离了两条控制线(信号),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做数据处理了,因为这样测频会多出很多结果用于处理.

主要做的工作有:

  1. 用PLL例化了一个200M的时钟,用于计数和检测边沿
  2. 写了个双边沿触发的移位寄存器
  3. 用了二级流水线算周期(就是上面那条式子)
  4. 写了个占空比测量的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语法,小班的同学和大三的老狗们好像都要开始学这门语言了,不妨结合夏宇闻教授的书来尝试一下

小班进阶作业:理解源码(已贴心地为您删掉大部分源码),删去冗余逻辑和错误部分

新年第一更,不过下年要考研,就可能要开始断更或者只发一些课外拓展咯.(无奈

想我尽早更新的方法之一

上一篇下一篇

猜你喜欢

热点阅读