通信协议-SPI理解及Verilog实现

一、简介

SPI(Serial Peripheral Interface)串行外设接口,是一种高速、全双工、同步的串行通信总线。SPI最初由Motorola在2000年提出,Motorola所定义的SPI标准并未被任何国际委员会定义为统一标准,但业界以此为基础广泛引用成为事实标准。不过不同半导体公司的实施细节可能有所不同,导致业界没有统一的SPI标准,这些区别体现在寄存器设置、信号定义、数据格式等,具体应用需要参考特定器件手册。

因此后文在尽量全面介绍该协议的同时,主要对最常用的方案进行总结。

SPI以主从方式工作,一般存在一个主设备和多个从设备,主设备通过选择不同的从设备进行数据传输和接收。SPI通常用于EEPROM,FLASH,AD转换器等芯片进行数据交互。

SPI通信协议的基本原理是利用时钟信号的同步作用,实现数据在多个设备之间的传递,主设备通过时钟线来规定数据的传输数据,并利用数据线进行数据的传输。。SPI通信一般由四根线或者五根线组成,包括时钟线(CLK)、数据线(MOSI或MISO)、主机选择线(SS或Chip Select, CS)与从机选择线(Chip Enable, CE),常用方案为四根线,主机选择线(CS)也用于从机使能(CE)。

图1-1 motorala SPI Block Diagram

二、基本协议

SPI以主从方式工作,一般存在一个主设备和多个从设备,需要至少4根信号线,事实上3根也可以(单向传输时)。四根信号线为:

(1)SCLK - Serial CLK,时钟信号,由主设备产生;

(2)CS - Chip Select,片选/从设备使能信号,由主设备控制;

(3)MOSI - Master Out Slave In,主设备数据输出,从设备数据输入;

(4)MISO - Master In Slave Out,主设备输入,从设备输出。

SPI通信过程中,CS信号首先使能(高电平或者低电平,根据从机datasheet而定)指定的从设备,然后MOSI和MISO(MSB或者LSB发送先后,传输字的字长,根据从机datasheet而定)基于SCLK信号(极性和相位根据从机datasheet而定)进行数据传输(交换);数据传输时,一个字节传输完后无需应答即可开始下一个字节的传送;CS信号变为非使能状态后,传输结束。注:常用方案为CS低电平使能,MSB先发送,8bit或16bit。

SCLK是SPI数据传输的基础,因此其非常重要,SCLK信号只有主设备进行控制,从设备不能控制SCLK信号线。SPI数据传输方式与SCLK的极性(CPOL)和相位(CPHA)两个因素有关:CPOL表示SCLK空闲时候的状态,CPOL为0表示空闲时SCLK为低电平,CPOL为1表示空闲时SCLK为高电平;CPHA表示传输采样时刻,CPHA为0表示每个周期的第一个时钟沿采样,CPHA为1表示每个周期的第二个时钟沿采样。根据SCLK两种不同因素的组合,SPI总线分成下图2-1四种不同的工作模式(mode),其黑色代表clk信号,其初始状态几位clk的空闲状态,红色线则代表数据采样的时刻。SPI配置模式则有00(0),01(1),10(2),11(3)四种情况,最常用的时Mode0和mode3。对于一个特定的从设备来说,一般在出场时就会将其设计为某种特定的工作模式;我们在使用该从设备的时候,需要对主设备的时钟极性和相位进行配置,使其与从设备的工作模式保持一致,否则无法进行通信。

图2-1 SPI 传输模式

图2-2为motorola SPI Block Guide  V03.06中CHPA=0的时序图,其片选信号先于时钟信号半个周期有效,片选后数据立即出现在MOSI/MISO管脚,数据在第一个时钟边沿采样(锁存),在第二个时钟边沿,将锁存的数据写入到移位寄存器中,经过16个时钟边沿后,MOSI数据线立即失效,MISO继续有效至低有效片选信号拉高,8位数据全部写入移位寄存器内,完成主从设备数据交换。个人理解这种方式更适合主设备向从设备写入数据。

注意图2-2中的三个时间要求:

minimum leading time:片选信号有效沿到第一个时钟沿的最小时间间隔,我理解为此处是为了满足触发器的建立时间(寄存器的写入时间);

minimum trailing time:数据传输完成时钟沿到片选信号恢复空闲状态边沿的最小时间间隔,我理解此处时为了满足出发其的保持时间;

minimum ilding time:两次传输之间时钟处于空闲状态的最小时间。

图2-2 CHPA = 0

图2-3为motorola SPI Block Guide  V03.06中CHPA=1的时序图,其片选信号先于时钟信号半个周期有效,片选后MISO数据立即有效,MOSI数据在第一个时钟边沿有效,数据在第一个时钟边沿传输,在第二个时钟边沿,将对数据采样,经过16个时钟边沿后,8位数据全部写入移位寄存器内,完成主从设备数据交换。个人理解这种方式更适合主设备读从设备数据。

图2-3 CHPA = 1

三、SPI通信实现方案

3.1  一主一从

图3-1 一主一从连接

3.2  一主多从(主设备多位片选信号)

每个从设备都需要单独的片选信号,主设备每次只能选择其中一个从设备进行通信。因为所有从设备的SCK、MOSI、MISO都是连在一起的,未被选中从设备的MISO要表现为高阻状态(Hi-Z)以避免数据传输错误。由于每个设备都需要单独的片选信号,如果需要的片选信号过多,可以使用译码器产生所有的片选信号。

图3-2 一主多从连接

3.3  一主多从(主设备一位片选信号,菊花链)

数据信号经过主从设备所有的移位寄存器构成闭环。数据通过主设备发送(绿色线)经过从设备返回(蓝色线)到主设备。在这种方式下,片选和时钟同时接到所有从设备,通常用于移位寄存器和LED驱动器。注意,菊花链方式的主设备需要发送足够长的数据以确保数据送达到所有从设备。切记主设备所发送的第一个数据需(移位)到达菊花链中最后一个从设备。

菊花链式连接常用于仅需主设备发送数据而不需要接收返回数据的场合,如LED驱动器。在这种应用下,主设备MISO可以不连。如果需要接收从设备的返回数据,则需要连接主设备的MISO形成闭环。同样地,切记要发送足够多的接收指令以确保数据(移位)送达主设备。

图3-3 菊花链连接

四、Verilog实现 SPI mode0 master

4.1 时钟产生模块


module sclk_gen
    #(
	parameter EVEN_DIV_CNT_VALUE = 4
	)
	(
	input		sys_clk,
	input		rst_n,
	input		clkgen_en,
	output reg  spi_clk,
	output    	spi_neg_clk
	);
	reg	[log2(EVEN_DIV_CNT_VALUE-1):0]	even_div_cnt;
	
    reg   clkgen_en_delay1;
    reg   clkgen_en_delay2;
    
    always@(posedge sys_clk )begin
        clkgen_en_delay1<= clkgen_en;
        clkgen_en_delay2<= clkgen_en_delay1;
	end
	always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n)
			even_div_cnt <= { log2(EVEN_DIV_CNT_VALUE){1'b0}};
		else if(clkgen_en == 1'b1)
			begin
				if(even_div_cnt == (EVEN_DIV_CNT_VALUE-1))
					even_div_cnt <= { log2(EVEN_DIV_CNT_VALUE){1'b0}};
				else
					even_div_cnt <= even_div_cnt + 1;
			end
		else
			even_div_cnt <= { log2(EVEN_DIV_CNT_VALUE){1'b0}};
	end
	

	always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n)
			spi_clk <= 1'b0;
		else if(even_div_cnt == (EVEN_DIV_CNT_VALUE-1))
			spi_clk <= ~spi_clk;
		else
			;
	end 
	

	assign spi_neg_clk =(clkgen_en /*|| clkgen_en_delay2*/)? ~spi_clk:1'b0;  
	
	function integer log2;
		input  [31:0] size;
		integer		  i;
		begin
			log2 = 1;
			for(i=0;2**i < size;i=i+1)
				log2 = i+1;
		end 	
	endfunction
	
endmodule

4.2 master 实现


///
//Function : implemention of spi mode 0 
//Authour:   liuchj
//Description: SPI (Serial Peripheral Interface) Master
//             support: 
//                    1. single chip-select capability,
//                    2. use SPI mode0
//                    3. arbitrary length byte transfers.
//                    //4. Big_Endian or Little_Endian select
//Parameters:  CLK_DIV_CNT_VALUE - set the value of the SPI clock generate counter  
//             DATA_WIDTH - input data,output data and trans data length
//                   
// Note:       1. If multiple CS signals are needed, will need to use different module, 
//                OR multiplex the CS from this at a higher level.
//             2. SPI CLK is even divided by sys_clk
// version: 1.0            
///

module spi_master
    #(
	  parameter CLK_DIV_CNT_VALUE = 2,
	  parameter DATA_WIDTH = 8
	 )
	(
	 input      				    sys_clk,
	 input 						    rst_n,
	 input      				    spi_start,
	 input      [DATA_WIDTH-1:0]  	data_in,
	 output reg [DATA_WIDTH-1:0]  	data_recv,
	 output	reg				        data_recv_vld,
	   
	 output                     spi_clk,
     input                      spi_miso,
     output     reg             spi_mosi,
     output     reg             spi_csn
			    
	 
	);
	
	parameter IDLE  = 2'd0;
	parameter START = 2'd1;
	parameter TRANS = 2'd2;
	parameter END   = 2'd3;
	 
    reg    [1:0]   c_state;
    reg	   [1:0]   n_state;
	wire 	       idle2start_flag ;
	wire	       trans2end_flag;
	wire	       end2idle_flag;
	
	reg    clkgen_en;
	 
	 
    //--------------- spi_clk generate ---------------//
always@(posedge sys_clk or negedge rst_n)begin
    if(!rst_n)
        clkgen_en<=1'b0;
    else if(c_state == END)
        clkgen_en <= 1'b0;
    else if(c_state == START)
        clkgen_en <= 1'b1;
end


	wire		spi_neg_clk; 
    sclk_gen 
		#(
		.EVEN_DIV_CNT_VALUE(CLK_DIV_CNT_VALUE)
		)
		u0_sclk_gen(
		.sys_clk(sys_clk),
		.rst_n(rst_n),
		.clkgen_en(clkgen_en),
		.spi_clk(spi_clk),
		.spi_neg_clk(spi_neg_clk)
		);

     //-------------- spi master state machine ------------//
    always@(posedge sys_clk or negedge rst_n)begin
	    if(!rst_n)
	 	   c_state <= IDLE;
	    else
	 	   c_state <=n_state;
    end
   
   
    always@(*)begin
		case(c_state)
			IDLE:
				begin
					if(idle2start_flag == 1'b1 )
						n_state = START;
					else 
						n_state = c_state;		
				end
			START:
				begin
					if(1)
						n_state = TRANS;
					else 
						n_state = c_state;		
				end
			TRANS:
				begin
					if(trans2end_flag ==1'b1 )
						n_state = END;
					else 
						n_state = c_state;		
				end			
			END:
				begin
					if(end2idle_flag )
						n_state = IDLE; 
					else 
						n_state = c_state;		
				end			
			default: n_state = IDLE;
		endcase
    end
	

	reg 	spi_start_delay;
	reg [log2(DATA_WIDTH-1):0] trans_cnt;
	reg [log2(DATA_WIDTH-1):0] spiclk_cnt;
	always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n)
			spi_start_delay <= 1'b0;
		else
			spi_start_delay <= spi_start;
	end 
	assign idle2start_flag =  ((~spi_start) && spi_start_delay) && (c_state == IDLE);
	assign trans2end_flag = spiclk_cnt == 4'd9 && c_state == TRANS;
	assign end2idle_flag  =  spiclk_cnt == 4'd9 && (c_state == END);
	
	reg 	spi_busy;
	always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n)
			spi_busy <= 1'b0;
		else if(c_state != IDLE )
			spi_busy <= 1'b1;
		else
			spi_busy <= 1'b0;
	end
	   
	always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n)
			spi_csn <= 1'b1;
		else if(c_state == START)
			spi_csn <= 1'b0;	
		else if(c_state == END)
			spi_csn <= 1'b1;
	end	
	
	always@(posedge spi_neg_clk or negedge rst_n)begin
		if(!rst_n)
			trans_cnt <= {log2(DATA_WIDTH){1'b0}};	
		else if(c_state == TRANS && trans_cnt < DATA_WIDTH)
			trans_cnt <= trans_cnt + 1;	
		else if(trans_cnt == DATA_WIDTH )
			trans_cnt <= {log2(DATA_WIDTH){1'b0}};
	end	
	
	reg  spi_neg_clk_delay1;
	
   always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n)
			spi_neg_clk_delay1 <= 1'b0;	
    	else if(clkgen_en == 1 )
		   spi_neg_clk_delay1 <= spi_neg_clk;	
	end	
	
	
	
    always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n)
			spiclk_cnt <= {log2(DATA_WIDTH){1'b0}};	
    	else if(clkgen_en == 1)
	       begin
		   if( (~spi_neg_clk_delay1 && spi_neg_clk))
		        spiclk_cnt <= spiclk_cnt + 1;
		   end	
		 else
		    spiclk_cnt <= {log2(DATA_WIDTH){1'b0}};
	end	
	
	reg [DATA_WIDTH-1:0] spi_data;	
	always@(posedge sys_clk or negedge rst_n )begin	
		if(!rst_n)
			spi_data <= {DATA_WIDTH{1'b0}};
		else if(idle2start_flag)
		    spi_data <= data_in;
		else
			;
	end 
	

		
	always@(posedge spi_neg_clk or negedge rst_n )begin	
		if(!rst_n)
			spi_mosi <= 1'b0;
		else if(c_state == TRANS)
		    spi_mosi <= spi_data[DATA_WIDTH - trans_cnt -1] ;
		else
			spi_mosi <= 1'b0;
	end 
	
	
	always@(posedge spi_clk or negedge rst_n )begin	
		if(!rst_n)
			data_recv <= {log2(DATA_WIDTH){1'b0}};
		else if(c_state == TRANS)
		    data_recv <= {spi_miso,data_recv[DATA_WIDTH-1:1]};
	end 
	
	
	always@(posedge sys_clk or negedge rst_n )begin	
		if(!rst_n)
			data_recv_vld <= 1'b0;
		else if(c_state == START)
			data_recv_vld <= 1'b0;
		else if(c_state == END)
		    data_recv_vld <= 1'b1;

	end 
	
	
	
	
	function integer log2;
		input  [31:0] size;
		integer		  i;
		begin
			log2 = 1;
			for(i=0;2**i < size;i=i+1)
				log2 = i+1;
		end 
	
	endfunction

endmodule

4.3 testbench 

`timescale 1ns/1ps
module spi_master_tb;

reg 		sys_clk  	  ;
reg 		rst_n    	  ;
reg     	spi_start	  ;
reg [7:0]	data_in  	  ;

wire   		data_recv_vld ;
wire		spi_clk       ;
reg   		spi_miso      ;
wire   		spi_mosi      ;
wire   		spi_csn       ;

always
     #50 sys_clk = ~sys_clk; 

initial begin
	sys_clk = 1'b0;
	rst_n  = 1'b0;
	#100 rst_n = 1'b1;
end 

initial begin
	data_in = 8'h55;
end 


initial begin
	spi_start = 1'b0;
	#160 spi_start = 1'b1;
	#100 spi_start = 1'b0;
	#4500 spi_start = 1'b1;
	#100 spi_start = 1'b0;
end

initial begin
	spi_miso = 1;
	#4760 spi_miso = 0;
end 




 spi_master
    #(
	 .CLK_DIV_CNT_value(4), 
	 .DATA_WIDTH(8)
	 ) u0_spi_master
	(
	.sys_clk          ( sys_clk        )    ,
	.rst_n            ( rst_n       )       ,
	.spi_start        ( spi_start      )    ,
	.data_in          ( data_in        )    ,
	.data_recv_vld    ( data_recv_vld  )    ,
											
	.spi_clk          ( spi_clk        )    ,
    .spi_miso         ( spi_miso       )    ,
    .spi_mosi         ( spi_mosi       )    ,
    .spi_csn          ( spi_csn        )			    
	 
	);












endmodule

4.4 功能仿真(by vivado)

具体实现时可在相应的引脚进行上拉或者下拉约束以避免数据传完后空闲状态下的X态。

五、参考资料

1. SPI总线协议 – 第22条军规 (wangdali.net)

2. SPI协议详解(图文并茂+超详细) - 知乎 (zhihu.com)

3. SPI总线协议及SPI时序图详解 - Ady Lee - 博客园 (cnblogs.com)

4. 创客学院 经典总线协议相关视频

5.  Motorala SPI Block Guide V03.06 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值