一、SPI協(xié)議
1、SPI協(xié)議概括
SPI(Serial Peripheral Interface)——串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在EEPROM、FLASH、實時時鐘,AD轉換器以及數(shù)字信號處理器和數(shù)字信號解碼器之間。SPI是一種高速,全雙工,同步的通信總線,在芯片上只占用四根線(CS、MOSI、MISO、SCK),極大的節(jié)約了芯片的引腳。
2、SPI物理層
SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主設備和一個或者多個從設備。圖1是一個主設備一個從設備的物理連接示意圖。圖中SCK是由主設備發(fā)送給從的時鐘,該時鐘決定了主設備發(fā)送數(shù)據(jù)的速率;MOSI是主設備發(fā)送給從設備的數(shù)據(jù);MISO是從設備發(fā)送給主設備的數(shù)據(jù);CS是片選信號,即只有片選信號為預先規(guī)定的使能信號時(高電平或者低電平)對此芯片的操作才有效。

圖1 點對點通信

圖2 一主多從通信
3、SPI協(xié)議層
SPI通信是四線串行通信,也就是說數(shù)據(jù)是一位一位傳輸?shù)?。這也即是SCK存在的意義,SCK提供通信所需的時鐘脈沖,MOSI和MISO則基于此時鐘進行數(shù)據(jù)傳輸。數(shù)據(jù)輸出通過MOSI線,數(shù)據(jù)在時鐘的上升沿或下降沿時改變,在緊接著的下降沿或者上升沿被讀取。完成一位數(shù)據(jù)傳輸,輸入也使用同樣原理。這樣,至少在8次時鐘信號的改變(上升沿和下降沿為一次),就可以實現(xiàn)8位數(shù)據(jù)的傳輸。
需要注意的是,SCK信號線只由主設備控制,從設備不能控制信號線。同樣,在一個基于SPI的設備中,至少要有一個主控設備。這樣傳輸?shù)奶攸c:此傳輸方式有一個優(yōu)點,與普通串行通信不同,普通的串行通信一次連續(xù)傳送至少8位數(shù)據(jù),而SPI允許數(shù)據(jù)一位一位的傳送,甚至允許暫停,因為SCK時鐘線由主控設備控制,當沒有時鐘跳變時,從設備不采集或傳送數(shù)據(jù)。也就是說,主設備通過對SCK時鐘線的控制可以完成對通信的控制。SPI協(xié)議還可以實現(xiàn)數(shù)據(jù)的交換:因為SPI的數(shù)據(jù)輸入和輸出線獨立所以允許同時完成數(shù)據(jù)的輸入和輸出。不同的SPI設備的實現(xiàn)方式不盡相同,主要時改變和采集數(shù)據(jù)的時間不同,在時鐘信號上升沿或下降沿采集有不同的定義。
SPI總線有四種工作方式(SPI0、SPI1、SPI2、SPI3),其中使用的最為廣泛的是SPI0和SPI3方式。
SPI模塊為了和外設進行數(shù)據(jù)交換,根據(jù)外設工作要求,其輸出串行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協(xié)議沒有重大的影響。如果CPOL=0,串行同步時鐘的空閑狀態(tài)為低電平;如果CPOL=1,串行同步時鐘的空閑狀態(tài)為高電平。時鐘相位(CPHA)能夠配置用于選擇兩種不同的傳輸協(xié)議之一進行數(shù)據(jù)傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升沿或下降沿)數(shù)據(jù)被采集;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升沿或下降沿)數(shù)據(jù)被采集。SPI主模塊和與之通信的外設時鐘相位和極性應該一致。
SPI時序圖詳解:SPI接口有四種不同的數(shù)據(jù)傳輸時序,取決于CPOL和CPHA的組合。圖3中給出了這四種時序,時序與CPOL和CPHA的關系也可以從圖中看出。

圖3 SPI四種時序
圖3中可以看出,CPOL是用來決定SCK時鐘信號空閑時的電平。CPOL=0,SCK空閑時為低電平;CPOL=1,SCK空閑時為高電平。CPHA是用來決定采樣輸入數(shù)據(jù)MISO時刻,CPHA = 0,在第一個SCK時鐘沿進行數(shù)據(jù)采樣;CPHA=1,在第二個SCK時鐘沿進行數(shù)據(jù)采集。(工作模式的確定:由SLAVE的工作模式確定MASTER的工作模式)。
二、SPI協(xié)議使用舉例
這里通過使用SPI3來實現(xiàn)主機發(fā)送數(shù)據(jù)。

圖4 SPI3 工作模式的主機發(fā)送數(shù)據(jù)
在SPI3模式下,CPOL = 1,CPHA = 1。SCK在空閑時為高電平,在SCK的第二個時鐘沿從機進行數(shù)據(jù)的采集(只考慮主機發(fā)送情況),在SCK的第一個時鐘沿發(fā)送數(shù)據(jù)MOSI。
三、使用verilog實現(xiàn)SPI3工作模式的時序
1、SPI3模式下工作過程如下圖所示,

圖5 SPI發(fā)送數(shù)據(jù)過程
接下來分析圖5所示SPI發(fā)送數(shù)據(jù)的過程,首先在復位信號到來時,進入s0狀態(tài),在s0狀態(tài)計數(shù)器和分頻器模塊加載初始值,如果發(fā)送數(shù)據(jù)開始信號spi_start有效進入s1狀態(tài),s1狀態(tài)加載待發(fā)送的數(shù)據(jù),同時計數(shù)器計數(shù)計數(shù),分頻器開始工作,如果i=1,進入s2狀態(tài),s2狀態(tài)主要用來發(fā)送數(shù)據(jù),如果i為偶數(shù),進入s3狀態(tài),該狀態(tài)是用來采集數(shù)據(jù),由于只考慮發(fā)送,因此此模塊不進行數(shù)據(jù)采集工作,如果i=15,進入s4狀態(tài),否則如果i為奇數(shù),則進入s2狀態(tài)。;在s4狀態(tài),發(fā)送最后一位數(shù)據(jù),如果i=16,進入s5狀態(tài),此時整個SPI時序模擬完成。
2、數(shù)據(jù)路徑
由圖5可知,構成SPI發(fā)送時序的基本電路塊包括計數(shù)器,移位寄存器和觸發(fā)器模塊。

圖6 數(shù)據(jù)路徑
圖6中,左移寄存器將8位的待發(fā)送的數(shù)據(jù)spi_data轉換為串行的數(shù)據(jù)mosi一位一位的發(fā)送出去,計數(shù)器用來計數(shù)發(fā)送數(shù)據(jù)的個數(shù),觸發(fā)器用來產(chǎn)生分頻后的sck時鐘信號。
3、控制信號

圖7 控制信號
圖7中給出了各個狀態(tài)哪些控制信號應該有效,參照圖5圖6圖7可以理清spi整個發(fā)送數(shù)據(jù)的過程。
四、 verilog描述
接下來使用verilog來描述圖6所示的電路,控制信號可根據(jù)圖7進行描述。
spi發(fā)送模塊(該模塊主要描述控制信號):
module SPI_SEND(input clk_50m,
input rst_n,
input spi_start,
input[7:0] spi_data,
output reg spi_done,
output sck,
output reg cs,
output mosi
);
reg load_c;
reg en_c;
reg load_a;
reg en_a;
reg load_b;
reg en_b;
wire [4:0]i;
parameter [4:0] s0 = 'b000001;
parameter [4:0] s1 = 'b000010;
parameter [4:0] s2 = 'b000100;
parameter [4:0] s3 = 'b001000;
parameter [4:0] s4 = 'b010000;
parameter [4:0] s5 = 'b100000;
reg [5:0]current_state = 'd0;
reg [5:0]next_state = 'd0;
always @(posedge clk_50m or negedge rst_n)
if(!rst_n)
current_state <= s0;
else
current_state <= next_state;
always @(*)
case(current_state)
s0:begin
if(spi_start)
next_state = s1;
else
next_state = s0;
end
s1:begin/該狀態(tài)加載待發(fā)送的數(shù)據(jù)
if(i == 'd1)
next_state = s2;
else
next_state = s1;
end
s2:begin1,3,5,7,9,11,13,15
if(i[0] == 1'b0)//
next_state = s3;
else
next_state = s2;
end
s3:begin2,4,6,8,10,12,14,16
if(i == 'd15)
next_state = s4;
else if(i[0] == 'd1)
next_state = s2;
else
next_state = s3;
end
s4:begin
if(i == 'd16)
next_state = s5;
else
next_state = s4;
end
s5:begin
if(i == 'd0)
next_state = s0;
else
next_state = s5;
end
default:next_state = s0;
endcase
always @(*)
case(current_state)
s0:begin///空閑狀態(tài)
load_c = 'd1;
en_c = 'd0;
load_a = 'd0;
en_a = 'd0;
load_b = 'd1;
en_b = 'd0;
spi_done = 'd0;
cs = 'd1;
end
s1:begin加載待發(fā)送數(shù)據(jù)狀態(tài)
load_c = 'd0;
en_c = 'd1;
load_a = 'd1;
en_a = 'd0;
load_b = 'd0;
en_b = 'd1;
spi_done = 'd0;
cs = 'd0;
end
s2:begin第一個時鐘沿發(fā)送數(shù)據(jù)
load_c = 'd0;
en_c = 'd1;
load_a = 'd0;
en_a = 'd1;
load_b = 'd0;
en_b = 'd1;
spi_done = 'd0;
cs = 'd0;
end
s3:begin第二個時鐘沿采樣數(shù)據(jù)
load_c = 'd0;
en_c = 'd1;
load_a = 'd0;
en_a = 'd0;
load_b = 'd0;
en_b = 'd1;
spi_done = 'd0;
cs = 'd0;
end
s4:begin數(shù)據(jù)發(fā)送完畢
load_c = 'd0;
en_c = 'd1;
load_a = 'd0;
en_a = 'd0;
load_b = 'd0;
en_b = 'd0;
spi_done = 'd0;
cs = 'd0;
end
s5:begin
load_c = 'd0;
en_c = 'd0;
load_a = 'd0;
en_a = 'd0;
load_b = 'd0;
en_b = 'd0;
spi_done = 'd1;
cs = 'd1;
end
default:begin
load_c = 'd1;
en_c = 'd0;
load_a = 'd0;
en_a = 'd0;
load_b = 'd1;
en_b = 'd0;
spi_done = 'd0;
cs = 'd1;
end
endcase
// Instantiate the module
count_num count_num (
.clk_50m(clk_50m),
.load_c(load_c),
.en_c(en_c),
.count(i)
);
// Instantiate the module
left_shifter left_shifter (
.clk_50m(clk_50m),
.load_a(load_a),
.en_a(en_a),
.spi_data_in(spi_data),
.mosi(mosi)
);
// Instantiate the module
sck_generate sck_generate (
.clk_50m(clk_50m),
.load_b(load_b),
.en_b(en_b),
.sck(sck)
);
endmodule
計數(shù)器電路描述:
module count_num(input clk_50m,
input load_c,
input en_c,
output reg[4:0]count
);
always @(posedge clk_50m)
if(load_c)
count <= 'd0;?
else if(en_c)begin
if(count == 'd16)
count <= 'd0;
else
count <= count + 'd1;
end
else
count <= count;
endmodule
移位寄存器電路描述:
module left_shifter(input clk_50m,
input load_a,
input en_a,
input [7:0]spi_data_in,
output mosi
);
reg [7:0]data_reg;
always @(posedge clk_50m)
if(load_a)
data_reg <= spi_data_in;
else if(en_a)
data_reg <= {data_reg[6:0],1'b0};
else
data_reg <= data_reg;
assign mosi = data_reg[7];
endmodule
觸發(fā)器電路描述:
//SPI3模式下工作,SCK空閑時為高電平
//
module sck_generate(input clk_50m,
input load_b,
input en_b,
output reg sck
);
always @(posedge clk_50m)
if(load_b)
sck <= 'd1;
else if(en_b)
sck <= ~sck;
else
sck <= 'd1;
endmodule
仿真激勵文件:
module test;
// Inputs
reg clk_50m;
reg rst_n;
reg spi_start;
reg [7:0]spi_data;
// Outputs
wire spi_done;
wire sck;
wire cs;
wire mosi;
// Instantiate the Unit Under Test (UUT)
SPI_SEND uut (
.clk_50m(clk_50m),
.rst_n(rst_n),
.spi_start(spi_start),
.spi_done(spi_done),
.sck(sck),
.cs(cs),
.spi_data(spi_data),
.mosi(mosi)
);
initial begin
// Initialize Inputs
clk_50m = 0;
rst_n = 0;
spi_start = 0;
spi_data = 'd0;
// Wait 100 ns for global reset to finish
#100;
// Add stimulus here
end
always #5 clk_50m = ~clk_50m;
reg [4:0] count = 'd0;
always @(posedge clk_50m)
if(count == 'd20)
count <= 'd20;
else
count <= count + 'd1;
always @(posedge clk_50m)
if(count <= 'd10)
rst_n <= 'd0;
else
rst_n <= 'd1;
reg [9:0]cnt = 'd0;
always @(posedge clk_50m)
if(spi_done)
cnt <= 'd0;
else if(cnt == 'd500)
cnt <= 'd500;
else
cnt <= cnt + 'd1;
always @(posedge clk_50m)
if(cnt=='d499)begin
spi_start <= 'd1;
spi_data <= 'b10101010;
end
elsebegin
spi_start <= 'd0;
spi_data <= spi_data;
end
endmodule
使用ISIM仿真結果:

圖8 仿真結果
圖8中待發(fā)送的數(shù)據(jù)spi_data[7:0]=10101010,由于使用的是SPI3模式(CPOL=1,CPHA=1),此模式下SCK空閑時為1,在SCK第一個時鐘沿進行數(shù)據(jù)發(fā)送(即圖中SCK下降沿進行數(shù)據(jù)發(fā)送),從圖中波形可以看出 ,在cs為低時,mosi被一位一位的送出(高位先輸出)。
審核編輯 :李倩
-
FPGA
+關注
關注
1655文章
22288瀏覽量
630339 -
SPI
+關注
關注
17文章
1866瀏覽量
99829
原文標題:FPGA學習-基于FPGA的SPI協(xié)議實現(xiàn)
文章出處:【微信號:gh_9d70b445f494,微信公眾號:FPGA設計論壇】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
FPGA實現(xiàn)基于SPI協(xié)議的Flash驅動控制芯片擦除
DSP和FPGA的SPI通信不能實現(xiàn)怎么辦
基于FPGA來介紹并設計標準的SPI總線協(xié)議
SPI協(xié)議是怎么實現(xiàn)的
SPI-4.2接口的FPGA實現(xiàn)
如何在FPGA中實現(xiàn)SPI4.2接口
基于FPGA與MCU通信的SPI協(xié)議設計
基于FPGA的SPI協(xié)議及設計實現(xiàn)
FPGA實現(xiàn)的SPI協(xié)議(二)----基于SPI接口的FLASH芯片M25P16的使用
FPGA實現(xiàn)的SPI協(xié)議(一)----SPI驅動
一文看懂SPI協(xié)議

基于FPGA的SPI協(xié)議實現(xiàn)
評論