SPI 簡介
SPI 全稱為 Serial Peripheral Interface,譯為串行外設接口。它是 Motorola 公司推出的一種相對高速的同步、全雙工的通信總線協議。
SPI 一般有以下幾個特點:
◆ 因為時鐘線的存在,SPI 是同步的串行通信總線。
◆ 因為 Master 與 Slave 之間存在兩根不同方向的數據線,所以 SPI 支持全雙工傳輸。
◆ SPI 通信協議簡單,數據傳輸速率較快,傳輸速率沒有特殊設定,一般在 10兆波特率以下,最高可支持 30Mbps 甚至 50Mbps。
◆ SPI 采用主從機通信模式,應用廣泛,多用于 EEPROM、Flash、實時時鐘 (RTC)、 數模轉換器 (ADC) 等模塊的通信。
◆ 缺點是沒有應答機制和校驗機制,不能確認是否接收到數據、是否傳輸有錯。
SPI 引腳
SPI 通信最少需要 4 根信號線,分別是 CSN、SCLK、MOSI 與 MISO。各個信號說明如下:
◆ CS/CSN: Chip Select,主設備產生的從設備片選信號。當 Slave 片選信號有效時,Slave 可被 Master 訪問并進行通信。一個 Master 可能有多個片選信號,但一個 Slave 只能有一個片選信號。CS 表示片選信號為高時開始數據傳輸,CSN 表示片選信號為低時開始數據傳輸。
◆ SCLK: Master 產生的時鐘信號,用于數據的同步傳輸。時鐘頻率決定了數據傳輸速率。
◆ MOSI: Mater Output Slave Input,主設備輸出、從設備輸入的數據線,用于 Master 向 Slave 進行數據傳輸。
◆ MISO: Mater Input Slave Output,主設備輸入、從設備輸輸出的數據線,用于 Slave 向 Master 進行數據傳輸。
SPI 的通信采用主從模式,通常會有一個主設備和一個或多個從設備。
某 Master 與 多個 Slave 通過 SPI 信號線的互聯示意圖如下。
SPI 協議
SPI 通信有 4 種傳輸模式,規定了數據在不同時鐘邊沿的采樣與發送規則,由時鐘極性 (CPOL,Clock Polarity) 和時鐘相位 (CPHA,Clock Phase) 兩兩組合來定義。其中,CPOL 參數決定了時鐘信號 SCLK 空閑狀態為低電平還是高電平,CPHA 參數決定了數據是在時鐘 SCKL 的上升沿采樣還是下降沿采樣。Slave 可能在出廠時就配置為某種模式不能修改的固定模式,這就要求 SPI Master 發送的傳輸模式要與 Slave 一致。
SPI 的 4 種傳輸模式示意圖如下:
SPI 的 4 種傳輸模式說明如下:
◆ CPOL=0,CPHA=0:空閑態時 SCLK 處于低電平,數據采樣發生在 SCLK 時鐘的第一個邊沿時刻。即 Slave 在 SCLK 由低電平到高電平的上升沿跳變時進行數據采樣,Master 在 SCLK 下降沿發送數據。
◆ CPOL=0,CPHA=1:空閑態時 SCLK 處于低電平,數據采樣發生在 SCLK 時鐘的第二個邊沿時刻。即 Slave 在 SCLK 下降沿接收數據,Master 在 SCLK 上升沿發送數據。
◆ CPOL=1,CPHA=0:空閑態時 SCLK 處于高電平,數據采樣發生在 SCLK 時鐘的第一個邊沿時刻。即 Slave 在 SCLK 下降沿接收數據,Master 在 SCLK 上升沿發送數據。
◆ CPOL=1,CPHA=1:空閑態時 SCLK 處于高電平,數據采樣發生在 SCLK 時鐘的第二個邊沿時刻。即 Slave 在 SCLK 上升沿接收數據,Master 在 SCLK 下降沿發送數據。
以 CPOL=1、CPHA=1 為例,說明 SPI Master 向 Slave 傳輸 4bit 數據、并讀取 4bit 數據的過程,示意圖如下。
(1) 空閑狀態,SCLK、CSN、MOSI、MISO 均為高電平;
(2) SPI Master CSN 拉低,選擇對應 SPI Slave,將開始傳輸數據;
(3) SCLK 拉低,同時 MOSI 輸出單 bit 數據 D1 ;
(4) SCLK 拉高,此時 SPI Slave 讀取 MOSI 對應的數據 D1 ;
(5) 重復此過程,直至 SPI Slave 接收到 4bit
(6) 如果 Slave 接收到的 4bit 數據包含 SPI Master 讀控制,則在 SPI Master 仍然會繼續輸出時鐘 SCLK,但無需做額外驅動 MOSI ;
(7) SPI Slave 在 SCLK 下降沿輸出數據至 MISO;
(8) SPI Master 在 SCLK 上升沿對 MISO 進行采集;
(9) 重復步驟 (6)~(8),直至 Slave 傳輸完 4bit 數據。
需要注意的是,SPI 協議中的時鐘線 SCLK、片選線 CSN 和數據線 MOSI 都是由 SPI Master 控制,并不像 UART 或 IIC 協議有明顯的通信起始信號、結束信號和通信周期,所以 SPI 通信時 SCLK 有效個數和 CSN 有效長度要控制得當。當沒有數據交互時,CSN(CS) 信號要保持為高電平 (低電平) 狀態,時鐘也需要保持高電平或持低電平狀態不變。
SPI 實現
假設某 SPI Slave 中存在 128 個可讀可寫的 8bit 位寬的寄存器。
規定某 SPI Master 與 該SPI Slave 每次通信時傳輸 16bit 數據,其中最高位 bit[15] 為讀寫控制位,次高位 bit[14:8] 為地址位,低 8bit 數據位。
下面,對 CPOL=1、CPHA=1 工作模式 (下降沿發送數據、上升沿接收數據) 的 SPI 進行 Verilog 設計與簡單仿真。
◆ 參數設計
工作時鐘:200 Mhz
波特率:20MHz
傳輸位度:16
地址位度:7
數據位寬:8
◆ SPI Master 設計
SCLK 由 SPI 模塊工作時鐘產生,為避免工作時鐘與 SCLK 交互時的相位差關系,SPI Master 輸出的 SCLK 、CSN 與 MOSI 信號均在工作時鐘下產生, SCLK 只作為輸出時鐘驅動。
SPI Master 信號端口說明如下:
SPI Master 代碼描述如下:
// spi master:
// at negedge send data
// at posedge recevie data
module spi_master
(
input rstn,
input clk,
//data sended and received
input [15:0] tx_data,
input tx_data_en,
output [7:0] rdata,
output rdata_valid,
output ready,
//spi intf
output sclk,
output csn,
output mosi,
input miso
);
//100MHz clk, 10MHz spi clk
parameter BAUD_NUM = 100/10 ;
//==========================================================
//baud clk generating by baud counter
reg [4:0] baud_cnt_r ;
//generating negedge sclk
wire baud_cnt_end = baud_cnt_r == BAUD_NUM-1;
//generating posedge sclk
wire baud_cnt_half = baud_cnt_r == BAUD_NUM/2-1;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
baud_cnt_r <= 'b0 ;
end
else if (csn) begin
baud_cnt_r <= 'b0 ;
end
else if (baud_cnt_end) begin
baud_cnt_r <= 'b0 ;
end
else begin
baud_cnt_r <= baud_cnt_r + 1'b1 ;
end
end
//==========================================================
//bit counter
reg [7:0] bit_cnt_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
bit_cnt_r <= 'b0 ;
end
else if (csn) begin
bit_cnt_r <= 'b0 ;
end
//add: at posedge sclk
else if (baud_cnt_half && bit_cnt_r != 16) begin
bit_cnt_r <= bit_cnt_r + 1'b1 ;
end
end
//(1) generate spi clk
reg sclk_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
sclk_r <= 1'b1 ;
end
else if (csn) begin
sclk_r <= 1'b1 ;
end
else if (baud_cnt_half && bit_cnt_r != 16) begin
sclk_r <= 1'b0 ;
end
else if (baud_cnt_end) begin
sclk_r <= 1'b1 ;
end
end
assign sclk = sclk_r ;
//==========================================================
//(2) generate csn
reg csn_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
csn_r <= 1'b1 ;
end
else if (tx_data_en) begin
csn_r <= 1'b0;
end
//16 data finished, delay half cycle
else if (!csn_r && bit_cnt_r == 16 && baud_cnt_half) begin
csn_r <= 1'b1 ;
end
end
assign csn = csn_r ;
//==========================================================
//tx_data buffer
reg [15:0] tx_data_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
tx_data_r <= 'b0 ;
end
else if (tx_data_en && ready) begin
tx_data_r <= tx_data ;
end
end
//(3) generate mosi
reg mosi_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
mosi_r <= 1'b1 ;
end
else if (csn) begin
mosi_r <= 1'b1 ;
end
//output tx_data
else if (baud_cnt_half && bit_cnt_r != 16 ) begin
mosi_r <= tx_data_r[15-bit_cnt_r] ;
end
end
assign mosi = mosi_r ;
//(4) receive data by miso
reg [7:0] rdata_r;
reg rdata_valid_r;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
rdata_r <= 8'b0 ;
rdata_valid_r <= 1'b0 ;
end
else if (rdata_valid_r) begin
rdata_valid_r <= 1'b0 ;
end
else if (!tx_data_r[15] && bit_cnt_r ==16 && baud_cnt_end) begin
rdata_r <= {rdata_r[6:0], miso} ;
rdata_valid_r <= 1'b1 ;
end
else if (!tx_data_r[15] && bit_cnt_r >=9 && baud_cnt_end) begin
rdata_r <= {rdata_r[6:0], miso} ;
end
end
reg ready_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
ready_r <= 1'b1 ;
end
else if (tx_data_en) begin
ready_r <= 1'b0 ;
end
else if (csn) begin
ready_r <= 1'b1 ;
end
end // always @ (negedge clk or negedge rstn)
assign ready = ready_r ;
assign rdata = rdata_r ;
assign rdata_valid = rdata_valid_r ;
endmodule
◆ SPI Slave 設計
SPI Master 模塊只保留 4 個信號即可,片選信號可以當做復位信號使用,說明如下。
SPI Slave 也是在 SCLK 下降沿開始接收數據,在 SCLK 上升沿輸出數據,代碼描述如下:
// spi slave:
// at negedge send data
// at posedge recevie data
module spi_slave
(
input sclk,
input csn,
input mosi,
output miso);
//===============================================
//bit counter
reg [3:0] bit_cnt_r ;
always @(posedge sclk or posedge csn) begin
if (csn) begin
bit_cnt_r <= 'b0 ;
end
else begin
bit_cnt_r <= bit_cnt_r + 1'b1 ;
end
end
//===============================================
//(1) receive rw cmd
reg rw_r ;
always @(posedge sclk or posedge csn) begin
if (csn) begin
rw_r <= 1'b0 ;
end
else if (bit_cnt_r == 0) begin
rw_r <= mosi ;
end
end
//(2) receive address
reg [6:0] addr_r;
always @(posedge sclk or posedge csn) begin
if (csn) begin
addr_r <= 6'b0 ;
end
else if (bit_cnt_r >= 1 && bit_cnt_r <= 7) begin
addr_r <= {addr_r[5:0], mosi} ;
end
end
//(3) receive data
reg [7:0] data_r;
always @(posedge sclk or posedge csn) begin
if (csn) begin
data_r <= 8'b0 ;
end
else if (rw_r && bit_cnt_r >=8 && bit_cnt_r <= 15) begin
data_r <= {data_r[6:0], mosi} ;
end
end
//===============================================
//write regs
reg [7:0] reg_group_r [127:0];
always @(posedge sclk) begin
if (rw_r && bit_cnt_r == 15) begin
reg_group_r[addr_r] <= {data_r[6:0], mosi} ;
end
end
//===============================================
//rd regs and send out
reg miso_r ;
always @(negedge sclk or posedge csn) begin
if (csn) begin
miso_r <= 'b0;
end
else if (!rw_r && bit_cnt_r >= 8 && bit_cnt_r <= 15) begin
miso_r <= reg_group_r[addr_r][15-bit_cnt_r] ;
end
end
assign miso = miso_r ;
endmodule
◆ Testbench
測試時,通過向 SPI 輸入包含讀寫控制位、讀寫地址、和讀寫數據的并行數據,來判斷 SPI Master 向 SPI Slave 寫入和讀出的數據是否一致,以達到仿真 SPI 功能的目的。
`timescale 1ns/1ps
module test ;
reg clk_200mhz ;
reg rstn ;
reg [15:0] tx_data ;
reg tx_data_en ;
wire sclk, csn, mosi, miso ;
wire [7:0] rdata ;
wire rdata_valid ;
wire ready ;
//==========================================
// clk and reset
initial begin
clk_200mhz = 0 ;
rstn = 0 ;
#11.3 rstn = 1 ;
end
always #(2.5) clk_200mhz = ~clk_200mhz ;
//==========================================
//driver task
task spi_cmd ;
input [15:0] data_send ;
begin
wait(ready) ;
@(posedge clk_200mhz) ;
# 0.7 ;
tx_data = data_send ;
tx_data_en = 1'b1 ;
@(posedge clk_200mhz) ;
# 0.7 ;
tx_data_en = 1'b0 ;
tx_data = 'b0 ;
wait(ready) ;
end
endtask // spi_rw
//==========================================
//driver
initial begin
tx_data = 16'b0 ;
tx_data_en = 1'b0 ;
//(1) wr address: 100-102
#133.7 ; spi_cmd({1'b1, 7'd100, 8'hAA}) ;
#501.3 ; spi_cmd({1'b1, 7'd101, 8'h55}) ;
#501.3 ; spi_cmd({1'b1, 7'd102, 8'hA5}) ;
//(2) rd address: 102-100
#2001.3 ; spi_cmd({1'b0, 7'd102, 8'h0}) ;
#501.3 ; spi_cmd({1'b0, 7'd101, 8'h0}) ;
#501.3 ; spi_cmd({1'b0, 7'd100, 8'h0}) ;
end
//finish
reg err_flag ;
initial begin
err_flag = 0 ;
#100;
//1st read
@(posedge rdata_valid) ;
@(negedge clk_200mhz) ;
if (rdata != 8'ha5) err_flag |= 1;
//2nd read
@(posedge rdata_valid) ;
@(negedge clk_200mhz) ;
if (rdata != 8'h55) err_flag |= 1;
//3rd 3read
@(posedge rdata_valid) ;
@(negedge clk_200mhz) ;
if (rdata != 8'haa) err_flag |= 1;
#13.7 ;
$display("-------------------------");
if (err_flag !== 0) begin
$display("Simulation Failed!");
end
else begin
$display("Simulation Succeed!");
end
$display();
$display();
#1000 ;
$finish ;
end
spi_master u_spi_master (
.rstn (rstn),
.clk (clk_200mhz),
//parallel
.tx_data (tx_data),
.tx_data_en (tx_data_en),
.ready (ready),
//spi intf
.sclk (sclk),
.csn (csn),
.mosi (mosi),
.miso (miso),
.rdata (rdata),
.rdata_valid (rdata_valid)
);
spi_slave u_spi_slave (
.sclk (sclk),
.csn (csn),
.mosi (mosi),
.miso (miso));
endmodule
◆ SPI Slave 設計
仿真中的自檢驗成功截圖如下所示。
Master 向第 100 個寄存器寫數據 0xAA 的仿真波形圖如下所示。
Master 發送讀取第 100 個寄存器的命令及 Slave 返回對應寄存器數據的波形圖如下所示。
評論
查看更多