从原理出发手撕SPI协议

SPI(Serial Peripheral interface,串行外围设备接口):

(1)简介:SPI由一个主设备(Master)和一个或多个从设备(Slave)组成。主设备通过启动与从设备的同步通信来交换数据。SPI的通信原理是以主从方式工作,一般有一个主设备和多个从设备,需要至少四根线,分别是MISO(主设备输入从设备输出)、MOSI(主设备输出从设备输入)、SCLK(时钟)、SS(片选)。当主设备要和某个从设备进行通信时,主设备需要先向对应从设备的片选线上发送使能信号(高电平或低电平)表示选中该从设备,这样通信才能开始。由于SPI使用引脚较少且布线方便,所以越来越多的芯片集成了这种通信协议。

(2)读写机制:主机发送一个数据必然接收一个数据,发送数据忽略接收数据(写din忽略dout),发送空字节接收数据(写0接收dout)

(3)步骤:

1.主设备发起信号,将片选SS拉低,选择从设备,启动通信。

2.主设备通过发送时钟信号,告诉从设备进行写数据或者读数据操作。具体的采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低),具体取决于SPI的模式。

3.主设备将要发送的数据写到发送数据缓存区,然后通过MOSI信号线一位一位地将数据移出去传送给从设备。同时,MISO接口接收到的数据经过移位寄存器一位一位地移到接收缓存区。

4.如果需要响应,从设备可以通过MISO线路将数据返回给主设备。

(4)确定采样边沿:

1.时钟极性(CPOL)定义了时钟空闲状态电平(先确认有效状态是高电平还是低电平):

CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平。

CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平。

2.时钟相位(CPHA)定义数据的采集时间(后确认采样边沿和发送边沿):

CPHA=0,在时钟有效态的第一个跳变沿进行数据采样。在第二个边沿改变数据。

CPHA=1,在时钟有效态的第二个跳变沿进行数据采样。在第一个边沿改变数据。

(5)模块:

spi_master模块:生成spi时钟、提取数据位宽、三段式状态机、4-16译码器、移位寄存器

`timescale	1ns/1ns
module spi_master#(

	parameter			DATA_WIDTH	=	8,
	parameter			SLAVE_COUNT	=	15,
	parameter			CPOL		=	0,
	parameter			CPHA		=	0


)(

	input									i_clk,
	input									i_rst_n,
						
	input									i_miso,
	input									i_valid,
	input									i_ready,
	input	[DATA_WIDTH-1:0]				i_din,
	input	[log2(SLAVE_COUNT-1)-1:0]			i_ss_n,
	
	output									o_mosi,
	output									o_finish,
	output	[DATA_WIDTH-1:0]				o_dout,
	output	[SLAVE_COUNT-1:0]					o_ss_n,
	output									o_run,
	
	output									o_spi_clk

);

/*********************************reg*********************************/
reg									r_spi_cnt;
reg	[log2(DATA_WIDTH-1)-1:0]		r_data_cnt;
reg	[DATA_WIDTH-1:0]				r_din;
reg	[DATA_WIDTH-1:0]				r_dout;
reg	[log2(SLAVE_COUNT-1)-1:0]			r_ss_n;
reg									r_run;
reg	[1:0]							cur_state;
reg	[1:0]							next_state;
reg									r_st_done;
reg									ro_finish;
reg	[SLAVE_COUNT-1:0]					ro_ss_n;
reg	[DATA_WIDTH-1:0]				ro_dout;	

/********************************wire********************************/
wire								w_start;

/*******************************function*******************************/
//the function to get the width of data 
function integer log2(input integer v);
  begin
	log2=0;
	while(v>>log2) 
	  log2=log2+1;
  end
endfunction

/******************************parameter******************************/
parameter		IDLE	=	0;
parameter		LOAD	=	1;
parameter		SHIFT	=	2;
parameter		DONE	=	3;

/*********************************port*********************************/


/*******************************machine*******************************/


always@(posedge o_spi_clk or negedge i_rst_n)
	if(i_rst_n == 1'b0)
		cur_state	<=	IDLE;
	else
		cur_state	<=	next_state;

always@(*)begin
	next_state	=	IDLE;
	case(cur_state)
		IDLE:		
			if(w_start)	
				next_state	=	LOAD;
			else
				next_state	=	IDLE;
		LOAD:
				next_state	=	SHIFT;				
		SHIFT:		
			if(r_st_done)	
				next_state	=	DONE;
			else
				next_state	=	SHIFT;
		DONE:	
				next_state	=	IDLE;			
	endcase
end
	
always@(posedge o_spi_clk or negedge i_rst_n)
	if(i_rst_n == 1'b0)begin
		r_st_done	<=	1'b0;
		ro_finish	<=	1'b0;
		r_ss_n		<=	'b1;
		r_din		<=	'b0;
		r_run		<=	1'b0;
	end
	else
		case(cur_state)
			IDLE:begin
				r_st_done	<=	1'b0;
				ro_finish	<=	1'b0;
				r_ss_n		<=	'b1;
				r_din		<=	'b0;
				r_run		<=	1'b0;		
			end
			LOAD:begin
				r_ss_n		<=	i_ss_n;
				r_din		<=	i_din;
				r_run		<=	1'b1;
			end
			SHIFT:
				//计数器为7时还没操作完成,还要再做一次移位操作
				if(r_data_cnt==7 && r_run)begin
					r_st_done	<=	1'b1;
					r_run		<=	1'b0;
					r_din		<=	{r_din[6:0],i_miso};
				end
				else if(r_data_cnt!=7 && r_run)begin
					r_st_done	<=	1'b0;
					r_run		<=	1'b1;
					r_din		<=	{r_din[6:0],i_miso};
				end
				else begin
					r_st_done	<=	r_st_done;
					r_run		<=	r_run;
					r_din		<=	r_din;
				end				
			DONE:begin
					r_st_done	<=	1'b0;
					ro_finish	<=	1'b1;
					r_ss_n		<=	'b1;
					r_dout		<=	r_din;
					r_run		<=	1'b0;
			end
			default:begin
					r_st_done	<=	1'b0;
					ro_finish	<=	1'b0;
					r_ss_n		<=	'b1;
					r_run		<=	1'b0;
			end
		endcase
		

/*****************************component*****************************/


/*******************************assign*******************************/
assign			o_spi_clk = r_spi_cnt ? 1'b1 : 1'b0;
assign			w_start	=	i_valid && i_ready;
assign			o_mosi	=	r_run ? r_din[7]:1'b0;
assign			o_dout	=	ro_dout;
assign			o_ss_n	=	ro_ss_n;
assign			o_finish=	ro_finish;
assign			o_run	=	r_run;
/*******************************always*******************************/
always@(posedge i_clk or negedge i_rst_n)
	if(i_rst_n == 1'b0)
		r_spi_cnt	<=	1'b0;
	else
		r_spi_cnt	<=	r_spi_cnt + 1'b1;

//4-16译码器
always@(*)
	case(r_ss_n)
		4'b0000:ro_ss_n=16'b1111_1111_1111_1110;
		4'b0001:ro_ss_n=16'b1111_1111_1111_1101;
		4'b0010:ro_ss_n=16'b1111_1111_1111_1011;
		4'b0011:ro_ss_n=16'b1111_1111_1111_0111;
		4'b0100:ro_ss_n=16'b1111_1111_1110_1111;
		4'b0101:ro_ss_n=16'b1111_1111_1101_1111;
		4'b0110:ro_ss_n=16'b1111_1111_1011_1111;
		4'b0111:ro_ss_n=16'b1111_1111_0111_1111;
		4'b1000:ro_ss_n=16'b1111_1110_1111_1111;
		4'b1001:ro_ss_n=16'b1111_1101_1111_1111;
		4'b1010:ro_ss_n=16'b1111_1011_1111_1111;
		4'b1011:ro_ss_n=16'b1111_0111_1111_1111;
		4'b1100:ro_ss_n=16'b1110_1111_1111_1111;
		4'b1101:ro_ss_n=16'b1101_1111_1111_1111;
		4'b1110:ro_ss_n=16'b1011_1111_1111_1111;
		4'b1111:ro_ss_n=16'b1111_1111_1111_1111;
		default:ro_ss_n=16'b1111_1111_1111_1111;
	endcase

always@(posedge o_spi_clk or negedge i_rst_n)
	if(i_rst_n == 1'b0)
		r_data_cnt	<=	'b0;
	else
		if(r_run)
			if(r_data_cnt==7)
				r_data_cnt	<=	'b0;	
			else
				r_data_cnt	<=	r_data_cnt + 1'b1;
		else
			r_data_cnt	<=	'b0;

always@(posedge o_spi_clk or negedge i_rst_n)
	if(i_rst_n == 1'b0)
		r_dout	<=	'b0;
	else
		if(ro_finish)
			ro_dout	<=	r_dout;
		else
			ro_dout	<=	'b0;


endmodule




spi_slave模块:略

module spi_slave#(

	parameter			DATA_WIDTH	 	=	8		,
	parameter			SLAVE_NUM		=	1		,
	parameter			CPOL			=	0		,
	parameter			CPHA			=	0		

)(

	input								i_spi_clk	,
	input								i_rst_n		,
	
	input								i_ss_n		,
	input								i_mosi		,
	input								i_run		,

	output								o_miso		
);


/*********************************reg*********************************/
reg	[log2(DATA_WIDTH-1)-1:0]		r_data_cnt	;
reg	[DATA_WIDTH-1 : 0]				r_dout		;

/********************************wire********************************/


/*******************************function*******************************/
//the function to get the width of data 
function integer log2(input integer v);
  begin
	log2=0;
	while(v>>log2) 
	  log2=log2+1;
  end
endfunction

/******************************parameter******************************/

/*********************************port*********************************/


/*******************************machine*******************************/


/*****************************component*****************************/


/*******************************assign*******************************/
assign				o_miso	=	r_dout[7];

/*******************************always*******************************/
always@(posedge i_spi_clk or negedge i_rst_n)
	if(i_rst_n == 1'b0)
		r_data_cnt	<=	'b0;
	else
		if(i_run && (!i_ss_n))
			if(r_data_cnt==7)
				r_data_cnt	<=	'b0;	
			else
				r_data_cnt	<=	r_data_cnt + 1'b1;
		else
			r_data_cnt	<=	'b0;

always@(posedge i_spi_clk or negedge i_rst_n)
	if(i_rst_n == 1'b0)
		r_dout	<=	'b0;
	else
		if(i_run && (!i_ss_n))
			r_dout	=	{r_dout[6:0],i_mosi};
		else
			r_dout	<=	r_dout;

endmodule

(6)学到的知识:

FPGA开发流程:了解项目需求功能→大致框图(模块的作用、模块之间的数据流、visio)→时序图(wavedorm、timegen)→状态图(如果需要、visio)。误区1:信号没定义全、框图不完整、时序图不完整,不敢下手写。把功能了解清楚,就可以开始写了,写到某些模块有思路了再进行补充。误区2:信号随便定义。信号定义的好坏对顶层模块的分析会有很大影响。

SPI协议的优点包括:

全双工串行通信,高速数据传输速率,简单的软件配置,及其灵活的数据传输,不限于8位,它可以是任意大小的字,非常简单的硬件结构,从站不需要唯一地址(与IIC不同),从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同),不需要收发器(与CAN不同)。

SPI协议的缺点包括:

没有硬件从机应答信号(主机可能在不知情的情况下无处发送)。

通常仅支持一个主设备。

与RS-232与CAN总线相比,只能支持非常短的距离。

SPI从机设备没有硬件流控,只能通过主机自主地延迟下个时钟周期到来的时间。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值