关于SPI的教程有很多,这里写下自己学习SPI协议后的总结。
什么是SPI?
SPI是Serial Peripheral Interface Bus的缩写,意为:串行外围接口。它是一种用于短距通信的同步串行通信接口标准,主要用于嵌入式系统。这个接口是Motorola在1980年末开发的,之后变成一种约定俗成的通信标准。SPI协议使用单个Master的主-从(Master-Slave)结构,以全双工的方式工作。主设备控制读写,多个从设备通过片选信号(SS)连接。
Interface
采用SPI协议通信的设备通常只需要四条线就可以完成数据的传输,因此,这种占用端口资源少的优点也被称为SPI协议的一个亮点。
SCLK:串行时钟,由Master输出,从机接受SCLK信号。它控制着数据传输的节拍,进而影响数据交换的快慢。
MOSI:(Master output Slave input)从字面意思就可以知道,这条线为主出从入,也就是主机的数据输出端口,从机的数据输入端口。(实际上,个人认为将MOSI拆为MO和SI理解更好)
MISO:(Master input Slave output)主入从出,即主机输入,从机输出。
SS:(Slave Select)片选信号。只有该Slave上的SS信号有效时,该Slave才被选中。
工作过程
SPI通信过程本质上来讲,就是数据的交换。在数据交换的过程中完成数据的发送和接收。
主机控制SS信号和SCLK信号的产生,在SS信号有效时,相应的从机被选中。在SCLK的节拍下完成数据的交换。
SPI因为SCLK的不同形式可以分为四种工作模式,四种工作模式受控于CPOL和CPHA。也就是串行时钟SCLK的极性和相位。
SPI模式 | 时钟极性(CPOL) | 时钟相位(CPHA) |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
2 | 1 | 0 |
3 | 1 | 1 |
为了讨论方便,给出一种模式来说明SPI如何工作。
以下就是SPI协议完成数据交换的时序图。
在SS有效的情况下,主机在SCLK的前沿通过MOSI输出数据(write),而在SCLK的后沿通过MISO采样数据(read)。对于从机而言,同理,SCLK的前沿通过MISO进行数据输出。SCLK的后沿通过MOSI完成数据的采样。
这样一来,一个SCLK时钟周期可以完成1bit的数据输出和1bit的数据读入,高效的利用了时钟资源。
实际上,根据时序图即可完成Verilog代码的编写,经过一番折腾,完成了master的数据发送。同时,通过test bench的测试,完成SPI协议的模拟。部分代码给出了一定的说明。
spi_master.v
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 10:41:00 07/29/2017
// Design Name:
// Module Name: spi_master
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module spi_master(
input wire[7:0] in_data,
input wire clk,
input wire[1:0] addr, // commonds
input wire wr,
input wire rd,
input wire cs,
output reg[7:0] out_data,
inout mosi,
input miso,
inout sclk
);
// --------define internal register and buffer--------
// output buffer stage
reg sclk_buf = 0;
reg mosi_buf = 0;
// idle flag , busy = 0 if no data to receive or send , or else set busy = 1
reg busy = 0;
// shift register
reg[7:0] in_buf = 0;
reg[7:0] out_buf = 0;
reg[7:0] clk_cnt = 0;
// division of clk , clk_div=0 means that clk is not be divide , and modify it could implement corresponding sck for device
reg[7:0] clk_div = 0;
reg[4:0] cnt = 0;
// --------------------------------------------------
// the port of module links internal buffer
assign sclk = sclk_buf;
assign mosi = mosi_buf;
//sclk positive edge read data into out-shift register from miso , implement read operation
always @(posedge sclk_buf) begin
out_buf[0] <= miso;
out_buf <= out_buf << 1;
end
// read data (combinatorial logic that level sensitive , detect all input)
always @(cs or wr or rd or addr or out_buf or busy or clk_div) begin
out_data = 8'bx;
if (cs && rd) begin
case(addr)
2'b00 : out_data = out_buf;
2'b01 : out_data = {7'b0 , busy}; // when send data encounter spi is busy , return busy singal
2'b10 : out_data = clk_div;
default : out_data = out_data;
endcase
end
end
// sclk negitive edge write data to mosi
always @(posedge clk) begin
if (!busy) begin // idle state load data into send buffer
if(cs && wr) begin
case(addr) // commonds
2'b00 : begin
in_buf <= in_data;
busy <= 1;
cnt <= 0;
end
2'b10 : begin
in_buf <= clk_div; // load number of division to slave for implement sync of sclk
end
default : in_buf <= in_buf;
endcase
end
else if(cs && rd) begin
busy <= 1;
cnt <= 0;
end
end
else begin // when 8-bits data write into buffer , begin send with bit by bit
clk_cnt <= clk_cnt + 1;
if (clk_cnt >= clk_div) begin // divide clk
clk_cnt <= 0;
if (cnt % 2 == 0) begin // when csk_buf is negitive , shift data into mosi buffer
mosi_buf <= in_buf[7];
in_buf <= in_buf << 1;
end
else begin
mosi_buf <= mosi_buf;
end
if (cnt > 0 && cnt < 17) begin
sclk_buf <= ~sclk_buf;
end
// 8-bits had sent over , spi regain idle
if (cnt >= 17) begin
cnt <= 0;
busy <= 0;
end
else begin
cnt <= cnt;
busy <= busy;
end
cnt <= cnt + 1;
end
end
end
endmodule
testbench.v
`timescale 1ns / 1ps
////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 13:12:38 07/29/2017
// Design Name: spi_master
// Module Name: E:/ISEProjece/SPI/spi_master_tb.v
// Project Name: SPI
// Target Device:
// Tool versions:
// Description:
//
// Verilog Test Fixture created by ISE for module: spi_master
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////////////////////
module spi_master_tb;
// Inputs
reg [7:0] in_data;
reg clk;
reg [1:0] addr;
reg wr;
reg rd;
reg cs;
reg miso;
// Outputs
wire [7:0] out_data;
// Bidirs
wire mosi;
wire sclk;
// Instantiate the Unit Under Test (UUT)
spi_master uut (
.in_data(in_data),
.clk(clk),
.addr(addr),
.wr(wr),
.rd(rd),
.cs(cs),
.out_data(out_data),
.mosi(mosi),
.miso(miso),
.sclk(sclk)
);
initial begin
// Initialize Inputs
in_data = 0;
clk = 0;
addr = 0;
wr = 0;
rd = 0;
cs = 0;
miso = 0;
// set clk_div , and out by out_data
#40;
addr = 0;
in_data = 8'haa;
wr = 1;
cs = 1;
// write data
#20 ;
wr = 0;
cs = 0;
#360 ;
wr = 1;
cs = 1;
in_data = 8'h91;
#20 ;
wr = 0;
cs = 0;
end
// define clock
initial begin
clk = 0;
forever #10 clk = ~clk;
end
endmodule
SPI详细资料参见SPI协议。
代码托管于https://github.com/caxElva/SPI。