FPGA作为从机与STM32进行SPI协议通信---Verilog实现


一.SPI协议简要介绍

SPI,是英语SerialPeripheralInterface的缩写,顾名思义就是串行外围设备接口。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。
SPI总线是Motorola公司推出的三线同步接口,同步串行3线方式进行通信:一条时钟线SCK,一条数据输入线MOSI,一条数据输出线MISO;用于CPU与各种外围器件进行全双工、同步串行通讯。SPI主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

SPI总线有四种工作方式(SP0,SP1,SP2,SP3),其中使用的最为广泛的是SPI0和SPI3方式。SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。

SPI主模块和与之通信的外设时钟相位和极性应该一致。

以下是SPI时序图:


主要讲解一下广泛使用的两种方式设置:

SPI0方式:CPOL=0,CPHA=0;SCK空闲状态为低电平,第一个跳变沿(上升沿)采样数据,无论对Master还是Slaver都是如此。

SPI3方式:CPOL=1,CPHA=1;SCK空闲状态为高电平,第二个跳变沿(上升沿采样数据,无论对Master还是Slaver都是如此。

其实对于SPI0和SPI1发送与接收数据,可以总结为一句话:上升沿采样数据,下降沿发送数据。全双工同时进行,当然,必须在CS拉低使能情况下。

二.FPGA作为Slaver实现SPI3方式与STM32通信

1.STM32方面:用库函数配置SPI1,设置CPOL=1,CPHA=1.

2.FPGA方面:

(1)通过边沿检测技术得出SCK上升沿与下降沿标志,用于下面状态机中的数据采样及发送。

(2)根据时序图,采用2个状态机分别在SCK上升沿实现数据采样,下降沿实现数据发送。无论是采样还是发送,都是高位在前,从Bit[7]到Bit[0],共8位数据。

(3)最后通过边沿检测技术得出数据采样完成标志,用于用户操作。

以下是SPI3的时序图:


.Verilog代码部分

测试工程代码:实现了STM32每隔200ms发送流水灯数据给FPGA,使FPGA系统板上的4个LED灯实现流水操作;同时,FPGA每隔1s发送计数数据给STM32,并在STM32系统板上的LCD屏出来,即:显示0-9循环计数。

但下面的代码只是SPI作为从机的驱动部分,包括SPI发送数据与接收数据。

/***********************************************************************
     ****************** name:SPI_Slaver_Driver **************
            ********** author:made by zzuxzt **********
     ****************** time:2014.4.29 **********************
***********************************************************************/
//use SPI 3 mode,CHOL = 1,CHAL = 1
module spi(input clk,
			  input rst_n,
			  input CS_N,
			  input SCK,
			  input MOSI,
			  input [7:0] txd_data,
			  output reg MISO,
			  output reg [7:0] rxd_data,
			  output rxd_flag);

//-------------------------capture the sck-----------------------------		  
reg sck_r0,sck_r1;
wire sck_n,sck_p;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			sck_r0 <= 1'b1;   //sck of the idle state is high 
			sck_r1 <= 1'b1;
		end
	else
		begin
			sck_r0 <= SCK;
			sck_r1 <= sck_r0;
		end
end

assign sck_n = (~sck_r0 & sck_r1)? 1'b1:1'b0;   //capture the sck negedge
assign sck_p = (~sck_r1 & sck_r0)? 1'b1:1'b0;   //capture the sck posedge

//-----------------------spi_slaver read data-------------------------------
reg rxd_flag_r;
reg [2:0] rxd_state;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			rxd_data <= 1'b0;
			rxd_flag_r <= 1'b0;
			rxd_state <= 1'b0;
		end
	else if(sck_p && !CS_N)   
		begin
			case(rxd_state)
				3'd0:begin
						rxd_data[7] <= MOSI;
						rxd_flag_r <= 1'b0;   //reset rxd_flag
						rxd_state <= 3'd1;
					  end
				3'd1:begin
						rxd_data[6] <= MOSI;
						rxd_state <= 3'd2;
					  end
				3'd2:begin
						rxd_data[5] <= MOSI;
						rxd_state <= 3'd3;
					  end
				3'd3:begin
						rxd_data[4] <= MOSI;
						rxd_state <= 3'd4;
					  end
				3'd4:begin
						rxd_data[3] <= MOSI;
						rxd_state <= 3'd5;
					  end
				3'd5:begin
						rxd_data[2] <= MOSI;
						rxd_state <= 3'd6;
					  end
				3'd6:begin
						rxd_data[1] <= MOSI;
						rxd_state <= 3'd7;
					  end
				3'd7:begin
						rxd_data[0] <= MOSI;
						rxd_flag_r <= 1'b1;  //set rxd_flag
						rxd_state <= 3'd0;
					  end
				default: ;
			endcase
		end
end


//--------------------capture spi_flag posedge--------------------------------
reg rxd_flag_r0,rxd_flag_r1;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			rxd_flag_r0 <= 1'b0;
			rxd_flag_r1 <= 1'b0;
		end
	else
		begin
			rxd_flag_r0 <= rxd_flag_r;
			rxd_flag_r1 <= rxd_flag_r0;
		end
end

assign rxd_flag = (~rxd_flag_r1 & rxd_flag_r0)? 1'b1:1'b0;   

//---------------------spi_slaver send data---------------------------
reg [2:0] txd_state;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			txd_state <= 1'b0;
		end
	else if(sck_n && !CS_N)
		begin
			case(txd_state)
				3'd0:begin
						MISO <= txd_data[7];
						txd_state <= 3'd1;
					  end
				3'd1:begin
						MISO <= txd_data[6];
						txd_state <= 3'd2;
					  end
				3'd2:begin
						MISO <= txd_data[5];
						txd_state <= 3'd3;
					  end
				3'd3:begin
						MISO <= txd_data[4];
						txd_state <= 3'd4;
					  end
				3'd4:begin
						MISO <= txd_data[3];
						txd_state <= 3'd5;
					  end
				3'd5:begin
						MISO <= txd_data[2];
						txd_state <= 3'd6;
					  end
				3'd6:begin
						MISO <= txd_data[1];
						txd_state <= 3'd7;
					  end
				3'd7:begin
						MISO <= txd_data[0];
						txd_state <= 3'd0;
					  end
				default: ;
			endcase
		end
end

endmodule



六.Modelsim仿真图

优质内容筛选与推荐>>
1、论女程序员的重要性
2、小心!黑客组织KovCoreG正在利用虚假的浏览器和Flash更新来传播恶意软件
3、欢迎使用开源的代码生成器Code-Builder
4、深度学习识别皮肤癌媲美医生,智能手机检测癌症将不再遥远
5、一个女程序员的情感历程


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号