用FPGA实现SPI通信

SPI是串行外设接口(Serial Peripheral Interface)的缩写。它是Motorola公司推出的一种高速、全双工的同步串行通信总线技术。
SPI的通信接口为:SDI或MISO(数据输入),SDO或MOSI(数据输出),SCK(时钟),CS(片选)。
另外,可以通过设置CPOL(时钟极性)和CPHA(时钟相位)来配置主控设备的通信模式。其中时钟极性CPOL用来配置SCLK的空闲态或者有效态电平,时钟相位CPHA 用来配置数据采样是在第几个边沿:
CPOL=0,表示SCLK的空闲态电平为0,有效态电平为1;
CPOL=1,表示SCLK的空闲态电平为1,有效态电平为0;
CPHA=0,表示在时钟的第一个边沿进行数据采集,时钟的第二个边沿进行数据发送;
CPHA=1,表示在时钟的第二个边沿进行数据采集,时钟的第一个边沿进行数据发送。

特别说明:以下所涉及代码参考网络、相关开发板厂家。此处进行了注解。

spi_master.v

module spi_master

(

    input                       sys_clk,                //系统时钟

    input                       rst,                        //复位

    output                      nCS,       //SPI 片选

    output                      DCLK,      //SPI时钟

    output                      MOSI,      //SPI输出

    input                       MISO,      //SPI 输入

    input                       CPOL,    //时钟极性

    input                       CPHA,     //时钟相位

    input                       nCS_ctrl,   //片选控制

    input[15:0]                 clk_div,      //时钟分频

    input                       wr_req,    //读写请求

    output                      wr_ack,    //读写应答

    input[7:0]                  data_in,     //数据输入

    output[7:0]                 data_out         //暑假输出

);

localparam                   IDLE            = 0;

localparam                   DCLK_EDGE       = 1;

localparam                   DCLK_IDLE       = 2;

localparam                   ACK             = 3;

localparam                   LAST_HALF_CYCLE = 4;

localparam                   ACK_WAIT        = 5;

reg                          DCLK_reg;

reg[7:0]                     MOSI_shift;

reg[7:0]                     MISO_shift;

reg[2:0]                     state;

reg[2:0]                     next_state;

reg[15:0]                   clk_cnt;

reg[4:0]                     clk_edge_cnt;

assign   MOSI = MOSI_shift[7];

assign   DCLK = DCLK_reg;

assign  data_out = MISO_shift;

assign wr_ack = (state == ACK);

assign nCS = nCS_ctrl;

//说明:以下代码实现状态机状态更新

always@(posedge sys_clk or posedge rst)

begin

   	 if(rst)

            	 state <= IDLE;

    	else

            	 state <= next_state;

end

//状态机

always@(*)

begin

    	case(state)

             IDLE:

                     if(wr_req == 1'b1)  //如果有读写请求,进入时钟空闲态

                              next_state <= DCLK_IDLE;

                     else

                              next_state <= IDLE;

             DCLK_IDLE:

                     //half a SPI clockcycle produces a clock edge

                     if(clk_cnt == clk_div)//在时钟空闲态中,如果分频时钟满,则进入时钟采集态

                              next_state <= DCLK_EDGE;

                     else

                              next_state <= DCLK_IDLE;

             DCLK_EDGE:

                     //a SPI byte with a total of 16 clock edges

                     if(clk_edge_cnt ==5'd15) //在时钟采集态,如果时钟沿达到15个,则进入最后半个时钟沿(因为8位数据,需要8个脉冲即16个时钟沿)

                              next_state <= LAST_HALF_CYCLE;

                     else

                              next_state <= DCLK_IDLE;

             //this is the last data edge

             LAST_HALF_CYCLE:

                     if(clk_cnt == clk_div) //如果在最后半个时钟沿,预分频时间到后进入应答态

                              next_state <= ACK;

                     else

                              next_state <= LAST_HALF_CYCLE;

             //send one byte complete

             ACK:

                     next_state <=ACK_WAIT;   //进入应答等待态

            	 //wait for one clock cycle, to ensure that the cancel request signal

             ACK_WAIT:

                     next_state <= IDLE;

             default:

                     next_state <= IDLE;

    endcase

end

//说明:产生数据时钟信号

always@(posedge sys_clk or posedge rst)

begin

    if(rst)

             DCLK_reg <= 1'b0;

    else if(state == IDLE)

             DCLK_reg <= CPOL;

    else if(state == DCLK_EDGE)

             DCLK_reg <= ~DCLK_reg;//SPI clock edge

end

//SPI 时钟等待计数

always@(posedge sys_clk or posedge rst)

begin

    if(rst)

             clk_cnt <= 16'd0;

    else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)

             clk_cnt <= clk_cnt + 16'd1;

    else

             clk_cnt <= 16'd0;

end

//SPI 时钟边沿计数

always@(posedge sys_clk or posedge rst)

begin

    if(rst)

             clk_edge_cnt <= 5'd0;

    else if(state == DCLK_EDGE)

             clk_edge_cnt <= clk_edge_cnt + 5'd1;

    else if(state == IDLE)

             clk_edge_cnt <= 5'd0;

end

//SPI数据输出

always@(posedge sys_clk or posedge rst)

begin

    if(rst)

             MOSI_shift <= 8'd0;

    else if(state == IDLE && wr_req)

             MOSI_shift <= data_in;

    else if(state == DCLK_EDGE)

             if(CPHA == 1'b0 &&
clk_edge_cnt[0] == 1'b1) //数据输出在第二个边沿进行

                     MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};

   else
if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] ==1'b0))  //数据输出在第一个边沿进行



                     MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};

end

//SPI 数据输入

always@(posedge sys_clk or posedge rst)

begin

    if(rst)

             MISO_shift <= 8'd0;

    else if(state == IDLE && wr_req)

             MISO_shift <= 8'h00;

    else if(state == DCLK_EDGE)

             if(CPHA == 1'b0 &&
clk_edge_cnt[0] == 1'b0)  //输入数据采集在第一个边沿进行

                     MISO_shift <= {MISO_shift[6:0],MISO};

             else if(CPHA == 1'b1 &&(clk_edge_cnt[0] == 1'b1)) //输入数据采集在第二个边沿进行

                     MISO_shift <={MISO_shift[6:0],MISO};

end

endmodule

猜你喜欢

转载自blog.csdn.net/csdnqiang/article/details/108345531