一、SPI協議
1、SPI協議概括
SPI(Serial Peripheral Interface)——串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在EEPROM、FLASH、實時時鐘,AD轉換器以及數字信號處理器和數字信號解碼器之間。SPI是一種高速,全雙工,同步的通信總線,在芯片上只占用四根線(CS、MOSI、MISO、SCK),極大的節約了芯片的引腳。
2、SPI物理層
SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主設備和一個或者多個從設備。圖1是一個主設備一個從設備的物理連接示意圖。圖中SCK是由主設備發送給從的時鐘,該時鐘決定了主設備發送數據的速率;MOSI是主設備發送給從設備的數據;MISO是從設備發送給主設備的數據;CS是片選信號,即只有片選信號為預先規定的使能信號時(高電平或者低電平)對此芯片的操作才有效。
圖1 點對點通信
圖2 一主多從通信
3、SPI協議層
SPI通信是四線串行通信,也就是說數據是一位一位傳輸的。這也即是SCK存在的意義,SCK提供通信所需的時鐘脈沖,MOSI和MISO則基于此時鐘進行數據傳輸。數據輸出通過MOSI線,數據在時鐘的上升沿或下降沿時改變,在緊接著的下降沿或者上升沿被讀取。完成一位數據傳輸,輸入也使用同樣原理。這樣,至少在8次時鐘信號的改變(上升沿和下降沿為一次),就可以實現8位數據的傳輸。
需要注意的是,SCK信號線只由主設備控制,從設備不能控制信號線。同樣,在一個基于SPI的設備中,至少要有一個主控設備。這樣傳輸的特點:此傳輸方式有一個優點,與普通串行通信不同,普通的串行通信一次連續傳送至少8位數據,而SPI允許數據一位一位的傳送,甚至允許暫停,因為SCK時鐘線由主控設備控制,當沒有時鐘跳變時,從設備不采集或傳送數據。也就是說,主設備通過對SCK時鐘線的控制可以完成對通信的控制。SPI協議還可以實現數據的交換:因為SPI的數據輸入和輸出線獨立所以允許同時完成數據的輸入和輸出。不同的SPI設備的實現方式不盡相同,主要時改變和采集數據的時間不同,在時鐘信號上升沿或下降沿采集有不同的定義。
SPI總線有四種工作方式(SPI0、SPI1、SPI2、SPI3),其中使用的最為廣泛的是SPI0和SPI3方式。
SPI模塊為了和外設進行數據交換,根據外設工作要求,其輸出串行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協議沒有重大的影響。如果CPOL=0,串行同步時鐘的空閑狀態為低電平;如果CPOL=1,串行同步時鐘的空閑狀態為高電平。時鐘相位(CPHA)能夠配置用于選擇兩種不同的傳輸協議之一進行數據傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升沿或下降沿)數據被采集;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升沿或下降沿)數據被采集。SPI主模塊和與之通信的外設時鐘相位和極性應該一致。
SPI時序圖詳解:SPI接口有四種不同的數據傳輸時序,取決于CPOL和CPHA的組合。圖3中給出了這四種時序,時序與CPOL和CPHA的關系也可以從圖中看出。
圖3 SPI四種時序
圖3中可以看出,CPOL是用來決定SCK時鐘信號空閑時的電平。CPOL=0,SCK空閑時為低電平;CPOL=1,SCK空閑時為高電平。CPHA是用來決定采樣輸入數據MISO時刻,CPHA = 0,在第一個SCK時鐘沿進行數據采樣;CPHA=1,在第二個SCK時鐘沿進行數據采集。(工作模式的確定:由SLAVE的工作模式確定MASTER的工作模式)。
二、SPI協議使用舉例
這里通過使用SPI3來實現主機發送數據。
圖4 SPI3 工作模式的主機發送數據
在SPI3模式下,CPOL = 1,CPHA = 1。SCK在空閑時為高電平,在SCK的第二個時鐘沿從機進行數據的采集(只考慮主機發送情況),在SCK的第一個時鐘沿發送數據MOSI。
三、使用verilog實現SPI3工作模式的時序
1、SPI3模式下工作過程如下圖所示,
圖5 SPI發送數據過程
接下來分析圖5所示SPI發送數據的過程,首先在復位信號到來時,進入s0狀態,在s0狀態計數器和分頻器模塊加載初始值,如果發送數據開始信號spi_start有效進入s1狀態,s1狀態加載待發送的數據,同時計數器計數計數,分頻器開始工作,如果i=1,進入s2狀態,s2狀態主要用來發送數據,如果i為偶數,進入s3狀態,該狀態是用來采集數據,由于只考慮發送,因此此模塊不進行數據采集工作,如果i=15,進入s4狀態,否則如果i為奇數,則進入s2狀態。;在s4狀態,發送最后一位數據,如果i=16,進入s5狀態,此時整個SPI時序模擬完成。
2、數據路徑
由圖5可知,構成SPI發送時序的基本電路塊包括計數器,移位寄存器和觸發器模塊。
圖6 數據路徑
圖6中,左移寄存器將8位的待發送的數據spi_data轉換為串行的數據mosi一位一位的發送出去,計數器用來計數發送數據的個數,觸發器用來產生分頻后的sck時鐘信號。
3、控制信號
圖7 控制信號
圖7中給出了各個狀態哪些控制信號應該有效,參照圖5圖6圖7可以理清spi整個發送數據的過程。
四、 verilog描述
接下來使用verilog來描述圖6所示的電路,控制信號可根據圖7進行描述。
spi發送模塊(該模塊主要描述控制信號):
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/該狀態加載待發送的數據
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///空閑狀態
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加載待發送數據狀態
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第一個時鐘沿發送數據
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第二個時鐘沿采樣數據
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數據發送完畢
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
計數器電路描述:
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
觸發器電路描述:
//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中待發送的數據spi_data[7:0]=10101010,由于使用的是SPI3模式(CPOL=1,CPHA=1),此模式下SCK空閑時為1,在SCK第一個時鐘沿進行數據發送(即圖中SCK下降沿進行數據發送),從圖中波形可以看出 ,在cs為低時,mosi被一位一位的送出(高位先輸出)。
審核編輯 :李倩
-
FPGA
+關注
關注
1630文章
21796瀏覽量
605198 -
SPI
+關注
關注
17文章
1721瀏覽量
91955
原文標題:FPGA學習-基于FPGA的SPI協議實現
文章出處:【微信號:gh_9d70b445f494,微信公眾號:FPGA設計論壇】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論