基于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整體框架

事先聲明:
- 本設(shè)計用的串口模塊從黑金大兄弟哪里改過來的...不是賣廣告,就是聲明一下...
- 放的代碼版本是v0.1,可粗略實現(xiàn)但未優(yōu)化未刪調(diào)試信號版,優(yōu)化會作為小班接下來的作業(yè)...
- 第一遍閱讀不建議閱讀源碼
端口說明
- ensig是外部按鍵控制腳,用來開啟閘門(調(diào)試用)
- sig是信號輸入腳
- rx是32串口返回的控制信號
- tx是輸出的頻率值
1s定時器
寫得有點冗余,主要是比外頭的做多了兩個操作:
- 沒有用異步來觸發(fā)這個定時器(不然用always @ensig 差不多就寫完了,選擇用三段式同步觸發(fā)信號
- 隔離了兩條控制線(信號),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
測頻模塊

直接測量法過于簡單,就不說了,就是一個計數(shù)器...
用雙邊沿其實就是想讓大家多體會體會雙邊沿移位寄存器的寫法這樣子,實際上算單邊沿就夠了.
可以看到其實我的測頻模塊和閘門信號是可以不相關(guān)的,但是由于題目有要求,我就不斷輸出這些周期數(shù),再給stm32做數(shù)據(jù)處理了,因為這樣測頻會多出很多結(jié)果用于處理.
主要做的工作有:
- 用PLL例化了一個200M的時鐘,用于計數(shù)和檢測邊沿
- 寫了個雙邊沿觸發(fā)的移位寄存器
- 用了二級流水線算周期(就是上面那條式子)
- 寫了個占空比測量的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ā)一些課外拓展咯.(無奈