基于FPGA的簡易頻率計設(shè)計

基于FPGA的簡易頻率計設(shè)計

假期著

[TOC]

小班的大作業(yè)之一.

之前做這個是因為這個是國賽題,然后上網(wǎng)找了半天的資料都是等精度測量,然后我就,手?jǐn)]了一個不太一樣的...

測頻原理

常用的數(shù)字頻率測量方法有三種

直接測量法

在給定的閘門時間(常見為1s)內(nèi)測量被測信號的脈沖個數(shù),進行換算得出被測信號的頻率。

周期測量法

通過測量被測信號一個周期時間計時信號的脈沖個數(shù),然后換算出被測信號的頻率。

這兩種測量法的精度都與被測信號有關(guān),因而它們屬于非等精度測量法。

綜合測量法

設(shè)實際閘門時間為t,被測信號周期數(shù)為Nx,則它通過測量被測信號數(shù)個周期的時間,然后換算得出被測信號的頻率,克服了測量精度對被測信號的依賴性。算法核心思想是通過閘門信號與被測信號同步,將閘門時間t控制為被測信號周期長度的整數(shù)倍。測量時,先打開預(yù)置閘門,當(dāng)檢測到被測閘門關(guān)閉時,標(biāo)準(zhǔn)信號并不立即停止計數(shù),而是等檢測到的被測信號脈沖到達是才停止,完成被測信號的整數(shù)個周期的測量。測量的實際閘門時間與預(yù)置閘門時間可能不完全相同,但最大差值不超過被測信號的一個周期。

測頻過程

由于是小班的作業(yè)講解之一(甚者他們數(shù)電還是虛空基礎(chǔ)),所以這里實現(xiàn)前兩種方法.(第三種可以聯(lián)系我拿源碼參考.

順便也寫寫我用來跑2015國賽F題的流程

整形電路

就是將待測信號整形變成計數(shù)器可以識別到的脈沖信號(方波).里面有點講究,很多人以為里面只需要實現(xiàn)一個高速比較器,但實際上為了測量各個幅值的信號,必須對信號進行限幅,AGC(自動增益控制),抬升再比較(聽朱老師說是為了降低噪聲).等各種操作啦,但是實際上硬件是由另一位小伙伴@渤兒來寫的,所以沒有太操心...好像也是有一塊芯片就可以直接實現(xiàn)上面的處理了.

FPGA整體框架

image

事先聲明:

  1. 本設(shè)計用的串口模塊從黑金大兄弟哪里改過來的...不是賣廣告,就是聲明一下...
  2. 放的代碼版本是v0.1,可粗略實現(xiàn)但未優(yōu)化未刪調(diào)試信號版,優(yōu)化會作為小班接下來的作業(yè)...
  3. 第一遍閱讀不建議閱讀源碼

端口說明

  1. ensig是外部按鍵控制腳,用來開啟閘門(調(diào)試用)
  2. sig是信號輸入腳
  3. rx是32串口返回的控制信號
  4. tx是輸出的頻率值

1s定時器

寫得有點冗余,主要是比外頭的做多了兩個操作:

  1. 沒有用異步來觸發(fā)這個定時器(不然用always @ensig 差不多就寫完了,選擇用三段式同步觸發(fā)信號
  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

直接測量法過于簡單,就不說了,就是一個計數(shù)器...
用雙邊沿其實就是想讓大家多體會體會雙邊沿移位寄存器的寫法這樣子,實際上算單邊沿就夠了.

可以看到其實我的測頻模塊和閘門信號是可以不相關(guān)的,但是由于題目有要求,我就不斷輸出這些周期數(shù),再給stm32做數(shù)據(jù)處理了,因為這樣測頻會多出很多結(jié)果用于處理.

主要做的工作有:

  1. 用PLL例化了一個200M的時鐘,用于計數(shù)和檢測邊沿
  2. 寫了個雙邊沿觸發(fā)的移位寄存器
  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位計數(shù)器
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部分

負(fù)責(zé)顯示屏顯示和將fpga輸出的周期轉(zhuǎn)換成頻率,由于串口使用黑金的,stm32不是我負(fù)責(zé)的,所以這里就不詳述了.

結(jié)語

如您是不小心看了源碼,然后心煩意燥滑到最后的,不妨不看源碼再看看字和圖...畢竟是初代的源碼,沒精簡過(甚至人為設(shè)計了語法錯誤)

可以看到,其實他的原理極其簡單,適合新手入門,和熟悉verilog語法,小班的同學(xué)和大三的老狗們好像都要開始學(xué)這門語言了,不妨結(jié)合夏宇聞教授的書來嘗試一下

小班進階作業(yè):理解源碼(已貼心地為您刪掉大部分源碼),刪去冗余邏輯和錯誤部分

新年第一更,不過下年要考研,就可能要開始斷更或者只發(fā)一些課外拓展咯.(無奈

想我盡早更新的方法之一

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,534評論 6 13
  • 專業(yè)考題類型管理運行工作負(fù)責(zé)人一般作業(yè)考題內(nèi)容選項A選項B選項C選項D選項E選項F正確答案 變電單選GYSZ本規(guī)程...
    小白兔去釣魚閱讀 10,694評論 0 13
  • 參加小白理財訓(xùn)練營的助教已經(jīng)很長時間了,之前只是批改學(xué)員們的作業(yè),這次申請了實習(xí)班主任的職位。需要在QQ群里分享并...
    溫妮小世界閱讀 419評論 0 0
  • 一早醒來,沒得由來的全身疲軟,耷拉著腦袋有點抗拒今天還得去上班的現(xiàn)實,穿好拖鞋站立在鏡子前伸胳膊伸腿做了一會拉伸也...
    簡小取閱讀 673評論 23 9
  • 準(zhǔn)備數(shù)據(jù) 轉(zhuǎn)換 輸出結(jié)果
    kikiki2閱讀 123評論 0 1

友情鏈接更多精彩內(nèi)容