1.串行外设接口SPI(Serial Peripheral Interface)是一种由Motorola 公司推出的一种同步串行接口,得到了广泛应用。SPI 接口可以共享,便于组成带多个SPI 接口器件的系统,且传送速率可编程,连接线少,具有良好的扩展性,是一种优秀的同步时序电路。
SPI,顾名思义就是串行外围设备接口,只需4 条线就可以完成主、从与各种外围器件全双工同步通讯。4 根接口线分别是:串行时钟线(SCK)、主机输入/从机输出数据线(MISO)、主机输出/从机输入数据线(MOSI)、低电平有效从机选择线CS。
2.SPI 接口的总线时序
SPI 的工作模式分为主模式和从模式,二者都需要在SCK 的作用下才能工作;但主模式不需要CS 信号,而从模式必须在CS 信号有效的情况下才能完成。不论是在主模式下还是在从模式下,都要在时钟极性(CPOL)和时钟相位(CPHA)的配合下才能有效地完成一次数据传输。其中,时钟极性表示时钟信号在空闲时的电平;时钟相位决定数据是在SCK的上升沿采样还是下降沿采样。根据时钟极性和时钟相位的不同组合,可以得到SPI 总线的4 种工作模式,如图11-45 所示。
SPI0模式下的CPOL为0,SCK的空闲电平为低;CPHA为0,数据在串行同步时钟的第一个跳变沿(由于CPOL为低,因此第1个跳变沿只能为上升沿)时数据被采样。
SPI1模式下的CPOL也为0,SCK的空闲电平为低;但是CPHA为1,数据在串行同步时钟的第二个跳变沿(由于CPOL为低,因此第2个跳变沿只能为下降沿)时数据被采样。
SPI2模式下的CPOL为1,SCK的空闲电平为高;CPHA为0,数据在串行同步时钟的第1个跳变沿(由于CPOL为高,因此第1个跳变沿只能为下降沿)时数据被采样。
SPI3模式下的CPOL为1,SCK的空闲电平为高;CPHA为1,数据在串行同步时钟的第2个跳变沿(由于CPOL为高,因此第1个跳变沿只能为上升沿)时数据被采样。
在上述4 种模式中,使用的最为广泛的是SPI0 和SPI3 方式。由于每一种模式都与其他三种不兼容,因此为了完成主、从设备间的通讯,主、从设备的CPOL 和CPHA 必须有相同的设置。读者需要注意的是:如果主设备/从设备在SCK 上升沿发送数据,则从设备/主设备最好在下降沿采样数据;如果主设备/从设备在SCK 下降沿发送数据,则从设备/主设备最好在SCK 上升沿采样数据。
在通过 HDL 语言实现SPI 接口协议完成通信或者对具有SPI 接口的芯片进行编程以及功能配置时,需要注意以下几个问题:
(1)确认接口芯片读入或送出数据发生在时钟信号的上升沿或是下降沿,并在数据保持稳定后再进行数据的读写操作;
(2)数据需保持的最短有效时间(一般而言查阅接口芯片的数据手册就可以得到),避免在接口芯片未完成读写数据时即进行下一次的操作;
(3)对于从节点主动寻求主动节点服务的接口芯片,应注意SPI 接口芯片发出中断数据请示信号后,所需的响应时间,以避免出现接口芯片发出请示服务信号后长时间处于等待状态而致使数据信息丢失等现象的出现。
(4)在进行数据通信时,确认通信字节位传输的顺序是以MSB→ LSB 方式进行,还是以LSB → MSB 的方式进行。
例子:使用Verilog HDL语言实现SPI0模式的SPI主模式,其中读、写操作都是低字节在前,高字节在后,每次传送1个字节。
module spi_master(
addr,in,out,rd,wr,cs,clk,miso,mosi,sclk
);
input [1:0]addr;
input [7:0]in;
output [7:0]out;
input rd,wr,cs,clk;
inout miso,mosi,sclk;
reg [7:0]out;
reg sclk_buffer = 0;
reg mosi_buffer = 0;
reg busy = 0;
reg [7:0] in_buffer = 0;
reg [7:0] out_buffer = 0;
reg [7:0] clkcount = 0;
reg [7:0] clkdiv = 0;
reg [4:0] count = 0;
always @(cs or rd or addr or out_buffer or clkdiv or busy)
begin//read
out=8'bx;
if(cs&&rd)
begin
case(addr)
2'b00:out=out_buffer;
2'b01:out={7'b0,busy};
2'b10:out=clkdiv;
endcase
end
end
always @ (posedge clk)
begin
if(!busy)
begin
if(cs&&wr)
begin
case(addr)
2'b00:
begin
in_buffer=in;
busy=1;
end
2'b10:
begin
clkdiv=in;
end
endcase
end
end
else
begin
clkcount=clkcount+1;
if(clkcount>=clkdiv)
begin
clkcount=0;
if((count%2)==0)
begin
mosi_buffer=in_buffer[7];
in_buffer=in_buffer<<1;
end
if(count>0&&count<17)
begin
sclk_buffer=~sclk_buffer;
end
count=count+1;
if(count>17)
begin
count=0;
busy=0;
end
end
end
end
always@(posedge sclk_buffer)
begin
out_buffer = out_buffer << 1;
out_buffer[0] = miso;
end
assign sclk = sclk_buffer;
assign mosi = mosi_buffer;
Endmodule