SPI總線傳輸只需要4根線就能完成,這四根線的作用分別如下:
SCK(Serial Clock):SCK是串行時鐘線,作用是Master向Slave傳輸時鐘信號,控制數據交換的時機和速率;
MOSI(Master Out Slave in):在SPI Master上也被稱爲Tx-channel,作用是SPI主機給SPI從機發送數據;
CS/SS(Chip Select/Slave Select):作用是SPI Master選擇與哪一個SPI Slave通信,低電平表示從機被選中(低電平有效);
MISO(Master In Slave Out):在SPI Master上也被稱爲Rx-channel,作用是SPI主機接收SPI從機傳輸過來的數據;
SPI總線傳輸一共有4中模式,這4種模式分別由時鐘極性(CPOL,Clock Polarity)和時鐘相位(CPHA,Clock Phase)來定義,其中CPOL參數規定了SCK時鐘信號空閒狀態的電平,CPHA規定了數據是在SCK時鐘的上升沿被採樣還是下降沿被採樣。
模式0:CPOL= 0,CPHA=0。SCK串行時鐘線空閒是爲低電平,數據在SCK時鐘的上升沿被採樣,數據在SCK時鐘的下降沿切換
模式1:CPOL= 0,CPHA=1。SCK串行時鐘線空閒是爲低電平,數據在SCK時鐘的下降沿被採樣,數據在SCK時鐘的上升沿切換
模式2:CPOL= 1,CPHA=0。SCK串行時鐘線空閒是爲高電平,數據在SCK時鐘的下降沿被採樣,數據在SCK時鐘的上升沿切換
模式3:CPOL= 1,CPHA=1。SCK串行時鐘線空閒是爲高電平,數據在SCK時鐘的上升沿被採樣,數據在SCK時鐘的下降沿切換
以模式0爲例
發送:當FPGA通過SPI總線往從機中發送一個字節(8-bit)的數據時,首先FPGA把CS/SS片選信號設置爲0,表示準備開始發送數據,整個發送數據過程其實可以分爲16個狀態:
狀態0:SCK爲0,MOSI爲要發送的數據的最高位,即I_data_in[7]
狀態1:SCK爲1,MOSI保持不變
狀態2:SCK爲0,MOSI爲要發送的數據的次高位,即I_data_in[6]
狀態3:SCK爲1,MOSI保持不變
狀態4:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[5]
狀態5:SCK爲1,MOSI保持不變
狀態6:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[4]
狀態7:SCK爲1,MOSI保持不變
狀態8:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[3]
狀態9:SCK爲1,MOSI保持不變
狀態10:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[2]
狀態11:SCK爲1,MOSI保持不變
狀態12:SCK爲0,MOSI爲要發送的數據的下一位,即I_data_in[1]
狀態13:SCK爲1,MOSI保持不變
狀態14:SCK爲0,MOSI爲要發送的數據的最低位,即I_data_in[0]
狀態15:SCK爲1,MOSI保持不變
一個字節數據發送完畢以後,產生一個發送完成標誌位O_tx_done並把CS/SS信號拉高完成一次發送;
接收:當FPGA通過SPI總線從從機中接收一個字節(8-bit)的數據時,首先FPGA把CS/SS片選信號設置爲0,表示準備開始接收數據,整個接收數據過程其實也可以分爲16個狀態,但是與發送過程不同的是,爲了保證接收到的數據準確,必須在數據的正中間採樣,也就是說模式0時序圖中灰色實線的地方纔是代碼中鎖存數據的地方,所以接收過程的每個狀態執行的操作爲:
狀態0:SCK爲0,不鎖存MISO上的數據
狀態1:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[7]
狀態2:SCK爲0,不鎖存MISO上的數據
狀態3:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[6]
狀態4:SCK爲0,不鎖存MISO上的數據
狀態5:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[5]
狀態6:SCK爲0,不鎖存MISO上的數據
狀態7:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[4]
狀態8:SCK爲0,不鎖存MISO上的數據
狀態9:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[3]
狀態10:SCK爲0,不鎖存MISO上的數據
狀態11:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[2]
狀態12:SCK爲0,不鎖存MISO上的數據
狀態13:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[1]
狀態14:SCK爲0,不鎖存MISO上的數據
狀態15:SCK爲1,鎖存MISO上的數據,即把MISO上的數據賦值給O_data_out[0]
一個字節數據接收完畢以後,產生一個接收完成標誌位O_rx_done並把CS/SS信號拉高完成一次數據的接收;
I_clk是系統時鐘;
I_rst_n是系統復位;
I_tx_en是主機給從機發送數據的使能信號,當I_tx_en爲1時主機才能給從機發送數據;
I_rx _en是主機從從機接收數據的使能信號,當I_rx_en爲1時主機才能從從機接收數據;
I_data_in是主機要發送的並行數據;
O_data_out是把從機接收回來的串行數據並行化以後的並行數據;
O_tx_done是主機給從機發送數據完成的標誌位,發送完成後會產生一個高脈衝;
O_rx_done是主機從從機接收數據完成的標誌位,接收完成後會產生一個高脈衝;
I_spi_miso、O_spi_cs、O_spi_sck和O_spi_mosi是標準SPI總線協議規定的四根線;
完整代碼如下:
module spi(clk,rd,wr,rst,data_in,si,so,sclk,cs,data_out);
parameter bit7=4'd0,bit6=4'd1,bit5=4'd2,bit4=4'd3,bit3=4'd4,bit2=4'd5,bit1=4'd6,bit0=4'd7,bit_end=4'd8;
parameter bit70=4'd0,bit60=4'd1,bit50=4'd2,bit40=4'd3,bit30=4'd4,bit20=4'd5,bit10=4'd6,bit00=4'd7,bit_end0=4'd8;
parameter size=8;
input clk,rst;
input wr,rd;//讀寫命令
input si;//spi數據輸入端
input [size-1:0]data_in;//待發送的數據
output[size-1:0]data_out;//待接收的數據
output sclk;//spi中的時鐘
output so;//spi的發送端
output cs;//片選信號
wire [size-1:0]data_out;
reg [size-1:0]dout_buf;
reg FF;
reg sclk;
reg so;
reg cs;
reg [3:0]send_state;//發送狀態寄存器
reg [3:0]receive_state;//接收狀態寄存器
always@(posedge clk)
begin
if(!rst)
begin
sclk<=0;
cs<=1;
send_state<=bit7;
receive_state<=bit70;
end
else
begin
if(rd|wr)
begin
sclk<=~sclk;//當開始讀或者寫的時候,需要啓動時鐘,將分頻時鐘作爲發送時鐘
cs<=0;//片選拉低
end
else
begin
sclk<=0;
cs<=1;
end
end
end
always@(posedge sclk)//發送數據
begin
if(wr)
begin
//send_state<=bit7;
send_data;
end
end
always@(posedge sclk)//接收數據
begin
if(rd)
begin
//receive_state<=bit70;
FF<=0;
receive_data;
end
end
assign data_out=(FF==1)?dout_buf:8'hz;
task send_data;//發送數據任務
begin
case(send_state)
bit7:
begin
so<=data_in[7];
send_state<=bit6;
end
bit6:
begin
so<=data_in[6];
send_state<=bit5;
end
bit5:
begin
so<=data_in[5];
send_state<=bit4;
end
bit4:
begin
so<=data_in[4];
send_state<=bit3;
end
bit3:
begin
so<=data_in[3];
send_state<=bit2;
end
bit2:
begin
so<=data_in[2];
send_state<=bit1;
end
bit1:
begin
so<=data_in[1];
send_state<=bit0;
end
bit0:
begin
so<=data_in[0];
send_state<=bit_end;
end
bit_end:
begin
so<=1'bz;
send_state<=bit7;
end
endcase
end
endtask
task receive_data;
begin
case (receive_state)
bit70:
begin
dout_buf[7]<=si;
receive_state<=bit60;
end
bit60:
begin
dout_buf[6]<=si;
receive_state<=bit50;
end
bit50:
begin
dout_buf[5]<=si;
receive_state<=bit40;
end
bit40:
begin
dout_buf[4]<=si;
receive_state<=bit30;
end
bit30:
begin
dout_buf[3]<=si;
receive_state<=bit20;
end
bit20:
begin
dout_buf[2]<=si;
receive_state<=bit10;
end
bit10:
begin
dout_buf[1]<=si;
receive_state<=bit00;
end
bit00:
begin
dout_buf[0]<=si;
receive_state<=bit_end0;
FF<=1;
end
bit_end0:
begin
dout_buf<=8'hzz;
receive_state<=bit70;
end
endcase
end
endtask
endmodule