SPI是串行外設接口(Serial Peripheral Interface)的縮寫。它是Motorola公司推出的一種高速、全雙工的同步串行通信總線技術。
SPI的通信接口為:SDI或MISO(資料輸入),SDO或MOSI(資料輸出),SCK(時鐘),CS(片選)。
另外,可以通過設定CPOL(時鐘極性)和CPHA(時鐘相位)來配置主要裝置的通信模式。其中時鐘極性CPOL用來配置SCLK的空閑态或者有效态電平,時鐘相位CPHA 用來配置資料采樣是在第幾個邊沿:
CPOL=0,表示SCLK的空閑态電平為0,有效态電平為1;
CPOL=1,表示SCLK的空閑态電平為1,有效态電平為0;
CPHA=0,表示在時鐘的第一個邊沿進行資料采集,時鐘的第二個邊沿進行資料發送;
CPHA=1,表示在時鐘的第二個邊沿進行資料采集,時鐘的第一個邊沿進行資料發送。
特别說明:以下所涉及代碼參考網絡、相關開發闆廠家。此處進行了注解。
spi_master.v
module spi_master
(
input sys_clk, //系統時鐘
input rst, //複位
output nCS, //SPI 片選
output DCLK, //SPI時鐘
output MOSI, //SPI輸出
input MISO, //SPI 輸入
input CPOL, //時鐘極性
input CPHA, //時鐘相位
input nCS_ctrl, //片選控制
input[15:0] clk_div, //時鐘分頻
input wr_req, //讀寫請求
output wr_ack, //讀寫應答
input[7:0] data_in, //資料輸入
output[7:0] data_out //暑假輸出
);
localparam IDLE = 0;
localparam DCLK_EDGE = 1;
localparam DCLK_IDLE = 2;
localparam ACK = 3;
localparam LAST_HALF_CYCLE = 4;
localparam ACK_WAIT = 5;
reg DCLK_reg;
reg[7:0] MOSI_shift;
reg[7:0] MISO_shift;
reg[2:0] state;
reg[2:0] next_state;
reg[15:0] clk_cnt;
reg[4:0] clk_edge_cnt;
assign MOSI = MOSI_shift[7];
assign DCLK = DCLK_reg;
assign data_out = MISO_shift;
assign wr_ack = (state == ACK);
assign nCS = nCS_ctrl;
//說明:以下代碼實作狀态機狀态更新
always@(posedge sys_clk or posedge rst)
begin
if(rst)
state <= IDLE;
else
state <= next_state;
end
//狀态機
always@(*)
begin
case(state)
IDLE:
if(wr_req == 1'b1) //如果有讀寫請求,進入時鐘空閑态
next_state <= DCLK_IDLE;
else
next_state <= IDLE;
DCLK_IDLE:
//half a SPI clockcycle produces a clock edge
if(clk_cnt == clk_div)//在時鐘空閑态中,如果分頻時鐘滿,則進入時鐘采集态
next_state <= DCLK_EDGE;
else
next_state <= DCLK_IDLE;
DCLK_EDGE:
//a SPI byte with a total of 16 clock edges
if(clk_edge_cnt ==5'd15) //在時鐘采集态,如果時鐘沿達到15個,則進入最後半個時鐘沿(因為8位資料,需要8個脈沖即16個時鐘沿)
next_state <= LAST_HALF_CYCLE;
else
next_state <= DCLK_IDLE;
//this is the last data edge
LAST_HALF_CYCLE:
if(clk_cnt == clk_div) //如果在最後半個時鐘沿,預分頻時間到後進入應答态
next_state <= ACK;
else
next_state <= LAST_HALF_CYCLE;
//send one byte complete
ACK:
next_state <=ACK_WAIT; //進入應答等待态
//wait for one clock cycle, to ensure that the cancel request signal
ACK_WAIT:
next_state <= IDLE;
default:
next_state <= IDLE;
endcase
end
//說明:産生資料時鐘信号
always@(posedge sys_clk or posedge rst)
begin
if(rst)
DCLK_reg <= 1'b0;
else if(state == IDLE)
DCLK_reg <= CPOL;
else if(state == DCLK_EDGE)
DCLK_reg <= ~DCLK_reg;//SPI clock edge
end
//SPI 時鐘等待計數
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_cnt <= 16'd0;
else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
clk_cnt <= clk_cnt + 16'd1;
else
clk_cnt <= 16'd0;
end
//SPI 時鐘邊沿計數
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_edge_cnt <= 5'd0;
else if(state == DCLK_EDGE)
clk_edge_cnt <= clk_edge_cnt + 5'd1;
else if(state == IDLE)
clk_edge_cnt <= 5'd0;
end
//SPI資料輸出
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MOSI_shift <= 8'd0;
else if(state == IDLE && wr_req)
MOSI_shift <= data_in;
else if(state == DCLK_EDGE)
if(CPHA == 1'b0 &&
clk_edge_cnt[0] == 1'b1) //資料輸出在第二個邊沿進行
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
else
if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] ==1'b0)) //資料輸出在第一個邊沿進行
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
end
//SPI 資料輸入
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MISO_shift <= 8'd0;
else if(state == IDLE && wr_req)
MISO_shift <= 8'h00;
else if(state == DCLK_EDGE)
if(CPHA == 1'b0 &&
clk_edge_cnt[0] == 1'b0) //輸入資料采集在第一個邊沿進行
MISO_shift <= {MISO_shift[6:0],MISO};
else if(CPHA == 1'b1 &&(clk_edge_cnt[0] == 1'b1)) //輸入資料采集在第二個邊沿進行
MISO_shift <={MISO_shift[6:0],MISO};
end
endmodule