計:從硬件搭建到上板調(diào)試全解析)
簡介一套基于NIOS II軟核處理器與FPGA的電子鐘設(shè)計完整工程適合FPGA初學(xué)者、嵌入式愛好者和電子設(shè)計競賽隊伍學(xué)習(xí)參考。工程針對數(shù)字鐘的常見功能需求給出了從硬件驅(qū)動到軟件控制的整體方案底層使用Verilog編寫數(shù)碼管驅(qū)動上層基于Qsys構(gòu)建的SOPC系統(tǒng)完成計時控制通過定時器產(chǎn)生基準時基驅(qū)動八位數(shù)碼管顯示時分秒三個按鍵分別實現(xiàn)模式切換、數(shù)值加一和減一調(diào)節(jié)中的對應(yīng)位會閃爍提示操作LED同步點亮反饋鬧鐘時間到后蜂鳴器報警并聯(lián)動流水燈指示。資源共602個文件壓縮包22.66MB包含設(shè)計源碼、C程序、Qsys系統(tǒng)配置、SDC約束以及可直接下載的SOF與ELF鏡像各類型文件用途清晰目錄結(jié)構(gòu)利于按模塊排查和二次修改。目前已有2274人學(xué)習(xí)下載適合希望從頭完整搭建NIOS II硬件系統(tǒng)、掌握軟核處理器與FPGA邏輯協(xié)同開發(fā)的讀者。 前陣子幫朋友做課程設(shè)計選的題目就是“FPGA基于NIOS II的電子鐘設(shè)計”做完之后發(fā)現(xiàn)這個項目特別適合分享出來。它麻雀雖小五臟俱全既要用Verilog把NIOS軟核、定時器、GPIO這些組件在FPGA里搭起來又要寫C語言讓處理器跑起來還要處理數(shù)碼管動態(tài)掃描、按鍵消抖這些硬件相關(guān)的細節(jié)非常適合想入門SoC方向的朋友。市面上很多電子鐘都是純邏輯狀態(tài)機做的但用NIOS II軟核做思路完全不一樣更像是“在FPGA里嵌入一顆CPU來控制外圍設(shè)備”這個思維轉(zhuǎn)換對后面學(xué)Zynq、學(xué)嵌入式都很有幫助。順便說一句如果你手頭是EGO1或者其他Altera/Intel系列的板子這套流程直接照著走就行。如果用的是Xilinx的板子NIOS換成MicroBlaze思路完全一樣。這篇文章不以具體板卡型號為限制重點把系統(tǒng)設(shè)計、硬件搭建、軟件開發(fā)、上板調(diào)試這幾個環(huán)節(jié)講透。1. 電子鐘軟硬件方案的整體拆解1.1 純邏輯方案與NIOS軟核方案的取舍先說說為什么選NIOS II。傳統(tǒng)電子鐘用純Verilog寫狀態(tài)機把計數(shù)、進位、譯碼、掃描全部用硬件邏輯實現(xiàn)好處是延時確定、不依賴代碼執(zhí)行流程壞處也很明顯改時間格式、加鬧鐘、調(diào)整顯示模式每一處改動都要動RTL代碼改完還要重新綜合布線調(diào)試周期很長。NIOS II方案的本質(zhì)是“在FPGA內(nèi)部生成一顆32位處理器軟核”外圍接上定時器、GPIO、UART這些標準外設(shè)然后像寫單片機程序一樣用C語言控制整個系統(tǒng)。這樣做有三個實際好處第一計時邏輯、按鍵掃描、顯示刷新全部變成軟件邏輯改起來無非是改C代碼重新編譯幾分鐘就能迭代一版第二NIOS II的開發(fā)流程和單片機很接近對于學(xué)過STM32的同學(xué)學(xué)習(xí)曲線非常平緩第三NIOS II外設(shè)都提供HAL庫和寄存器級驅(qū)動不用自己造輪子省下大量時間。我也必須說兩句實在話。NIOS II方案在資源占用上比純邏輯大不少還要占用不少內(nèi)部存儲做程序和數(shù)據(jù)空間但對于EGO1這種Cyclone IV級別板子資源完全夠用。另外純邏輯方案的實時性天然更好而NIOS II依賴中斷和主循環(huán)調(diào)度只要程序設(shè)計得當做電子鐘的計時精度毫無問題。1.2 系統(tǒng)整體架構(gòu)的劃分思路整個電子鐘系統(tǒng)的架構(gòu)可以分成兩大部分FPGA硬件邏輯部分和NIOS II軟件部分。硬件部分通過Platform Designer老版本叫QSys搭建一個完整的最小系統(tǒng)包括NIOS II處理器軟核、片上RAM、定時器IP、PIO控制器。軟件部分跑在NIOS II內(nèi)部通過外設(shè)寄存器地址訪問硬件。我把架構(gòu)畫出來你感受下CPUNIOS II/e經(jīng)濟型內(nèi)核適合做控制類任務(wù)時鐘由FPGA外部50MHz晶振統(tǒng)一供給存儲片上RAM存放程序指令和數(shù)據(jù)定時器Interval Timer IP產(chǎn)生1ms周期中斷作為計時基準PIO若干組GPIO分別控制數(shù)碼管段選、位選和獨立按鍵UART可選調(diào)試打印信息用這個架構(gòu)說白了就是一個微型計算機。硬件上的連線相當于把CPU、內(nèi)存、定時器、GPIO用總線連接起來軟件上的邏輯相當于在CPU上跑一個裸機程序。這個模型一旦建立后面做任何基于NIOS II的項目基本都是在往這個骨架上添磚加瓦。2. Platform Designer下的硬件平臺搭建2.1 在Platform Designer里搭一個最小NIOS系統(tǒng)打開Quartus新建工程后在Tools菜單找到Platform Designer并打開。這一步是在圖形化環(huán)境下把各個IP拖出來連線操作起來像搭積木但每根線的含義都要清楚。第一步添加NIOS II處理器。雙擊添加NIOS II Processor選擇Nios II/e經(jīng)濟型。這里有個關(guān)鍵選擇JTAG調(diào)試模塊要不要加我建議選上JTAG調(diào)試模塊因為上板調(diào)試的時候很多問題要依賴它查看內(nèi)部變量。如果空間緊張可以在最后階段把調(diào)試模塊去掉以省資源。第二步添加片上存儲。選擇On-Chip Memory設(shè)置總?cè)萘俊N矣玫氖?4KB對電子鐘這種程序量極小的項目32KB其實也夠但64KB更從容Quartus綜合的時候也不會因為容量不足報錯。存儲類型選RAM不要選ROM。第三步添加串口調(diào)試選擇JTAG UART。這個IP很有用可以把printf的輸出映射到Quartus的調(diào)試終端我調(diào)試時習(xí)慣在代碼里加一串打印信息觀察狀態(tài)機變化沒有串口會很不方便。第四步添加Interval Timer定時器也就是核心的計時器IP。這里有個比較重要的配置項后面單獨講。第五步添加PIO控制器根據(jù)外設(shè)引腳需求創(chuàng)建。我用了兩組PIO一組輸出給數(shù)碼管段選和位選一組輸入讀按鍵。所有組件添加完后最關(guān)鍵的步驟來了在System Contents里雙擊各個IP的時鐘和復(fù)位連接確保它們都在System 50MHz這個時鐘總線上。然后點擊Assign Base Address讓系統(tǒng)自動分配基地址。最后點擊Generate生成整個硬件系統(tǒng)。生成的.v文件會在工程目錄下自動關(guān)聯(lián)到頂層模塊。2.2 定時器與PIO外設(shè)的參數(shù)規(guī)劃定時器的參數(shù)規(guī)劃是這個項目硬件設(shè)計的重頭戲。我使用的是Full-featured定時器模式計數(shù)器寬度設(shè)為32位。為什么是32位而不是16位因為系統(tǒng)時鐘是50MHz也就是一個時鐘周期20ns如果我要實現(xiàn)1ms定時需要的計數(shù)次數(shù)是50000這個數(shù)字16位能放下但考慮到以后可能要擴展成更長的計時周期32位更保險省得以后返工。定時器的工作方式選擇往下計數(shù)模式Count down當計數(shù)值從初始值減到0會輸出一個脈沖并觸發(fā)中斷。初始值設(shè)置在軟件里通過寄存器寫入這里硬件上只需要把周期值預(yù)留出來。PIO參數(shù)的規(guī)劃更加瑣碎但不能錯。我把段選引腳配置為8位輸出位選配置為6位輸出按鍵配置為4位輸入。PIO的方向設(shè)置是個經(jīng)典坑在Platform Designer里Output Only和Input Only都會自動配置方向但如果你選的是Bidirectional一定要在代碼里寫方向寄存器否則輸入輸出方向是亂的。我的習(xí)慣是用Input Only和Output Only把方向控制交給硬件軟件少操心。另外每個PIO都要單獨設(shè)置中斷條件。按鍵PIO我選擇了“上升沿觸發(fā)中斷”和“下降沿觸發(fā)中斷”都打開這樣按鍵按下和松開都能感知到。定時器PIO不需要中斷PIO對應(yīng)的是中斷控制器里的中斷號這個中斷號要記下來后面寫軟件ISR要用。2.3 硬件生成后的頂層集成方法Platform Designer生成的是一個帶固定接口名稱的模塊名稱通常是my_system之類的名字加上你生成時填寫的實例名。回到Quartus工程需要新建一個頂層Verilog文件把這個模塊例化進去。頂層模塊要解決兩件事一是把Platform Designer生成的NIOS系統(tǒng)各個接口連接到FPGA的真實引腳上二是把沒有被NIOS系統(tǒng)占用的引腳比如數(shù)碼管的公共端根據(jù)位選信號進行分配。這里有個非常實用的經(jīng)驗先把Platform Designer生成的模塊源碼添加進工程然后在頂層文件里例化用wire定義好各接口信號再把與引腳相關(guān)的信號通過Quartus的Pin Planner綁定到具體引腳。綁定引腳時務(wù)必在Pin Planner里確認每個信號的IO標準我用的板子大多是3.3V所以IO Standard選3.3V-LVTTL選錯會導(dǎo)致信號不穩(wěn)定甚至損壞引腳。3. Nios II軟件的編寫與邏輯實現(xiàn)3.1 BSP工程創(chuàng)建與代碼結(jié)構(gòu)約定硬件平臺生成后進入Nios II SBT for Eclipse或更新版本的Nios II Command Line Shell創(chuàng)建BSP包和軟件工程。BSP包相當于板級支持包會自動根據(jù)sopcinfo文件生成對每個外設(shè)的寄存器定義和驅(qū)動函數(shù)。我的做法是先用File New Nios II Board Support Package選中生成的.sopcinfo使用默認設(shè)置直接生成再把軟件工程和它關(guān)聯(lián)起來。這里有個重要選擇BSP的設(shè)置中關(guān)于stdout設(shè)備的選項。默認會指向jtag_uart這個不用改方便printf調(diào)試。另外在BSP Editor里確認timer的驅(qū)動支持是打開的如果沒有生成對應(yīng)的timer驅(qū)動代碼里訪問寄存器會報錯。工程建好后代碼建議分成三個文件main.c放主循環(huán)和初始化邏輯timer.c放定時器中斷服務(wù)程序display.c放數(shù)碼管掃描和按鍵響應(yīng)。這樣結(jié)構(gòu)清晰調(diào)試也方便。如果你之后要往工程里加鬧鐘、秒表功能也是在這三個文件上繼續(xù)擴展。3.2 定時器中斷電子鐘的核心計時邏輯計時邏輯是整個軟件的核心我把它全部寫在定時器中斷服務(wù)程序里。思路是把1ms作為最小時間粒度中斷每觸發(fā)一次就對ms計數(shù)加1累加到1000說明過了1秒然后秒計數(shù)加1。秒滿60進分鐘分鐘滿60進小時小時滿24自動歸零。中斷服務(wù)程序里有個細節(jié)硬件定時器觸發(fā)中斷后需要清除中斷標志位否則中斷會反復(fù)進入。清除方式是在狀態(tài)寄存器里寫0。代碼上我習(xí)慣用IOWR_ALTERA_AVALON_TIMER_STATUS宏把寄存器值為0寫入狀態(tài)寄存器這條語句不能漏。很多人做NIOS的時候定時器一直狂觸發(fā)中斷原因多半就是沒清標志位。另外在ISR中盡量不要做耗時操作。我早期版本在ISR里直接做數(shù)碼管刷新結(jié)果計時明顯變慢主循環(huán)和ISR搶資源。后來把ISR只負責(zé)計數(shù)和置標志位主循環(huán)里檢查標志位再做刷新問題立刻解決。這下你明白為什么ISR要短小精悍了吧。3.3 數(shù)碼管動態(tài)掃描與按鍵輸入的工程實現(xiàn)數(shù)碼管的動態(tài)掃描原理是一個共陰或共陽數(shù)碼管靠位選信號選通某一位然后用段選信號決定這一位顯示什么數(shù)字。因為人眼有視覺暫留效應(yīng)只要循環(huán)掃描每一位點亮的頻率足夠高通常1ms切換一次看起來就像所有位同時亮著。實現(xiàn)上我在主循環(huán)里用一個scan_index變量從0到5循環(huán)每次循環(huán)做兩件事先把前一位的位選信號拉低避免顯示重疊再往段選寄存器寫入當前位要顯示的字符對應(yīng)的段碼最后把當前位的位選信號拉高。整個過程本質(zhì)上是一個小型狀態(tài)機狀態(tài)切換靠主循環(huán)輪詢不阻塞。段碼表的編寫要特別留意共陽共陰的區(qū)別。共陽數(shù)碼管公共端接高電平某個段要點亮對應(yīng)段信號必須為低電平。比如要顯示數(shù)字0共陽段碼是0xC0。共陰極正好相反對應(yīng)位為高電平。我用的是六位共陽數(shù)碼管所以段碼表做了注釋以免換板子時搞混。按鍵輸入方面我用PIO中斷加軟件消抖的方式來處理。按鍵按下時PIO會產(chǎn)生一次中斷我在ISR里只記錄當前按鍵狀態(tài)并置一個按鍵事件標志真正執(zhí)行功能調(diào)整放在主循環(huán)。消抖采用延時法檢測到按鍵狀態(tài)改變后延時20ms再讀取一次如果狀態(tài)一致就確認是有效按鍵。這樣做的好處是簡單可靠缺點是需要主循環(huán)及時處理但對電子鐘這種低頻應(yīng)用完全夠用。4. 上板調(diào)試的完整流程與排查技巧4.1 從燒寫硬件配置到運行軟件程序的步驟上板調(diào)試的第一步是綜合編譯Quartus硬件工程生成.sof文件。打開Programmer選擇JTAG模式添加.sof文件勾選Program/Configure點擊Start。這一步會把FPGA的硬件配置燒到板子上的FPGA配置RAM中掉電就丟失適合調(diào)試階段使用。注意如果NIOS II核里配置了JTAG調(diào)試模塊燒寫完.sof之后需要把軟件工程也編譯好生成.elf文件然后在Eclipse里右鍵工程名選擇Run As Nios II Hardware。這一步會自動連接JTAG先下載.sof也可以選擇跳過如果你已經(jīng)燒過.sof再下載.elf最后復(fù)位并運行程序。我建議分開操作先用Programmer燒.sof再用Eclipse運行.elf這樣能明確區(qū)分到底是硬件問題還是軟件問題。一個常見的連鎖現(xiàn)象是只燒了.sof忘記運行.elf板子上數(shù)碼管不亮或者只運行了.elf沒有燒.sofJTAG找不到設(shè)備。所以每次上電后第一件事先燒.sof再確認Nios II的JTAG調(diào)試模塊能被識別最后才運行軟件。4.2 通過調(diào)試器和串口定位異常的實戰(zhàn)思路調(diào)試NIOS II的時候最大的優(yōu)勢是可以看到變量設(shè)置斷點。我用的是Eclipse自帶的Debug配置斷點打在ISR里或主循環(huán)中觀察sec、min、hour變量是否正確累加。如果發(fā)現(xiàn)秒數(shù)不正常優(yōu)先檢查定時器初始值是否算錯以及標志位有沒有清除。另一個調(diào)試法寶是JTAG UART打印。我在初始化完成后打印一行System Ready然后在按鍵響應(yīng)里打印按鍵編號。有次按鍵功能失效我打印后發(fā)現(xiàn)按鍵中斷一直在觸發(fā)原因是PIO中斷使能后在ISR里沒有清除PIO的中斷捕獲標志。這個通過打印很快就能定位。數(shù)碼管顯示異常的排查思路稍微復(fù)雜一些。如果顯示亂碼優(yōu)先懷疑段碼表錯誤或共陽共陰搞反如果只有個別位不亮優(yōu)先檢查位選驅(qū)動如果整體閃爍優(yōu)先檢查掃描延時是否過大。我用示波器量了一下位選波形發(fā)現(xiàn)掃描周期大約6ms刷新一遍視覺上正好穩(wěn)定。這里也分享一個技巧如果不方便量頻率可以用肉眼觀察閃爍程度來估算一般1到2ms刷新一位整體刷新周期不要超過8ms。4.3 常見問題速查表現(xiàn)象可能原因解決辦法編譯通過但上板無反應(yīng)未燒.sof或.elf按順序燒寫.sof并運行.elf定時中斷不停觸發(fā)ISR未清除定時器狀態(tài)標志在ISR首行寫狀態(tài)寄存器清0按鍵無響應(yīng)PIO中斷捕獲標志未清除讀取并清除PIO捕獲寄存器數(shù)碼管亂碼共陽共陰段碼表弄反核對段碼表極性個別位不亮位選信號方向或電平不對檢查PIO方向和位選驅(qū)動極性計時明顯偏慢ISR耗時太長把耗時操作移到主循環(huán)程序跑飛或硬件報錯片上RAM容量不足增大On-Chip Memory容量5. 從基礎(chǔ)功能到擴展玩法的優(yōu)化路線5.1 顯示刷新算法和計時精度的進一步優(yōu)化如果覺得動態(tài)掃描的主循環(huán)占CPU太多可以做一個簡單的優(yōu)化把掃描放到定時器中斷里用1ms中斷切換一位。這樣主循環(huán)就解放出來了可以做更復(fù)雜的事情。我當時實測把掃描放進中斷后主循環(huán)CPU占用幾乎為零按鍵響應(yīng)也更靈敏。計時精度的優(yōu)化要關(guān)注晶振頻率。板載晶振標稱50MHz但實際可能有些偏差長期運行會累積誤差。NIOS II提供了altera_avalon_timer驅(qū)動可以在軟件里做一個簡單的校正用秒表看一小時差多少秒然后在Cnt子寄存器里補償Tick數(shù)。這個思路和機械表調(diào)快慢是一個道理。5.2 鬧鐘、整點報時和擴展外設(shè)的加入思路有了NIOS II軟核加功能主要就是軟件層面的工作。加鬧鐘只需要定義一個鬧鐘變量在秒進位后比較當前時間和鬧鐘時間一致則觸發(fā)蜂鳴器整點報時同理在分進位時判斷當前分鐘數(shù)是否為0即可。如果要用到蜂鳴器或繼電器只需要在Platform Designer里再加一組PIO然后在軟件里控制高低電平。這個工作量比純邏輯方案小太多也是軟核方案最大的價值所在。不過要注意新增外設(shè)后硬件工程要重新綜合生成.sof軟件的BSP也要重新生成否則地址映射會不一致導(dǎo)致訪問到錯誤的外設(shè)地址。我自己做完電子鐘后又把顯示從六位數(shù)碼管換成了LCD1602加了溫濕度讀取只是多加了一個I2C的IP和相應(yīng)驅(qū)動一周不到就弄完了。可見架構(gòu)搭好之后擴展性是相當大的。5.3 給新手學(xué)習(xí)者的實踐建議如果你第一次接觸NIOS II建議按這個順序推進第一步什么都不改只跑通Platform Designer自帶的hello_world工程確認JTAG下載、軟件運行、printf打印都正常第二步在這個基礎(chǔ)上加一個LED閃爍程序理解PIO輸出第三步對照本文加定時器、數(shù)碼管顯示、按鍵中斷最后再整合成完整電子鐘。每步都生成一個獨立版本的工程方便回溯。代碼維護上建議用Git管理工程每次能穩(wěn)定運行就打個tag因為FPGA工程文件多隨便改個底層可能就編譯不通過沒有版本管理容易心態(tài)崩潰。這個建議不僅適用于本項目做任何FPGA大項目都值得遵循。我實際操作中最深刻的體會是NIOS II電子鐘這個項目真正的難點不在代碼量而在于理解“硬件生成軟件、軟件控制硬件”的邊界。硬件上少一個IP、少連接一條總線軟件層面就多一次崩潰軟件上少清一次標志位硬件上就多一次異常。所以遇到問題不要慌先從硬件配置檢查起再從軟件驅(qū)動程序查起一步步縮小范圍最終一定能解決。以后你往Zynq這類ARMFPGA異構(gòu)架構(gòu)切換時這個項目的經(jīng)驗會給你很扎實的底氣。本文還有配套的精品資源點擊獲取