一、 軟件平臺與硬件平臺
軟件平臺:
1、操作系統(tǒng):Windows-8.1
2、開發(fā)套件:ISE14.7
硬件平臺:
1、 FPGA型號:Xilinx公司的XC6SLX45-2CSG324
2、 Flash型號:WinBond公司的W25Q128BV ? Qual SPI Flash存儲器
二、 原理介紹
SPI(Serial Peripheral Interface,串行外圍設(shè)備接口),是Motorola公司提出的一種同步串行接口技術(shù),是一種高速、全雙工、同步通信總線,在芯片中只占用四根管腳用來控制及數(shù)據(jù)傳輸,廣泛用于EEPROM、Flash、RTC(實時時鐘)、ADC(數(shù)模轉(zhuǎn)換器)、DSP(數(shù)字信號處理器)以及數(shù)字信號解碼器上。SPI通信的速度很容易達(dá)到好幾兆bps,所以可以用SPI總線傳輸一些未壓縮的音頻以及壓縮的視頻。
下圖是只有2個chip利用SPI總線進(jìn)行通信的結(jié)構(gòu)圖
可知SPI總線傳輸只需要4根線就能完成,這四根線的作用分別如下:
SCK(Serial Clock):SCK是串行時鐘線,作用是Master向Slave傳輸時鐘信號,控制數(shù)據(jù)交換的時機(jī)和速率;
MOSI(Master Out Slave in):在SPI Master上也被稱為Tx-channel,作用是SPI主機(jī)給SPI從機(jī)發(fā)送數(shù)據(jù);
CS/SS(Chip Select/Slave Select):作用是SPI Master選擇與哪一個SPI Slave通信,低電平表示從機(jī)被選中(低電平有效);
MISO(Master In Slave Out):在SPI Master上也被稱為Rx-channel,作用是SPI主機(jī)接收SPI從機(jī)傳輸過來的數(shù)據(jù);
SPI總線主要有以下幾個特點:
1、 采用主從模式(Master-Slave)的控制方式,支持單Master多Slave。SPI規(guī)定了兩個SPI設(shè)備之間通信必須由主設(shè)備Master來控制從設(shè)備Slave。也就是說,如果FPGA是主機(jī)的情況下,不管是FPGA給芯片發(fā)送數(shù)據(jù)還是從芯片中接收數(shù)據(jù),寫Verilog邏輯的時候片選信號CS與串行時鐘信號SCK必須由FPGA來產(chǎn)生。同時一個Master可以設(shè)置多個片選(Chip Select)來控制多個Slave。SPI協(xié)議還規(guī)定Slave設(shè)備的clock由Master通過SCK管腳提供給Slave,Slave本身不能產(chǎn)生或控制clock,沒有clock則Slave不能正常工作。單Master多Slave的典型結(jié)構(gòu)如下圖所示
? 2、 SPI總線在傳輸數(shù)據(jù)的同時也傳輸了時鐘信號,所以SPI協(xié)議是一種同步(Synchronous)傳輸協(xié)議。Master會根據(jù)將要交換的數(shù)據(jù)產(chǎn)生相應(yīng)的時鐘脈沖,組成時鐘信號,時鐘信號通過時鐘極性(CPOL)和時鐘相位(CPHA)控制兩個SPI設(shè)備何時交換數(shù)據(jù)以及何時對接收數(shù)據(jù)進(jìn)行采樣,保證數(shù)據(jù)在兩個設(shè)備之間是同步傳輸?shù)摹?/p>
3、 SPI總線協(xié)議是一種全雙工的串行通信協(xié)議,數(shù)據(jù)傳輸時高位在前,低位在后。SPI協(xié)議規(guī)定一個SPI設(shè)備不能在數(shù)據(jù)通信過程中僅僅充當(dāng)一個發(fā)送者(Transmitter)或者接受者(Receiver)。在片選信號CS為0的情況下,每個clock周期內(nèi),SPI設(shè)備都會發(fā)送并接收1 bit數(shù)據(jù),相當(dāng)于有1 bit數(shù)據(jù)被交換了。數(shù)據(jù)傳輸高位在前,低位在后(MSB first)。SPI主從結(jié)構(gòu)內(nèi)部數(shù)據(jù)傳輸示意圖如下圖所示
SPI總線傳輸?shù)哪J剑?/p>
SPI總線傳輸一共有4中模式,這4種模式分別由時鐘極性(CPOL,Clock Polarity)和時鐘相位(CPHA,Clock Phase)來定義,其中CPOL參數(shù)規(guī)定了SCK時鐘信號空閑狀態(tài)的電平,CPHA規(guī)定了數(shù)據(jù)是在SCK時鐘的上升沿被采樣還是下降沿被采樣。這四種模式的時序圖如下圖所示:
模式0:CPOL= 0,CPHA=0。SCK串行時鐘線空閑是為低電平,數(shù)據(jù)在SCK時鐘的上升沿被采樣,數(shù)據(jù)在SCK時鐘的下降沿切換
模式1:CPOL= 0,CPHA=1。SCK串行時鐘線空閑是為低電平,數(shù)據(jù)在SCK時鐘的下降沿被采樣,數(shù)據(jù)在SCK時鐘的上升沿切換
模式2:CPOL= 1,CPHA=0。SCK串行時鐘線空閑是為高電平,數(shù)據(jù)在SCK時鐘的下降沿被采樣,數(shù)據(jù)在SCK時鐘的上升沿切換
模式3:CPOL= 1,CPHA=1。SCK串行時鐘線空閑是為高電平,數(shù)據(jù)在SCK時鐘的上升沿被采樣,數(shù)據(jù)在SCK時鐘的下降沿切換
其中比較常用的模式是模式0和模式3。為了更清晰的描述SPI總線的時序,下面展現(xiàn)了模式0下的SPI時序圖
上圖清晰的表明在模式0下,在空閑狀態(tài)下,SCK串行時鐘線為低電平,當(dāng)SS被主機(jī)拉低以后,數(shù)據(jù)傳輸開始,數(shù)據(jù)線MOSI和MISO的數(shù)據(jù)切換(Toggling)發(fā)生在時鐘的下降沿(上圖的黑色虛線),而數(shù)據(jù)線MOSI和MISO的數(shù)據(jù)的采樣(Sampling)發(fā)生在數(shù)據(jù)的正中間(上圖中的灰色實線)。下圖清晰的描述了其他三種模式數(shù)據(jù)線MOSI和MISO的數(shù)據(jù)切換(Toggling)位置和數(shù)據(jù)采樣位置的關(guān)系圖
下面我將以模式0為例用Verilog編寫SPI通信的代碼。
三、 目標(biāo)任務(wù)
1、編寫SPI通信的Verilog代碼并利用ModelSim進(jìn)行時序仿真
2、閱讀Qual SPI的芯片手冊,理解操作時序,并利用任務(wù)1編寫的代碼與Qual SPI進(jìn)行SPI通信,讀出Qual SPI Flash的Manufacturer/Device? ID
3、用SPI總線把存放在ROM里面的數(shù)據(jù)發(fā)出去,這在實際項目中用來配置SPI外設(shè)芯片很有用
四、 設(shè)計思路與Verilog代碼編寫
4.1、 SPI模塊的接口定義與整體設(shè)計
Verilog編寫的SPI模塊除了進(jìn)行SPI通信的四根線以外還要包括一些時鐘、復(fù)位、使能、并行的輸入輸出以及完成標(biāo)志位。其框圖如下所示
?
其中:
I_clk是系統(tǒng)時鐘;
I_rst_n是系統(tǒng)復(fù)位;
I_tx_en是主機(jī)給從機(jī)發(fā)送數(shù)據(jù)的使能信號,當(dāng)I_tx_en為1時主機(jī)才能給從機(jī)發(fā)送數(shù)據(jù);
I_rx _en是主機(jī)從從機(jī)接收數(shù)據(jù)的使能信號,當(dāng)I_rx_en為1時主機(jī)才能從從機(jī)接收數(shù)據(jù);
I_data_in是主機(jī)要發(fā)送的并行數(shù)據(jù);
O_data_out是把從機(jī)接收回來的串行數(shù)據(jù)并行化以后的并行數(shù)據(jù);
O_tx_done是主機(jī)給從機(jī)發(fā)送數(shù)據(jù)完成的標(biāo)志位,發(fā)送完成后會產(chǎn)生一個高脈沖;
O_rx_done是主機(jī)從從機(jī)接收數(shù)據(jù)完成的標(biāo)志位,接收完成后會產(chǎn)生一個高脈沖;
I_spi_miso、O_spi_cs、O_spi_sck和O_spi_mosi是標(biāo)準(zhǔn)SPI總線協(xié)議規(guī)定的四根線;
要想實現(xiàn)上文模式0的時序,最簡單的辦法還是設(shè)計一個狀態(tài)機(jī)。為了方便說明,這里把模式0的時序再在下面貼一遍
由于是要用FPGA去控制或讀寫QSPI Flash,所以FPGA是SPI主機(jī),QSPI是SPI從機(jī)。
發(fā)送:當(dāng)FPGA通過SPI總線往QSPI Flash中發(fā)送一個字節(jié)(8-bit)的數(shù)據(jù)時,首先FPGA把CS/SS片選信號設(shè)置為0,表示準(zhǔn)備開始發(fā)送數(shù)據(jù),整個發(fā)送數(shù)據(jù)過程其實可以分為16個狀態(tài):
狀態(tài)0:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的最高位,即I_data_in[7]
狀態(tài)1:SCK為1,MOSI保持不變
狀態(tài)2:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的次高位,即I_data_in[6]
狀態(tài)3:SCK為1,MOSI保持不變
狀態(tài)4:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[5]
狀態(tài)5:SCK為1,MOSI保持不變
狀態(tài)6:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[4]
狀態(tài)7:SCK為1,MOSI保持不變
狀態(tài)8:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[3]
狀態(tài)9:SCK為1,MOSI保持不變
狀態(tài)10:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[2]
狀態(tài)11:SCK為1,MOSI保持不變
狀態(tài)12:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[1]
狀態(tài)13:SCK為1,MOSI保持不變
狀態(tài)14:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的最低位,即I_data_in[0]
狀態(tài)15:SCK為1,MOSI保持不變
一個字節(jié)數(shù)據(jù)發(fā)送完畢以后,產(chǎn)生一個發(fā)送完成標(biāo)志位O_tx_done并把CS/SS信號拉高完成一次發(fā)送。通過觀察上面的狀態(tài)可以發(fā)現(xiàn)狀態(tài)編號為奇數(shù)的狀態(tài)要做的操作實際上是一模一樣的,所以寫代碼的時候為了精簡代碼,可以把狀態(tài)號為奇數(shù)的狀態(tài)全部整合到一起。
接收:當(dāng)FPGA通過SPI總線從QSPI Flash中接收一個字節(jié)(8-bit)的數(shù)據(jù)時,首先FPGA把CS/SS片選信號設(shè)置為0,表示準(zhǔn)備開始接收數(shù)據(jù),整個接收數(shù)據(jù)過程其實也可以分為16個狀態(tài),但是與發(fā)送過程不同的是,為了保證接收到的數(shù)據(jù)準(zhǔn)確,必須在數(shù)據(jù)的正中間采樣,也就是說模式0時序圖中灰色實線的地方才是代碼中鎖存數(shù)據(jù)的地方,所以接收過程的每個狀態(tài)執(zhí)行的操作為:
狀態(tài)0:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)1:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[7]
狀態(tài)2:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)3:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[6]
狀態(tài)4:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)5:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[5]
狀態(tài)6:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)7:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[4]
狀態(tài)8:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)9:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[3]
狀態(tài)10:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)11:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[2]
狀態(tài)12:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)13:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[1]
狀態(tài)14:SCK為0,不鎖存MISO上的數(shù)據(jù)
狀態(tài)15:SCK為1,鎖存MISO上的數(shù)據(jù),即把MISO上的數(shù)據(jù)賦值給O_data_out[0]
一個字節(jié)數(shù)據(jù)接收完畢以后,產(chǎn)生一個接收完成標(biāo)志位O_rx_done并把CS/SS信號拉高完成一次數(shù)據(jù)的接收。通過觀察上面的狀態(tài)可以發(fā)現(xiàn)狀態(tài)編號為偶數(shù)的狀態(tài)要做的操作實際上是一模一樣的,所以寫代碼的時候為了精簡代碼,可以把狀態(tài)號為偶數(shù)的狀態(tài)全部整合到一起。而這一點剛好與發(fā)送過程的狀態(tài)剛好相反。
思路理清楚以后就可以直接編寫Verilog代碼了,spi_module模塊的代碼如下:
?
module spi_module ( input I_clk , // 全局時鐘50MHz input I_rst_n , // 復(fù)位信號,低電平有效 input I_rx_en , // 讀使能信號 input I_tx_en , // 發(fā)送使能信號 input [7:0] I_data_in , // 要發(fā)送的數(shù)據(jù) output reg [7:0] O_data_out , // 接收到的數(shù)據(jù) output reg O_tx_done , // 發(fā)送一個字節(jié)完畢標(biāo)志位 output reg O_rx_done , // 接收一個字節(jié)完畢標(biāo)志位 // 四線標(biāo)準(zhǔn)SPI信號定義 input I_spi_miso , // SPI串行輸入,用來接收從機(jī)的數(shù)據(jù) output reg O_spi_sck , // SPI時鐘 output reg O_spi_cs , // SPI片選信號 output reg O_spi_mosi // SPI輸出,用來給從機(jī)發(fā)送數(shù)據(jù) ); reg [3:0] R_tx_state ; reg [3:0] R_rx_state ; always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_tx_state <= 4'd0 ; R_rx_state <= 4'd0 ; O_spi_cs <= 1'b1 ; O_spi_sck <= 1'b0 ; O_spi_mosi <= 1'b0 ; O_tx_done <= 1'b0 ; O_rx_done <= 1'b0 ; O_data_out <= 8'd0 ; end else if(I_tx_en) // 發(fā)送使能信號打開的情況下 begin O_spi_cs <= 1'b0 ; // 把片選CS拉低 case(R_tx_state) 4'd1, 4'd3 , 4'd5 , 4'd7 , 4'd9, 4'd11, 4'd13, 4'd15 : //整合奇數(shù)狀態(tài) begin O_spi_sck <= 1'b1 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd0: // 發(fā)送第7位 begin O_spi_mosi <= I_data_in[7] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd2: // 發(fā)送第6位 begin O_spi_mosi <= I_data_in[6] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd4: // 發(fā)送第5位 begin O_spi_mosi <= I_data_in[5] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd6: // 發(fā)送第4位 begin O_spi_mosi <= I_data_in[4] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd8: // 發(fā)送第3位 begin O_spi_mosi <= I_data_in[3] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd10: // 發(fā)送第2位 begin O_spi_mosi <= I_data_in[2] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd12: // 發(fā)送第1位 begin O_spi_mosi <= I_data_in[1] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 4'd14: // 發(fā)送第0位 begin O_spi_mosi <= I_data_in[0] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b1 ; end default:R_tx_state <= 4'd0 ; endcase end else if(I_rx_en) // 接收使能信號打開的情況下 begin O_spi_cs <= 1'b0 ; // 拉低片選信號CS case(R_rx_state) 4'd0, 4'd2 , 4'd4 , 4'd6 , 4'd8, 4'd10, 4'd12, 4'd14 : //整合偶數(shù)狀態(tài) begin O_spi_sck <= 1'b0 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; end 4'd1: // 接收第7位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[7] <= I_spi_miso ; end 4'd3: // 接收第6位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[6] <= I_spi_miso ; end 4'd5: // 接收第5位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[5] <= I_spi_miso ; end 4'd7: // 接收第4位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[4] <= I_spi_miso ; end 4'd9: // 接收第3位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[3] <= I_spi_miso ; end 4'd11: // 接收第2位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[2] <= I_spi_miso ; end 4'd13: // 接收第1位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[1] <= I_spi_miso ; end 4'd15: // 接收第0位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b1 ; O_data_out[0] <= I_spi_miso ; end default:R_rx_state <= 4'd0 ; endcase end else begin R_tx_state <= 4'd0 ; R_rx_state <= 4'd0 ; O_tx_done <= 1'b0 ; O_rx_done <= 1'b0 ; O_spi_cs <= 1'b1 ; O_spi_sck <= 1'b0 ; O_spi_mosi <= 1'b0 ; O_data_out <= 8'd0 ; end end endmodule
?
整個代碼的流程與之前分析的流程完全一致。接下來就對這個代碼用ModelSim進(jìn)行基本的仿真。由于接收部分不再硬件上不太好測,所以這里只對發(fā)送部分進(jìn)行測試,接收部分等把代碼下載到板子里面以后用ChipScope抓接收部分時序就一清二楚了。
發(fā)射部分的測試激勵代碼如下:
?
`timescale 1ns / 1ps module tb_spi_module; // Inputs reg I_clk; reg I_rst_n; reg I_rx_en; reg I_tx_en; reg [7:0] I_data_in; reg I_spi_miso; // Outputs wire [7:0] O_data_out; wire O_tx_done; wire O_rx_done; wire O_spi_sck; wire O_spi_cs; wire O_spi_mosi; // Instantiate the Unit Under Test (UUT) spi_module uut ( .I_clk (I_clk ), .I_rst_n (I_rst_n ), .I_rx_en (I_rx_en ), .I_tx_en (I_tx_en ), .I_data_in (I_data_in ), .O_data_out (O_data_out ), .O_tx_done (O_tx_done ), .O_rx_done (O_rx_done ), .I_spi_miso (I_spi_miso ), .O_spi_sck (O_spi_sck ), .O_spi_cs (O_spi_cs ), .O_spi_mosi (O_spi_mosi ) ); initial begin // Initialize Inputs I_clk = 0; I_rst_n = 0; I_rx_en = 0; I_tx_en = 1; I_data_in = 8'h00; I_spi_miso = 0; // Wait 100 ns for global reset to finish #100; I_rst_n = 1; end always #10 I_clk = ~I_clk ; always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) I_data_in <= 8'h00; else if(I_data_in == 8'hff) begin I_data_in <= 8'hff; I_tx_en <= 0; end else if(O_tx_done) I_data_in <= I_data_in + 1'b1 ; end endmodule
?
ModelSim的仿真圖如下圖所示:
由圖可以看到仿真得到的時序與SPI模式0的時序完全一致。
4.2、 W25Q128BV?? Qual SPI Flash存儲器時序分析
W25Q128BV,支持SPI, Dual SPI和Quad SPI接口方式。在Fast Read模式,接口的時鐘速率最大可以達(dá)到 104Mhz。FLASH 的容量由 65536個256-byte的Page組成。W25Q128 的擦除方法有三種,一種為 Sector 擦除(16 個 page,共 4KB),一種為 Block 擦除(128 個 page,共 32KB), 另一種為 Chip 擦除(整個擦除)。為了簡單起見,順便測試一下上面寫的代碼,這里只使用W25Q128BV的標(biāo)準(zhǔn)SPI總線操作功能,并且只完成一個讀取ID的操作,其他更高級的操作請看下一篇文章《QSPI Flash的原理與QSPI時序的Verilog實現(xiàn)》(鏈接:https://www.cnblogs.com/liujinggang/p/9651170.html)。我的開發(fā)板上W25Q128BV的硬件原理圖如下圖所示
由于我們的任務(wù)是利用標(biāo)準(zhǔn)四線SPI總線讀取QSPI FLASH的Manufacturer/Device ?ID,所以先到W25Q128BV的芯片手冊中找到它的讀Manufacturer/Device ?ID的時序。時序如下圖所示:
整個讀QSPI FLASH的過程為:FPGA先拉低CS片選信號,然后通過SPI總線發(fā)送命令碼90,命令碼發(fā)完以后,發(fā)送24-bit的地址24’h000000,接著在第32個SCK的下降沿準(zhǔn)備接收Manufacturer ID,Manufacturer ID接收完畢以后開始接收Device ID,最后把CS片選拉高,一次讀取過程全部結(jié)束。這里既涉及到了SPI的寫操作,也涉及到了SPI的讀操作,剛好可以測試一下上面寫的代碼。
4.3、 構(gòu)思狀態(tài)機(jī)并用ChipScope抓讀寫時序
由時序圖可以很輕松的分析出,用一個7個狀態(tài)的狀態(tài)機(jī)來實現(xiàn)讀ID的過程,其中狀態(tài)的跳變可通過發(fā)送完成標(biāo)志O_tx_done與接收完成標(biāo)志O_rx_done來切換,各個狀態(tài)的功能如下:
狀態(tài)0:打開spi_module的發(fā)送使能開關(guān),并初始化命令字90,等O_tx_done標(biāo)志為高后切換到下一狀態(tài)并設(shè)置好下一次要發(fā)送的數(shù)據(jù);
狀態(tài)1:打開spi_module的發(fā)送使能開關(guān),并設(shè)置低8位地址00,等O_tx_done標(biāo)志為高后切換到下一狀態(tài)并設(shè)置好下一次要發(fā)送的數(shù)據(jù);
狀態(tài)2:打開spi_module的發(fā)送使能開關(guān),并設(shè)置中8位地址00,等O_tx_done標(biāo)志為高后切換到下一狀態(tài)并設(shè)置好下一次要發(fā)送的數(shù)據(jù);
狀態(tài)3:打開spi_module的發(fā)送使能開關(guān),并設(shè)置高8位地址00,等O_tx_done標(biāo)志為高后切換到下一狀態(tài)并設(shè)置好下一次要發(fā)送的數(shù)據(jù);
狀態(tài)4:關(guān)閉spi_module的發(fā)送使能開關(guān),打開spi_module的接收使能開關(guān),等O_rx_done標(biāo)志為高后切換到下一狀態(tài);
狀態(tài)5:關(guān)閉spi_module的發(fā)送使能開關(guān),打開spi_module的接收使能開關(guān),等O_rx_done標(biāo)志為高后切換到下一狀態(tài),并關(guān)閉spi_module所有使能開關(guān);
狀態(tài)6:結(jié)束狀態(tài),關(guān)閉spi_module所有使能開關(guān);
讀ID的完整代碼如下:
?
`timescale 1ns / 1ps module spi_read_id_top ( input I_clk , // 全局時鐘50MHz input I_rst_n , // 復(fù)位信號,低電平有效 output [3:0] O_led_out , // 四線標(biāo)準(zhǔn)SPI信號定義 input I_spi_miso , // SPI串行輸入,用來接收從機(jī)的數(shù)據(jù) output O_spi_sck , // SPI時鐘 output O_spi_cs , // SPI片選信號 output O_spi_mosi // SPI輸出,用來給從機(jī)發(fā)送數(shù)據(jù) ); wire W_rx_en ; wire W_tx_en ; wire [7:0] W_data_in ; // 要發(fā)送的數(shù)據(jù) wire [7:0] W_data_out ; // 接收到的數(shù)據(jù) wire W_tx_done ; // 發(fā)送最后一個bit標(biāo)志位,在最后一個bit產(chǎn)生一個時鐘的高電平 wire W_rx_done ; // 接收一個字節(jié)完畢(End of Receive) reg R_rx_en ; reg R_tx_en ; reg [7:0] R_data_in ; // 要發(fā)送的數(shù)據(jù) reg [2:0] R_state ; reg [7:0] R_spi_pout ; assign W_rx_en = R_rx_en ; assign W_tx_en = R_tx_en ; assign W_data_in = R_data_in ; assign O_led_out = R_spi_pout[3:0] ; always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_state <= 3'd0 ; R_tx_en <= 1'b0 ; R_rx_en <= 1'b0 ; end else case(R_state) 3'd0: // 發(fā)送命令字90 begin if(W_tx_done) begin R_state <= R_state + 1'b1 ; R_data_in <= 8'h00 ; // 提前設(shè)定好下一次要發(fā)送的數(shù)據(jù) end else begin R_tx_en <= 1'b1 ; R_data_in <= 8'h90 ; end end 3'd1,3'd2,3'd3: // 發(fā)送24位的地址信號 begin if(W_tx_done) begin R_state <= R_state + 1'b1 ; R_data_in <= 8'h00 ; // 提前設(shè)定好下一次要發(fā)送的數(shù)據(jù) end else begin R_tx_en <= 1'b1 ; R_data_in <= 8'h00 ; end end 3'd4: // 接收ID EF begin if(W_rx_done) begin R_state <= R_state + 1'b1 ; R_spi_pout <= W_data_out ; end else begin R_tx_en <= 1'b0 ; R_rx_en <= 1'b1 ; end end 3'd5: // 接收ID 17 begin if(W_rx_done) begin R_state <= R_state + 1'b1 ; R_spi_pout <= W_data_out ; R_tx_en <= 1'b0 ; R_rx_en <= 1'b0 ; end else begin R_tx_en <= 1'b0 ; R_rx_en <= 1'b1 ; end end 3'd6: //結(jié)束 begin R_state <= R_state ; R_tx_en <= 1'b0 ; R_rx_en <= 1'b0 ; end endcase end spi_module U_spi_module ( .I_clk (I_clk), // 全局時鐘50MHz .I_rst_n (I_rst_n), // 復(fù)位信號,低電平有效 .I_rx_en (W_rx_en), // 讀使能信號 .I_tx_en (W_tx_en), // 發(fā)送使能信號 .I_data_in (W_data_in), // 要發(fā)送的數(shù)據(jù) .O_data_out (W_data_out), // 接收到的數(shù)據(jù) .O_tx_done (W_tx_done), // 發(fā)送最后一個bit標(biāo)志位,在最后一個bit產(chǎn)生一個時鐘的高電平 .O_rx_done (W_rx_done), // 接收一個字節(jié)完畢(End of Receive) // 四線標(biāo)準(zhǔn)SPI信號定義 .I_spi_miso (I_spi_miso), // SPI串行輸入,用來接收從機(jī)的數(shù)據(jù) .O_spi_sck (O_spi_sck), // SPI時鐘 .O_spi_cs (O_spi_cs), // SPI片選信號 .O_spi_mosi (O_spi_mosi) // SPI輸出,用來給從機(jī)發(fā)送數(shù)據(jù) ); //////// Debug ////////////////////////////////////////////////////////////// wire [35:0] CONTROL0 ; wire [39:0] TRIG0 ; icon icon ( .CONTROL0(CONTROL0) // INOUT BUS [35:0] ); ila ila ( .CONTROL(CONTROL0), // INOUT BUS [35:0] .CLK(I_clk), // IN .TRIG0(TRIG0) // IN BUS [39:0] ); assign TRIG0[0] = W_rx_en ; assign TRIG0[1] = W_tx_en ; assign TRIG0[9:2] = W_data_in ; assign TRIG0[17:10] = W_data_out ; assign TRIG0[18] = W_tx_done ; assign TRIG0[19] = W_rx_done ; assign TRIG0[27:20] = R_spi_pout ; assign TRIG0[30:28] = R_state ; assign TRIG0[31] = O_spi_sck ; assign TRIG0[32] = O_spi_cs ; assign TRIG0[33] = O_spi_mosi ; assign TRIG0[34] = I_spi_miso ; assign TRIG0[35] = I_rst_n ; /////////////////////////////////////////////////////////////////////////////// endmodule
?
用ChipScope抓取的時序圖如下圖所示:
通過對比與芯片手冊的時序圖可以發(fā)現(xiàn),每個節(jié)拍與芯片手冊提供的讀ID的時序完全一致。
4.4、 用FPGA通過SPI總線配置外設(shè)芯片
上文的例子已經(jīng)包括了連續(xù)發(fā)送4個字節(jié)數(shù)據(jù)和連續(xù)接收2個字節(jié)數(shù)據(jù),實際上在很多應(yīng)用中只需要FPGA通過SPI總線給芯片發(fā)送相應(yīng)寄存器的值就可以對芯片的功能進(jìn)行配置了,而并不需要接收芯片返回的數(shù)據(jù),大家可以依著葫蘆畫瓢把硬件工程師發(fā)過來的芯片寄存器表(實際上很多芯片都有配置軟件,硬件工程師在配置軟件中設(shè)定好參數(shù)以后可以自動生成寄存器表)通過像上文那樣寫一個狀態(tài)機(jī)發(fā)出去來配置芯片的功能。
在寄存器數(shù)目比較少的情況下,比如就30~40個以下的寄存器需要配置的情況下,完全可以按照上面的思路寫一個30~40個狀態(tài)的狀態(tài)機(jī),每個狀態(tài)通過SPI總線發(fā)送一個數(shù)據(jù),這樣做的好處是以后想要在其他地方移植這套代碼或者做版本的維護(hù)與升級時只需要復(fù)制上一版本的代碼就可以了,移植起來非常方便。但是如果需要配置的寄存器有好幾百甚至上千個或者需要用SPI總線往一些顯示設(shè)備(比如OLED屏,液晶顯示屏)里面發(fā)送數(shù)據(jù)的話,如果去寫一個上千個狀態(tài)的狀態(tài)機(jī)顯然不是最好的選擇,所以對于這種需要用SPI傳輸大量數(shù)據(jù)的情況,我比較推薦的方式是先把數(shù)據(jù)存放在ROM里面,然后通過上面的SPI代碼發(fā)出去。
在做這件事情之前,在重復(fù)理解一下SPI發(fā)送過程的時序:
狀態(tài)0:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的最高位,即I_data_in[7],拉低O_tx_done信號
狀態(tài)1:SCK為1,MOSI保持不變,拉低O_tx_done信號
狀態(tài)2:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的次高位,即I_data_in[6] ,拉低O_tx_done信號
?? ? ? 狀態(tài)3:SCK為1,MOSI保持不變,拉低O_tx_done信號
狀態(tài)4:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[5] ,拉低O_tx_done信號
狀態(tài)5:SCK為1,MOSI保持不變,拉低O_tx_done信號
狀態(tài)6:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[4] ,拉低O_tx_done信號
狀態(tài)7:SCK為1,MOSI保持不變,拉低O_tx_done信號
狀態(tài)8:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[3] ,拉低O_tx_done信號
狀態(tài)9:SCK為1,MOSI保持不變,拉低O_tx_done信號
狀態(tài)10:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[2] ,拉低O_tx_done信號
狀態(tài)11:SCK為1,MOSI保持不變,拉低O_tx_done信號
狀態(tài)12:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的下一位,即I_data_in[1] ,拉低O_tx_done信號
狀態(tài)13:SCK為1,MOSI保持不變,拉低O_tx_done信號
狀態(tài)14:SCK為0,MOSI為要發(fā)送的數(shù)據(jù)的最低位,即I_data_in[0] ,拉高O_tx_done信號
狀態(tài)15:SCK為1,MOSI保持不變,拉低O_tx_done信號
可以看出,每一個bit為實際上是占了2個時鐘周期(這里的時鐘周期指的是系統(tǒng)時鐘I_clk),發(fā)送一個字節(jié)完成標(biāo)志位O_tx_done信號是在第14個狀態(tài)拉高的,也就是在最后一個bit的前時鐘周期產(chǎn)生了一個高電平,我之所以這么做的目的一是為了更好的整合代碼,把偶數(shù)狀態(tài)全部歸類到一起,二是為了在連續(xù)發(fā)送數(shù)據(jù)時,在檢測到O_tx_done信號為高以后,可以提前把下一次要發(fā)送的數(shù)據(jù)準(zhǔn)備好。大家可以在對照著下面時序圖理解一下,下面這張圖可以很清晰的看到,O_tx_done信號是在最后一個數(shù)據(jù)的前一個時鐘周期拉高的。
現(xiàn)在我們的目的是想要把ROM里面的數(shù)據(jù)通過SPI總線發(fā)出來,但是由于ROM是更新了地址以后的下一個時鐘周期才能讀出新數(shù)據(jù),也就是說,如果我們在檢測到O_tx_done為高時更新ROM地址的話,新的數(shù)據(jù)其實并沒有準(zhǔn)備好,直接看代碼和時序圖。
在此之前先把ROM配置好,我配置的ROM非常簡單,Read Width設(shè)置為8,Read Depth設(shè)置為10,
ROM的初始化數(shù)據(jù).coe文件的內(nèi)容如下所示:
MEMORY_INITIALIZATION_RADIX=16;
MEMORY_INITIALIZATION_VECTOR=
33,
24,
98,
24,
00,
47,
00,
ff,
a3,
49;
頂層代碼如下所示:
?
`timescale 1ns / 1ps module spi_reg_cfg ( input I_clk , // 全局時鐘50MHz input I_rst_n , // 復(fù)位信號,低電平有效 // 四線標(biāo)準(zhǔn)SPI信號定義 input I_spi_miso , // SPI串行輸入,用來接收從機(jī)的數(shù)據(jù) output O_spi_sck , // SPI時鐘 output O_spi_cs , // SPI片選信號 output O_spi_mosi // SPI輸出,用來給從機(jī)發(fā)送數(shù)據(jù) ); wire W_rx_en ; wire W_tx_en ; wire [7:0] W_data_out ; // 接收到的數(shù)據(jù) wire W_tx_done ; // 發(fā)送最后一個bit標(biāo)志位,在最后一個bit產(chǎn)生一個時鐘的高電平 wire W_rx_done ; // 接收一個字節(jié)完畢 reg R_rx_en ; reg R_tx_en ; reg [2:0] R_state ; assign W_rx_en = R_rx_en ; assign W_tx_en = R_tx_en ; parameter C_REG_NUM = 10 ; // 要配置的寄存器個數(shù),也是ROM的深度 parameter C_IDLE = 3'd0 , C_SEND_DATA = 3'd1 , C_DONE = 3'd2 ; reg [3:0] R_rom_addr ; wire [7:0] W_rom_out ; always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_state <= 3'd0 ; R_tx_en <= 1'b0 ; R_rx_en <= 1'b0 ; R_rom_addr <= 4'd0 ; end else case(R_state) C_IDLE: // 空閑狀態(tài) begin R_state <= C_SEND_DATA; R_tx_en <= 1'b0 ; R_rx_en <= 1'b0 ; end C_SEND_DATA: // 發(fā)送數(shù)據(jù)狀態(tài) begin R_tx_en <= 1'b1 ; if(R_rom_addr == C_REG_NUM) begin R_state <= C_DONE; R_tx_en <= 1'b0 ; R_rx_en <= 1'b0 ; end else if(W_tx_done) R_rom_addr <= R_rom_addr + 1'b1 ; else R_rom_addr <= R_rom_addr ; end C_DONE: begin R_state <= C_DONE ; R_tx_en <= 1'b0 ; R_rx_en <= 1'b0 ; end endcase end rom_cfg rom_cfg_inst ( .clka (I_clk ), // input clka .addra (R_rom_addr ), // input [3 : 0] addra .douta (W_rom_out ) // output [7 : 0] douta ); spi_module U_spi_module ( .I_clk (I_clk), // 全局時鐘50MHz .I_rst_n (I_rst_n), // 復(fù)位信號,低電平有效 .I_rx_en (W_rx_en), // 讀使能信號 .I_tx_en (W_tx_en), // 發(fā)送使能信號 .I_data_in (W_rom_out), // 要發(fā)送的數(shù)據(jù) .O_data_out (W_data_out), // 接收到的數(shù)據(jù) .O_tx_done (W_tx_done), // 發(fā)送最后一個bit標(biāo)志位,在最后一個bit產(chǎn)生一個時鐘的高電平 .O_rx_done (W_rx_done), // 接收一個字節(jié)完畢(End of Receive) // 四線標(biāo)準(zhǔn)SPI信號定義 .I_spi_miso (I_spi_miso), // SPI串行輸入,用來接收從機(jī)的數(shù)據(jù) .O_spi_sck (O_spi_sck), // SPI時鐘 .O_spi_cs (O_spi_cs), // SPI片選信號 .O_spi_mosi (O_spi_mosi) // SPI輸出,用來給從機(jī)發(fā)送數(shù)據(jù) ); //////// Debug ////////////////////////////////////////////////////////////// wire [35:0] CONTROL0 ; wire [39:0] TRIG0 ; icon icon ( .CONTROL0(CONTROL0) // INOUT BUS [35:0] ); ila ila ( .CONTROL(CONTROL0), // INOUT BUS [35:0] .CLK(I_clk), // IN .TRIG0(TRIG0) // IN BUS [39:0] ); assign TRIG0[0] = W_rx_en ; assign TRIG0[1] = W_tx_en ; assign TRIG0[9:2] = W_rom_out ; assign TRIG0[17:10] = W_data_out ; assign TRIG0[18] = W_tx_done ; assign TRIG0[19] = W_rx_done ; assign TRIG0[30:28] = R_state ; assign TRIG0[31] = O_spi_sck ; assign TRIG0[32] = O_spi_cs ; assign TRIG0[33] = O_spi_mosi ; assign TRIG0[34] = I_spi_miso ; assign TRIG0[35] = I_rst_n ; assign TRIG0[39:36] = R_rom_addr ; /////////////////////////////////////////////////////////////////////////////// endmodule
?
時序圖如下所示:
從上面的時序圖可以很清楚的看出,當(dāng)ROM的地址加1以后,ROM的數(shù)據(jù)是滯后了一個時鐘才輸出的,而ROM數(shù)據(jù)輸出的時刻(這個時候ROM的輸出數(shù)據(jù)并沒有穩(wěn)定)剛好是spi_module模塊發(fā)送下個數(shù)據(jù)最高位的時刻,那么這就有可能導(dǎo)致數(shù)據(jù)發(fā)送錯誤,從以上時序圖就可以看出8’h33和8’h24兩個數(shù)據(jù)正確發(fā)送了,但是8’h98這個數(shù)據(jù)就發(fā)送錯誤了。
為了解決這個問題,其實只需要把spi_module模塊的發(fā)送狀態(tài)機(jī)在加一個冗余狀態(tài)就行了,spi_module模塊的發(fā)送狀態(tài)機(jī)一共有0~15總共16個狀態(tài),那么我在加一個冗余狀態(tài),這個狀態(tài)執(zhí)行的操作和最后那個狀態(tài)執(zhí)行的操作完全相同,這樣就預(yù)留了一個時鐘的時間用來預(yù)先設(shè)置好要發(fā)送的數(shù)據(jù),這樣的效果是發(fā)送數(shù)據(jù)的最后一個bit實際上占用了3個時鐘周期,其中第一個時鐘周期把O_tx_done拉高,后兩個時鐘周期把O_tx_done拉低。修改后的spi_module模塊的代碼如下:
?
module spi_module ( input I_clk , // 全局時鐘50MHz input I_rst_n , // 復(fù)位信號,低電平有效 input I_rx_en , // 讀使能信號 input I_tx_en , // 發(fā)送使能信號 input [7:0] I_data_in , // 要發(fā)送的數(shù)據(jù) output reg [7:0] O_data_out , // 接收到的數(shù)據(jù) output reg O_tx_done , // 發(fā)送一個字節(jié)完畢標(biāo)志位 output reg O_rx_done , // 接收一個字節(jié)完畢標(biāo)志位 // 四線標(biāo)準(zhǔn)SPI信號定義 input I_spi_miso , // SPI串行輸入,用來接收從機(jī)的數(shù)據(jù) output reg O_spi_sck , // SPI時鐘 output reg O_spi_cs , // SPI片選信號 output reg O_spi_mosi // SPI輸出,用來給從機(jī)發(fā)送數(shù)據(jù) ); reg [4:0] R_tx_state ; reg [3:0] R_rx_state ; always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_tx_state <= 5'd0 ; R_rx_state <= 4'd0 ; O_spi_cs <= 1'b1 ; O_spi_sck <= 1'b0 ; O_spi_mosi <= 1'b0 ; O_tx_done <= 1'b0 ; O_rx_done <= 1'b0 ; O_data_out <= 8'd0 ; end else if(I_tx_en) // 發(fā)送使能信號打開的情況下 begin O_spi_cs <= 1'b0 ; // 把片選CS拉低 case(R_tx_state) 5'd1, 5'd3 , 5'd5 , 5'd7 , 5'd9, 5'd11, 5'd13, 5'd15 : //整合奇數(shù)狀態(tài) begin O_spi_sck <= 1'b1 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd0: // 發(fā)送第7位 begin O_spi_mosi <= I_data_in[7] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd2: // 發(fā)送第6位 begin O_spi_mosi <= I_data_in[6] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd4: // 發(fā)送第5位 begin O_spi_mosi <= I_data_in[5] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd6: // 發(fā)送第4位 begin O_spi_mosi <= I_data_in[4] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd8: // 發(fā)送第3位 begin O_spi_mosi <= I_data_in[3] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd10: // 發(fā)送第2位 begin O_spi_mosi <= I_data_in[2] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd12: // 發(fā)送第1位 begin O_spi_mosi <= I_data_in[1] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b0 ; end 5'd14: // 發(fā)送第0位 begin O_spi_mosi <= I_data_in[0] ; O_spi_sck <= 1'b0 ; R_tx_state <= R_tx_state + 1'b1 ; O_tx_done <= 1'b1 ; end 5'd16: // 增加一個冗余狀態(tài) begin O_spi_sck <= 1'b0 ; R_tx_state <= 5'd0 ; O_tx_done <= 1'b0 ; end default:R_tx_state <= 5'd0 ; endcase end else if(I_rx_en) // 接收使能信號打開的情況下 begin O_spi_cs <= 1'b0 ; // 拉低片選信號CS case(R_rx_state) 4'd0, 4'd2 , 4'd4 , 4'd6 , 4'd8, 4'd10, 4'd12, 4'd14 : //整合偶數(shù)狀態(tài) begin O_spi_sck <= 1'b0 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; end 4'd1: // 接收第7位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[7] <= I_spi_miso ; end 4'd3: // 接收第6位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[6] <= I_spi_miso ; end 4'd5: // 接收第5位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[5] <= I_spi_miso ; end 4'd7: // 接收第4位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[4] <= I_spi_miso ; end 4'd9: // 接收第3位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[3] <= I_spi_miso ; end 4'd11: // 接收第2位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[2] <= I_spi_miso ; end 4'd13: // 接收第1位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b0 ; O_data_out[1] <= I_spi_miso ; end 4'd15: // 接收第0位 begin O_spi_sck <= 1'b1 ; R_rx_state <= R_rx_state + 1'b1 ; O_rx_done <= 1'b1 ; O_data_out[0] <= I_spi_miso ; end default:R_rx_state <= 4'd0 ; endcase end else begin R_tx_state <= 4'd0 ; R_rx_state <= 4'd0 ; O_tx_done <= 1'b0 ; O_rx_done <= 1'b0 ; O_spi_cs <= 1'b1 ; O_spi_sck <= 1'b0 ; O_spi_mosi <= 1'b0 ; O_data_out <= 8'd0 ; end end endmodule
?
時序圖如下所示:
觀察上面的時序圖可以發(fā)現(xiàn),增加冗余狀態(tài)以后,ROM里面的10個數(shù)據(jù)全部發(fā)送正確了。最后把代碼綜合生成bit文件,下載到開發(fā)板里面用ChipScope抓出時序圖如下所示
可以看出,時序和用ModelSim得到的一模一樣。至此,整個用SPI總線傳輸ROM里面數(shù)據(jù)的實驗全部結(jié)束。
五、 進(jìn)一步思考
5.1、 如果外設(shè)芯片的數(shù)據(jù)位寬是16-bit或者32-bit怎么辦?
上文已經(jīng)完成了8-bit數(shù)據(jù)從ROM里面通過SPI發(fā)送出去的例子,16-bit和32-bit可以照著葫蘆畫瓢,無非就是多增加幾個狀態(tài)而已。
5.2、 發(fā)送數(shù)據(jù)的狀態(tài)機(jī)和接收數(shù)據(jù)的狀態(tài)機(jī)可以用移位的方式來做
事實上那個狀態(tài)機(jī)的發(fā)送8-bit數(shù)據(jù)和接收8-bit數(shù)據(jù)的部分只有一行代碼是不同的,所以也可以用移位的方法來做,然后把偶數(shù)狀態(tài)也可以整合到一起,這樣寫的代碼會更短更精煉。但出于理解更容易的角度,還是分開寫較好。
審核編輯:劉清
評論
查看更多