SPI总线协议介绍SPI(Serion Perpheral Interface)[3]是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用4根线,节约了芯片的管脚,同时为PCB的布局节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议。SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(用于单向传输时,也就是半双工方式)。也是所有基于SPI的设备共有,分别是MISO(数据输入),MOSI(数据输出),SCK(时钟),NSS(片选)。
SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL="0",串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设音时钟相位和极性应该一致
CPHA = 1,就表示数据的输出是在一个时钟周期的第一个沿上,至于这个沿是上升沿还是下降沿,这要看CPOL的值而定,CPOL=1那就是下降沿,反之就是上升沿,数据的采样就是在第二个沿上,CPHA = 0,就表示数据的采样是在一个时钟周期的第一个沿上,那么数据的输出就在第二个沿上了。
作为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
always@(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
always@(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
有了固定的SCK,接下来的接收和发送就简单了,只需要在SCK的上升沿或下降沿,从MISO引脚一位一位读取数据,将要发送的数据一位一位的写入到MOSI引脚。
reg[7:0] rxd_outr;
reg [2:0] rec_cnt;
always@(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;
always@(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
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
always@(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
always@(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;
always@(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;
always@(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
测试用的从机在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
测试结果
从仿真图可以看出,通信双方都可以正常的收发。