SPI協(xié)議介紹
spi是serial peripheral interface的縮寫,即串行擴(kuò)展總線。SPI是單主設(shè)備通信,總線中只有一個主設(shè)備發(fā)起通信,能發(fā)起通信的設(shè)備稱為主設(shè)備。當(dāng)SPI主設(shè)備想讀寫從設(shè)備時,首先拉低對應(yīng)從設(shè)備的ss線(低電平有效)。然后發(fā)送工作麥種到時鐘線上,在相應(yīng)的脈沖時間上,主設(shè)備把信號發(fā)送到MOSI實現(xiàn)讀寫,同時又可以對MISO采樣實現(xiàn)讀。一般SPI通信涉及到一下術(shù)語:
SCLK | serial clock (來自主設(shè)備) |
---|---|
MOSI | Master Output Slave Input(來自主設(shè)備) |
MISO | Master Input Slave Output(來自從設(shè)備) |
SS | Slave Select(低電平有效,來自主設(shè)備) |
主設(shè)備和從設(shè)備的兩種鏈接方式
一主一從
一主一從模式表明有一個主機(jī)和一個從機(jī),如下所示:(其中還有SCK信號,由主機(jī)到從機(jī))
一主多從
一主多從表示有一個主機(jī)和一個從機(jī),唯一不同的點是要為每個從機(jī)配備一個選擇信號。
SPI協(xié)議的工作模式
SPI有四種工作模式,主要由時鐘極性CPOL(Clock Polarity),時鐘相位CPHA(Clock Phase)的組合決定。
- CPOL為0,表示SCK在空閑狀態(tài)為0,為1,則表示SCK在空閑狀態(tài)為1
- CPHA為0,表示在SCK的第一個邊沿時輸出輸出數(shù)據(jù)有效,CPHA為1時,表示在SCK的第二個邊沿輸入輸出數(shù)據(jù)有效
CPOL = 0, CPHA = 0
CPOL = 0, CPHA = 1
CPOL = 1. CPHA = 0
CPOL = 1, CPHA = 1
SPI MASTER 的verilog設(shè)計思路
設(shè)計的引腳說明:
信號名 | 方向 +解釋 |
---|---|
clk | 輸入,時鐘信號 |
rst_n | 輸入,復(fù)位信號 |
miso | 輸入,從機(jī)輸入到主機(jī) |
data_i | 輸入,主機(jī)發(fā)送從機(jī)的數(shù)據(jù)(一定比特位寬) |
start | 輸入,開始的使能信號 |
mosi | 輸出,主機(jī)到從機(jī) |
sclk | 輸出,時鐘信號 |
ss_n | 輸出,從機(jī)的選擇信號 |
finish | 一次傳輸完成信號 |
首先將需要的宏定義收錄在一個文件defines.v中,其中的代碼如下所示:
`define CPOL 0 //clock polarity
`define CPHA 0 //clock phase
`define CLK_FREQ 50_000_000 // input clk frequency
`define SCLK_FREQ 5_000_000 // sclk frequency
`define DATA_WIDTH 8 // a word width
`define CLK_CYCLE 20
其次時SPI MASTER的設(shè)計思路,總體思路時采用一個狀態(tài)機(jī),首先狀態(tài)機(jī)是在IDLE狀態(tài),然后接收開始信號后,會將寄存器data_I中數(shù)據(jù)一個一個的發(fā)送出去,當(dāng)指定寬距的比特位發(fā)送完成后。此時有兩種選擇,一種是跳轉(zhuǎn)到FINISH狀態(tài),另一種是跳轉(zhuǎn)到EXTRA狀態(tài),在跳轉(zhuǎn)到FINISH狀態(tài)。主要是由于當(dāng)數(shù)據(jù)發(fā)送完成之后要判斷此時的SCLK狀態(tài)是不是空閑下的默認(rèn)狀態(tài),如果不是,則需要跳轉(zhuǎn)到EXTRA狀態(tài)。結(jié)束狀態(tài)下一個狀態(tài)回到IDLE狀態(tài),得到start命令。
IDLE | 空閑狀態(tài) |
---|---|
DATA | 發(fā)送數(shù)據(jù)狀態(tài) |
EXTRA | 額外狀態(tài) |
FINISH | 結(jié)束狀態(tài) |
verilog的代碼如下:
`include "defines.v"
module SPI_MASTER(
input wire clk ,
input wire rst_n ,
input wire miso ,
input wire [`DATA_WIDTH-1:0] data_i ,
input wire start ,
output wire mosi ,
output reg sclk ,
output reg ss_n ,
output wire finish
);
parameter IDLE = 5'b00001 ,
//CHOOSE = 5'b00010 ,
DATA = 5'b00100 ,
EXTRA = 5'b01000 ,
FINISH = 5'b10000 ;
parameter CNT_MAX = `CLK_FREQ / `SCLK_FREQ - 1;
reg [31:0] cnt ; //sclk的時鐘周期的計數(shù)器
reg [4:0] state ;
reg [4:0] nx_state ;
wire [3:0] cnt_data ; //輸出的數(shù)據(jù)計數(shù)器
reg sclk_dly ; //sclk的打一拍信號
reg [3:0] cnt_sclk_pos ; //sclk的上升沿計數(shù)器信號
reg [3:0] cnt_sclk_neg ; //sclk的下降沿計數(shù)器信號
reg start_dly ; //start的打一拍信號
reg [3:0] cnt_data_dly ;
wire cnt_max_flag ; //計數(shù)器cnt達(dá)到最大值的信號
wire dec_pos_or_neg_sample ; //1 posedge sample, 0 negedge sample
wire sclk_posedge ; //sclk的上升沿
wire sclk_negedge ; //sclk的下降沿
assign dec_pos_or_neg_sample = (`CPOL == `CPHA) ? 1'b1 : 1'b0;
assign cnt_max_flag = (cnt == CNT_MAX ) ? 1'b1 : 1'b0;
assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
assign cnt_data = dec_pos_or_neg_sample ? cnt_sclk_pos : cnt_sclk_neg;
always @(posedge clk or negedge rst_n) begin
sclk_dly <= sclk;
start_dly <= start;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_pos <= 4'd0;
end
else if(state == FINISH) begin
cnt_sclk_pos <= 4'd0;
end
//else if((sclk_posedge) && (cnt_sclk_pos == `DATA_WIDTH - 1)) begin
// cnt_sclk_pos <= `DATA_WIDTH - 1;
//end
else if(sclk_posedge) begin
cnt_sclk_pos <= cnt_sclk_pos + 1'b1;
end
else begin
cnt_sclk_pos <= cnt_sclk_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_neg <= 4'd0;
end
else if(state == FINISH) begin
cnt_sclk_neg <= 4'd0;
end
//else if((sclk_negedge) && (cnt_sclk_neg == `DATA_WIDTH - 1)) begin
// cnt_sclk_neg <= `DATA_WIDTH - 1;
//end
else if(sclk_negedge) begin
cnt_sclk_neg <= cnt_sclk_neg + 1'b1;
end
else begin
cnt_sclk_neg <= cnt_sclk_neg;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
end
else begin
state <= nx_state;
end
end
always @(*) begin
nx_state <= IDLE;
case(state)
IDLE: nx_state <= start_dly ? DATA : IDLE;
DATA: nx_state <= (cnt_data == `DATA_WIDTH) ? (`CPHA == 0) ? EXTRA : FINISH : DATA;
EXTRA: nx_state <= cnt_max_flag ? FINISH : EXTRA ;
FINISH: nx_state <= IDLE;
default:nx_state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 'd0;
end
else if((state == DATA) && (nx_state == FINISH) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if((state == DATA) && (nx_state == EXTRA) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if((state == DATA) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if((state == EXTRA) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if(state == DATA) begin
cnt <= cnt + 1'b1;
end
else if(state == EXTRA) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 'd0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sclk <= (`CPOL) ? 1'b1 : 1'b0;
end
else if(start_dly) begin
sclk <= ~sclk;
end
else if((state == DATA) && (cnt_max_flag) && (cnt_data < `DATA_WIDTH) ) begin
sclk <= ~sclk;
end
else if((state == DATA) && (cnt_max_flag) && (cnt_data == `DATA_WIDTH) && (nx_state == EXTRA)) begin
sclk <= ~sclk;
end
else if((state == EXTRA) && (cnt_max_flag) && (nx_state == FINISH)) begin
sclk <= ~sclk;
end
else begin
sclk <= sclk;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ss_n <= 1'b1;
end
else if(start) begin
ss_n <= 1'b0;
end
else if(state == FINISH) begin
ss_n <= 1'b1;
end
else begin
ss_n <= ss_n;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_data_dly <= 'd0;
end
else begin
cnt_data_dly <= cnt_data;
end
end
assign finish = (state == FINISH) ? 1'b1 : 1'b0;
assign mosi = (state == DATA) ? ((cnt_data_dly < `DATA_WIDTH) ? data_i[cnt_data_dly] : data_i[`DATA_WIDTH-1]) : data_i[0];
endmodule
仿真的testbench如下:
`timescale 1ns/1ns
`include "defines.v"
module tb_master_slave();
reg clk ;
reg rst_n ;
//reg mosi ;
reg [`DATA_WIDTH-1:0] data_i ;
reg start ;
reg miso ;
wire mosi ;
wire sclk ;
wire finish ;
wire ss_n ;
wire [`DATA_WIDTH-1:0] data_o ;
wire r_finish ;
SPI_MASTER u_spi_master (
.clk (clk) ,
.rst_n (rst_n) ,
.miso (miso) ,
.data_i (data_i) ,
.start (start) ,
.mosi (mosi) ,
.sclk (sclk) ,
.finish (finish) ,
.ss_n (ss_n)
);
SPI_SLAVE u_spi_slave(
.clk (clk) ,
.rst_n (rst_n) ,
.mosi (mosi) ,
.sclk (sclk) ,
.tx_finish(finish),
.start (start) ,
.ss_n (ss_n) ,
.data_o (data_o) ,
.r_finish(r_finish)
);
initial begin
clk = 1'b0;
rst_n = 1'b0;
start = 1'b0;
data_i = 8'h35;
miso = 1'b0;
#30
rst_n = 1'b1;
#10;
@(posedge clk);
start <= 1'b1;
@(posedge clk);
start <= 1'b0;
@(negedge finish);
data_i = 8'h44;
repeat(2) @(posedge clk);
start = 1'b1;
@(posedge clk);
start = 1'b0;
end
always #(`CLK_CYCLE / 2) clk = ~clk;
endmodule
仿真波形如下,可以通過mosi成功發(fā)出了數(shù)據(jù)。
SPI SLAVE設(shè)計思路
SPI SPLAVE的設(shè)計思路大體如master類似,端口說明如下:
信號名 | 方向 +解釋 |
---|---|
clk | 輸入,時鐘信號 |
rst_n | 輸入,復(fù)位信號 |
miso | 輸入,從機(jī)輸入到主機(jī) |
data_i | 輸入,主機(jī)發(fā)送從機(jī)的數(shù)據(jù)(一定比特位寬) |
start | 輸入,開始的使能信號 |
mosi | 輸出,主機(jī)到從機(jī) |
sclk | 輸入,時鐘信號 |
ss_n | 輸入,從機(jī)的選擇信號 |
r_finish | 一次傳輸完成信號 |
data_o | 輸出,收集到的數(shù)據(jù) |
依然采用的是狀態(tài)機(jī)思路,首先在IDLE狀態(tài),當(dāng)開始信號使能之后,會跳轉(zhuǎn)到RV_DATA接收到數(shù)據(jù)狀態(tài)。RV_DATA數(shù)據(jù)接收完成,之后回跳轉(zhuǎn)到FINISH狀態(tài),表明此次讀取完成。
IDLE | 空閑狀態(tài) |
---|---|
RV_ DATA | 接收數(shù)據(jù)狀態(tài) |
FINISH | 結(jié)束狀態(tài) |
spi slave的代碼如下所示: |
`include "defines.v"
module SPI_SLAVE(
input wire clk ,
input wire rst_n ,
input wire mosi ,
input wire sclk ,
input wire tx_finish ,
input wire start ,
input wire ss_n ,
output wire [`DATA_WIDTH-1:0] data_o ,
//output wire miso ,
output wire r_finish
);
parameter IDLE = 4'b0001 ,
RV_DATA = 4'b0010 ,
FINISH = 4'b0100 ;
wire sclk_posedge ;
wire sclk_negedge ;
wire dec_pos_or_neg_sample;
//wire sclk_posedge ;
//wire sclk_negedge ;
reg sclk_dly ;
reg [`DATA_WIDTH-1:0] data_shift_pos ;
reg [`DATA_WIDTH-1:0] data_shift_neg ;
reg [3:0] state ;
reg [3:0] nx_state ;
reg [3:0] cnt_sclk_pos ;
reg [3:0] cnt_sclk_neg ;
wire [3:0] num_sample_data ;
assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
assign dec_pos_or_neg_sample = (`CPOL == `CPHA) ? 1'b1 : 1'b0;
//assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
//assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
assign num_sample_data = (dec_pos_or_neg_sample) ? cnt_sclk_pos : cnt_sclk_neg;
always @(posedge clk or negedge rst_n) begin
sclk_dly <= sclk;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
end
else begin
state <= nx_state;
end
end
always @(*) begin
nx_state <= IDLE;
case(state)
IDLE: nx_state <= start ? RV_DATA :IDLE;
RV_DATA: begin
if((num_sample_data == 7) && (dec_pos_or_neg_sample) && (sclk_posedge) && (!ss_n)) begin
nx_state <= FINISH;
end
else if((num_sample_data == 7) && (~dec_pos_or_neg_sample) && (sclk_negedge) && (!ss_n)) begin
nx_state <= FINISH;
end
else begin
nx_state <= RV_DATA;
end
end
FINISH: nx_state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_pos <= 4'd0;
end
else if((state == FINISH)) begin
cnt_sclk_pos <= 4'd0;
end
else if(sclk_posedge) begin
cnt_sclk_pos <= cnt_sclk_pos + 1'b1;
end
else begin
cnt_sclk_pos <= cnt_sclk_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_neg <= 4'd0;
end
else if (state == FINISH) begin
cnt_sclk_neg <= 4'd0;
end
else if (sclk_negedge) begin
cnt_sclk_neg <= cnt_sclk_neg + 1'b1;
end
else begin
cnt_sclk_neg <= cnt_sclk_neg;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_shift_pos <= {`DATA_WIDTH{1'b0}};
end
else if((state == RV_DATA) && (sclk_posedge)) begin
data_shift_pos <= {mosi, data_shift_pos[`DATA_WIDTH-1:1]};
end
else if (state == FINISH) begin
data_shift_pos <= {`DATA_WIDTH{1'b0}};
end
else begin
data_shift_pos <= data_shift_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_shift_neg <= {`DATA_WIDTH{1'b0}};
end
else if((state == RV_DATA) && (sclk_negedge)) begin
data_shift_neg <= {mosi, data_shift_neg[`DATA_WIDTH-1:1]};
end
else if(state == FINISH) begin
data_shift_neg <= {`DATA_WIDTH{1'b0}};
end
else begin
data_shift_neg <= data_shift_neg;
end
end
//assign data_o = dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg;
assign data_o = (state == FINISH) ? (dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg): {`DATA_WIDTH{1'b0}};
assign r_finish = (state == FINISH);
endmodule
SPI MASTRT 和 SLAVE聯(lián)合仿真
testbench如下所示:
`timescale 1ns/1ns
`include "defines.v"
module tb_master_slave();
reg clk ;
reg rst_n ;
//reg mosi ;
reg [`DATA_WIDTH-1:0] data_i ;
reg start ;
reg miso ;
wire mosi ;
wire sclk ;
wire finish ;
wire ss_n ;
wire [`DATA_WIDTH-1:0] data_o ;
wire r_finish ;
SPI_MASTER u_spi_master (
.clk (clk) ,
.rst_n (rst_n) ,
.miso (miso) ,
.data_i (data_i) ,
.start (start) ,
.mosi (mosi) ,
.sclk (sclk) ,
.finish (finish) ,
.ss_n (ss_n)
);
SPI_SLAVE u_spi_slave(
.clk (clk) ,
.rst_n (rst_n) ,
.mosi (mosi) ,
.sclk (sclk) ,
.tx_finish(finish),
.start (start) ,
.ss_n (ss_n) ,
.data_o (data_o) ,
.r_finish(r_finish)
);
initial begin
clk = 1'b0;
rst_n = 1'b0;
start = 1'b0;
data_i = 8'h35;
miso = 1'b0;
#30
rst_n = 1'b1;
#10;
@(posedge clk);
start <= 1'b1;
@(posedge clk);
start <= 1'b0;
@(negedge finish);
data_i = 8'h44;
repeat(2) @(posedge clk);
start = 1'b1;
@(posedge clk);
start = 1'b0;
end
always #(`CLK_CYCLE / 2) clk = ~clk;
endmodule
仿真的波形如下,可知成功的讀取到了8’h35和8’h44。驗證成功。文章來源:http://www.zghlxwxcb.cn/news/detail-787304.html
總結(jié)
通過這次spi的協(xié)議的編寫,感覺自己對于狀態(tài)機(jī)的掌握,以及腦子里可以浮現(xiàn)波形。加油加油?。?!文章來源地址http://www.zghlxwxcb.cn/news/detail-787304.html
到了這里,關(guān)于SPI協(xié)議的verilog實現(xiàn)(spi master slave聯(lián)合實現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!