目录
1 .介绍
2. SPI总线的主要特点
3. SPI总线工作方式
3.1 工作方式1(CPHA=0、CPOL=0)
3.2 工作方式2(CPHA=0、CPOL=1)
3.3 工作方式3(CPHA=1、CPOL=0)
3.4 工作方式4(CPHA=1、CPOL=1)
4 协议举例
SPI : serial peripheral interface,串行外围设备接口;是Motorola公司提出的一种同步串行接口技术。
应用 : SPI接口主要应用在EEPROM、FLASH、实时时钟(RTC)、ADC转换器,还有数字信号处理器和数字信号解码器之间,用于CPU与各种外围器件进行全双工、同步串行通讯.
对于点对点的通信, SPI不需要地址寻址,数据传输速度总体来说比I2C总线要快,速度可达到几Mbps,SPI通信的速度很容易达到好几兆bps,所以可以用SPI总线传输一些未压缩的音频以及压缩的视频。。
SPI的通信原理很简单,它以 主从方式 工作,这种模式 通常有一个主设备和一个或多个从设备 ,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)。
信号线:
SPI的连接:
1. 只有两个芯片利用SPI 总线通信时:
2.一主多从时:
SPI工作过程:
当SPI工作时,在SPI模块内部的移位寄存器中的数据逐位从输出引脚(MOSI)输出(高位在前),同时从输入引脚(MISO)接收的数据逐位移到移位寄存器(高位在前)。发送完一个字节后,从另一个外围器件接收的字节数据也将进入移位寄存器中。即完成一个字节数据传输的实质是两个器件寄存器内容的交换。
主机提供SPI的时钟信号(SCK)使传输同步。其典型系统框图如下图所示。
SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。(Interlaken协议中加入了control word)。
1、 采用主从模式(Master-Slave)的控制方式,支持单Master多Slave。SPI规定了两个SPI设备之间通信必须由主设备Master来控制从设备Slave。也就是说,如果FPGA是主机的情况下,不管是FPGA给芯片发送数据还是从芯片中接收数据,写Verilog逻辑的时候片选信号CS与串行时钟信号SCK必须由FPGA来产生。同时一个Master可以设置多个片选(Chip Select)来控制多个Slave。SPI协议还规定Slave设备的clock由Master通过SCK管脚提供给Slave,Slave本身不能产生或控制clock,没有clock则Slave不能正常工作。
2、 SPI总线在传输数据的同时也传输了时钟信号,所以SPI协议是一种同步(Synchronous)传输协议。Master会根据将要交换的数据产生相应的时钟脉冲,组成时钟信号,时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制两个SPI设备何时交换数据以及何时对接收数据进行采样,保证数据在两个设备之间是同步传输的。
3、 SPI总线协议是一种全双工的串行通信协议,数据传输时高位在前,低位在后。SPI协议规定一个SPI设备不能在数据通信过程中仅仅充当一个发送者(Transmitter)或者接受者(Receiver)。在片选信号CS为0的情况下,每个clock周期内,SPI设备都会发送并接收1 bit数据,相当于有1 bit数据被交换了。数据传输高位在前,低位在后(MSB first)。SPI主从结构内部数据传输示意图如下图所示:
在SPI接口中,有两个重要的寄存器决定其工作模式,分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)两个寄存器来定义,共有四种组合分配对应SPI总线的4种工作模式。
时序详解:
CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平
CPHA:时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样。
CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。
工作模式 | CPOL | CPHA | 描述 |
SPI Mode0 | 0 | 0 | 空闲时SCK为低电平,SPI在时钟的 |
SPI Mode1 | 0 | 1 | |
SPI Mode2 | 1 | 0 | |
SPI Mode3 | 1 | 1 |
这四种模式的时序图如下图所示:其中最常用的位SPI0与SPI3
详解:
数据线MOSI和MISO的数据切换(Toggling)位置和数据采样位置的关系图:
SPI总线工作在方式1,SCK串行时钟线空闲时为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换。
分析:该工作方式下,在空闲状态时SCK串行时钟线为低电平,一旦当SS被主机拉低以后,数据传输开始。MISO和MOSI 引脚上的数据在第一个SCK沿跳变之前已经上线了,数据线MOSI和MISO的数据的采样(Sampling)发生在数据的正中间(上图中的灰色实线),数据线MOSI和MISO的数据切换(Toggling)发生在时钟的下降沿(上图的黑色虚线), ,然后在同步时钟信号的上升沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(下降沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
下图清晰的描述了其他三种模式数据线MOSI和MISO的数据切换(Toggling)位置和数据采样位置的关系图
SPI总线工作在方式2。与前者唯一不同之处只是在同步时钟信号的下降沿时捕捉位信号,上升沿时下一位数据上线。
SPI总线工作在方式3。MISO引脚和MOSI引脚上的数据的MSB位必须与SPSCK的第一个边沿同步,在SPI传输过程中,在同步时钟信号周期开始时(上升沿)数据上线,然后在同步时钟信号的下降沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(上升沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
SPI总线工作在方式4。与前者唯一不同之处只是在同步时钟信号的上升沿时捕捉位信号,下降沿时下一位数据上线。
SPI是一个环形总线结构,由ss(cs)、sck、sdi(MISO)、sdo(MOSI)构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换。
假设下面的8位寄存器主机装的是待发送的数据10101010,从机装的是01010101,上升沿发送、下降沿接收、高位先发送。那么第一个上升沿来的时候 数据将会是sdo=1,sdi=0;主机寄存器=0101010x,从机寄存器1010101x。下降沿到来的时候,sdi上的电平将所存到主机寄存器中去,那么这时主机寄存器=0101010sdi=01010100,sdo上电平所存到从机寄存器此时从机寄存器=1010101sdo=10101011这样在 8个时钟脉冲以后,两个寄存器的内容互相交换一次。这样就完成里一个spi时序。 这样就完成了两个寄存器8位的交换,sdi、sdo相对于主机而言的。其中ss引脚作为主 机的时候,从机可以把它拉低被动选为从机,作为从机的是时候,可以作为片选脚用。根据以上分析,一个完整的传送周期是16位??,即两个字节,因为,首先主机要发送命令过去,然后从机根据主机的命令准备数据,主机在下一个8位时钟周期才把数据读回来
SPI Master接口
module spi_module
(
input I_clk , // 全局时钟50MHz
input I_rst_n , // 复位信号,低电平有效
input I_rx_en , // 读使能信号
input I_tx_en , // 发送使能信号
input [7:0] I_data_in , // 要发送的数据
output reg [7:0] O_data_out , // 接收到的数据
output reg O_tx_done , // 发送一个字节完毕标志位
output reg O_rx_done , // 接收一个字节完毕标志位
// 四线标准SPI信号定义
input I_spi_miso , // SPI串行输入,用来接收从机的数据
output reg O_spi_sck , // SPI时钟
output reg O_spi_cs , // SPI片选信号
output reg O_spi_mosi // SPI输出,用来给从机发送数据
);
reg [3:0] R_tx_state ;
reg [3:0] R_rx_state ;
always @(posedge I_clk or negedge I_rst_n)
begin
if(!I_rst_n)
begin
R_tx_state <= 4'd0 ;
R_rx_state <= 4'd0 ;
O_spi_cs <= 1'b1 ;
O_spi_sck <= 1'b0 ;
O_spi_mosi <= 1'b0 ;
O_tx_done <= 1'b0 ;
O_rx_done <= 1'b0 ;
O_data_out <= 8'd0 ;
end
else if(I_tx_en) // 发送使能信号打开的情况下
begin
O_spi_cs <= 1'b0 ; // 把片选CS拉低
case(R_tx_state)
4'd1, 4'd3 , 4'd5 , 4'd7 ,
4'd9, 4'd11, 4'd13, 4'd15 : //整合奇数状态
begin
O_spi_sck <= 1'b1 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd0: // 发送第7位
begin
O_spi_mosi <= I_data_in[7] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd2: // 发送第6位
begin
O_spi_mosi <= I_data_in[6] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd4: // 发送第5位
begin
O_spi_mosi <= I_data_in[5] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd6: // 发送第4位
begin
O_spi_mosi <= I_data_in[4] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd8: // 发送第3位
begin
O_spi_mosi <= I_data_in[3] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd10: // 发送第2位
begin
O_spi_mosi <= I_data_in[2] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd12: // 发送第1位
begin
O_spi_mosi <= I_data_in[1] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd14: // 发送第0位
begin
O_spi_mosi <= I_data_in[0] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b1 ;
end
default:R_tx_state <= 4'd0 ;
endcase
end
else if(I_rx_en) // 接收使能信号打开的情况下
begin
O_spi_cs <= 1'b0 ; // 拉低片选信号CS
case(R_rx_state)
4'd0, 4'd2 , 4'd4 , 4'd6 ,
4'd8, 4'd10, 4'd12, 4'd14 : //整合偶数状态
begin
O_spi_sck <= 1'b0 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
end
4'd1: // 接收第7位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[7] <= I_spi_miso ;
end
4'd3: // 接收第6位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[6] <= I_spi_miso ;
end
4'd5: // 接收第5位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[5] <= I_spi_miso ;
end
4'd7: // 接收第4位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[4] <= I_spi_miso ;
end
4'd9: // 接收第3位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[3] <= I_spi_miso ;
end
4'd11: // 接收第2位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[2] <= I_spi_miso ;
end
4'd13: // 接收第1位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[1] <= I_spi_miso ;
end
4'd15: // 接收第0位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b1 ;
O_data_out[0] <= I_spi_miso ;
end
default:R_rx_state <= 4'd0 ;
endcase
end
else
begin
R_tx_state <= 4'd0 ;
R_rx_state <= 4'd0 ;
O_tx_done <= 1'b0 ;
O_rx_done <= 1'b0 ;
O_spi_cs <= 1'b1 ;
O_spi_sck <= 1'b0 ;
O_spi_mosi <= 1'b0 ;
O_data_out <= 8'd0 ;
end
end
endmodule
SPI Slave 口:
//从机SPI接口 : Clk provided by Master
//工作方式 : CPOL=0 CPHA = 0 即SCK低电平空闲,第一个跳变沿采样
//数据传输 : MSB First
//通信数据宽 : 8bit
module spi_slave(nrst, clk, din,send_en,dout,ncs, mosi, miso, sck);
input clk ;
input nrst ;
input send_en ;
input [7:0] din ;
input [7:0] dout ;
//SPI slave口
input sck ; //
input ncs ;
input mosi ;
output miso ;
//==============================================================\
//*************************** Main Code **********************
//=============================================================/
//sck边沿检测:上升沿 、下降沿
reg[2:0] sck_edge;
always @ (posedge clk or negedge nrst)
begin
if(~nrst)
begin
sck_edge <= 3'b000;
end
else
begin
sck_edge <= {sck_edge[1:0], sck};
end
end
wire sck_rise_edge, sck_fall_edge;
assign sck_rise_edge = (sck_edge[2:1] == 2'b01); //检测到SCK由0变成1,则认为发现上跳沿
assign sck_fall_edge = (sck_edge[2:1] == 2'b10); //检测到SCK由1变成0,则认为发现下跳沿
// SPI从机 接收逻辑
reg[7:0] byte_received; //SPI内部数据接收寄存器,初始值为8'h00
reg[3:0] bit_received_cnt; //接收数据位计数器,统计接收数据
reg rec_flag;
reg[1:0] rec_status; //SPI接收部分状态机
reg[7:0] rec_data;
reg[2:0] rec_flag_width; //SPI接收完成标志位脉冲宽度寄存器
always @ (posedge clk or negedge nrst) //每次sck都会接收数据,spi的顶端模块状态机决定是否取用
begin
if(~nrst)
begin
byte_received <= 8'h00;
bit_received_cnt <= 4'h0;
rec_flag <= 1'b0;
rec_status <= 2'b00;
rec_flag_width <= 3'b000;
end
else
begin
if(~ncs) //从机被选中
begin
case (rec_status)
2'b00: begin
if(sck_rise_edge) //检测到上升沿 :SCK为低时为空闲状态,为高为忙碌状态
begin //每当sck上升沿,对MOSI采样
byte_received <= {byte_received[6:0], mosi}; //每来当上升沿到来,通过一个移位寄存器左移,采样的数据低位填充
if(bit_received_cnt == 4'h7)
begin
bit_received_cnt <= 4'b0000; // 数据位计数器完成对接收数据的统计
rec_status <= 2'b01; //只有将一个data数据位全部接收完成,才会状态跳转
end
else
begin
bit_received_cnt <= bit_received_cnt+1;
end
end
end
2'b01: begin //完成数据接收后
rec_data <= byte_received; //将以为寄存器内部数据取出
rec_flag <= 1'b1; //拉高接收完成标志位,通知4后续处理模块可以取rec_data
if(rec_flag_width==3'b100) begin
rec_flag_width <= 3'b000; //接收完成标志保持5个clk的cycle(注意,这里是SPI接收模块的工作时钟,而不是SPI接口传输时钟)
rec_status <= 2'b11; //进入下一状态
end
else begin
rec_flag_width <= rec_flag_width+1;
end
end
2'b11: begin
rec_flag <= 1'b0; //在上一状态rec_flag保持5个cycle脉宽后,拉低rec_flag
rec_status <= 2'b00; //回到空闲状态
end
default: rec_status <= 2'b00;
endcase
end
end
end
//SPI 从机发送模块
reg miso;
reg sending_flag; //正在发送标志位
reg[7:0] byte_sended; //发送移位寄存器,存放SPI待发送的数据
reg[3:0] bit_sended_cnt; //SPI发送位计数器
reg[1:0] send_status; //SPI发送部分状态机
always @ (posedge clk or negedge nrst)
begin
if(~nrst)
begin
byte_sended <= 8'h00;
bit_sended_cnt <= 4'b0000;
send_status <= 2'b00;
sending_flag <= 1'b0;
end
else
begin
if(~ncs) //从机被选中
begin
case (send_status)
2'b00: begin
if(send_en)
begin //锁存发送数据
byte_sended <= din ;
sending_flag <= 1'b1 ;
miso <= send_data[7] ;
send_status <= 2'b01 ; //2'b01;
end
end
2'b01: begin //发送数据移入移位寄存器
if(sck_rise_edge) begin
//miso <= byte_sended[7] ;
//byte_sended <= {byte_sended[6:0], 1'b0};
send_status <= 2'b11;
end
end
2'b11: begin //根据sck下降沿改变数据
miso <= byte_sended[7]; //高位先传
if(sck_fall_edge) ///---------------------------------------这里多移了一位
begin
//miso <= byte_sended[7];
byte_sended <= {byte_sended[6:0], 1'b0};
if(bit_sended_cnt == 4'b0111)
begin
send_status <= 2'b10 ;
bit_sended_cnt <= 4'b0000 ;
sending_flag <= 1'b0 ;
end
else
begin
bit_sended_cnt <= bit_sended_cnt+1;
end
end
end
2'b10: begin //数据发送完毕
send_status <= 2'b00;
//sending_flag <= 1'b0;
miso <= 1'b0;
end
endcase
end
end
end
endmodule