SPI总线学习

目录

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 协议举例


 

                                                 1 .介绍

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(片选)。

信号线:

  • CSK:串行时钟线(CSK);
  • MISO/SDI:主机输入/从机输出数据线;
  • MOSI/SDO:主机输出/从机输入数据线;
  • CS/SS:从设备选择信号,由主设备决定,低电平有效从机选择线(片选信号)。

SPI总线学习_第1张图片

 SPI的连接:

 1.  只有两个芯片利用SPI 总线通信时:

SPI总线学习_第2张图片

  2.一主多从时:

SPI总线学习_第3张图片

SPI工作过程:

        当SPI工作时,在SPI模块内部的移位寄存器中的数据逐位从输出引脚(MOSI)输出(高位在前),同时从输入引脚(MISO)接收的数据逐位移到移位寄存器(高位在前)。发送完一个字节后,从另一个外围器件接收的字节数据也将进入移位寄存器中。即完成一个字节数据传输的实质是两个器件寄存器内容的交换。

主机提供SPI的时钟信号(SCK)使传输同步。其典型系统框图如下图所示。

SPI总线学习_第4张图片

 

 

2. SPI总线的主要特点

  • 全双工;
  • 可以当作主机或从机工作;
  • 提供频率可编程时钟;
  • 发送结束中断标志;
  • 写冲突保护;
  • 总线竞争保护等。

 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总线学习_第5张图片

 

3. 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

 

SPI总线学习_第6张图片

 

SPI总线学习_第7张图片

详解:

SPI总线学习_第8张图片

数据线MOSI和MISO的数据切换(Toggling)位置和数据采样位置的关系图:

SPI总线学习_第9张图片

 

3.1 工作方式1(CPHA=0、CPOL=0)

    SPI总线工作在方式1,SCK串行时钟线空闲时为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换

SPI总线学习_第10张图片

     分析:该工作方式下,在空闲状态时SCK串行时钟线为低电平,一旦当SS被主机拉低以后,数据传输开始。MISO和MOSI 引脚上的数据在第一个SCK沿跳变之前已经上线了,数据线MOSI和MISO的数据的采样(Sampling)发生在数据的正中间(上图中的灰色实线)数据线MOSI和MISO的数据切换(Toggling)发生在时钟的下降沿(上图的黑色虚线),       ,然后在同步时钟信号的上升沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(下降沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。

下图清晰的描述了其他三种模式数据线MOSI和MISO的数据切换(Toggling)位置和数据采样位置的关系图

3.2 工作方式2(CPHA=0、CPOL=1)

     SPI总线工作在方式2。与前者唯一不同之处只是在同步时钟信号的下降沿时捕捉位信号,上升沿时下一位数据上线。

3.3 工作方式3(CPHA=1、CPOL=0)

     SPI总线工作在方式3。MISO引脚和MOSI引脚上的数据的MSB位必须与SPSCK的第一个边沿同步,在SPI传输过程中,在同步时钟信号周期开始时(上升沿)数据上线,然后在同步时钟信号的下降沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(上升沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。

3.4 工作方式4(CPHA=1、CPOL=1)

     SPI总线工作在方式4。与前者唯一不同之处只是在同步时钟信号的上升沿时捕捉位信号,下降沿时下一位数据上线。

 

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总线学习_第11张图片

 

SPI总线学习_第12张图片

Verilog设计

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 

SPI总线学习_第13张图片

你可能感兴趣的:(协议)