SPI通信:從時序細節(jié)到Verilog代碼實戰(zhàn))
SPI通信FPGA實現(xiàn)這個話題隔三差五就會在群里被提起來一次。見得最多的提問是我的SCLK已經(jīng)降到十幾兆了為什么從機還是收不到數(shù)據(jù)——其實很多人沒有意識到SCLK慢不等于時序?qū)ΑPGA上做SPI的真正難點不在速度而在你是否在正確的邊沿做了正確的事。SPI本身只有四根線SCLK、MOSI、MISO、CS_N全雙工主從結(jié)構(gòu)速度可以拉得很高。FPGA實現(xiàn)SPI和單片機不一樣單片機調(diào)用的SPI外設(shè)已經(jīng)把數(shù)據(jù)移位、邊沿采樣、片選時序全封裝好了你只需要配寄存器FPGA里一切都回到最底層你得自己盯著每一位數(shù)據(jù)什么時候放到總線上、什么時候從總線上取下來。換個角度說你等于用硬件邏輯重新發(fā)明一個SPI外設(shè)。這篇文章把協(xié)議拆解、模塊設(shè)計、仿真聯(lián)調(diào)、Flash讀寫這一整條鏈路都講一遍。適合兩種人看剛學(xué)完Verilog不知道怎么寫第一個通信模塊的入門者以及已經(jīng)有模塊但總在某些莫名其妙的通信問題上卡殼的開發(fā)者。看完之后SPI對你來說就不再是一個會讀時序圖但寫不出代碼的東西了。1. SPI協(xié)議并沒有想象中簡單四個信號背后的時序細節(jié)1.1 四個信號與主從關(guān)系SPI是典型的Master-Slave結(jié)構(gòu)主機負責(zé)產(chǎn)生SCLK和CS_N從機被動響應(yīng)。四條信號各干各的SCLK串行時鐘由主機產(chǎn)生給從機提供采樣節(jié)奏。MOSIMaster Output Slave Input主機發(fā)數(shù)據(jù)從機收數(shù)據(jù)。MISOMaster Input Slave Output從機發(fā)數(shù)據(jù)主機收數(shù)據(jù)。CS_N片選低有效主機控制。CS_N拉低從機開始工作CS_N拉高從機進入空閑狀態(tài)。這里有個新手容易忽略的點SPI是同時收發(fā)。MOSI和MISO在同一個SCLK周期里各自傳輸一位數(shù)據(jù)所以它天生是全雙工。后面做W25Q64這類SPI Flash讀寫時你想讀數(shù)據(jù)就必須繼續(xù)給SCLK哪怕主機沒有數(shù)據(jù)要發(fā)也要假裝發(fā)幾個空字節(jié)把時鐘頂出來。還有一個關(guān)鍵認知SPI協(xié)議本身沒有定義應(yīng)答機制。主機發(fā)一個字節(jié)從機是否收到、是否正確協(xié)議層完全不知道。所以實際項目里要么依賴從機返回的數(shù)據(jù)做校驗要么靠CRC、命令應(yīng)答這類應(yīng)用層機制來保證可靠性。這一點和I2C的ACK機制差別很大很多人從I2C轉(zhuǎn)過來會不適應(yīng)。1.2 四種工作模式與采樣邊沿SPI模式由CPOL時鐘極性和CPHA時鐘相位兩個參數(shù)決定CPOL決定SCLK空閑電平。CPOL0空閑低CPOL1空閑高。CPHA決定數(shù)據(jù)采樣相位。CPHA0在第一個時鐘沿采樣CPHA1在第二個時鐘沿采樣。組合起來就是四種模式模式CPOLCPHA主機輸出數(shù)據(jù)時機主機采樣數(shù)據(jù)時機Mode 000SCLK下降沿SCLK上升沿Mode 101SCLK上升沿SCLK下降沿Mode 210SCLK上升沿SCLK下降沿Mode 311SCLK下降沿SCLK上升沿判斷從機工作在哪個模式最好的辦法是打開數(shù)據(jù)手冊看時序圖而不是猜。時序圖上一般會畫出數(shù)據(jù)變化沿和采樣沿照著比對就行。比如W25Q64支持Mode 0和Mode 3大部分場景用Mode 0。OLED驅(qū)動芯片SSD1306、SD卡、各種傳感器多數(shù)也是默認Mode 0或Mode 3所以入門階段把Mode 0吃透已經(jīng)能覆蓋很多從機了。給新手一個建議如果從機手冊實在看不懂可以用邏輯分析儀抓主機一個已知從機的波形反推模式。我調(diào)過一塊音頻芯片手冊上寫的是Mode 0 compatible但用標(biāo)準(zhǔn)Mode 0怎么都讀不到正確寄存器最后抓波形發(fā)現(xiàn)它在SCLK上升沿更新數(shù)據(jù)主機應(yīng)該在下降沿采樣實際是Mode 1。所以手冊最大經(jīng)驗次之波形最靠譜。1.3 FPGA實現(xiàn)SPI和單片機實現(xiàn)SPI的本質(zhì)區(qū)別單片機上的SPI外設(shè)是一個硬件模塊寄存器配好了硬件自動完成移位、采樣的全部過程CPU只需要往數(shù)據(jù)寄存器寫值、讀值。FPGA上沒有這個現(xiàn)成的外設(shè)你需要用寄存器、狀態(tài)機、計數(shù)器和少量組合邏輯自己搭建一條數(shù)據(jù)通路。這里有一個本質(zhì)區(qū)別單片機的SPI時序由硬件固定可配置項也就模式、時鐘分頻、位序這幾個FPGA的SPI時序完全由你的代碼決定你可以隨時插入等待周期、自定義CS建立時間、甚至讓同一個模塊支持多種模式。靈活性高但代價是每一個時序細節(jié)都要自己負責(zé)。另一個區(qū)別是并行性。FPGA里你可以放下多個SPI控制器實例同時驅(qū)動不同的從機互不干擾。單片機上很多SPI外設(shè)只有一個或兩個總線多了就得靠軟件分時復(fù)用。這也是很多項目選FPGA做傳感器采集板的原因——SPI設(shè)備多、數(shù)據(jù)量大軟件分時處理容易來不及。2. 系統(tǒng)架構(gòu)劃分與狀態(tài)機設(shè)計SPI Master的核心決策2.1 從頂?shù)降椎哪K劃分寫SPI設(shè)計前先想清楚系統(tǒng)里有哪些模塊別一上來就寫時序邏輯。一個典型的FPGA SPI系統(tǒng)從上往下大致是層級模塊職責(zé)應(yīng)用層用戶邏輯產(chǎn)生要發(fā)送的數(shù)據(jù)消費接收的數(shù)據(jù)接口層FIFO/寄存器組緩沖數(shù)據(jù)提供握手信號協(xié)議層SPI Master控制器完成字節(jié)級時序產(chǎn)生SCLK/MOSI/CS_N物理層IO約束/電平轉(zhuǎn)換管腳分配電平匹配很多初學(xué)者直接拿一個always塊邊發(fā)邊收看起來代碼量小但后續(xù)要加FIFO、要支持多從機、要切換模式全部堆在一起根本改不動。我建議至少拆成兩個模塊一個純粹的SPI字節(jié)控制器負責(zé)把一字節(jié)數(shù)據(jù)按SPI時序送出去并讀回一字節(jié)另一個業(yè)務(wù)狀態(tài)機負責(zé)按業(yè)務(wù)需求組合多個字節(jié)的傳輸。SPI控制器對外接口可以這樣設(shè)計clk系統(tǒng)時鐘。rst_n異步復(fù)位低有效。start啟動信號單周期脈沖。tx_data待發(fā)送數(shù)據(jù)。rx_data接收數(shù)據(jù)。done傳輸完成單周期脈沖。busy模塊忙標(biāo)志busy1時不能再啟動新傳輸。這種接口的好處是通用后面不管接Flash、接ADC、接OLED只需要換業(yè)務(wù)狀態(tài)機SPI控制器可以原樣復(fù)用。2.2 為什么狀態(tài)機比直接計數(shù)更適合SPI時序SPI的每一比特都要經(jīng)歷數(shù)據(jù)建立-采樣-數(shù)據(jù)變化幾個階段如果只用計數(shù)器翻轉(zhuǎn)SCLK代碼寫起來確實簡單但遇到下面的場景就會很難受某些從機要求CS_N拉低后至少等幾個時鐘周期再給SCLK某些從機要求兩個字節(jié)之間插入延時某些從機要求主機在采樣前留出額外等待時間。這些插入等待的需求用純計數(shù)器也能做但狀態(tài)一多代碼就變成一團亂麻。狀態(tài)機的優(yōu)勢就在于顯式表達時序階段每個狀態(tài)代表SCLK的一個半周期或一個空閑階段狀態(tài)跳轉(zhuǎn)條件就是計數(shù)到某個值??创a的人一眼就能知道當(dāng)前處于什么階段后續(xù)加時序約束也只需要在狀態(tài)機里加狀態(tài)。而且狀態(tài)機的輸出邏輯非常自然。比如Mode 0下SCLK空閑為低進入第一個狀態(tài)后保持SCLK為低計滿半周期后跳到高電平狀態(tài)高電平狀態(tài)再計滿半周期才采樣。這樣SCLK不是一個獨立的自由運行時鐘而是受狀態(tài)機控制的受控時鐘CS_N、MOSI的變化時機都能和SCLK精確對齊不會再出現(xiàn)那種波形看起來對實際上沿的位置差了一拍的問題。2.3 波特率和分頻系數(shù)的取舍SCLK的最高頻率由兩個東西決定從機支持的最大SCLK頻率以及FPGA到從機之間的物理鏈路質(zhì)量。不要一上來就拉滿工程上合理的選擇是先定一個穩(wěn)妥的基準(zhǔn)頻率跑通后再慢慢提頻。分頻系數(shù)的計算很簡單SCLK頻率等于系統(tǒng)時鐘頻率除以分頻比。以100MHz系統(tǒng)時鐘為例想要10MHz的SCLK分頻比就是10半周期就是5個系統(tǒng)時鐘周期。如果從機最高SCLK是40MHz我通常不會直接用40MHz而是先跑到10MHz把協(xié)議調(diào)通再逐步提到25MHz、30MHz。原因很簡單高頻下信號完整性問題、采樣沿余量問題全都會冒出來到時候你分不清是協(xié)議代碼的問題還是硬件鏈路的問題。另外注意一點SCLK低電平時間和高電平時間最好對稱。分頻系數(shù)是偶數(shù)時天然對稱奇數(shù)時會有高低電平不均的問題。所以參數(shù)化設(shè)計里HALF_CLK這個值盡量用偶數(shù)分頻折算哪怕SCLK頻率不是整數(shù)也問題不大通信時序最重要的是采樣點位置穩(wěn)定而不是頻率剛好卡在整數(shù)兆赫茲。3. 可復(fù)用的Verilog實現(xiàn)從零手寫一個Mode 0的SPI Master3.1 核心代碼框架下面是一個參數(shù)化的SPI Master適用于Mode 0CPOL0CPHA0支持任意字節(jié)寬度時鐘分頻可配。代碼量不大但把SPI時序的核心流程完整覆蓋了。module spi_master #( parameter DATA_WIDTH 8, parameter HALF_CLK 5 // 系統(tǒng)時鐘 / (SCLK * 2) )( 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 done, output reg sclk, output reg cs_n, output reg mosi, input wire miso ); localparam IDLE 3d0; localparam CLK_LO 3d1; localparam CLK_HI 3d2; localparam STOP 3d3; reg [2:0] state; reg [DATA_WIDTH-1:0] shift_reg; reg [$clog2(DATA_WIDTH)-1:0] bit_cnt; reg [15:0] hal_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; sclk 1b0; cs_n 1b1; mosi 1b0; done 1b0; rx_data {DATA_WIDTH{1b0}}; end else begin case (state) IDLE: begin done 1b0; cs_n 1b1; sclk 1b0; if (start) begin cs_n 1b0; shift_reg tx_data; mosi tx_data[DATA_WIDTH-1]; bit_cnt 0; hal_cnt 0; state CLK_LO; end end CLK_LO: begin if (hal_cnt HALF_CLK - 1) begin hal_cnt 0; sclk 1b1; state CLK_HI; end else begin hal_cnt hal_cnt 1b1; end end CLK_HI: begin if (hal_cnt HALF_CLK - 1) begin hal_cnt 0; sclk 1b0; rx_data {rx_data[DATA_WIDTH-2:0], miso}; if (bit_cnt DATA_WIDTH - 1) begin state STOP; end else begin bit_cnt bit_cnt 1b1; shift_reg {shift_reg[DATA_WIDTH-2:0], 1b0}; mosi shift_reg[DATA_WIDTH-2]; state CLK_LO; end end else begin hal_cnt hal_cnt 1b1; end end STOP: begin cs_n 1b1; done 1b1; state IDLE; end endcase end end endmodule這段代碼的主體思路是IDLE狀態(tài)等待startCS_N拉低的同時把待發(fā)送數(shù)據(jù)的最高位放到MOSI上然后進入CLK_LO狀態(tài)。CLK_LO保持SCLK低電平半周期結(jié)束后拉高SCLK進入CLK_HI。CLK_HI保持SCLK高電平半周期結(jié)束時采樣MISO、拉低SCLK同時把下一位數(shù)據(jù)移到MOSI上。循環(huán)8次后進入STOP狀態(tài)釋放CS_N并拉高done。3.2 發(fā)送與接收并行處理的關(guān)鍵點很多人第一次寫SPI時會問一個問題發(fā)送和接收是不是要分別寫兩個計數(shù)器不需要。SPI是同時移位的發(fā)送一個bit的同時必然接收一個bit靠同一個bit_cnt控制就行。注意代碼里發(fā)送和接收的移位方向發(fā)送時每過一個周期把shift_reg左移一位新的最高位變成原次高位送出的順序是MSB first接收時用rx_data {rx_data[DATA_WIDTH-2:0], miso}把采到的bit放到最低位同時整個寄存器左移。這樣第一個采到的bit會慢慢移動到最高位最終rx_data就是標(biāo)準(zhǔn)的MSB first數(shù)據(jù)。這段代碼里采樣點放在CLK_HI結(jié)束前的半周期位置也就是SCLK高電平的末尾。Mode 0下從機通常是在SCLK下降沿更新MISO所以SCLK高電平期間MISO已經(jīng)穩(wěn)定。在末尾采樣相當(dāng)于給從機輸出留出了接近一個半周期的建立時間比在上升沿瞬間采樣更穩(wěn)。有的FPGA工程師習(xí)慣在上升沿打一拍采樣也能工作但如果從機輸出延遲較大偶發(fā)MISO采樣錯誤就很頭疼。我的做法是優(yōu)先保證建立時間余量采樣點稍微往后挪一點代價是SCLK前沿到采樣點之間有一個半周期的延遲這完全不影響協(xié)議正確性。3.3 從Mode 0擴展到Mode 1/2/3的思路上面的代碼默認CPOL0、CPHA0想支持其他模式核心就兩個改動SCLK空閑電平以及移位和采樣分別發(fā)生在哪個沿。CPOL決定空閑電平。Mode 2和Mode 3下IDLE狀態(tài)里SCLK應(yīng)該是高電平STOP之后也要回到高電平。CPHA決定采樣沿。Mode 1里主機在SCLK上升沿移位、下降沿采樣正好和Mode 0相反。改成參數(shù)化設(shè)計時可以加兩個parameterCPOL和CPHA然后在狀態(tài)機里根據(jù)參數(shù)決定CLK_LO和CLK_HI時SCLK的電平以及采樣和移位動作放在哪個狀態(tài)里。不過說實話工程中我更推薦用Vivado或Quartus自帶的SPI IP或者針對某種模式單獨寫一個專用模塊。一個模塊支持全部四模式代碼可讀性和時序余量都會受影響除非項目需求明確否則沒必要把參數(shù)化做得太復(fù)雜。4. 從仿真到上板實測過程中才暴露的幾個坑4.1 Testbench搭建時一定要模擬真實從機行為仿真最大的價值不是看波形多漂亮而是在上板之前驗證協(xié)議的正確性。很多人的Testbench只給SPI Master灌數(shù)據(jù)然后把MISO拉高或拉低這其實測不到時序問題。建議寫一個簡單的SPI從機模型邏輯就是檢測到CS_N拉低后在每個SCLK的有效邊沿采樣MOSI存到寄存器并在下一個邊沿把預(yù)設(shè)數(shù)據(jù)放到MISO上。代碼如下module spi_slave_model #( parameter DATA_WIDTH 8 )( input wire sclk, input wire cs_n, input wire mosi, output reg miso, input wire [DATA_WIDTH-1:0] response ); reg [DATA_WIDTH-1:0] shift; always (posedge sclk or negedge cs_n) begin if (!cs_n) begin miso response[DATA_WIDTH-1]; end else begin miso response[DATA_WIDTH-2:0]; // 這里只是為了仿真不追求完全精確的從機行為 end end endmoduleTestbench里用always #5 clk ~clk;產(chǎn)生100MHz系統(tǒng)時鐘start信號拉高一個周期然后等待done再比較rx_data和response是否一致。跑通之后再用$display打印每一次收發(fā)的結(jié)果比肉眼看波形高效得多。4.2 仿真沒問題、上板就出錯的三個高頻問題第一個是start信號毛刺。如果start來自按鍵或者跨時鐘域信號直接進狀態(tài)機有可能觸發(fā)半路復(fù)位或者狀態(tài)跳飛。解決方法是加兩級同步器或者做邊沿檢測確保狀態(tài)機只在信號的穩(wěn)定沿觸發(fā)。第二個是CS_N和SCLK的相位關(guān)系。很多從機要求CS_N拉低后至少幾微秒才能給SCLK或者CS_N釋放后要等一段才能進行下一次傳輸。我的控制器里沒有在IDLE到CLK_LO之間做CSN建立時間的等待所以如果從機手冊里有tCSSN這類參數(shù)你需要在外層狀態(tài)機里自行控制間隔。最簡單的方式是在每個字節(jié)傳輸完成后強制等待幾個時鐘周期再發(fā)下一個字節(jié)。第三個是MISO沒有做同步處理。雖然SPI時鐘是FPGA自己產(chǎn)生的但MISO信號從引腳進來之后相對系統(tǒng)時鐘仍然是異步輸入。如果不做任何處理直接采樣極端情況下會出現(xiàn)亞穩(wěn)態(tài)導(dǎo)致偶發(fā)的一個bit錯誤。穩(wěn)妥的做法是在控制器入口打兩拍同步或者至少在采樣路徑上加一級寄存器。這個在上板信號質(zhì)量差的時候尤其重要。4.3 用邏輯分析儀看波形時的一些建議上板調(diào)試時ILAIntegrated Logic Analyzer是你的眼睛。但很多新手用ILA看SPI抓不到有效數(shù)據(jù)原因往往是觸發(fā)條件沒設(shè)好。建議把采樣時鐘設(shè)為系統(tǒng)時鐘不要用SCLK。觸發(fā)條件設(shè)為cs_n 0的下降沿采樣窗口稍微設(shè)大一點保證能抓到CS_N拉低期間的全部SCLK周期。觀察時重點看四個要素CS_N拉低后MOSI的第一個bit是否已經(jīng)穩(wěn)定SCLK上升沿時MOSI是否處于數(shù)據(jù)窗口內(nèi)SCLK下降沿時MISO的變化點每個字節(jié)結(jié)束后CS_N是否及時拉高。如果發(fā)現(xiàn)MOSI在SCLK有效沿附近變化說明移位時機太晚了要把移位動作往前挪。如果MISO采樣結(jié)果全為1說明沒有正確驅(qū)動MISO先檢查從機供電和片選。ILA看到的問題絕大多數(shù)都是時序相位問題不是頻率問題。5. 多從機擴展和SPI Flash讀寫實戰(zhàn)5.1 硬件片選與軟件片選怎么選多從機系統(tǒng)首先要解決片選問題。硬件片選就是每個從機單獨接一根CS_N主機有多少個GPIO就能接多少個從機簡單粗暴互不影響。軟件片選則是通過譯碼器或IO擴展器用少量GPIO擴展出多路片選適合從機數(shù)量多但GPIO緊張的場景。實際工程里我強烈建議用硬件片選尤其是從機MISO不是三態(tài)輸出的情況。SPI總線共享時如果兩個從機的MISO同時驅(qū)低電平總線就沖突了。硬件片選至少保證同一時刻只有一個從機被激活。還要注意片選的編碼方式。如果設(shè)計成00選從機1、01選從機2、10選從機3、11選從機4這種二進制的寫法狀態(tài)跳變時有中間毛刺風(fēng)險兩個CS可能瞬間同時有效。FPGA里最穩(wěn)的寫法是獨熱碼4b0001選從機14b0010選從機2這樣任意時刻最多只有一個位為1杜絕競爭風(fēng)險。共享總線的場景比如ESP32用一根SPI同時接屏幕和SD卡核心也是片選切換。屏幕和SD卡分時工作同一時刻只選通一個。這里真正要注意的是模式兼容性如果屏幕工作在Mode 0而SD卡要求Mode 3那么每次切換設(shè)備都要重新配置SPI模式在FPGA里這就意味著每個從機要有獨立的模式配置寄存器。5.2 用SPI Master讀寫W25Q64的關(guān)鍵流程W25Q64是SPI NOR Flash非常適合用來驗證自研SPI模塊。它支持Mode 0和Mode 3最高SCLK能達到幾十兆甚至更高。上手第一步不是寫Flash而是讀JEDEC ID確認通信鏈路基本正常。讀ID的命令是0x9F。操作流程是CS_N拉低發(fā)送0x9F然后連續(xù)接收3個字節(jié)接收過程中主機要給SCLK所以主機側(cè)要連續(xù)發(fā)起3次空寫。這里暴露了我前面那個單字節(jié)控制器的一個問題讀ID要求CS_N在整個過程中保持低電平不能每個字節(jié)都拉高。而我的spi_master在每次字節(jié)傳輸結(jié)束后都會拉高CS_N如果直接用它讀W25Q64一字節(jié)后Flash就認為命令結(jié)束了后面根本讀不到ID。解決辦法是給控制器增加一個last信號或者在外層用一個burst狀態(tài)機把整個讀ID過程包起來。更簡單的做法是寫一個專門控制W25Q64的頂層狀態(tài)機里面復(fù)用SPI發(fā)送和接收的移位邏輯// 偽代碼 CS_N 0; send_byte(8h9F); // 發(fā)送命令 dummy send_byte(8h00); // 讀第1字節(jié) mid send_byte(8h00); // 讀第2字節(jié) did send_byte(8h00); // 讀第3字節(jié) CS_N 1;W25Q64的廠商ID是0xEF內(nèi)存類型是0x40容量是0x17。如果讀出這三個字節(jié)和手冊一致說明你的SPI Master時序完全正確可以繼續(xù)做頁編程和讀數(shù)據(jù)。頁編程一次最多256字節(jié)超過256字節(jié)必須拆成多頁操作而且每頁編程前要發(fā)0x06寫使能命令。這些細節(jié)在Flash手冊里寫著但真正上手以后你會發(fā)現(xiàn)FPGA側(cè)最容易出問題的還是CS_N的控制而不是SCLK頻率。5.3 常見故障排查思路調(diào)SPI出問題時有一個排查順序可以救命先看CS_N再看SCLK再看模式最后看數(shù)據(jù)。現(xiàn)象可能原因優(yōu)先排查方向讀回全為0xFFFlash未響應(yīng)或總線浮空CS_N是否正常拉低SPI模式是否匹配讀回數(shù)據(jù)固定錯一位采樣沿與從機輸出沿沖突調(diào)整采樣沿檢查是Mode 0還是Mode 1偶發(fā)錯位時好時壞MISO未同步或信號鏈路太長加同步寄存器降低SCLK頻率多從機總線數(shù)據(jù)混亂多個從機MISO同時驅(qū)動檢查片選是否為獨熱碼MISO是否三態(tài)命令發(fā)出去沒反應(yīng)CS_N在每個字節(jié)之間拉高使用burst模式保證CS_N全程低有效這里再強調(diào)一次很多人遇到通信不穩(wěn)定第一反應(yīng)是把SCLK調(diào)低這只能掩蓋問題不能解決根因。正確做法是用ILA把波形抓出來對照從機手冊的時序圖逐項檢查找到真正的相位錯誤。調(diào)SPI最有成就感的一刻就是看到ILA里的MISO數(shù)據(jù)和預(yù)期完全一致那一刻你會發(fā)現(xiàn)自己真正理解了什么叫時序。這篇文章寫到這里我不打算做什么高屋建瓴的總結(jié)。最后分享一個我自己的習(xí)慣SPI模塊不要一開始就追求多功能先用Mode 0和最基礎(chǔ)的從機把鏈路打通再加參數(shù)、加FIFO、加多從機支持。調(diào)不通的時候先用低速加長半周期排除協(xié)議問題再用ILA抓波形確認相位。SPI作為FPGA入門最適合練手的串行總線它不復(fù)雜但足夠讓你把時序這兩個字刻進腦子里。等你真正寫完一個穩(wěn)定運行的SPI Master再回頭去看I2C、UART會發(fā)現(xiàn)所有串行協(xié)議的本質(zhì)都是相通的。