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