戰(zhàn):手寫(xiě)SPI主機(jī)模塊的協(xié)議解析與調(diào)試經(jīng)驗(yàn))
寫(xiě)SPI的FPGA實(shí)現(xiàn)這事兒我太熟了。這幾年調(diào)試各種ADC、Flash、傳感器SPI是我在FPGA上寫(xiě)過(guò)最多次的接口協(xié)議沒(méi)有之一。很多朋友一上來(lái)就找現(xiàn)成IP核或者去GitHub上扒代碼我倒覺(jué)得SPI這玩意兒把時(shí)序理清楚之后自己動(dòng)手寫(xiě)一個(gè)真的不難而且自寫(xiě)的主機(jī)/從機(jī)模塊用起來(lái)比啥都順手。這篇就把我在實(shí)際項(xiàng)目中摸索出來(lái)的經(jīng)驗(yàn)和思路做個(gè)總結(jié)從協(xié)議細(xì)節(jié)到Verilog實(shí)現(xiàn)再到板級(jí)調(diào)試一步步拆開(kāi)聊希望能給正在搞FPGA通信接口的朋友一些參考。1. 動(dòng)手之前先把SPI協(xié)議的底褲扒干凈SPI的全稱(chēng)是Serial Peripheral Interface串行外設(shè)接口是Motorola公司搞出來(lái)的一種同步串行通信協(xié)議。FPGA開(kāi)發(fā)里它出鏡率極高幾乎所有板上外設(shè)都留了SPI接口。它跟UART這類(lèi)異步協(xié)議不一樣SPI靠獨(dú)立的時(shí)鐘線來(lái)同步收發(fā)數(shù)據(jù)所以時(shí)序天然就可預(yù)測(cè)只要時(shí)鐘沿干凈基本不需要擔(dān)心UART那種波特率誤差的問(wèn)題。SPI總線上一般有四種信號(hào)線SCLK串行時(shí)鐘、MOSI主設(shè)備輸出/從設(shè)備輸入、MISO主設(shè)備輸入/從設(shè)備輸出、CS/SS片選信號(hào)。主機(jī)產(chǎn)生時(shí)鐘同時(shí)決定片選拉低哪個(gè)從設(shè)備所有通信由主機(jī)掌控。這里經(jīng)常有新手問(wèn)MOSI和MISO能不能合并成一根線當(dāng)然可以那就是半雙工模式三線制SPI。不過(guò)大多數(shù)普通器件都是標(biāo)準(zhǔn)的四線全雙工結(jié)構(gòu)除非是某些特殊傳感器或節(jié)省IO的應(yīng)用否則不建議自己搞三線排查問(wèn)題會(huì)麻煩不少。SPI協(xié)議最核心也最容易繞暈的一點(diǎn)就是它的四種工作模式。這個(gè)模式由時(shí)鐘極性CPOL和時(shí)鐘相位CPHA兩個(gè)參數(shù)決定。CPOL決定空閑時(shí)SCLK是高還是低CPHA決定數(shù)據(jù)在第一個(gè)還是第二個(gè)時(shí)鐘沿被采樣。不少工程師在這里栽過(guò)跟頭因?yàn)閿?shù)據(jù)手冊(cè)上寫(xiě)著“Mode 3”或者“CPOL1, CPHA1”翻譯到代碼里就是一堆糾結(jié)。這兩個(gè)參數(shù)我建議這樣理解CPOL0表示空閑時(shí)時(shí)鐘是低電平CPOL1表示空閑時(shí)時(shí)鐘是高電平CPHA0表示數(shù)據(jù)在第一個(gè)時(shí)鐘邊沿采樣CPHA1表示在第二個(gè)時(shí)鐘邊沿采樣。把所有組合都列出來(lái)就是下面這張表模式CPOLCPHA采樣邊沿常見(jiàn)應(yīng)用場(chǎng)景Mode 000上升沿大多數(shù)Flash、ADC、SD卡Mode 101下降沿較少見(jiàn)部分傳感器Mode 210下降沿部分工業(yè)級(jí)DACMode 311上升沿SPI FlashW25Q系列常用這張表不是讓你背的是讓你查的。我開(kāi)始做FPGA那會(huì)兒每次遇到新器件都要翻出這張表對(duì)照手冊(cè)確認(rèn)一次。用Verilog寫(xiě)主機(jī)的時(shí)候我傾向于把CPOL和CPHA做成模塊參數(shù)而不是直接寫(xiě)死在代碼里。這樣遇到換器件、換模式的時(shí)候改個(gè)頂層參數(shù)就行不用動(dòng)核心時(shí)序邏輯。實(shí)測(cè)下來(lái)這個(gè)習(xí)慣幫我省了非常多的時(shí)間強(qiáng)烈推薦。還有個(gè)關(guān)鍵概念MSB First還是LSB First。絕大多數(shù)SPI器件是MSB先行但個(gè)別器件反著來(lái)比如某些LCD驅(qū)動(dòng)芯片就是LSB First。Verilog設(shè)計(jì)時(shí)需要把移位方向做成可配置否則換器件又得改代碼。我自己習(xí)慣用參數(shù)配置加上通用移位邏輯后面會(huì)放代碼詳細(xì)介紹。2. 主機(jī)還是從機(jī)先定方案再寫(xiě)代碼SPI設(shè)備分主從但FPGA這邊做主機(jī)的場(chǎng)景占絕大多數(shù)。比如給ADC采集數(shù)據(jù)、給DAC送波形、讀寫(xiě)Flash、配置傳感器全是FPGA當(dāng)主機(jī)去主動(dòng)發(fā)起通信。少數(shù)場(chǎng)景下FPGA要當(dāng)從機(jī)比如跟MCU通信、被外部處理器配置但這種一般用現(xiàn)成IP核更省事自己寫(xiě)從機(jī)的收益不大。我在項(xiàng)目里遇到最多的組合是FPGA主機(jī) 多個(gè)SPI從設(shè)備??刂扑悸泛芎?jiǎn)單每個(gè)從設(shè)備分配一個(gè)獨(dú)立的CS信號(hào)MOSI、MISO、SCLK可以共享。但實(shí)際上這里有個(gè)坑如果多個(gè)從設(shè)備的MISO都掛在同一根線上那么必須保證同一時(shí)刻只有一個(gè)從設(shè)備的CS被拉低否則MISO上會(huì)出現(xiàn)總線沖突。更穩(wěn)妥的做法是給每個(gè)從設(shè)備加上輸出使能控制從設(shè)備沒(méi)被選中時(shí)它的MISO輸出必須處于高阻態(tài)。這一點(diǎn)在選型和設(shè)計(jì)階段就要確認(rèn)清楚有些器件的手冊(cè)會(huì)注明“MISO is tri-stated when CS is high”但有些器件不會(huì)遇到后者只能軟件上額外處理。關(guān)于硬件片選和軟件片選我也簡(jiǎn)單說(shuō)說(shuō)。硬件片選就是CS直接由FPGA的GPIO控制拉低即選中拉高即釋放簡(jiǎn)單直接。軟件片選則是在有些場(chǎng)景下希望通過(guò)發(fā)送特定的命令字來(lái)切換從設(shè)備或者片選信號(hào)由某個(gè)移位寄存器的輸出去控制這樣做的好處是省IO壞處是時(shí)序復(fù)雜度上來(lái)了而且一旦軟件跑飛總線可能一直掛著。我個(gè)人的建議是在FPGA資源允許的情況下老老實(shí)實(shí)用硬件片選一個(gè)設(shè)備一個(gè)CS引腳代碼清晰時(shí)序可控排查問(wèn)題也方便。只有在GPIO嚴(yán)重不足的極端場(chǎng)景下才考慮軟件片選方案。主從方案定了之后接下來(lái)就是傳輸速率和SCLK頻率的規(guī)劃。SPI的速率上限取決于從設(shè)備能跑多快以及FPGA內(nèi)部邏輯能不能跟上。比如W25Q64這款Flash新的版本支持104MHz甚至更高但老版本或者走長(zhǎng)飛線的時(shí)候就只能跑20MHz以?xún)?nèi)。FPGA側(cè)生成SCLK有兩種思路一種是直接用系統(tǒng)時(shí)鐘分頻得到整數(shù)分頻的SCLK另一種是使用PLL/MMCM產(chǎn)生精確的時(shí)鐘。前者適合低速通信后者的抖動(dòng)特性更好適合告訴傳輸。這里有個(gè)容易被新手忽略的問(wèn)題分頻產(chǎn)生的SCLK相位抖動(dòng)和占空比不對(duì)稱(chēng)在高速模式下會(huì)限制通信距離。如果只是跑一個(gè)板級(jí)Flash30MHz以下隨便分頻都能用如果是要拉長(zhǎng)線跟外部設(shè)備通信或者SCLK超過(guò)50MHz就必須用PLL去生成干凈時(shí)鐘了。我在一個(gè)需要跟外部工業(yè)設(shè)備通信的項(xiàng)目里吃過(guò)這個(gè)虧代碼看著沒(méi)問(wèn)題就是偶發(fā)傳錯(cuò)數(shù)據(jù)最后用示波器一測(cè)發(fā)現(xiàn)是SCLK在邊沿附近有毛刺改成PLL出時(shí)鐘后問(wèn)題立刻消失。3. RTL實(shí)戰(zhàn)寫(xiě)一個(gè)參數(shù)化的SPI主機(jī)模塊我平時(shí)寫(xiě)SPI主機(jī)核心就是一個(gè)帶狀態(tài)機(jī)的移位寄存器。別看網(wǎng)上講得玄乎其實(shí)就三步拉低CS按位收發(fā)數(shù)據(jù)拉高CS。復(fù)雜點(diǎn)的場(chǎng)景會(huì)加FIFO做緩沖加DMA做數(shù)據(jù)搬運(yùn)但通信內(nèi)核沒(méi)變。先上一個(gè)最精簡(jiǎn)的四線主機(jī)代碼框架這也是我項(xiàng)目里反復(fù)在用、逐步完善的版本module spi_master #( parameter CPOL 1, // 時(shí)鐘極性 parameter CPHA 1, // 時(shí)鐘相位 parameter DATA_WIDTH 8, // 數(shù)據(jù)位寬 parameter SCLK_DIV 4, // SCLK clk / (SCLK_DIV*2) parameter MSB_FIRST 1 // 1: MSB先行, 0: LSB先行 )( input wire clk, input wire rst_n, input wire start, input wire [DATA_WIDTH-1:0] tx_data, output reg [DATA_WIDTH-1:0] rx_data, output reg busy, output reg sclk, output reg cs_n, output reg mosi, input wire miso ); localparam IDLE 2d0; localparam TX 2d1; localparam DONE 2d2; reg [1:0] state; reg [7:0] clk_cnt; reg [3:0] bit_cnt; reg sclk_r; wire sclk_rise (sclk_r 1b0) (sclk 1b1); wire sclk_fall (sclk_r 1b1) (sclk 1b0); always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; busy 0; cs_n 1; sclk CPOL; mosi 0; bit_cnt 0; clk_cnt 0; end else begin case (state) IDLE: begin cs_n 1; busy 0; if (start) begin cs_n 0; busy 1; bit_cnt 0; clk_cnt 0; state TX; end end TX: begin // SCLK 分頻生成 if (clk_cnt SCLK_DIV - 1) begin clk_cnt 0; sclk ~sclk; end else begin clk_cnt clk_cnt 1; end // 發(fā)送和接收數(shù)據(jù) if (sclk_fall) begin if (MSB_FIRST) mosi tx_data[DATA_WIDTH - 1 - bit_cnt]; else mosi tx_data[bit_cnt]; end if (sclk_rise) begin if (MSB_FIRST) rx_data[DATA_WIDTH - 1 - bit_cnt] miso; else rx_data[bit_cnt] miso; if (bit_cnt DATA_WIDTH - 1) state DONE; else bit_cnt bit_cnt 1; end end DONE: begin cs_n 1; busy 0; state IDLE; end endcase end end always (posedge clk or negedge rst_n) begin if (!rst_n) sclk_r CPOL; else sclk_r sclk; end endmodule這段代碼默認(rèn)支持CPOL/CPHA參數(shù)化采樣和更新沿也是通過(guò)sclk_rise和sclk_fall這兩個(gè)脈沖信號(hào)來(lái)統(tǒng)一控制的。有個(gè)細(xì)節(jié)值得注意我在發(fā)送數(shù)據(jù)的時(shí)候用sclk_fall更新MOSI在sclk_rise采樣MISO這樣能保證數(shù)據(jù)變化沿和采樣沿錯(cuò)開(kāi)半個(gè)周期從設(shè)備端更不容易采到跳變中的數(shù)據(jù)。當(dāng)然如果你的CPHA配置不同這兩個(gè)沿的對(duì)應(yīng)的采樣關(guān)系會(huì)變但只要抓住“數(shù)據(jù)在采樣沿穩(wěn)定即可”這個(gè)原則代碼微調(diào)一下就能適配所有模式。這個(gè)模塊在功能上是能用的但實(shí)際項(xiàng)目里我不會(huì)直接這么裸用。因?yàn)檎鎸?shí)場(chǎng)景下需要處理連續(xù)傳輸、FIFO對(duì)接、多設(shè)備切換等復(fù)雜邏輯所以我會(huì)在頂層再包一層狀態(tài)機(jī)負(fù)責(zé)裝配命令、啟動(dòng)傳輸、校驗(yàn)返回值。比如在Flash芯片的讀操作里主機(jī)先發(fā)送讀命令和24位地址然后再產(chǎn)生額外的時(shí)鐘來(lái)讀數(shù)據(jù)。你會(huì)發(fā)現(xiàn)一個(gè)單次的8位SPI主機(jī)是不夠用的需要把單次傳輸?shù)奈粚挃U(kuò)展或者連續(xù)多次調(diào)用模塊接口。我后來(lái)把模塊擴(kuò)展成可配置位寬最長(zhǎng)支持64位單次傳輸這樣不管讀Flash還是讀ADC狀態(tài)寄存器都?jí)蛴昧?。再說(shuō)從機(jī)實(shí)現(xiàn)。FPGA做從機(jī)代碼上不復(fù)雜但驗(yàn)證起來(lái)麻煩。核心思路是借助外部時(shí)鐘直接采樣數(shù)據(jù)。但有一種場(chǎng)景我會(huì)特別注意就是從機(jī)是FPGA主機(jī)是MCU或DSP時(shí)鐘線上可能有毛刺或者電平不穩(wěn)這時(shí)候需要加輸入同步器和邊沿檢測(cè)。我的做法是先把SCLK打兩拍同步到系統(tǒng)時(shí)鐘域然后再取上升沿和下降沿。注意這里的同步打拍會(huì)引入兩個(gè)時(shí)鐘周期的延遲對(duì)從機(jī)來(lái)說(shuō)問(wèn)題不大因?yàn)镾PI本身就是主機(jī)主導(dǎo)的協(xié)議主機(jī)發(fā)送的時(shí)序不會(huì)等從機(jī)反饋。但如果你要在這個(gè)場(chǎng)景下做高精度的回環(huán)測(cè)試就要把這幾個(gè)周期的延遲算進(jìn)去了。4. 上板調(diào)試別急著接外設(shè)先用仿真和ILA把時(shí)序跑穩(wěn)我在工程調(diào)試中養(yǎng)成了一個(gè)習(xí)慣任何SPI模塊先做仿真再用ILA集成邏輯分析儀實(shí)測(cè)最后才接真實(shí)外設(shè)。這三步順序不能亂否則出了問(wèn)題很難定位是邏輯錯(cuò)了、時(shí)序約束不夠還是外部器件本身響應(yīng)異常。仿真階段的重點(diǎn)是檢查波形邊沿關(guān)系。我給SPI模塊搭的testbench會(huì)生成一個(gè)模擬從機(jī)響應(yīng)這樣能驗(yàn)證MISO數(shù)據(jù)的接收路徑。很多人只在發(fā)送側(cè)看MOSI波形忽略了回讀校驗(yàn)實(shí)際上SPI是雙向的仿真階段把回讀路徑打通后面接真實(shí)器件會(huì)順暢很多。用Vivado的同學(xué)仿真完之后記得拉一個(gè)ILA核來(lái)抓實(shí)際波形。我這里說(shuō)幾個(gè)ILA調(diào)試的小技巧觸發(fā)條件可以用start信號(hào)的上升沿也可以用cs_n下降沿看你要觀察哪個(gè)階段采樣深度不用開(kāi)太大SPI波形比較規(guī)律512或1024足夠如果需要觀察SCLK邊沿附近的毛刺ILA的采樣時(shí)鐘必須比SCLK高4倍以上才有意義否則采出來(lái)的波形是混疊的別把ILA的probe加太多我見(jiàn)過(guò)有人把每個(gè)內(nèi)部寄存器都拉出來(lái)最后調(diào)試界面根本看不過(guò)來(lái)。實(shí)測(cè)階段有一個(gè)經(jīng)驗(yàn)每次接新板子我都會(huì)先做一個(gè)最簡(jiǎn)單的回環(huán)測(cè)試把MOSI直接短接到MISO然后讓FPGA發(fā)送一組遞增數(shù)據(jù)并回讀比對(duì)是否一致。這個(gè)測(cè)試做通了說(shuō)明主機(jī)的發(fā)送、接收、時(shí)序都沒(méi)問(wèn)題之后再去接外部器件。回環(huán)測(cè)試通過(guò)之后再去連真實(shí)從設(shè)比如Flash芯片先讀JEDEC ID0x9F命令如果ID能讀出來(lái)通常說(shuō)明菜鳥(niǎo)階段最擔(dān)心的“時(shí)序不對(duì)”已經(jīng)過(guò)去了后面就是根據(jù)具體協(xié)議操作寄存器的事了。我遇到很多新手上來(lái)就懟著W25Q64的讀數(shù)據(jù)操作調(diào)半天結(jié)果發(fā)現(xiàn)寄存器配置都還沒(méi)理清。所以這里特別強(qiáng)調(diào)接口調(diào)通和芯片功能調(diào)通是兩回事。接口調(diào)通靠的是SPI邏輯正確芯片功能調(diào)通需要吃透芯片手冊(cè)。建議先從最簡(jiǎn)單的操作入手一步一步來(lái)。5. 便宜好用的“接口測(cè)試?yán)鳌边壿嫹治鰞x和必要的上拉電阻調(diào)試SPI最痛苦的事情之一是遇到“看起來(lái)波形都對(duì)但數(shù)據(jù)不對(duì)”的情況。這里我強(qiáng)烈建議備一個(gè)便宜的邏輯分析儀不需要什么高級(jí)貨市面上幾十塊錢(qián)的USB邏輯分析儀就行采樣率24MHz的夠用了。用邏輯分析儀去抓SCLK、MOSI、MISO、CS四根線跟用ILA相比有一個(gè)好處不占用FPGA內(nèi)部資源也不需要重新綜合布線而且能看到真實(shí)的引腳電平而不是內(nèi)部信號(hào)。用邏輯分析儀時(shí)要注意探頭的輸入電容會(huì)引入信號(hào)邊沿變緩。低速率比如幾MHz下影響不大但速率一高波形邊沿可能變得不那么陡峭容易誤判。所以排查高速SPI問(wèn)題最好還是用示波器邏輯分析儀適合看協(xié)議層示波器適合看物理層兩者各有分工。另外特別想說(shuō)一下上拉電阻的問(wèn)題。SPI的MOSI、SCLK、CS一般由主機(jī)強(qiáng)驅(qū)動(dòng)不需要上拉。但MISO這條線如果從設(shè)備支持三態(tài)輸出建議加一個(gè)10kΩ左右的上拉電阻避免在所有從設(shè)備都未被選中時(shí)MISO浮空。浮空的輸入引腳在邏輯器件看來(lái)是高電平還是低電平是不確定的容易導(dǎo)致主機(jī)在非通信期間采到隨機(jī)數(shù)據(jù)。如果板上已經(jīng)預(yù)留了上拉位置就焊上沒(méi)有預(yù)留就在FPGA內(nèi)部把MISO引腳的弱上拉打開(kāi)。當(dāng)然如果所有從設(shè)備的MISO都是推挽輸出且不會(huì)同時(shí)使能可以不處理但這個(gè)習(xí)慣養(yǎng)成了以后做項(xiàng)目會(huì)少很多奇怪的問(wèn)題。說(shuō)到這順便提一個(gè)容易踩的坑FPGA和外部設(shè)備的電平標(biāo)準(zhǔn)不一致?,F(xiàn)在很多傳感器和Flash是1.8V或2.5V供電而FPGA的Bank電壓可能是3.3V。直接相連輕則通信異常重則燒引腳。做SPI對(duì)接之前一定先查清楚兩邊IO的電平標(biāo)準(zhǔn)和Bank電壓設(shè)置。Xilinx 7系列FPGA的HR Bank支持1.2V到3.3VHP Bank最高支持1.8V選Bank和分配引腳時(shí)就得考慮電平匹配不能隨便找一個(gè)空閑引腳就分配出去。6. 常見(jiàn)問(wèn)題排查與波形實(shí)例我把自己和身邊朋友做SPI時(shí)踩過(guò)的一些典型坑整理成了下面這張表遇到問(wèn)題直接按圖索驥問(wèn)題現(xiàn)象可能原因排查方法通信完全沒(méi)反應(yīng)MISO一直高電平CS信號(hào)沒(méi)拉低、從設(shè)備供電異常、MISO上拉導(dǎo)致空閑被拉高先查從設(shè)備電源再用邏輯分析儀看CS有沒(méi)有正確拉低數(shù)據(jù)偶爾錯(cuò)一位或錯(cuò)幾位SCLK頻率過(guò)高、走線過(guò)長(zhǎng)導(dǎo)致信號(hào)質(zhì)量差、時(shí)鐘采樣沿選錯(cuò)降低SCLK頻率試一下再用示波器看SCLK邊沿是否惡化讀回來(lái)的數(shù)據(jù)全FF或全00SPI模式不匹配CPOL/CPHA配錯(cuò)、從設(shè)備沒(méi)被正確喚醒對(duì)著手冊(cè)確認(rèn)Mode用回環(huán)測(cè)試驗(yàn)證主機(jī)側(cè)多個(gè)從設(shè)備掛在總線上時(shí)互相干擾某個(gè)從設(shè)備MISO沒(méi)有三態(tài)導(dǎo)致總線沖突逐個(gè)拉CS測(cè)試判斷是哪個(gè)設(shè)備在搗亂高速通信時(shí)波形有回溝信號(hào)反射、阻抗不匹配、走線過(guò)孔過(guò)多降低速率先驗(yàn)證功能再優(yōu)化PCB走線或串聯(lián)33Ω電阻這里重點(diǎn)講一下SPI模式不匹配的問(wèn)題。你寫(xiě)代碼的時(shí)候CPOL和CPHA各配一個(gè)參數(shù)看著很簡(jiǎn)單但一旦碰到那種“必須完全匹配器件手冊(cè)才能工作”的從設(shè)備就很容易抓狂。我記得有次調(diào)試一顆ADC數(shù)據(jù)手冊(cè)明明寫(xiě)著Mode 3結(jié)果按Mode 3配怎么讀都是亂碼后來(lái)用邏輯分析儀對(duì)比了廠家驅(qū)動(dòng)板卡的波形才發(fā)現(xiàn)它實(shí)際是Mode 0。這種情況雖不常見(jiàn)但一旦遇到記住示波器和邏輯分析儀上看到的實(shí)際數(shù)據(jù)才是王道別迷信手冊(cè)。另一個(gè)高頻問(wèn)題就是跨時(shí)鐘域。如果你的SPI主機(jī)模塊工作在100MHz系統(tǒng)時(shí)鐘下而從設(shè)備的響應(yīng)時(shí)間是微妙級(jí)別那么在狀態(tài)機(jī)設(shè)計(jì)時(shí)必須有超時(shí)處理機(jī)制否則從設(shè)備無(wú)響應(yīng)時(shí)整個(gè)模塊會(huì)卡在某個(gè)狀態(tài)出不來(lái)。我的做法是在狀態(tài)機(jī)里加一個(gè)超時(shí)計(jì)數(shù)器超過(guò)閾值自動(dòng)回到IDLE并置位錯(cuò)誤標(biāo)志。這個(gè)在ADC或Flash等響應(yīng)時(shí)間不確定的器件上特別實(shí)用。7. 進(jìn)階思路FIFO緩沖、DMA和時(shí)鐘域隔離到這里你已經(jīng)能用自寫(xiě)的SPI主機(jī)跟Flash、ADC、傳感器打交道了。但遇到高吞吐量的應(yīng)用比如高速數(shù)據(jù)采集或圖像數(shù)據(jù)讀取純靠CPU / 狀態(tài)機(jī)搬運(yùn)數(shù)據(jù)就不夠用了。這時(shí)候就需要在SPI主機(jī)外面再加兩層?xùn)|西FIFO和DMA控制器。FIFO的作用是緩沖數(shù)據(jù)避免主機(jī)和SPI之間頻繁握手。主機(jī)模塊負(fù)責(zé)往FIFO里寫(xiě)讀到的數(shù)據(jù)DMA或者上層讀邏輯再?gòu)腇IFO里批量取走。這個(gè)結(jié)構(gòu)能讓SPI一直跑在滿速率狀態(tài)不會(huì)因?yàn)榈却∽邤?shù)據(jù)而停頓。DMA控制器跟SPI對(duì)接的時(shí)候主要注意兩點(diǎn)一是FIFO的空滿標(biāo)志要能正確觸發(fā)DMA請(qǐng)求二是DMA傳輸結(jié)束之后要能產(chǎn)生中斷或標(biāo)志位通知上層軟件處理。在Xilinx平臺(tái)上通常的做法是用AXI4-Stream接口把SPI數(shù)據(jù)流接到AXI DMA IP核上這樣能跟PS側(cè)內(nèi)存直接打交道。不過(guò)這套做法的學(xué)習(xí)曲線比較陡建議基礎(chǔ)功能跑通之后再一步步加。時(shí)鐘域隔離是另一個(gè)容易被忽視的點(diǎn)。假設(shè)你的SPI跑在25MHz而系統(tǒng)總線跑在150MHz如果直接讓跨時(shí)鐘域信號(hào)在系統(tǒng)邏輯里使用可能需要加同步器或者異步FIFO來(lái)處理。我在使用專(zhuān)用IP核時(shí)一般都能滿足這個(gè)需求但如果自己寫(xiě)主機(jī)還接了很多外設(shè)就需要注意時(shí)鐘域的約束問(wèn)題。Vivado里可以建不同的時(shí)鐘域約束文件把SCLK分頻時(shí)鐘和主時(shí)鐘定義清楚不然綜合結(jié)果可能不理想。芯片設(shè)計(jì)里常說(shuō)“時(shí)序收斂”FPGA開(kāi)發(fā)也一樣只是層次不同。8. 拓展應(yīng)用SPI與I2C怎么選以及后續(xù)擴(kuò)展方向很多朋友在項(xiàng)目選型時(shí)會(huì)問(wèn)SPI和I2C到底用哪個(gè)其實(shí)沒(méi)有絕對(duì)更好的答案只有更合適的應(yīng)用場(chǎng)景。SPI優(yōu)點(diǎn)是速率高、全雙工、時(shí)序簡(jiǎn)單缺點(diǎn)是引腳多4根起步且每個(gè)從設(shè)備都要單獨(dú)拉一根CS。I2C優(yōu)點(diǎn)就是省引腳兩根線上能掛一票設(shè)備還有設(shè)備地址機(jī)制缺點(diǎn)是速率低、半雙工、時(shí)序協(xié)議相對(duì)復(fù)雜還需要處理ACK仲裁之類(lèi)的問(wèn)題。如果只是在FPGA內(nèi)部跟板級(jí)器件打交道而且速率要求不低SPI壓倒性勝出。但如果是要跟一堆低速傳感器交互比如溫度傳感器、EEPROMI2C可能更合適。我之前有一塊板子板級(jí)溫度監(jiān)控用的是I2C數(shù)據(jù)采集鏈路用的是SPI各司其職互不沖突。所以別糾結(jié)誰(shuí)取代誰(shuí)作為FPGA開(kāi)發(fā)者我建議SPI和I2C的接口代碼都能隨手寫(xiě)出基本功扎實(shí)了后續(xù)擴(kuò)展做什么都順。至于SPI的后續(xù)擴(kuò)展方向有兩條我覺(jué)得值得關(guān)注一個(gè)是把SPI主機(jī)封裝成AXI4-Lite或AXI4-Stream接口方便做SoC集成這也是從邏輯設(shè)計(jì)走向系統(tǒng)設(shè)計(jì)必須跨過(guò)的一步另一個(gè)是在高速SPI場(chǎng)景下去研究硬核SPI控制器比如Zynq UltraScale里的SPI IP功耗和時(shí)序表現(xiàn)都比純邏輯好很多。FPGA開(kāi)發(fā)就是這樣先把基礎(chǔ)接口吃透后面無(wú)論做什么項(xiàng)目這些底子都會(huì)成為你最穩(wěn)定的“技術(shù)底盤(pán)”。我個(gè)人在實(shí)際項(xiàng)目中還有一個(gè)習(xí)慣就是每做好一個(gè)SPI模塊就把它的Testbench、約束文件和調(diào)試筆記歸檔到一個(gè)模板工程里。下次遇到類(lèi)似的從設(shè)備直接在這個(gè)模板上改參數(shù)半天就能把接口調(diào)通。這比每次從零開(kāi)始寫(xiě)要快得多而且隨著沉淀的模塊越來(lái)越多后續(xù)項(xiàng)目的開(kāi)發(fā)速度會(huì)有明顯提升。分享出來(lái)希望能幫到你。