SPI 簡(jiǎn)介
SPI 全稱為 Serial Peripheral Interface,譯為串行外設(shè)接口。它是 Motorola 公司推出的一種相對(duì)高速的同步、全雙工的通信總線協(xié)議。
SPI 一般有以下幾個(gè)特點(diǎn):
◆ 因?yàn)?a href="http://www.www27dydycom.cn/tags/時(shí)鐘/" target="_blank">時(shí)鐘線的存在,SPI 是同步的串行通信總線。
◆ 因?yàn)?Master 與 Slave 之間存在兩根不同方向的數(shù)據(jù)線,所以 SPI 支持全雙工傳輸。
◆ SPI 通信協(xié)議簡(jiǎn)單,數(shù)據(jù)傳輸速率較快,傳輸速率沒(méi)有特殊設(shè)定,一般在 10兆波特率以下,最高可支持 30Mbps 甚至 50Mbps。
◆ SPI 采用主從機(jī)通信模式,應(yīng)用廣泛,多用于 EEPROM、Flash、實(shí)時(shí)時(shí)鐘 (RTC)、 數(shù)模轉(zhuǎn)換器 (ADC) 等模塊的通信。
◆ 缺點(diǎn)是沒(méi)有應(yīng)答機(jī)制和校驗(yàn)機(jī)制,不能確認(rèn)是否接收到數(shù)據(jù)、是否傳輸有錯(cuò)。
SPI 引腳
SPI 通信最少需要 4 根信號(hào)線,分別是 CSN、SCLK、MOSI 與 MISO。各個(gè)信號(hào)說(shuō)明如下:
◆ CS/CSN: Chip Select,主設(shè)備產(chǎn)生的從設(shè)備片選信號(hào)。當(dāng) Slave 片選信號(hào)有效時(shí),Slave 可被 Master 訪問(wèn)并進(jìn)行通信。一個(gè) Master 可能有多個(gè)片選信號(hào),但一個(gè) Slave 只能有一個(gè)片選信號(hào)。CS 表示片選信號(hào)為高時(shí)開(kāi)始數(shù)據(jù)傳輸,CSN 表示片選信號(hào)為低時(shí)開(kāi)始數(shù)據(jù)傳輸。
◆ SCLK: Master 產(chǎn)生的時(shí)鐘信號(hào),用于數(shù)據(jù)的同步傳輸。時(shí)鐘頻率決定了數(shù)據(jù)傳輸速率。
◆ MOSI: Mater Output Slave Input,主設(shè)備輸出、從設(shè)備輸入的數(shù)據(jù)線,用于 Master 向 Slave 進(jìn)行數(shù)據(jù)傳輸。
◆ MISO: Mater Input Slave Output,主設(shè)備輸入、從設(shè)備輸輸出的數(shù)據(jù)線,用于 Slave 向 Master 進(jìn)行數(shù)據(jù)傳輸。
SPI 的通信采用主從模式,通常會(huì)有一個(gè)主設(shè)備和一個(gè)或多個(gè)從設(shè)備。
某 Master 與 多個(gè) Slave 通過(guò) SPI 信號(hào)線的互聯(lián)示意圖如下。
SPI 協(xié)議
SPI 通信有 4 種傳輸模式,規(guī)定了數(shù)據(jù)在不同時(shí)鐘邊沿的采樣與發(fā)送規(guī)則,由時(shí)鐘極性 (CPOL,Clock Polarity) 和時(shí)鐘相位 (CPHA,Clock Phase) 兩兩組合來(lái)定義。其中,CPOL 參數(shù)決定了時(shí)鐘信號(hào) SCLK 空閑狀態(tài)為低電平還是高電平,CPHA 參數(shù)決定了數(shù)據(jù)是在時(shí)鐘 SCKL 的上升沿采樣還是下降沿采樣。Slave 可能在出廠時(shí)就配置為某種模式不能修改的固定模式,這就要求 SPI Master 發(fā)送的傳輸模式要與 Slave 一致。
SPI 的 4 種傳輸模式示意圖如下:
SPI 的 4 種傳輸模式說(shuō)明如下:
◆ CPOL=0,CPHA=0:空閑態(tài)時(shí) SCLK 處于低電平,數(shù)據(jù)采樣發(fā)生在 SCLK 時(shí)鐘的第一個(gè)邊沿時(shí)刻。即 Slave 在 SCLK 由低電平到高電平的上升沿跳變時(shí)進(jìn)行數(shù)據(jù)采樣,Master 在 SCLK 下降沿發(fā)送數(shù)據(jù)。
◆ CPOL=0,CPHA=1:空閑態(tài)時(shí) SCLK 處于低電平,數(shù)據(jù)采樣發(fā)生在 SCLK 時(shí)鐘的第二個(gè)邊沿時(shí)刻。即 Slave 在 SCLK 下降沿接收數(shù)據(jù),Master 在 SCLK 上升沿發(fā)送數(shù)據(jù)。
◆ CPOL=1,CPHA=0:空閑態(tài)時(shí) SCLK 處于高電平,數(shù)據(jù)采樣發(fā)生在 SCLK 時(shí)鐘的第一個(gè)邊沿時(shí)刻。即 Slave 在 SCLK 下降沿接收數(shù)據(jù),Master 在 SCLK 上升沿發(fā)送數(shù)據(jù)。
◆ CPOL=1,CPHA=1:空閑態(tài)時(shí) SCLK 處于高電平,數(shù)據(jù)采樣發(fā)生在 SCLK 時(shí)鐘的第二個(gè)邊沿時(shí)刻。即 Slave 在 SCLK 上升沿接收數(shù)據(jù),Master 在 SCLK 下降沿發(fā)送數(shù)據(jù)。
以 CPOL=1、CPHA=1 為例,說(shuō)明 SPI Master 向 Slave 傳輸 4bit 數(shù)據(jù)、并讀取 4bit 數(shù)據(jù)的過(guò)程,示意圖如下。
(1) 空閑狀態(tài),SCLK、CSN、MOSI、MISO 均為高電平;
(2) SPI Master CSN 拉低,選擇對(duì)應(yīng) SPI Slave,將開(kāi)始傳輸數(shù)據(jù);
(3) SCLK 拉低,同時(shí) MOSI 輸出單 bit 數(shù)據(jù) D1 ;
(4) SCLK 拉高,此時(shí) SPI Slave 讀取 MOSI 對(duì)應(yīng)的數(shù)據(jù) D1 ;
(5) 重復(fù)此過(guò)程,直至 SPI Slave 接收到 4bit
(6) 如果 Slave 接收到的 4bit 數(shù)據(jù)包含 SPI Master 讀控制,則在 SPI Master 仍然會(huì)繼續(xù)輸出時(shí)鐘 SCLK,但無(wú)需做額外驅(qū)動(dòng) MOSI ;
(7) SPI Slave 在 SCLK 下降沿輸出數(shù)據(jù)至 MISO;
(8) SPI Master 在 SCLK 上升沿對(duì) MISO 進(jìn)行采集;
(9) 重復(fù)步驟 (6)~(8),直至 Slave 傳輸完 4bit 數(shù)據(jù)。
需要注意的是,SPI 協(xié)議中的時(shí)鐘線 SCLK、片選線 CSN 和數(shù)據(jù)線 MOSI 都是由 SPI Master 控制,并不像 UART 或 IIC 協(xié)議有明顯的通信起始信號(hào)、結(jié)束信號(hào)和通信周期,所以 SPI 通信時(shí) SCLK 有效個(gè)數(shù)和 CSN 有效長(zhǎng)度要控制得當(dāng)。當(dāng)沒(méi)有數(shù)據(jù)交互時(shí),CSN(CS) 信號(hào)要保持為高電平 (低電平) 狀態(tài),時(shí)鐘也需要保持高電平或持低電平狀態(tài)不變。
SPI 實(shí)現(xiàn)
假設(shè)某 SPI Slave 中存在 128 個(gè)可讀可寫(xiě)的 8bit 位寬的寄存器。
規(guī)定某 SPI Master 與 該SPI Slave 每次通信時(shí)傳輸 16bit 數(shù)據(jù),其中最高位 bit[15] 為讀寫(xiě)控制位,次高位 bit[14:8] 為地址位,低 8bit 數(shù)據(jù)位。
下面,對(duì) CPOL=1、CPHA=1 工作模式 (下降沿發(fā)送數(shù)據(jù)、上升沿接收數(shù)據(jù)) 的 SPI 進(jìn)行 Verilog 設(shè)計(jì)與簡(jiǎn)單仿真。
◆ 參數(shù)設(shè)計(jì)
工作時(shí)鐘:200 Mhz
波特率:20MHz
傳輸位度:16
地址位度:7
數(shù)據(jù)位寬:8
◆ SPI Master 設(shè)計(jì)
SCLK 由 SPI 模塊工作時(shí)鐘產(chǎn)生,為避免工作時(shí)鐘與 SCLK 交互時(shí)的相位差關(guān)系,SPI Master 輸出的 SCLK 、CSN 與 MOSI 信號(hào)均在工作時(shí)鐘下產(chǎn)生, SCLK 只作為輸出時(shí)鐘驅(qū)動(dòng)。
SPI Master 信號(hào)端口說(shuō)明如下:
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 設(shè)計(jì)
SPI Master 模塊只保留 4 個(gè)信號(hào)即可,片選信號(hào)可以當(dāng)做復(fù)位信號(hào)使用,說(shuō)明如下。
SPI Slave 也是在 SCLK 下降沿開(kāi)始接收數(shù)據(jù),在 SCLK 上升沿輸出數(shù)據(jù),代碼描述如下:
// 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
測(cè)試時(shí),通過(guò)向 SPI 輸入包含讀寫(xiě)控制位、讀寫(xiě)地址、和讀寫(xiě)數(shù)據(jù)的并行數(shù)據(jù),來(lái)判斷 SPI Master 向 SPI Slave 寫(xiě)入和讀出的數(shù)據(jù)是否一致,以達(dá)到仿真 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 設(shè)計(jì)
仿真中的自檢驗(yàn)成功截圖如下所示。
Master 向第 100 個(gè)寄存器寫(xiě)數(shù)據(jù) 0xAA 的仿真波形圖如下所示。
Master 發(fā)送讀取第 100 個(gè)寄存器的命令及 Slave 返回對(duì)應(yīng)寄存器數(shù)據(jù)的波形圖如下所示。
評(píng)論