天天看點

用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