概述
全雙工與半雙工
全雙工是指收發可以在同一時刻進行,而半雙工是指在同一時刻只能進行一項操作
SPI
SPI是serial peripheral interface(串行外設接口)的縮寫,是一種同步串行通信協議。於1979年,由Motorola公司推出,用於節省自家芯片的PCB空間布局。
特點
主從控制
主機可以輸出時鍾信號和片選信號來控制從設備,從設備只能從主設備處獲取時鍾。、
同步傳輸
主設備根據將要交換的數據來產生相應的時鍾脈沖(clock pulse),時鍾脈沖組成時鍾信號(clock signal),時鍾信號通過時鍾極性(CPOL)和時鍾相位(CPHA)控制主從設備之間何時交換數據、何時對數據進行采樣,來保證數據傳輸同步進行。
缺點
沒有應答機制確認是否接收到數據,所以跟IIC比降低了可靠性
管腳
- MOSI:主設備數據輸出,從設備數據輸入
- MISO:主設備數據輸入,從設備數據輸出
- SCLK:主設備產生,發送給從設備
- CS:從設備使能信號,由主設備控制。
傳輸模式

SPI總線共有四種傳輸模式,主要是{CPOL,CPHA}的組合。CPOL代表SPI總線空閑時刻的電平,CPHA規定數據在第幾個邊沿采樣,在第幾個邊沿切換。
比如第一種模式,CPOL=0,CPHA=0,表示空閑時刻為低電平,數據在上升沿采樣,數據在時鍾下降沿切換。
常用的為模式0和模式3,下面為模式0時序圖

在空閑時刻,片選信號為高電平,時鍾信號為低電平。在需要傳送數據時,片選信號拉低,MOSI於MISO在時鍾上升沿進行采樣,在時鍾下降沿數據切換。可以看到時鍾上升沿正對數據中間。
SPI設備可以理解為一個大的移位寄存器,它在通信過程中不能單純充當發送或接受,而應該是在每個CLK周期內進行1bit數據交換。
代碼實現
spi-master
module spi_master
#(
parameter CLK_FRE = 50 , // system clk frequence
parameter SPI_FRE = 5 , // SCLK frequence
parameter DATA_WIDTH = 8 , // serial bit number
parameter CPOL = 0 , // clock polarity
parameter CPHA = 0 // clock phase
)(
input clk ,
input rst_n ,
input [DATA_WIDTH-1:0] data_tx , //the serial data from master to slave
input start ,
input miso ,
output o_sclk ,
output reg mosi ,
output reg cs_n ,
output reg [DATA_WIDTH-1:0] data_rx , //the serial data from slave to master
output reg finish
);
localparam CYCLE = CLK_FRE / SPI_FRE - 1 ;
localparam SHIFT_CNT = clogb2(DATA_WIDTH) ;
localparam SCLK_CNT = clogb2(CYCLE) ;
function integer clogb2 ( input integer bit_depth) ;
begin
for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
bit_depth = bit_depth >> 1;
end
endfunction
localparam IDLE = 3'b000 ;
localparam LOAD_DATA = 3'b001 ;
localparam SEND_DATA = 3'b010 ;
localparam DONE = 3'b100 ;
reg [2:0] state,next_state ;
reg sclk ;
reg sclk_valid ;
reg [SCLK_CNT-1:0] sclk_cnt ;
reg [SHIFT_CNT-1:0]shift_cnt ;
reg [DATA_WIDTH-1:0] data_tx_temp;
reg sclk_delay ;
wire sclk_posedge ;
wire sclk_negedge ;
reg data_sample ;
reg data_switch ;
// generate sclk
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk_cnt <= 'd0;
else if(sclk_valid)
begin
if(sclk_cnt == CYCLE)
sclk_cnt <= 'd0 ;
else
sclk_cnt <= sclk_cnt + 1 ;
end
else
sclk_cnt <= 'd0;
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk <= CPOL ;
else if(sclk_valid)
begin
if(sclk_cnt == CYCLE)
sclk <= ~sclk ;
else
sclk <= sclk ;
end
else
sclk <= CPOL ;
end
// detect the sclk edge
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk_delay <= CPOL ;
else if (sclk_valid)
sclk_delay <= sclk ;
else
sclk_delay <= CPOL ;
end
assign sclk_posedge = sclk & (!sclk_delay) ;
assign sclk_negedge = (!sclk) & sclk_delay ;
always @(*)
begin
case ({CPOL,CPHA})
2'b00 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
2'b01 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b10 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b11 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
default : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
endcase
end
//FSM
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
state <= IDLE ;
else
state <= next_state ;
end
always @(*)
begin
case(state)
IDLE : next_state = start ? LOAD_DATA : IDLE ;
LOAD_DATA : next_state = SEND_DATA ;
SEND_DATA : next_state = (shift_cnt == DATA_WIDTH) ? DONE : SEND_DATA;
DONE : next_state = IDLE ;
endcase
end
//the control signal
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
sclk_valid <= 1'b0;
data_tx_temp<= 'd0;
cs_n <= 1'b1;
shift_cnt <= 'd0 ;
finish <= 1'b0;
end
else
begin
case(state)
IDLE : begin
sclk_valid <= 1'b0 ;
data_tx_temp <= 'd0 ;
cs_n <= 1'b1 ;
shift_cnt <= 'd0 ;
finish <= 1'b0 ;
end
LOAD_DATA : begin
sclk_valid <= 1'b0 ;
data_tx_temp <= data_tx ;
cs_n <= 1'b0 ;
shift_cnt <= 'd0 ;
finish <= 1'b0 ;
end
SEND_DATA : begin
sclk_valid <= 1'b1 ;
if(data_switch)
begin
shift_cnt <= shift_cnt + 1;
data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
end
else
begin
shift_cnt <= shift_cnt ;
data_tx_temp <= data_tx_temp ;
end
end
DONE : begin
sclk_valid <= 1'b0;
data_tx_temp <= 'd0;
cs_n <= 1'b1 ;
finish <= 1'b1;
shift_cnt <= 'd0 ;
end
endcase
end
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_rx <= 'd0;
else if(data_sample)
data_rx <= {data_rx[DATA_WIDTH-2:0],miso} ;
else
data_rx <= data_rx ;
end
assign mosi = data_tx_temp[DATA_WIDTH-1];
assign o_sclk = sclk_delay;
endmodule
仿真結果
思路
作為spi master端,可以先產生sclk,通過檢測sclk的上升沿和下降沿來進行數據采樣和數據切換進行數據傳輸。
在實際應用中spi最為接口信號,可以約定指令格式,分為指令位、讀寫位、數據位,通過spi master端向slave端發送指令。
spi-slave
module spi_slave
#(
parameter CLK_FRE = 50 ,
parameter SCLK_FRE = 5 ,
parameter DATA_WIDTH = 8 ,
parameter CPOL = 0 ,
parameter CPHA = 0
)(
input clk ,
input rst_n ,
input [DATA_WIDTH-1:0] data_tx,
input data_tx_valid ,
input sclk ,
input cs_n ,
input mosi ,
output miso ,
output finish ,
output reg [DATA_WIDTH-1:0] data_rx
);
localparam SAMPLE_CNT = clogb2(DATA_WIDTH);
reg [DATA_WIDTH-1:0] data_tx_temp ;
reg sclk_delay ;
reg cs_n_delay ;
wire sclk_posedge ;
wire sclk_negedge ;
wire cs_negedge ;
reg data_sample ;
reg data_switch ;
reg [SAMPLE_CNT-1:0] sample_cnt ;
//capture the edge of sclk
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk_delay <= CPOL ;
else if(!cs_n)
sclk_delay <= sclk ;
else
sclk_delay <= CPOL ;
end
assign sclk_posedge = sclk & (!sclk_delay) ;
assign sclk_negedge = (!sclk) & sclk_delay ;
//capture the edge of cs_n
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cs_n_delay <= 1'b1 ;
else
cs_n_delay <= cs_n ;
end
assign cs_negedge = (!cs_n) & cs_n_delay ;
always @(*)
begin
case ({CPOL,CPHA})
2'b00 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
2'b01 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b10 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b11 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
default : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
endcase
end
//trans data from slave to master
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_tx_temp <= 'd0;
else if(data_tx_valid)
data_tx_temp <= data_tx ;
else if(!cs_n && data_switch)
data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
else
data_tx_temp <= data_tx_temp ;
end
assign miso = !cs_n ? data_tx_temp[DATA_WIDTH-1] : 1'b0 ;
//sample data from master to slave
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_rx <= 'd0;
else if(!cs_n && data_sample)
data_rx <= {data_rx[DATA_WIDTH-2:0],mosi} ;
else
data_rx <= data_rx ;
end
//finish signal
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sample_cnt <= 'd0;
else if(!cs_n && data_sample)
sample_cnt = sample_cnt + 1 ;
else if(cs_n)
sample_cnt <= 'd0;
else
sample_cnt <= sample_cnt ;
end
assign finish = (sample_cnt == DATA_WIDTH) ;
function integer clogb2 ( input integer bit_depth);
begin
for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
bit_depth = bit_depth >> 1;
end
endfunction
endmodule