目錄
1 SPI總線協定
2 工作方式
本設計中:CPOL=1,CPHA = 1
3 Verilog程式
3.1 時鐘産生部分
3.2 資料接收部分
3.3 資料發送部分
4 全部程式
5 測試結果
6 部落客的閑聊群
1 SPI總線協定
SPI總線協定介紹SPI(Serion Perpheral Interface)[3]是一種高速的、全雙工、同步的通信總線,并且在晶片的管腳上隻占用4根線,節約了晶片的管腳,同時為PCB的布局節省空間,提供友善,正是出于這種簡單易用的特性,越來越多的晶片內建了這種通信協定。SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主裝置和一個或多個從裝置,需要至少4根線,事實上3根也可以(用于單向傳輸時,也就是半雙工方式)。也是所有基于SPI的裝置共有,分别是MISO(資料輸入),MOSI(資料輸出),SCK(時鐘),NSS(片選)。
2 工作方式
SPI子產品為了和外設進行資料交換,根據外設工作要求,其輸出串行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協定沒有重大的影響。如果 CPOL="0",串行同步時鐘的空閑狀态為低電平;如果CPOL=1,串行同步時鐘的空閑狀态為高電平。時鐘相位(CPHA)能夠配置用于選擇兩種不同的傳輸協定之一進行資料傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)資料被采樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)資料被采樣。SPI主子產品和與之通信的外設音時鐘相位和極性應該一緻
CPHA = 1,就表示資料的輸出是在一個時鐘周期的第一個沿上,至于這個沿是上升沿還是下降沿,這要看CPOL的值而定,CPOL=1那就是下降沿,反之就是上升沿,資料的采樣就是在第二個沿上,CPHA = 0,就表示資料的采樣是在一個時鐘周期的第一個沿上,那麼資料的輸出就在第二個沿上了。
本設計中:CPOL=1,CPHA = 1
3 Verilog程式
3.1 時鐘産生部分
作為SPI通信中的主動方,SPI Master要提供給SPI Slaver一個同步通信時鐘SCK,通信的雙方都在SCK的上升沿和下降沿進行資料的交換,是以設計SPI Master的第一步應該是産生一個SCK,供雙方有一個統一的資料交換時鐘。
reg csr;
assign sck = cnt1[2];
assign cs = csr;
task clk_states;
if(cnt1==3'b000) begin cnt1 <= 3'b111;cnt <= cnt+1'b1;end
else cnt1 <= cnt1 - 1'b1;
endtask
[email protected](posedge clk or negedge rst)
begin
if(!rst)
begin
txd_flag <= 1'b0;
txd_start<=3'd1;
cnt <=4'd0;
cnt1<=3'b111;
csr <= 1'b1;
end
else
begin
if(txd_start==txd_start_outside)
begin
csr <= 1'b0;
txd_flag <=1'b1;//txd_flag 信号與cs信号相反,電路當處于通信時為高電平,表示忙碌狀态
case(cnt) //為低電平表示空閑狀态
0:clk_states();
1:clk_states();
2:clk_states();
3:clk_states();
4:clk_states();
5:clk_states();
6:clk_states();
7:
begin
if(cnt1==3'b000) begin
cnt1 <= 3'b111;
cnt <= 4'd8;
end
else cnt1 <= cnt1 - 1'b1;
end
8://多加一個狀态延長cs的低電平時間,給從機足夠的時間接受資料
begin
txd_start <= txd_start+1'b1;
cnt <= 4'd0;
end
endcase
end
else begin csr <= 1'b1; txd_flag <= 1'b0; end
end
end
[email protected](posedge txd_signal or negedge rst) //外部觸發發送脈沖,上升沿觸發發送一次
begin //每次觸發産生8個周期的SCK脈沖,完成一次8bit資料
if(!rst) //的通信
txd_start_outside <= 1'b0;
else
begin
if(!txd_flag) txd_start_outside <= txd_start_outside+1'b1; //空閑狀态才允許觸發通信
else;
end
end
3.2 資料接收部分
有了固定的SCK,接下來的接收和發送就簡單了,隻需要在SCK的上升沿或下降沿,從MISO引腳一位一位讀取資料,将要發送的資料一位一位的寫入到MOSI引腳。
reg[7:0] rxd_outr;
reg [2:0] rec_cnt;
[email protected](posedge sck or negedge rst) //sck上升沿采樣資料
begin
if(!rst)
begin
rxd_out <= 8'h00;
rec_cnt <= 3'd0;
end
else
begin
case(rec_cnt)
0:begin rxd_outr[7] <= miso;rec_cnt<=3'd1;end
1:begin rxd_outr[6] <= miso;rec_cnt<=3'd2;end
2:begin rxd_outr[5] <= miso;rec_cnt<=3'd3;end
3:begin rxd_outr[4] <= miso;rec_cnt<=3'd4;end
4:begin rxd_outr[3] <= miso;rec_cnt<=3'd5;end
5:begin rxd_outr[2] <= miso;rec_cnt<=3'd6;end
6:begin rxd_outr[1] <= miso;rec_cnt<=3'd7;end
7:begin
rxd_outr[0] <= miso;
rxd_out <= {rxd_outr[7:1],miso};
rec_cnt<=3'd0;
end
default:;
endcase
end
end
3.3 資料發送部分
reg [2:0] send_cnt;
[email protected](negedge sck or negedge rst)//sck下降沿沿發送資料
begin
if(!rst)
begin
send_cnt <= 3'd0;
end
else
case(send_cnt)
0:begin mosi <= txd_in[7];send_cnt<=3'd1;end
1:begin mosi <= txd_in[6];send_cnt<=3'd2;end
2:begin mosi <= txd_in[5];send_cnt<=3'd3;end
3:begin mosi <= txd_in[4];send_cnt<=3'd4;end
4:begin mosi <= txd_in[3];send_cnt<=3'd5;end
5:begin mosi <= txd_in[2];send_cnt<=3'd6;end
6:begin mosi <= txd_in[1];send_cnt<=3'd7;end
7:begin mosi <= txd_in[0];send_cnt<=3'd0;end
default:;
endcase
end
4 全部程式
module SPI_M(
clk,
rst,
txd_flag, //高電平表示為發送完成處于忙碌狀态,低電平表示處于空閑狀态
sck,
cs,
mosi,
miso,
txd_signal,
rxd_out,
txd_in
);
input clk,rst,miso,txd_signal;
output reg mosi,txd_flag;
output wire sck,cs;
output reg[7:0] rxd_out;
input [7:0] txd_in;
reg[2:0] txd_start;
reg[2:0] txd_start_outside;
reg[3:0] cnt;
reg[2:0] cnt1;
reg csr;
assign sck = cnt1[2];
assign cs = csr;
task clk_states;
if(cnt1==3'b000) begin cnt1 <= 3'b111;cnt <= cnt+1'b1;end
else cnt1 <= cnt1 - 1'b1;
endtask
[email protected](posedge clk or negedge rst)
begin
if(!rst)
begin
txd_flag <= 1'b0;
txd_start<=3'd1;
cnt <=4'd0;
cnt1<=3'b111;
csr <= 1'b1;
end
else
begin
if(txd_start==txd_start_outside)
begin
csr <= 1'b0;
txd_flag <=1'b1;//txd_flag 信号與cs信号相反,電路當處于通信時為高電平,表示忙碌狀态
case(cnt) //為低電平表示空閑狀态
0:clk_states();
1:clk_states();
2:clk_states();
3:clk_states();
4:clk_states();
5:clk_states();
6:clk_states();
7:
begin
if(cnt1==3'b000) begin
cnt1 <= 3'b111;
cnt <= 4'd8;
end
else cnt1 <= cnt1 - 1'b1;
end
8://多加一個狀态延長cs的低電平時間,給從機足夠的時間接受資料
begin
txd_start <= txd_start+1'b1;
cnt <= 4'd0;
end
endcase
end
else begin csr <= 1'b1; txd_flag <= 1'b0; end
end
end
[email protected](posedge txd_signal or negedge rst) //外部觸發發送脈沖,上升沿觸發發送一次
begin
if(!rst)
txd_start_outside <= 1'b0;
else
begin
if(!txd_flag) txd_start_outside <= txd_start_outside+1'b1;
else;
end
end
reg[7:0] rxd_outr;
reg [2:0] rec_cnt;
[email protected](posedge sck or negedge rst) //sck上升沿采樣資料
begin
if(!rst)
begin
rxd_out <= 8'h00;
rec_cnt <= 3'd0;
end
else
begin
case(rec_cnt)
0:begin rxd_outr[7] <= miso;rec_cnt<=3'd1;end
1:begin rxd_outr[6] <= miso;rec_cnt<=3'd2;end
2:begin rxd_outr[5] <= miso;rec_cnt<=3'd3;end
3:begin rxd_outr[4] <= miso;rec_cnt<=3'd4;end
4:begin rxd_outr[3] <= miso;rec_cnt<=3'd5;end
5:begin rxd_outr[2] <= miso;rec_cnt<=3'd6;end
6:begin rxd_outr[1] <= miso;rec_cnt<=3'd7;end
7:begin
rxd_outr[0] <= miso;
rxd_out <= {rxd_outr[7:1],miso};
rec_cnt<=3'd0;
end
default:;
endcase
end
end
reg [2:0] send_cnt;
[email protected](negedge sck or negedge rst)//sck下降沿沿發送資料
begin
if(!rst)
begin
send_cnt <= 3'd0;
end
else
case(send_cnt)
0:begin mosi <= txd_in[7];send_cnt<=3'd1;end
1:begin mosi <= txd_in[6];send_cnt<=3'd2;end
2:begin mosi <= txd_in[5];send_cnt<=3'd3;end
3:begin mosi <= txd_in[4];send_cnt<=3'd4;end
4:begin mosi <= txd_in[3];send_cnt<=3'd5;end
5:begin mosi <= txd_in[2];send_cnt<=3'd6;end
6:begin mosi <= txd_in[1];send_cnt<=3'd7;end
7:begin mosi <= txd_in[0];send_cnt<=3'd0;end
default:;
endcase
end
endmodule
5 測試結果
測試用的從機在https://blog.csdn.net/qq_40893012/article/details/103995154
SPI子產品:将SPI主機和SPI從機連接配接起來
module SPI(
clk,
rst,
M_txd_signal,
M_rxd_out,
M_txd_in,
S_rxd_out,
M_txd_flag,
S_rxd_flag,
sck
);
input clk,rst,M_txd_signal;
output M_txd_flag,S_rxd_flag,sck;
output [7:0] S_rxd_out,M_rxd_out;
input [7:0] M_txd_in;
wire sck,cs,mosi,miso;
SPI_M SPIM(
.clk(clk),
.rst(rst),
.txd_flag(M_txd_flag),
.sck(sck),
.cs(cs),
.mosi(mosi),
.miso(miso),
.txd_signal(M_txd_signal),
.rxd_out(M_rxd_out),
.txd_in(M_txd_in)
);
spi_slaver_ctrl spi_slaver_ctrl1(
.clk(clk),
.rst(rst),
.cs(cs),
.sck(sck),
.MOSI(mosi),
.MISO(miso),
.rxd_out(S_rxd_out),
.rxd_flag(S_rxd_flag)
);
endmodule
測試腳本程式
`timescale 1 ns/ 1 ps
module SPI_vlg_tst();
reg clk;
reg rst;
reg M_txd_signal;
reg [7:0] M_txd_in;
// wires
wire [7:0] M_rxd_out;
wire [7:0] S_rxd_out;
wire S_rxd_flag;
wire sck;
// assign statements (if any)
SPI i1 (
.clk(clk),
.rst(rst),
.M_txd_signal(M_txd_signal),
.M_rxd_out(M_rxd_out),
.M_txd_in(M_txd_in),
.S_rxd_out(S_rxd_out),
.M_txd_flag(M_txd_flag),
.S_rxd_flag(S_rxd_flag),
.sck(sck)
);
initial
begin
M_txd_signal = 0;
clk = 0;
rst = 1;
#100;
rst = 0;
#50;
rst = 1; //複位完成
#50;
M_txd_in = 8'ha5;
M_txd_signal = 1; //啟動發送;
#50;
M_txd_signal = 0;
#1400;
M_txd_in = 8'h33; //正常應該接受到8'ha5+1
M_txd_signal = 1; //啟動發送;
#50;
M_txd_signal = 0;
#2000;
M_txd_in = 8'h66; //正常應該接受到8'h33+1
M_txd_signal = 1; //啟動發送;
#50;
M_txd_signal = 0;
#2000;
$stop;
// --> end
$display("Running testbench");
end
always
begin
#10 clk = ~clk; //50M時鐘
end
endmodule
測試結果
從仿真圖可以看出,通信雙方都可以正常的收發。
6 部落客的閑聊群
QQ:701359719