FPGA_Verilog_SPI从机

1 SPI概述

SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200。

             FPGA_Verilog_SPI从机_第1张图片

    SCK(Serial Clock):SCK是串行时钟线,作用是Master向Slave传输时钟信号,控制数据交换的时机和速率;

    MOSI(Master Out Slave in):在SPI Master上也被称为Tx-channel,作用是SPI主机给SPI从机发送数据;

    CS/SS(Chip Select/Slave Select):作用是SPI Master选择与哪一个SPI Slave通信,低电平表示从机被选中(低电平有效);

    MISO(Master In Slave Out):在SPI Master上也被称为Rx-channel,作用是SPI主机接收SPI从机传输过来的数据;

             SPI协议的主要特点:

                                     1.采用主从模式(Master-Slave)的控制方式,支持单Master多Slave。SPI规定了两个SPI设备之间通信必须由主设备Master来控制从设备Slave。

                                      2.SPI总线在传输数据的同时也传输了时钟信号,所以SPI协议是一种同步(Synchronous)传输协议。

                                     3.  SPI总线协议是一种全双工的串行通信协议,数据传输时高位在前,低位在后。

2 SPI的工作模式

       在SPI中,主机可以选择时钟极性和时钟相位。在空闲状态期间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。CPHA位选择时钟相位。根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。

                           FPGA_Verilog_SPI从机_第2张图片

                                其中最常用的是模式3,本设计也是根据模式3编写。

3 Verilog程序

//极性特点:时钟空闲时为高电平
//在上升沿采样数据,获取MOSI的输入
//在下降沿发送数据,将数据发送到MISO
//sck的时钟要大大小于clk的时钟,至多为clk/8
module spi_slaver(
				clk,
				rst,
				cs,
				sck,
				MOSI,
				MISO,
				rxd_out,
				txd_data,
				rxd_flag //接受数据脉冲信号 接受到一个数据会产生一个正脉冲信号
				);
input clk,cs,rst,sck,MOSI;
output rxd_flag;
output reg MISO;
output reg[7:0] rxd_out;
reg[7:0] rxd_data;
input [7:0] txd_data;
reg sck_r0,sck_r1;
wire sck_n,sck_p;

always @(posedge clk or negedge rst)
begin
   if(!rst)
    begin
	   sck_r0<=1'b1;
	   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; 
assign sck_p = (~sck_r1 & sck_r0)? 1'b1:1'b0;
//-----------------------spi_slaver read data-------------------------------
reg rxd_flag_r;
reg [2:0] rxd_state;
always@(posedge clk or negedge rst)
begin
    if(!rst)
        begin
            rxd_data <= 1'b0;
            rxd_flag_r <= 1'b0;
            rxd_state <= 1'b0;
        end
    else if(sck_p && !cs)   
        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_out<={rxd_data[7:1],MOSI};
                        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)
begin
    if(!rst)
        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)
begin
    if(!rst)
        begin
            txd_state <= 3'd0;
			 MISO<=1'bz;
        end
    else if(sck_n && !cs)
        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

4 简单控制模块

/*测试spi从机用 每个接受一个8位的数据
   然后把接收到的数据加一在发送回去
*/

module spi_slaver_ctrl(
						clk,
						rst,
						cs,
						sck,
						MOSI,
						MISO,
						rxd_out,
						rxd_flag
						);
input clk,rst,cs,sck,MOSI;
output MISO,rxd_flag;
output [7:0] rxd_out;
reg [7:0] txd_dat;
wire rxd_flag;
reg [2:0] cnt;

always@(posedge rxd_flag or negedge rst)
begin
   if(!rst)
     //cnt <= 3'd0;
	 txd_dat <= 8'b11000011;
	else
	 begin
      txd_dat <= rxd_out + 1'b1; //把数据+1送到发送端
	 end
end

spi_slaver spi_slaver1(
				.clk(clk),
				.rst(rst),
				.cs(cs),
				.sck(sck),
				.MOSI(MOSI),
				.MISO(MISO),
				.rxd_out(rxd_out),
				.txd_data(txd_dat),
				.rxd_flag(rxd_flag)
				);

endmodule

       控制模块中,当接收到第一个数据时,把txd_dat的初始数据发回SPI主机,在接下来的通信中,没接收到一个数据,在下一次通信时把上一次接收到的数据+1,发回主机。

你可能感兴趣的:(FPGA)