量模塊設(shè)計(jì))
最近幫朋友調(diào)試一臺(tái)設(shè)備輸出的一路方波信號(hào)頻率很低大概在幾十赫茲到幾十千赫茲之間來(lái)回變占空比還會(huì)跟著工況變化。手頭沒(méi)有頻率計(jì)示波器雖然能看波形但總不能一直讓人盯著屏幕讀數(shù)據(jù)。我干脆用手里一塊閑置的FPGA開(kāi)發(fā)板寫(xiě)了一個(gè)低頻方波測(cè)量模塊把頻率和占空比直接測(cè)出來(lái)顯示在數(shù)碼管上順便通過(guò)串口發(fā)給上位機(jī)。做完之后發(fā)現(xiàn)這個(gè)需求其實(shí)非常典型很多做傳感器、電機(jī)驅(qū)動(dòng)、電源控制的人都會(huì)遇到所以把這個(gè)項(xiàng)目的完整思路、代碼結(jié)構(gòu)、仿真方法和踩過(guò)的坑整理出來(lái)。這個(gè)方案的核心思路是用FPGA內(nèi)部的高頻基準(zhǔn)時(shí)鐘去“數(shù)”輸入方波一個(gè)周期內(nèi)跑了多少拍以及高電平期間跑了多少拍然后反算出頻率和占空比。聽(tīng)上去不復(fù)雜但真正做起來(lái)有幾個(gè)容易翻車(chē)的地方輸入信號(hào)怎么整形、計(jì)數(shù)器位寬怎么定、除法怎么做、數(shù)碼管怎么刷新不閃爍、低頻信號(hào)仿真花的時(shí)間太長(zhǎng)怎么辦。這篇文章會(huì)一個(gè)一個(gè)說(shuō)清楚并且給出可以直接拿來(lái)改的Verilog代碼框架。適合剛?cè)腴T(mén)FPGA、想做一個(gè)完整小項(xiàng)目的朋友也適合已經(jīng)在做信號(hào)測(cè)量、想了解測(cè)周期法和測(cè)頻法區(qū)別的工程師。1. 需求拆解與方案選型1.1 低頻方波測(cè)量到底難在哪如果只測(cè)頻率很多人第一反應(yīng)是“數(shù)脈沖不就行了”開(kāi)一個(gè)固定時(shí)間窗口比如1秒統(tǒng)計(jì)這個(gè)窗口內(nèi)來(lái)了多少個(gè)上升沿頻率就是計(jì)數(shù)結(jié)果。這個(gè)方法叫測(cè)頻法原理很簡(jiǎn)單但對(duì)低頻信號(hào)非常不友好。假設(shè)被測(cè)信號(hào)是10Hz1秒窗口內(nèi)只能數(shù)到10個(gè)脈沖計(jì)數(shù)結(jié)果稍微波動(dòng)一下顯示就變成9Hz或11Hz誤差直接到10%。就算把窗口時(shí)間拉長(zhǎng)到10秒分辨率也只能到0.1Hz而且刷新速度慢得讓人抓狂。低頻方波的周期長(zhǎng)這是它的物理特性決定的想要用固定窗口測(cè)頻法拿到高分辨率代價(jià)非常大。占空比測(cè)量就更麻煩一點(diǎn)。占空比等于高電平時(shí)間除以整個(gè)周期時(shí)間要得到這兩個(gè)量必須精確知道信號(hào)什么時(shí)候變高、什么時(shí)候變低。如果信號(hào)邊沿不干凈帶著毛刺或者上升沿很緩計(jì)數(shù)結(jié)果就會(huì)忽大忽小顯示值跳得沒(méi)法看。所以這個(gè)項(xiàng)目真正要解決的不是“怎么數(shù)數(shù)”這種簡(jiǎn)單問(wèn)題而是三個(gè)層面的問(wèn)題信號(hào)進(jìn)來(lái)之后怎么處理才能讓計(jì)數(shù)器穩(wěn)定工作計(jì)數(shù)器和計(jì)算邏輯怎么設(shè)計(jì)才能保證精度結(jié)果顯示怎么做才能既直觀又不閃爍。1.2 為什么選測(cè)周期法而不是測(cè)頻法既然固定窗口測(cè)頻法不適合低頻那就反過(guò)來(lái)想不數(shù)“一個(gè)窗口內(nèi)有多少個(gè)被測(cè)信號(hào)周期”而是數(shù)“一個(gè)被測(cè)信號(hào)周期內(nèi)有多少個(gè)基準(zhǔn)時(shí)鐘周期”。這就是測(cè)周期法。具體做法是FPGA內(nèi)部跑一個(gè)高頻基準(zhǔn)時(shí)鐘比如100MHz。被測(cè)信號(hào)一個(gè)完整的周期內(nèi)基準(zhǔn)時(shí)鐘會(huì)走過(guò)很多個(gè)時(shí)鐘沿用計(jì)數(shù)器把這些時(shí)鐘沿?cái)?shù)出來(lái)記作N。那么被測(cè)信號(hào)的周期就是N乘以基準(zhǔn)時(shí)鐘周期頻率就是基準(zhǔn)時(shí)鐘頻率除以N頻率 100MHz / N舉個(gè)例子被測(cè)信號(hào)是1kHz時(shí)周期是1ms100MHz時(shí)鐘在一個(gè)周期內(nèi)能數(shù)到100000拍。用這個(gè)計(jì)數(shù)值反推頻率分辨率遠(yuǎn)高于測(cè)頻法。而且被測(cè)信號(hào)頻率越低一個(gè)周期內(nèi)數(shù)到的拍數(shù)越多測(cè)量精度越高。這和固定窗口測(cè)頻法正好相反天生就是為低頻信號(hào)準(zhǔn)備的。我這次把目標(biāo)測(cè)量范圍定在1Hz到100kHz占空比范圍1%到99%。在這個(gè)范圍內(nèi)測(cè)周期法的精度完全夠用。如果被測(cè)信號(hào)頻率很高比如幾兆赫茲以上才需要考慮等精度測(cè)量法或者純測(cè)頻法。對(duì)于低頻方波測(cè)量這個(gè)題目測(cè)周期法是最合適的方案沒(méi)有之一。1.3 設(shè)計(jì)指標(biāo)與整體架構(gòu)動(dòng)手之前先定了幾個(gè)指標(biāo)后續(xù)所有設(shè)計(jì)都圍繞這幾個(gè)指標(biāo)展開(kāi)指標(biāo)項(xiàng)設(shè)計(jì)目標(biāo)頻率測(cè)量范圍1Hz ~ 100kHz頻率分辨率最低1Hz低頻段遠(yuǎn)優(yōu)于0.1Hz占空比測(cè)量范圍1% ~ 99%占空比顯示精度0.1%輸入信號(hào)電平3.3V / 5V兼容基準(zhǔn)時(shí)鐘100MHz刷新率每個(gè)被測(cè)信號(hào)周期更新一次整體架構(gòu)分成五塊信號(hào)預(yù)處理電路、邊沿檢測(cè)與同步模塊、雙計(jì)數(shù)器模塊、計(jì)算模塊、顯示輸出模塊。信號(hào)進(jìn)來(lái)先經(jīng)過(guò)施密特觸發(fā)器整形變成干凈的方波然后進(jìn)FPGA做兩級(jí)同步消除亞穩(wěn)態(tài)再進(jìn)入計(jì)數(shù)器。計(jì)數(shù)器在每個(gè)被測(cè)信號(hào)周期內(nèi)同時(shí)統(tǒng)計(jì)周期計(jì)數(shù)值和高電平計(jì)數(shù)值鎖存后送給計(jì)算模塊做除法最后把頻率和占空比送到數(shù)碼管和串口。2. 硬件預(yù)處理與核心參數(shù)計(jì)算2.1 輸入信號(hào)整形與電平適配FPGA的IO不能直接接亂七八糟的信號(hào)。很多傳感器輸出的所謂“方波”實(shí)際邊沿很緩甚至帶有明顯的振鈴和毛刺。如果直接把這種信號(hào)送進(jìn)FPGA引腳計(jì)數(shù)器會(huì)在邊沿附近反復(fù)觸發(fā)測(cè)出來(lái)的數(shù)據(jù)完全是亂的。我習(xí)慣在輸入端加一級(jí)74HC14施密特觸發(fā)器。施密特觸發(fā)器的特點(diǎn)是有遲滯特性輸入電壓超過(guò)上門(mén)限才輸出翻轉(zhuǎn)低于下門(mén)限才再次翻轉(zhuǎn)中間有一段“死區(qū)”。這個(gè)死區(qū)能天然濾掉一部分毛刺同時(shí)能把緩慢變化的邊沿整形成陡峭的標(biāo)準(zhǔn)方波。實(shí)測(cè)下來(lái)輸入信號(hào)哪怕是從幾百毫伏緩慢爬升的三角波經(jīng)過(guò)74HC14之后也能變成質(zhì)量很好的方波。電平適配方面要特別注意。FPGA的Bank電壓如果是3.3VIO引腳一般不能直接接受5V信號(hào)。如果被測(cè)設(shè)備輸出5V方波必須做電平轉(zhuǎn)換。最簡(jiǎn)單的辦法是電阻分壓把5V分到3.3V以下。我在BNC接口后面放了一個(gè)10kΩ和6.8kΩ的分壓電阻配合穩(wěn)壓管鉗位實(shí)測(cè)很穩(wěn)。如果被測(cè)信號(hào)是12V甚至更高的電壓就不要用分壓了直接上光耦隔離更安全順便把地環(huán)路也斷開(kāi)了。2.2 時(shí)鐘選擇與計(jì)數(shù)器位寬推導(dǎo)基準(zhǔn)時(shí)鐘直接決定測(cè)量分辨率。我用了開(kāi)發(fā)板上的100MHz有源晶振時(shí)鐘周期10ns。為什么不用更常見(jiàn)的50MHz道理很簡(jiǎn)單50MHz時(shí)鐘周期20ns同樣是1kHz信號(hào)一個(gè)周期只能數(shù)到50000拍而100MHz能數(shù)到100000拍分辨率提高了一倍。功耗和資源占用在這個(gè)量級(jí)幾乎沒(méi)差別但測(cè)量精度實(shí)打?qū)嵦嵘艘槐哆@個(gè)性?xún)r(jià)比很劃算。計(jì)數(shù)器位寬需要根據(jù)最低測(cè)量頻率來(lái)算。最低頻率1Hz周期1秒100MHz時(shí)鐘在一個(gè)周期內(nèi)會(huì)走100000000個(gè)時(shí)鐘沿。100000000轉(zhuǎn)換成二進(jìn)制需要27位。為了留余量、防止意外情況導(dǎo)致計(jì)數(shù)溢出我直接全部用32位計(jì)數(shù)器。FPGA上32位計(jì)數(shù)器也就占用十幾個(gè)邏輯單元完全不用省。這里有個(gè)容易忽略的點(diǎn)計(jì)算頻率時(shí)要把計(jì)數(shù)值加1再拿去用。因?yàn)橛?jì)數(shù)器在上升沿清零下一次上升沿到來(lái)時(shí)鎖存實(shí)際數(shù)到的值會(huì)比真實(shí)周期少一個(gè)時(shí)鐘周期。這是測(cè)量原理帶來(lái)的系統(tǒng)性偏差雖然只有10ns但如果不修正頻率高的時(shí)候誤差會(huì)明顯偏大。計(jì)算模塊里我統(tǒng)一做了加1修正。2.3 顯示與通信接口選擇顯示部分我做了兩路輸出。頻率用一組四位數(shù)碼管顯示單位Hz直接顯示整數(shù)部分就夠用。占空比用另一組兩位數(shù)碼管加一個(gè)小數(shù)點(diǎn)顯示顯示成百分比形式比如“50.0%”。數(shù)碼管動(dòng)態(tài)掃描容易出問(wèn)題后面專(zhuān)門(mén)講。我的建議是如果條件允許優(yōu)先把UART串口調(diào)通用串口把頻率和占空比打出來(lái)調(diào)試。串口數(shù)據(jù)能直接看到完整數(shù)值還能看到原始計(jì)數(shù)值排查問(wèn)題時(shí)比數(shù)碼管直觀得多。數(shù)碼管更適合最終產(chǎn)品使用而串口更適合開(kāi)發(fā)調(diào)試階段。這次我兩個(gè)都做了但在開(kāi)發(fā)的前半段幾乎所有問(wèn)題都是靠串口打印發(fā)現(xiàn)的。3. FPGA核心模塊實(shí)現(xiàn)3.1 頂層模塊劃分整個(gè)工程在Vivado里面建了一個(gè)頂層文件下面掛了六個(gè)子模塊clk_gen時(shí)鐘管理把有源晶振的100MHz時(shí)鐘處理一下必要時(shí)加PLLsync_edge輸入信號(hào)兩級(jí)同步同時(shí)產(chǎn)生上升沿和下降沿脈沖counter雙計(jì)數(shù)器統(tǒng)計(jì)周期計(jì)數(shù)值和高電平計(jì)數(shù)值calc計(jì)算模塊做除法和格式轉(zhuǎn)換display數(shù)碼管動(dòng)態(tài)掃描驅(qū)動(dòng)uart_tx串口發(fā)送把測(cè)量結(jié)果格式化輸出模塊化設(shè)計(jì)的好處是每個(gè)模塊可以單獨(dú)寫(xiě)testbench驗(yàn)證出了問(wèn)題定位很快。很多初學(xué)者習(xí)慣把代碼全部堆在一個(gè)always塊里看起來(lái)省事但一旦結(jié)果不對(duì)很難判斷是計(jì)數(shù)器問(wèn)題還是計(jì)算問(wèn)題還是顯示問(wèn)題。頂層例化的時(shí)候要注意counter模塊輸出的valid信號(hào)要連到calc模塊表示“本次計(jì)數(shù)值有效”。calc模塊內(nèi)部有一個(gè)除法器除法器不是組合邏輯需要幾個(gè)時(shí)鐘周期才能出結(jié)果所以valid信號(hào)也要經(jīng)過(guò)相應(yīng)延遲保證數(shù)據(jù)同步。3.2 跨時(shí)鐘域同步與邊沿檢測(cè)輸入信號(hào)來(lái)自外部設(shè)備和FPGA的100MHz時(shí)鐘沒(méi)有確定的相位關(guān)系屬于異步信號(hào)。異步信號(hào)直接進(jìn)觸發(fā)器有可能采到介于高低電平之間的中間狀態(tài)也就是亞穩(wěn)態(tài)。亞穩(wěn)態(tài)會(huì)導(dǎo)致寄存器輸出不確定嚴(yán)重的時(shí)候整個(gè)邏輯都會(huì)卡死。解決亞穩(wěn)態(tài)的標(biāo)準(zhǔn)做法是兩級(jí)寄存器同步。輸入信號(hào)打兩拍第二拍輸出的信號(hào)基本可以認(rèn)為是和100MHz時(shí)鐘同步的了。邊沿檢測(cè)在這兩級(jí)同步之后做用同步后的信號(hào)和它延遲一拍的信號(hào)做比較reg [1:0] sync_reg; reg sig_dly; wire rising; wire falling; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sync_reg 2b00; sig_dly 1b0; end else begin sync_reg {sync_reg[0], sig_in}; sig_dly sync_reg[1]; end end assign rising sync_reg[1] ~sig_dly; assign falling ~sync_reg[1] sig_dly;這里有一個(gè)細(xì)節(jié)rising信號(hào)比真正的輸入邊沿晚了一個(gè)到兩個(gè)時(shí)鐘周期。這個(gè)延遲是固定的對(duì)周期測(cè)量沒(méi)有影響因?yàn)殒i存和清零用的都是同一個(gè)延遲后的邊沿信號(hào)誤差只是整體平移不會(huì)影響周期值。真正影響測(cè)量值的是上一節(jié)說(shuō)的“少一拍”問(wèn)題通過(guò)加1修正解決。3.3 周期計(jì)數(shù)與高電平計(jì)數(shù)時(shí)序核心計(jì)數(shù)器邏輯是整個(gè)項(xiàng)目的靈魂我貼出關(guān)鍵代碼reg [31:0] cnt_total; reg [31:0] cnt_high; reg [31:0] period_cnt; reg [31:0] high_cnt; reg valid; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_total 32d0; cnt_high 32d0; period_cnt 32d0; high_cnt 32d0; valid 1b0; end else begin valid 1b0; cnt_total cnt_total 1b1; if (sync_reg[1] 1b1) cnt_high cnt_high 1b1; if (rising) begin period_cnt cnt_total 1b1; // 加1修正 high_cnt cnt_high 1b1; // 占空比測(cè)量同樣修正 cnt_total 32d0; cnt_high 32d0; valid 1b1; end end end這個(gè)邏輯的思路是cnt_total 一直在走不管輸入信號(hào)是高還是低每個(gè)clk都加1cnt_high 只在輸入信號(hào)為高的時(shí)候加1。每檢測(cè)到一個(gè)上升沿說(shuō)明上一個(gè)周期結(jié)束了把當(dāng)前計(jì)數(shù)值鎖存到period_cnt和high_cnt然后把兩個(gè)計(jì)數(shù)器清零開(kāi)始新一周期的計(jì)數(shù)。上升沿鎖存的是“上一周期”的數(shù)據(jù)這一點(diǎn)要特別注意。第一次上電后需要等待一個(gè)完整周期才會(huì)輸出第一個(gè)有效數(shù)據(jù)這是正常的。valid信號(hào)會(huì)在鎖存數(shù)據(jù)的那一個(gè)時(shí)鐘周期拉高告訴后面的計(jì)算模塊“數(shù)據(jù)來(lái)了”。占空比的計(jì)算公式就是占空比百分比 high_cnt / period_cnt × 100%注意high_cnt在信號(hào)剛好等于50%占空比時(shí)理論上應(yīng)該等于period_cnt的一半。由于計(jì)數(shù)器清零和邊沿檢測(cè)存在最多1拍的相位差實(shí)際會(huì)差幾個(gè)計(jì)數(shù)值但這在低頻測(cè)量中完全可以忽略。3.4 頻率占空比計(jì)算從除法到定點(diǎn)數(shù)頻率計(jì)算公式是100000000除以period_cnt占空比公式是high_cnt乘100再除以period_cnt。這里涉及FPGA最頭疼的運(yùn)算之一除法。FPGA里實(shí)現(xiàn)除法有兩種常見(jiàn)方式。一種是直接用廠商提供的除法器IP核Vivado里面有Divider GeneratorQuartus里面有LPM_DIVIDE。配置好位寬和流水線級(jí)數(shù)把被除數(shù)和除數(shù)十進(jìn)制輸入幾十個(gè)時(shí)鐘周期后能拿到商和余數(shù)。另一種是自己寫(xiě)移位相減除法器結(jié)構(gòu)簡(jiǎn)單但占資源速度也慢。我建議這個(gè)項(xiàng)目直接用IP核原因很簡(jiǎn)單數(shù)據(jù)更新頻率很低高頻除法器根本沒(méi)有必要IP核雖然延遲幾十個(gè)時(shí)鐘周期但結(jié)果穩(wěn)定不會(huì)出錯(cuò)。自己寫(xiě)除法器如果位寬沒(méi)控制好很容易出現(xiàn)商和余數(shù)錯(cuò)位的問(wèn)題排查起來(lái)非常頭疼。計(jì)算模塊的狀態(tài)機(jī)大致是這樣檢測(cè)到valid信號(hào)后把period_cnt和100000000送入除法器等到除法器輸出有效信號(hào)時(shí)把商和余數(shù)鎖存轉(zhuǎn)換成BCD碼送入顯示模塊。占空比計(jì)算類(lèi)似先把high_cnt乘100直接移位加幾個(gè)加法器就能實(shí)現(xiàn)乘以100再送除法器。顯示部分用到BIN轉(zhuǎn)BCD。四位十進(jìn)制數(shù)可以用純組合邏輯的查表法也可以用移位加3算法。我這次用的是移位加3寫(xiě)了一個(gè)小函數(shù)逐位處理代碼很短效果穩(wěn)定。3.5 結(jié)果輸出數(shù)碼管動(dòng)態(tài)顯示與UART數(shù)碼管動(dòng)態(tài)顯示的原理并不復(fù)雜就是利用人眼的視覺(jué)暫留效應(yīng)快速輪流點(diǎn)亮每一位數(shù)碼管。四位數(shù)碼管每一位點(diǎn)亮1ms整個(gè)循環(huán)4ms刷新率250Hz看起來(lái)就是同時(shí)亮的。這里有個(gè)非常容易踩的坑動(dòng)態(tài)掃描一定要保證每一位的顯示數(shù)據(jù)在切換時(shí)不出現(xiàn)毛刺。我一開(kāi)始直接在時(shí)鐘上升沿同時(shí)切換位選和段選結(jié)果數(shù)碼管亮度不均勻還出現(xiàn)鬼影。后來(lái)把操作拆成兩步先在時(shí)鐘低電平準(zhǔn)備好段碼數(shù)據(jù)再在時(shí)鐘上升沿切換位選問(wèn)題就消失了。其實(shí)就是給段碼一個(gè)建立時(shí)間道理和普通時(shí)序約束一樣。UART部分我寫(xiě)了一個(gè)簡(jiǎn)單的發(fā)送模塊波特率96008位數(shù)據(jù)1位停止位。上位機(jī)用串口助手接收每秒刷新一次。發(fā)送的內(nèi)容格式化之后大概長(zhǎng)這樣F: 1000Hz D: 50.0%串口調(diào)試最重要的作用是能看到原始計(jì)數(shù)值。我把period_cnt和high_cnt也一起打出來(lái)格式是P:100000 H:50000 F:1000Hz D:50.0%這樣一旦顯示值不對(duì)立刻能看出來(lái)是計(jì)數(shù)階段出了問(wèn)題還是計(jì)算階段出了問(wèn)題。開(kāi)發(fā)調(diào)試階段這個(gè)輸出格式幫我省了大量時(shí)間。4. 仿真驗(yàn)證與實(shí)測(cè)結(jié)果4.1 Testbench編寫(xiě)要點(diǎn)仿真驗(yàn)證階段我寫(xiě)了兩個(gè)testbench。第一個(gè)很簡(jiǎn)單產(chǎn)生一個(gè)固定1kHz方波占空比40%100MHz時(shí)鐘下理論上period_cnt應(yīng)該大約是100000high_cnt應(yīng)該是40000左右。產(chǎn)生帶占空比的信號(hào)Verilog里最直接的方式是用兩組延時(shí)控制initial begin clk 0; forever #5 clk ~clk; // 100MHz時(shí)鐘 end initial begin sig_in 0; #10000; // 先給10us低電平 forever begin sig_in 1; #400; // 高電平保持400us占空比40% sig_in 0; #600; // 低電平保持600us end end這里有個(gè)細(xì)節(jié)被測(cè)信號(hào)1kHz一個(gè)周期1ms仿真至少要跑到幾個(gè)完整周期才能看到有效輸出。在ModelSim里1ms仿真時(shí)間對(duì)應(yīng)的時(shí)鐘沿?cái)?shù)量是100000個(gè)跑起來(lái)還算流暢但如果測(cè)的是10Hz信號(hào)一個(gè)周期100ms仿真跑完就是1000萬(wàn)個(gè)時(shí)鐘沿這時(shí)候ModelSim會(huì)明顯變慢。所以仿真階段用1kHz以上的信號(hào)驗(yàn)證邏輯正確性實(shí)測(cè)階段再去驗(yàn)證低頻場(chǎng)景這樣可以節(jié)省大量仿真時(shí)間。第二個(gè)testbench模擬了上電后立刻有信號(hào)的場(chǎng)景用來(lái)驗(yàn)證第一個(gè)測(cè)量周期是否會(huì)產(chǎn)生異常值。實(shí)測(cè)發(fā)現(xiàn)上電后第一個(gè)有效輸出之前計(jì)算模塊可能拿到一個(gè)無(wú)效的中間值需要通過(guò)一個(gè)計(jì)數(shù)器屏蔽前兩次輸出保證穩(wěn)定后再刷新顯示。4.2 精度分析誤差從哪里來(lái)測(cè)周期法的理論誤差來(lái)源主要是量化誤差也就是計(jì)數(shù)器只能數(shù)整數(shù)個(gè)時(shí)鐘周期結(jié)果和真實(shí)值之間最多差1個(gè)時(shí)鐘周期。100MHz時(shí)鐘下這個(gè)量化誤差就是10ns。不同頻率下的理論誤差可以列表對(duì)比被測(cè)頻率周期周期計(jì)數(shù)值頻率理論誤差1Hz1s100000000約0.000001%100Hz10ms1000000約0.0001%1kHz1ms100000約0.001%10kHz100us10000約0.01%100kHz10us1000約0.1%從表格能清楚看到頻率越低精度越高。在100kHz時(shí)誤差到了0.1%這個(gè)量級(jí)對(duì)于通用測(cè)量還是可以接受的但如果你要做精密測(cè)量就不要用純測(cè)周期法了得換等精度測(cè)量方案。占空比的誤差除了量化誤差還受高電平計(jì)數(shù)的精度影響。高電平時(shí)間同樣是10ns分辨率所以占空比誤差大約是占空比誤差 ≈ 2 × 10ns / 周期低頻時(shí)這個(gè)誤差幾乎可以忽略高頻時(shí)會(huì)逐漸變大。實(shí)測(cè)下來(lái)1kHz信號(hào)占空比測(cè)量的誤差在0.1%以?xún)?nèi)完全滿(mǎn)足設(shè)計(jì)指標(biāo)。4.3 實(shí)測(cè)數(shù)據(jù)對(duì)比與波形觀察用信號(hào)發(fā)生器輸出了幾組標(biāo)準(zhǔn)信號(hào)和FPGA模塊的顯示值做了對(duì)比信號(hào)發(fā)生器設(shè)定FPGA實(shí)測(cè)頻率FPGA實(shí)測(cè)占空比100Hz / 50.0%100.0Hz50.0%1kHz / 40.0%1000Hz40.1%10kHz / 60.0%9999Hz59.9%50kHz / 30.0%50014Hz30.2%頻率越高偏差越明顯這和理論誤差分析的趨勢(shì)完全一致。整體來(lái)看低頻段精度表現(xiàn)非常好100kHz以?xún)?nèi)誤差都在0.3%以?xún)?nèi)符合預(yù)期。有一個(gè)有意思的細(xì)節(jié)信號(hào)發(fā)生器輸出50%占空比示波器上看起來(lái)也是完美的方波但FPGA讀出來(lái)卻是49.8%左右。一開(kāi)始以為是代碼問(wèn)題查了很久才發(fā)現(xiàn)是74HC14的閾值電壓不是正好在輸入電壓中點(diǎn)導(dǎo)致輸出高電平時(shí)間比理論值略短。這不是FPGA測(cè)錯(cuò)了是整形電路固有的偏移。做高精度占空比測(cè)量的話(huà)這個(gè)偏移可以在軟件里做校準(zhǔn)。5. 常見(jiàn)問(wèn)題與排查技巧5.1 計(jì)數(shù)溢出或長(zhǎng)時(shí)間無(wú)更新如果被測(cè)信號(hào)頻率高于設(shè)計(jì)上限period_cnt會(huì)變得很小此時(shí)除法器的結(jié)果跳動(dòng)會(huì)很厲害。比如輸入1MHz信號(hào)一個(gè)周期只數(shù)到100拍頻率值可能從990kHz跳到1010kHz看起來(lái)就像電路壞了。這個(gè)問(wèn)題可以通過(guò)增加檔位判斷解決當(dāng)period_cnt小于某個(gè)閾值比如1000時(shí)自動(dòng)切換到測(cè)頻法模式兩種方法結(jié)合就能覆蓋更寬的頻率范圍。長(zhǎng)時(shí)間無(wú)更新先檢查邊沿檢測(cè)信號(hào)有沒(méi)有輸出。可以用調(diào)試邏輯分析儀或者直接把rising信號(hào)引到LED上看有沒(méi)有閃爍。如果LED不閃說(shuō)明輸入信號(hào)沒(méi)有正確進(jìn)入FPGA檢查外部整形電路和引腳分配如果LED閃但顯示不刷新問(wèn)題在計(jì)數(shù)器清零邏輯或valid信號(hào)鏈路上。5.2 顯示亂跳、數(shù)值不穩(wěn)定顯示亂跳最常見(jiàn)的原因是輸入信號(hào)帶毛刺。毛刺會(huì)觸發(fā)額外的上升沿導(dǎo)致計(jì)數(shù)器在錯(cuò)誤位置清零周期值和高電平值完全是亂的。解決辦法是外部加施密特觸發(fā)器并且在FPGA內(nèi)部對(duì)同步后的信號(hào)再做一次毛刺濾波比如連續(xù)采樣到多個(gè)高電平才認(rèn)為信號(hào)真正變高。另一個(gè)原因是數(shù)碼管動(dòng)態(tài)掃描刷新率太低。如果每位點(diǎn)亮?xí)r間太長(zhǎng)人眼能看到明顯的閃爍如果太短數(shù)碼管亮度不夠而且可能顯示不均勻。我最終把每位點(diǎn)亮?xí)r間設(shè)為1ms四位數(shù)碼管一個(gè)循環(huán)4ms刷新率250Hz既看不到閃爍亮度也足夠。5.3 輸入引腳損壞與接口保護(hù)這個(gè)坑我替大家踩過(guò)了。最開(kāi)始圖省事直接從信號(hào)發(fā)生器輸出5V方波接到FPGA板子的3.3V Bank引腳上結(jié)果用了不到半天那個(gè)引腳就失靈了。FPGA的IO引腳不是5V兼容的超壓輸入會(huì)直接損壞引腳內(nèi)部的ESD保護(hù)結(jié)構(gòu)。正確做法是做好防護(hù)再進(jìn)FPGA。我在輸入端加了串聯(lián)電阻和二極管鉗位串聯(lián)電阻選100Ω左右既能限制電流又和引腳寄生電容組成低通濾波能稍微濾掉一些高頻噪聲。如果輸入信號(hào)來(lái)自工業(yè)現(xiàn)場(chǎng)強(qiáng)烈建議用光耦隔離雖然光耦會(huì)引入額外的上升沿延遲但對(duì)于低頻方波測(cè)量來(lái)說(shuō)這點(diǎn)延遲完全不影響精度。光耦還能徹底解決地電位不一致的問(wèn)題避免地環(huán)路干擾。5.4 ModelSim仿真常見(jiàn)坑仿真低頻信號(hào)最大的問(wèn)題是時(shí)間尺度太大。我第一次用10Hz信號(hào)仿真跑了20分鐘波形窗口里還看不到一個(gè)完整周期后來(lái)想通了仿真只是驗(yàn)證邏輯正確性不是驗(yàn)證低頻性能完全可以用1kHz或10kHz信號(hào)代替邏輯只要對(duì)了低頻下只是計(jì)數(shù)值更大不會(huì)有本質(zhì)區(qū)別。另一個(gè)常見(jiàn)坑是仿真時(shí)寄存器初始值不定。如果復(fù)位信號(hào)設(shè)計(jì)不好一開(kāi)始所有寄存器都是X態(tài)計(jì)數(shù)器加來(lái)加去還是X波形一片紅。我的做法是設(shè)計(jì)一個(gè)異步復(fù)位、同步釋放的復(fù)位信號(hào)并且在testbench里一開(kāi)始就拉低復(fù)位釋放之后等幾個(gè)時(shí)鐘周期再送被測(cè)信號(hào)。同時(shí)用ModelSim的“wave”窗口觀察內(nèi)部信號(hào)時(shí)重點(diǎn)關(guān)注reset、sync_reg、cnt_total這幾個(gè)關(guān)鍵信號(hào)從源頭排查比瞎猜快得多。這個(gè)模塊做完之后我又把它改成了脈沖寬度測(cè)量的小工具原理完全一樣只是換了一下計(jì)數(shù)觸發(fā)條件。如果你也想做一個(gè)類(lèi)似的東西我的建議是不要一上來(lái)就搞數(shù)碼管先UART把數(shù)據(jù)打出來(lái)確認(rèn)計(jì)數(shù)和計(jì)算都對(duì)再做顯示部分這樣問(wèn)題不會(huì)混在一起。低頻方波測(cè)量這個(gè)題目看似簡(jiǎn)單但把信號(hào)處理、跨時(shí)鐘域、計(jì)數(shù)器設(shè)計(jì)、除法運(yùn)算、顯示刷新這些點(diǎn)全部串起來(lái)對(duì)理解FPGA開(kāi)發(fā)流程很有幫助。