FPGA基础之SPI通信

本文主要描述SPI在FPGA上的应用实现,描述SPI时序在FPGA上的具体实现方式
SPI协议
SPI(Serial Peripheral Interface,串行外围设备接口),是Motorola公司提出的一种同步串行接口技术,三线或四线接口,收发独立。在使用中常用四线接口,SCLK决定了SPI协议的通信速率,一般在1~20M之间。SPI以主从模式进行通信,缺省数据应答,通常为一主一从或者一主多从的模式连接,连接示图如下:
在这里插入图片描述在这里插入图片描述
SPI协议因空闲状态的SCLK电平极性(CPOL)和时钟采集相位(CPHA)的不同,分成了四种模式,四种模式的时序如下图所示:
在这里插入图片描述
模式0:CPOL = 0,CPHA = 0。SCLK串行时钟在空闲是为低电平,数据再SCLK的上升沿采样,下降沿切换。
模式1:CPOL = 0,CPHA = 1。SCLK串行时钟在空闲是为低电平,数据再SCLK的下降沿采样,上升沿切换。
模式2:CPOL = 1,CPHA = 0。SCLK串行时钟在空闲是为高电平,数据再SCLK的上升沿采样,下降沿切换。
模式3:CPOL = 1,CPHA = 1。SCLK串行时钟在空闲是为高电平,数据再SCLK的下降沿采样,上升沿切换。
FPGA实现
下面以模式0为例,进行SPI协议在FPGA一主一从的实现。
首先是SPI协议的发送模块,此模块中,输入为系统时钟、复位、发送信号使能位、发送的数据(8bit)、SPI的主接口端口,输出为SPI的片选、SPI时钟以及SPI的主发送端,模型如下:
在这里插入图片描述
此模块中,当send信号来临,表示需要发送当前tx_data中的数据,此时cs信号拉低,并且通过一个时钟节拍,抓取cs信号的下降沿和上升沿,代码如下:

	reg tmp_cs;
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			spi_cs <= 1'b1;
		else if(send_en == 1'b1)
			spi_cs <= 1'b0;
		else if(send_cnt == 4'd8)
			spi_cs <= 1'b1;
		else
			spi_cs <= spi_cs;
	end
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			tmp_cs <= 1'b1;
		else
			tmp_cs <= spi_cs;
	end
	
	wire cs_rsing;
	wire cs_falling;
	assign cs_rsing = (spi_cs & (~tmp_cs));
	assign cs_falling = ((~spi_cs) & tmp_cs);

在完成cs的输出信号处理后,则需要根据实际需要,进行clk信号的发送,此模块的输入时钟需要高于SPI的发送时钟的4倍,便于能够稳定的输出clk时钟,此时计算出spi_clk与fclk的计数值,在根绝计数值,进行spi_clk的输出,代码实现如下:

	localparam CLK_HZ = 135_000_000;  //clk frequency;
	localparam SCK_HZ = 10_000_000;  //sck frequency;
	localparam DIV_NUMBER = CLK_HZ / SCK_HZ;
	localparam CNT_MAX = (DIV_NUMBER >>1) - 1'b1;

/**************生成SPI时钟*************/	
	reg [4:0]spi_div_cnt;
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			spi_div_cnt <= 4'd0;
		else if(spi_cs == 1'b1)
			spi_div_cnt <= CNT_MAX;
		else if(spi_div_cnt == CNT_MAX)
			spi_div_cnt <= 4'd0;
		else
			spi_div_cnt <= spi_div_cnt + 1'b1;
	end
	reg tmp_sck;
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			tmp_sck <= 1'b0;
		else if(spi_cs == 1'b1)
			tmp_sck <= 1'b0;
		else if(send_cnt == 4'd8)
			tmp_sck <= 1'b0;
		else if(spi_div_cnt == CNT_MAX)
			tmp_sck <= ~tmp_sck;
		else
			tmp_sck <= tmp_sck;
	end
		
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n) begin
			spi_clk <= 1'b0;
		end
		else begin
			spi_clk <= tmp_sck;
		end
	end

在完成clk时钟的输出后,需要进行时钟边沿判断,以此来进行数据的采集及变换,时钟边沿提取如下:

	wire sck_rsing;
	wire sck_falling;
	assign sck_rsing = (tmp_sck & (~spi_clk));
	assign sck_falling = ((~tmp_sck) & spi_clk);

完成spi_clk的边沿判断之后,则进行数据的发送,按照标准的单字节数据发送为例,当发送8个spi_clk时钟之后,则完成当前时钟的发送,此时发送计数模块如下:

	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			send_cnt <= 4'd0;
		else if(spi_cs == 1'b1)
			send_cnt <= 4'd0;
		else if(send_cnt == 4'd8)
			send_cnt <= 4'd0;
		else if(sck_falling == 1'b1)
			send_cnt <= send_cnt + 1'b1;
		else
			send_cnt <= send_cnt;
	end

最后,则是spi_mosi信号的输出,在spi协议中,mosi接口始终先发送tx_data的高位,再依次移位发送,在发送完成后,保持当前的电平,spi_mosi的发送模块如下:

	reg [7:0]tmp_tx_data;
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			tmp_tx_data <= 8'b0;
		else if(cs_falling == 1'b1)
			tmp_tx_data <= spi_tx_data;
		else if(sck_rsing == 1'b1)
			tmp_tx_data <= {tmp_tx_data[6:0],1'b0};
		else
			tmp_tx_data <= tmp_tx_data;
	end
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			spi_mosi <= 1'b1;
		else if(spi_cs == 1'b1)
			spi_mosi <= 1'b1;
		else if(cs_falling == 1'b1)
			spi_mosi <= spi_tx_data[7];
		else if(sck_falling == 1'b1)
			spi_mosi <= tmp_tx_data[7];
		else
			spi_mosi <= spi_mosi;
	end

以上,则是完成了SPI的发送模块在FPGA上的实现。接下来则是SPI的接收模块在FPGA上的实现。
在SPI的接收模块中,输入为系统时钟、复位、SPI片选、SPI的主发送基于SPI时钟,输出为SPI接收数据、SPI接收完成信号以及SPI的从发送端,模块模型如下:
在这里插入图片描述
在此模块中,输入的系统时钟依旧需要是SPI时钟的4倍频以上,才能够正常的抓取到spi_clk和spi_cs的边沿信号。首先需要抓取cs的边沿信号,代码如下:

	reg tmp_spi_cs;
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			tmp_spi_cs <= 1'b0;
		else
			tmp_spi_cs <= spi_cs;
	end
	
	wire spi_cs_falling;
	wire spi_cs_rising;
	reg tmp_spi_cs_rising;
	
	assign spi_cs_rising = spi_cs & (~tmp_spi_cs);
	assign spi_cs_falling = (~spi_cs) & tmp_spi_cs;
	
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			tmp_spi_cs_rising <= 1'b0;
		else
			tmp_spi_cs_rising <= spi_cs_rising;	
		end

之后,则是进行spi_clk信号的抓取:

	reg tmp_spi_clk;
	wire spi_clk_rising;
	wire spi_clk_falling;
	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			tmp_spi_clk <= 1'b0;
		else
			tmp_spi_clk <= spi_clk;
	end
	assign spi_clk_rising = spi_clk & (~tmp_spi_clk);
	assign spi_clk_falling = (~spi_clk) & tmp_spi_clk;

最后则是对spi_mosi信号的抓取,通过clk的边沿信号,进行rx_data的数据移位,代码如下:

	always@(posedge pclk or negedge rst_n)begin
		if(!rst_n)
			spi_rx_data <= 8'b0;
		else if(spi_cs ==1'b1)
			spi_rx_data <= spi_rx_data;
		else if(spi_clk_rising == 1'b1)
			spi_rx_data <= {spi_rx_data[6:0],spi_mosi};
		else
			spi_rx_data <= spi_rx_data;
	end

在此模块中,over信号则是完成了一字节数据接收之后,cs的上升沿信号作为接收完成的信号,代码如下:
assign transfer_over = tmp_spi_cs_rising;
在此模块中,未进行spi_miso的从发送信号的处理,如果从接收端需要反馈数据,则在slave模块中添加需要反馈的tx_data数据,处理代码如下:

	always@(posedge pclk or negedge rst_n)begin	
		if(!rst_n)
			spi_tx_shift <= 8'b0;
		else if(spi_cs_falling == 1'b1)
			spi_tx_shift <= spi_tx_data;
		else if(spi_clk_falling == 1'b1)
			spi_tx_shift <= {spi_tx_shift[6:0],1'b0};	//发送最高位
		else
			spi_tx_shift <= spi_tx_shift;
	end
	assign spi_miso = (spi_cs == 1'b0)?spi_tx_shift[7]:1'bz;	//

最后则是进行两个模块的通信仿真,仿真结果如下:
在这里插入图片描述

从上图可以看出,clk_135m表示内部倍频后的时钟,输入到spi的master和slave模块,作为模块的时钟,最后spi_rx_data表示为接受到的数据。可以看到,发送的数据为0x12,接受的数据也是0x12,在slave_transfor_over之后,可从模块中获取到spi接收到的数据。

  • 7
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值