計:原理、Verilog實現(xiàn)與調(diào)試實戰(zhàn))
做了快十年測量相關(guān)的嵌入式項目我最大的一個感受是頻率計這東西原理看著簡單做起來全是坑。尤其是一開始我用最常規(guī)的“直接計數(shù)法”寫了個FPGA頻率計低頻段測出來慘不忍睹1 kHz信號在1秒閘門里只能數(shù)到幾千個脈沖分辨率差到?jīng)]法看。后來改成等精度頻率計reciprocal frequency counter才把1 Hz到幾十MHz的測量精度真正拉平。這篇文章就把我當(dāng)時從原理分析、Verilog模塊劃分到硬件調(diào)試翻車的全過程完整復(fù)盤一遍。工程里幾個核心模塊的源碼我會貼在后面給正在做頻率計類課程設(shè)計、或者想在自己系統(tǒng)里加一個高精度測頻模塊的朋友做個參考。內(nèi)容是項目實戰(zhàn)向的不會只丟給你一堆代碼重點會放在“為什么這樣設(shè)計”和“調(diào)試時到底在查什么”上。1. 直測法為什么測不快閘門時間與量化誤差同漲同跌很多人一開始接觸頻率計用的都是最樸素的思路給一個1秒的閘門信號數(shù)這1秒內(nèi)被測信號來了多少個上升沿頻率自然就是計數(shù)值。原理聽著沒問題可一旦真的拿去做寬范圍測量馬上就會發(fā)現(xiàn)這個方案有硬傷。1.1 直接計數(shù)法的“兩頭吃虧”直接計數(shù)法的公式是fx N / Tgate其中N是閘門內(nèi)被測脈沖個數(shù)。這個方案在測高頻時很舒服比如測10 MHz信號1秒閘門里能數(shù)到10,000,000個脈沖分辨率和精度都不錯。但測低頻就尷尬了測一個1 kHz的信號1秒閘門里只有1000個脈沖分辨率直接掉到1 Hz量級測1 Hz信號1秒里只能數(shù)到1個脈沖誤差直接是100%甚至測不出來。那有人說我反過來用“周期法”行不行用基準(zhǔn)時鐘去量被測信號一個周期有多長再取倒數(shù)。這個方案測低頻確實很準(zhǔn)但測高頻時一個周期只有幾十納秒基準(zhǔn)時鐘也數(shù)不了幾個數(shù)誤差又上來了。所以直接計數(shù)法和周期法本質(zhì)上是一回事只是把“數(shù)脈沖”和“數(shù)周期”這兩件事放到了不同頻率段。無論你用哪種都會遇到“高頻好用低頻崩、低頻好使高頻廢”的問題。根因在哪兒在于計數(shù)對象本身太不穩(wěn)定——高頻時脈沖太密低頻時脈沖太稀一個閘門里永遠(yuǎn)只有一小段有效信息。1.2 量化誤差的數(shù)學(xué)本質(zhì)1/N不可逃如果非要用一句話概括直接計數(shù)法為什么不行那就是它的量化誤差公式太苛刻Δfx / fx ±1 / NN是閘門內(nèi)測到的脈沖數(shù)。要提高精度只能加大閘門時間或者倍頻被測信號。比如測1 kHz信號想要萬分之一誤差N至少要10,000也就是閘門至少10秒想要十萬分之一就得等100秒。這在工程上基本不可接受。我當(dāng)時算完這個公式第一反應(yīng)是能不能想辦法讓閘門內(nèi)的N永遠(yuǎn)足夠大換句話說不管被測頻率是1 Hz還是10 MHz我能不能都讓閘門內(nèi)被測信號剛好包含一個非常大的“可數(shù)脈沖數(shù)”這就是等精度測頻的核心出發(fā)點我不再直接數(shù)被測信號本身有多密而是把被測信號當(dāng)作閘門的“標(biāo)尺”用它的整數(shù)個周期去卡基準(zhǔn)時鐘。1.3 換一個思路讓被測信號自己決定測量窗口等精度測頻的關(guān)鍵設(shè)計思想就是讓測量窗口的開啟和關(guān)閉都發(fā)生在被測信號的上升沿上。預(yù)置一個“大致閘門時間”比如1秒但實際計數(shù)的窗口是從預(yù)置閘門開始后的第一個被測信號上升沿開始到預(yù)置閘門結(jié)束后的第一個被測信號上升沿截止。這樣一來實際測量的窗口天然就是被測信號周期的整數(shù)倍。在這個窗口里被測信號被數(shù)了多少個上升沿就是一個精確值沒有±1的量化誤差真正存在量化誤差的只?;鶞?zhǔn)時鐘在那個窗口里數(shù)出來的脈沖數(shù)。因為基準(zhǔn)時鐘通常非常高100 MHz、200 MHz甚至更高窗口哪怕是0.5秒基準(zhǔn)時鐘也能數(shù)到5000萬個數(shù)量化誤差就被壓到了天花板級別。2. 等精度測頻的系統(tǒng)結(jié)構(gòu)與同步使能設(shè)計理清了原理接下來就是怎么用FPGA把這套邏輯落地。這里有一個必須想清楚的點預(yù)置閘門和實際計數(shù)閘門不是同一個信號。很多人寫代碼時直接把“1秒閘門”拿來當(dāng)計數(shù)使能那就又變成直接計數(shù)法了。2.1 頂層數(shù)據(jù)流三個模塊之間的握手關(guān)系等精度頻率計在FPGA里至少需要幾個核心部分預(yù)置閘門發(fā)生器、被測信號邊沿同步檢測、基準(zhǔn)時鐘計數(shù)器、被測信號計數(shù)器、數(shù)據(jù)鎖存與計算、顯示輸出。它們之間的數(shù)據(jù)流關(guān)系大概是這樣預(yù)置閘門發(fā)生器產(chǎn)生一個寬度可配置的pretime_gate信號本質(zhì)就是一個按基準(zhǔn)時鐘計數(shù)的定時窗口。被測信號clk_fx_in進(jìn)入FPGA后先經(jīng)過兩級同步寄存器消除亞穩(wěn)態(tài)再做上升沿檢測產(chǎn)生一個單周期的脈沖信號sig_rise。核心的狀態(tài)機(jī)負(fù)責(zé)生成真正的計數(shù)使能gate_en當(dāng)pretime_gate為高之后等待第一個sig_rise然后立刻拉高gate_en當(dāng)pretime_gate變低之后繼續(xù)等到一個sig_rise再拉低gate_en?;鶞?zhǔn)計數(shù)器在gate_en期間對基準(zhǔn)時鐘計數(shù)得到N0被測信號計數(shù)器在gate_en期間對sig_rise脈沖計數(shù)得到Nx。gate_en下降沿到來時把N0和Nx同時鎖存進(jìn)寄存器然后輸出fx Nx × f0 / N0。這套流程里最關(guān)鍵的就是第3步。gate_en的開啟和關(guān)閉完全由被測信號的上升沿來“框定”而不是由預(yù)置閘門直接框定。這也是“等精度”三個字的核心來源。2.2 參考時鐘的穩(wěn)定度決定了精度的天花板等精度測頻雖然消除了計數(shù)法對低頻信號的不友好但它并沒有消除對參考時鐘的依賴。你可以把fx Nx × f0 / N0里的f0理解成一個“乘法比例因子”基準(zhǔn)時鐘只要自己漂了測量結(jié)果一定跟著漂。所以工程上有一個優(yōu)先級參考時鐘的穩(wěn)定度 代碼邏輯的正確性。我當(dāng)時用的是開發(fā)板上板載的普通晶振頻率穩(wěn)定度大概是幾十ppm量級配合FPGA內(nèi)部PLL倍頻到200 MHz做參考時鐘。這樣做出來的精度已經(jīng)能到10^-7量級左右。如果產(chǎn)品里要求更高下一步就是換溫補(bǔ)晶振TCXO或者恒溫晶振OCXO把設(shè)備本身的時基穩(wěn)定度拉上去。有一個容易踩的坑不要拿被測信號自己做過零檢測形成的“邊沿”去當(dāng)時鐘信號再用這個時鐘去驅(qū)動計數(shù)器邏輯。原因有兩個一是被測信號本身可能有噪聲沿位置抖動會直接污染計數(shù)二是跨時鐘域握手很容易引入亞穩(wěn)態(tài)。更穩(wěn)妥的做法是把被測信號只當(dāng)作一個“異步輸入”在參考時鐘域內(nèi)同步化之后作為脈沖使能去加計數(shù)而不是作為真正的時鐘源。2.3 預(yù)置閘門發(fā)生器與邊沿檢測模塊預(yù)置閘門發(fā)生器實現(xiàn)起來很簡單本質(zhì)就是按參考時鐘計數(shù)的可配置定時器。以100 MHz參考時鐘為例想要0.1秒閘門就數(shù)10,000,000個時鐘周期想要1秒閘門就數(shù)100,000,000個周期。module gate_gen ( input wire clk_ref, // 參考時鐘例如100MHz input wire rst_n, input wire [31:0] gate_cycles, // 預(yù)置閘門對應(yīng)的時鐘周期數(shù) output reg pretime_gate ); reg [31:0] cnt; always (posedge clk_ref or negedge rst_n) begin if (!rst_n) begin cnt 32d0; pretime_gate 1b0; end else if (cnt gate_cycles - 1) begin cnt 32d0; pretime_gate ~pretime_gate; // 翻轉(zhuǎn)形成高/低閘門 end else begin cnt cnt 1b1; end end endmodule注意這里我把預(yù)置閘門做成了周期翻轉(zhuǎn)模式預(yù)置閘門的高電平寬度就是測量窗口的“大致時間”低電平寬度也相同。這樣連續(xù)測量時高電平結(jié)束一次就產(chǎn)生一組測量結(jié)果低電平期間可以用來做數(shù)據(jù)鎖存和顯示刷新給了下游時間余量。邊沿檢測模塊同樣不復(fù)雜但細(xì)節(jié)值得多說一句module edge_sync ( input wire clk_ref, input wire rst_n, input wire sig_in, output reg sig_r2, output wire sig_rise ); reg sig_r1; always (posedge clk_ref or negedge rst_n) begin if (!rst_n) begin sig_r1 1b0; sig_r2 1b0; end else begin sig_r1 sig_in; sig_r2 sig_r1; end end assign sig_rise sig_r2 ~sig_r1; // 檢測sig_r2的上升沿 endmodule兩次寄存器的目的是把被測信號同步到參考時鐘域避免第一級寄存器采到變化的信號時出現(xiàn)亞穩(wěn)態(tài)。這里有一個很多教程不會寫、但實際非常關(guān)鍵的細(xì)節(jié)邊沿檢測用的是sig_r2和~sig_r1也就是檢測同步寄存器的輸出沿而不是直接對原始輸入做邊沿檢測。如果直接拿原始輸入做邊沿檢測那個沿和參考時鐘沒有任何同步關(guān)系極容易在后續(xù)組合邏輯里產(chǎn)生毛刺。2.4 計數(shù)器與鎖存對被測信號計數(shù)不是拿它當(dāng)clk接下來是計數(shù)器部分。最經(jīng)典的做法有兩種一種是真的把被測信號直接接到一個計數(shù)器的時鐘端另一種是把同步化以后的脈沖信號當(dāng)作計數(shù)使能。我強(qiáng)烈建議用后一種也就是被測信號的同步脈沖作為cnt_fx的加1使能而不是作為時鐘。原因有兩點。第一被測信號直接進(jìn)全局時鐘網(wǎng)絡(luò)會占用寶貴的時鐘資源而且信號質(zhì)量不可控第二如果有多個計數(shù)器需要在同一測量窗口內(nèi)同時工作拿異步信號當(dāng)時鐘會導(dǎo)致不同模塊之間的時序關(guān)系非常難約束。而統(tǒng)一在clk_ref域內(nèi)做脈沖使能計數(shù)整個邏輯都是單一時鐘域RTL一眼就能看穿時序關(guān)系。計數(shù)使能gate_en的產(chǎn)生可以用一段簡單的狀態(tài)機(jī)來實現(xiàn)預(yù)置閘門為高后等待第一個上升沿再開啟計數(shù)器預(yù)置閘門為低后等待下一個上升沿再關(guān)閉計數(shù)器。gate_en下降沿同時產(chǎn)生一個鎖存脈沖把N0和Nx寫入寄存器。這里值得注意的一個工程點是鎖存時序必須走寄存器同步不要用組合邏輯去直接觸發(fā)鎖存。很多第一次寫頻率計的人直接在賦值語句里讓鎖存器在gate_en下降沿讀數(shù)據(jù)結(jié)果下游計算模塊拿到的是還沒有穩(wěn)定的中間值顯示數(shù)字亂跳。正確做法是鎖存脈沖采用獨立的寄存信號在sig_rise確認(rèn)關(guān)閉窗口之后再打一拍確保N0和Nx都已經(jīng)完成最后一次遞增。3. Verilog模塊劃分與關(guān)鍵代碼同步使能邏輯是靈魂這一章我直接把工程里的模塊清單和幾個核心代碼片段整理出來。完整的工程除了下面這些模塊還包括數(shù)碼管掃描顯示、按鍵量程切換和簡單濾波邏輯但最核心的測頻部分基本都在這里了。3.1 工程文件清單與模塊分工文件/模塊職責(zé)備注freq_meter_top頂層模塊完成信號例化與端口連接包含輸入引腳、按鍵、數(shù)碼管接口gate_gen預(yù)置閘門發(fā)生器寬度通過gate_cycles配置sync_gate_ctrl實際計數(shù)閘門狀態(tài)機(jī)根據(jù)預(yù)置門和sig_rise產(chǎn)生gate_enedge_sync被測信號雙級同步 上升沿檢測所有跨時鐘域信號都經(jīng)過這里counter_bankN0/Nx計數(shù)器與鎖存寄存器鎖存脈沖由sync_gate_ctrl給出calc_result用整數(shù)運算計算顯示頻率值實際項目里我把計算放在了后級顯示模塊seg_display7段數(shù)碼管動態(tài)掃描顯示8位有效數(shù)字的顯示緩沖這個模塊劃分是我反復(fù)調(diào)過之后的版本。最開始版本里我把預(yù)置閘門狀態(tài)機(jī)和邊沿檢測混在一個模塊里結(jié)果調(diào)試的時候一個問題不知道是出在閘門狀態(tài)機(jī)還是出在同步邏輯上非常難定位。切成獨立模塊以后每個模塊都可以單獨用仿真波形驗證排查問題效率高很多。3.2 實際計數(shù)使能狀態(tài)機(jī)的代碼邏輯這一段是等精度頻率計的“靈魂”所在。我把它獨立成一個模塊就是為了讓那幾步“等一個被測沿再開/關(guān)門”的邏輯夠清晰module sync_gate_ctrl ( input wire clk_ref, input wire rst_n, input wire pretime_gate, input wire sig_rise, output reg gate_en, output reg lock_pulse ); localparam IDLE 2d0, OPEN 2d1, WAIT_CLOSE 2d2; reg [1:0] state; wire open_ready pretime_gate sig_rise; // 預(yù)置門高且被測沿到來 wire close_ready ~pretime_gate sig_rise; // 預(yù)置門低且被測沿到來 always (posedge clk_ref or negedge rst_n) begin if (!rst_n) begin state IDLE; gate_en 1b0; lock_pulse 1b0; end else begin lock_pulse 1b0; case (state) IDLE: begin gate_en 1b0; if (open_ready) state OPEN; end OPEN: begin gate_en 1b1; if (close_ready) begin gate_en 1b0; state IDLE; lock_pulse 1b1; end end endcase end end endmodule這個狀態(tài)機(jī)的流程說白了就是“按信號節(jié)奏開門、按信號節(jié)奏關(guān)門”。lock_pulse會在關(guān)門那一刻打一拍給下游鎖存器提供一個明確的寫使能。你會看到這里面沒有專門為“預(yù)置門剛變高、第一個沿還沒來”設(shè)置暫停狀態(tài)因為open_ready本身就是“預(yù)置門為高且被測沿到來”的組合條件只要當(dāng)前是IDLE拉高gate_en的時機(jī)就是預(yù)置門高電平期間的第一個被測沿完全符合需求。3.3 數(shù)據(jù)計算Nx、N0與f0之間的關(guān)系當(dāng)lock_pulse有效時counter_bank里已經(jīng)寄存了Nx和N0。計算頻率的公式是fx Nx × f0 / N0比如參考時鐘f0 100 MHz某次測量Nx 500,000N0 50,000,000那么fx 500,000 × 100,000,000 / 50,000,000 1,000,000 Hz也就是1 MHz。在FPGA里做這個除法工程量不大但需要想清楚數(shù)據(jù)位寬。N0最大可能到幾千萬甚至上億Nx也同樣可能到幾百上千萬所以至少要用32位計數(shù)器。而計算頻率時如果直接做64位乘法再除以N0綜合出來的資源也不會太大因為這是一次性的低速計算不是每個時鐘周期都跑。我實際用的是更省資源的方式給顯示模塊傳原始Nx和N0然后在顯示模塊里用整數(shù)除法配合移位把結(jié)果轉(zhuǎn)成BCD碼給數(shù)碼管。畢竟頻率計顯示的刷新率也就每秒幾十次用“慢速除法器 狀態(tài)機(jī)”完全夠用沒必要為這個功能專門上一個硬件除法器IP核。3.4 顯示刷新與測量節(jié)奏的配合這里有個容易被忽略的工程細(xì)節(jié)顯示刷新頻率和測量閘門時長必須分開考慮。如果測量一次需要1秒數(shù)碼管卻每2毫秒掃描一次那不是問題但如果顯示模塊在測量窗口尚未結(jié)束時就去讀計數(shù)器的當(dāng)前值看到的就是一堆還在跳變的數(shù)字看起來就像頻率計“不穩(wěn)定”。我的做法是在lock_pulse到來后先把Nx、N0鎖存進(jìn)一個“顯示數(shù)據(jù)緩沖”顯示模塊每2毫秒從緩沖里讀一次而不是直接讀計數(shù)器。這樣顯示出來的數(shù)字就是上一次測量已凍結(jié)的結(jié)果完全不會閃跳。加上動態(tài)掃描數(shù)碼管本身有刷新頻率要求這個緩沖區(qū)的價值就體現(xiàn)出來了測量模塊和顯示模塊徹底解耦。4. 調(diào)試實錄自檢全對、外接信號卻偏差一個數(shù)量級的排查鏈路代碼寫完之后我原以為按原理圖接好線、編譯下載就能收工。結(jié)果真正上板調(diào)試的時候被一個現(xiàn)象折磨了一個下午用開發(fā)板自帶的信號測精度非常好換上外部信號發(fā)生器的信號低頻段誤差直接差了一個數(shù)量級。這個坑非常典型我把完整排查鏈路寫出來希望你能少走點彎路。4.1 先復(fù)現(xiàn)現(xiàn)象自檢源和外接源的對比開發(fā)板上一般會有一個由板上晶振分頻出來的固定信號比如10 MHz、5 MHz之類。我用自檢源測無論怎么換預(yù)置閘門誤差都穩(wěn)在10^-7量級數(shù)據(jù)非常漂亮。然后我把信號發(fā)生器輸出調(diào)到1 kHz方波接進(jìn)同一個輸入引腳結(jié)果測出來的頻率顯示成了1.024 kHz左右偏差超過百分之二。1024這個數(shù)字太顯眼了讓我立刻懷疑是不是計數(shù)器多計了24個脈沖。進(jìn)一步測試把信號發(fā)生器輸出調(diào)到100 kHz偏差變成了大約0.6 Hz調(diào)到10 MHz偏差又變得很小。很明顯偏差集中發(fā)生在輸入頻率較低、邊沿較緩的區(qū)間。這和代碼本身的量化誤差規(guī)律對不上因為如果是量化誤差頻率越低應(yīng)該越準(zhǔn)才對。于是我開始懷疑輸入通路本身在搞鬼。4.2 排查方向一自檢源為什么“騙人”第一反應(yīng)我想查是不是信號發(fā)生器的電平標(biāo)準(zhǔn)不匹配。因為開發(fā)板輸入引腳一般是3.3V LVCMOS標(biāo)準(zhǔn)而信號發(fā)生器輸出我習(xí)慣性設(shè)成了50歐姆負(fù)載下的高電平擺幅可能只有0到2V閾值判斷時可能會出現(xiàn)不穩(wěn)定的邏輯翻轉(zhuǎn)。但這里有個更深的原因自檢源和參考時鐘來自同一顆晶振、同一個時鐘樹它們之間的相位關(guān)系是固定的測量自然準(zhǔn)。外接信號發(fā)生器和參考晶振是兩套獨立時基一旦輸入信號邊沿不夠陡或者電平不夠干凈跨時鐘域同步時就會出問題。所以自檢源通過只能證明代碼邏輯基本正確完全不能證明輸入通路可靠。排查的第一步是把信號發(fā)生器輸出調(diào)到CMOS電平標(biāo)準(zhǔn)、占空比50%、上升沿時間壓到最低重新測。結(jié)果偏差確實縮小了但沒完全消除。這說明問題不止在電平擺幅上。4.3 排查方向二輸入整形電路的遲滯與噪聲看示波器波形問題變得很直觀信號發(fā)生器輸出的方波在1 kHz時經(jīng)過連接線和板上走線后上升沿帶有明顯的振鈴。在FPGA輸入緩沖器的翻轉(zhuǎn)閾值附近信號可能來回穿過閾值好幾次也就是所謂的“多次翻轉(zhuǎn)”。每一次超過閾值edge_sync都當(dāng)成一個上升沿于是Nx憑空多出幾十個甚至上百個。我原來的輸入電路非常簡單信號進(jìn)來直接接FPGA引腳靠引腳內(nèi)部的上拉/下拉和施密特觸發(fā)器整一下形。低頻率下信噪比不足時這種簡單整形根本壓不住振鈴。解決方案是做一個小型輸入調(diào)理前端信號進(jìn)來先經(jīng)過高速比較器比較器加一點正反饋電阻構(gòu)成遲滯再把比較器輸出接到FPGA引腳。遲滯的寬度大約設(shè)成幾十毫伏保證噪聲無法在閾值附近反復(fù)觸發(fā)。改完之后1 kHz信號讀到的Nx從“多出24個”變成了“完全不差”。這也是我在實際工程里最強(qiáng)烈的一個建議外接信號做頻率計輸入調(diào)理電路不是選項是必需品。4.4 排查方向三跨時鐘域同步寄存器的位置是否放對繼續(xù)深挖我還發(fā)現(xiàn)一個隱藏比較深的問題。最早的代碼里我是把sig_in先接到計數(shù)器的時鐘端然后再在計數(shù)器內(nèi)部做同步。這樣即便同步邏輯本身沒問題計數(shù)器時鐘端接收到的脈沖仍然來自外部信號域gate_en和clk_ref之間的時鐘關(guān)系在綜合時序分析里是沒法好好約束的。按照我前面建議的做法把edge_sync放在輸入引腳之后、計數(shù)邏輯之前會帶來一個立竿見影的好處整個計數(shù)邏輯完全在參考時鐘域里所有時序約束都變得清晰可查。改完之后我直接在時序報告里看到所有路徑都有明確的setup/hold余量而不是一堆“clock crossing warning”。這個改動和遲滯整形加在一起才算真正把輸入通路的問題清零。4.5 排查方向四測量窗口寬度與實際能達(dá)到的分辨率還有一個偏“參數(shù)配置”的坑。不到萬不得已不要為了追求刷新速度把預(yù)置閘門設(shè)得太短。等精度測頻的理論量化誤差是|Δfx| / fx ≈ ±1 / (f0 × T實際)如果參考時鐘是100 MHz實際測量窗口是1毫秒那么誤差就只有10^-5量級看起來還行。但如果你用的是50 MHz參考時鐘、又開了10毫秒的短閘門誤差會掉到2×10^-5配合輸入整形不干凈顯示結(jié)果就會在最后一位上不停抖動。我當(dāng)時為了調(diào)試方便把預(yù)置門設(shè)成了10毫秒結(jié)果發(fā)現(xiàn)測1 kHz信號的顯示結(jié)果總是在最后2到3位跳動。后來我加了一檔“長測”模式把預(yù)置門放大到1秒安定下來之后讀數(shù)穩(wěn)如磐石。經(jīng)驗是調(diào)試階段用短閘門看邏輯通不通驗收階段用長閘門看精度夠不夠。5. 測量時間、顯示位寬與實用擴(kuò)展精度不是唯一指標(biāo)等精度頻率計做到這一步核心功能已經(jīng)穩(wěn)定了。但真正拿到項目里去用還有一些工程取舍需要想清楚。我把我在實測中積累的參數(shù)選擇經(jīng)驗和擴(kuò)展思路也整理在下面。5.1 不同測量閘門下理論和實際能達(dá)到的精度以100 MHz參考時鐘為例不同預(yù)置閘門下理論量化誤差大約如下表預(yù)置閘門實際測量窗口約N0數(shù)量級理論量化誤差10 ms10 ms10^610^-6100 ms100 ms10^710^-71 s1 s10^810^-8這只是一次測量的量化誤差。放在實際環(huán)境里晶振本身的頻率穩(wěn)定度、輸入信號邊沿抖動、溫度漂移都會疊加進(jìn)來。我用普通晶振做參考時鐘測1 kHz信號1秒閘門下的實測重復(fù)性大約在10^-7到10^-8之間也就是說顯示出來的最后兩位偶爾會跳一下但前6位非常穩(wěn)定。這就完全滿足一般工程測量了。所以更實用的建議是如果設(shè)備工作在實驗室環(huán)境、測量對象是靜止頻率采用1秒閘門如果是動態(tài)掃頻、需要快速響應(yīng)的場景100毫秒閘門配合適當(dāng)位寬限制比硬撐1秒閘門更好用。5.2 顯示位寬與刷新速度的權(quán)衡8位數(shù)碼管實際能穩(wěn)定顯示的位數(shù)受限于閘門時長。1秒閘門下100 MHz參考時鐘能分辨到10^-8顯示8位有效數(shù)字沒問題100毫秒閘門下最多穩(wěn)定顯示6位。如果你在100毫秒閘門下強(qiáng)行顯示8位最后兩位就是純粹的數(shù)字噪聲除了讓讀數(shù)變“花哨”以外毫無意義。我在設(shè)計里加了一個簡單的位寬控制按鍵循環(huán)切換“快速模式”100毫秒閘門顯示6位和“精確模式”1秒閘門顯示8位。這個交互設(shè)計簡單粗暴但非常實用。測量儀器最忌諱的就是給用戶看一堆沒有意義的變化數(shù)字有效數(shù)字個數(shù)必須和實際測量不確定度匹配。另外如果你希望把數(shù)據(jù)傳到上位機(jī)完全可以把Nx和N0通過串口發(fā)出來讓上位機(jī)做浮點計算。FPGA這邊只做計數(shù)計算交給上位機(jī)既省邏輯資源又減少了在FPGA里做除法帶來的位寬和舍入問題。5.3 可擴(kuò)展的方向周期、脈寬與占空比等精度測頻的本質(zhì)是用被測信號的沿作為測量窗口的“標(biāo)尺”。沿著這個思路你能做的遠(yuǎn)不止頻率。比如把gate_en改成“測量連續(xù)兩個上升沿之間的時間差”就是高精度的周期測量把測量窗口改成“上升沿到下降沿”就是脈寬測量再結(jié)合頻率測量占空比也就出來了。我在這個工程之后把同一套同步計數(shù)結(jié)構(gòu)復(fù)用到了一臺簡易脈沖參數(shù)分析儀里改動很小核心的edge_sync和sync_gate_ctrl模塊基本原封不動。這也是FPGA可重構(gòu)性的典型體現(xiàn)一套穩(wěn)定的時序骨架換一下計數(shù)目標(biāo)和狀態(tài)機(jī)約束就能衍生出好幾種測量功能。6. 關(guān)于調(diào)試工具和波形觀察的一點經(jīng)驗結(jié)構(gòu)部分基本講完了最后補(bǔ)一點關(guān)于調(diào)試工具的心得。雖然代碼邏輯可以通過仿真驗證但等精度頻率計的很多問題只有接通硬件之后才能暴露。尤其輸入通路上的信號完整性問題仿真里根本不可能出現(xiàn)。我的調(diào)試流程一般是先用一個干凈的方波信號源從1 Hz掃到幾十MHz每個數(shù)量級選幾個點用上位機(jī)或者數(shù)碼管人工記錄讀數(shù)和信號源設(shè)定值對比畫出誤差曲線。一旦某個頻段誤差突然變大立刻用示波器看那個頻段的輸入波形。大多數(shù)時候問題都出在邊沿不夠陡、振鈴過大或者觸發(fā)電平不對上。調(diào)完輸入整形電路誤差曲線基本能做到全程平直。如果你做的是課程設(shè)計沒有專門的信號發(fā)生器也可以用另一塊FPGA板輸出頻率可調(diào)的方波來當(dāng)信號源。關(guān)鍵是保證輸出的信號邊沿干凈盡量用邏輯電平輸出而不要用灌電流驅(qū)動長線。等精度測頻這個主題聽起來是教科書里的經(jīng)典電路但真正寫到FPGA里、跑到真實信號上才發(fā)現(xiàn)原理理解和工程實現(xiàn)之間隔著整整一條“調(diào)試?!?。如果你正在被頻率計的顯示亂跳、低頻測不準(zhǔn)、高頻毛刺這些問題折磨希望這篇復(fù)盤能幫你少走點彎路。