ARM與FPGA經過spi通訊設計2.spi master的實現

這裏主要放兩個代碼第一個是正常的不使用狀態機的SPI主機代碼;第二個是狀態機SPI代碼網絡

1.不使用狀態機:特權同窗《深刻淺出玩轉FPGA》中DIY數碼相框部分代碼:this

////////////////////////////////////////////////////////////////////////////////
module spi_ctrl(
            clk,rst_n,
            spi_miso,spi_mosi,spi_clk,
            spi_tx_en,spi_tx_rdy,spi_rx_en,spi_rx_rdy,spi_tx_db,spi_rx_db
            );

input clk;        //FPAG輸入時鐘信號25MHz
input rst_n;    //FPGA輸入復位信號

input spi_miso;        //SPI主機輸入從機輸出數據信號
output spi_mosi;    //SPI主機輸出從機輸入數據信號
output spi_clk;        //SPI時鐘信號,由主機產生

input spi_tx_en;        //SPI數據發送使能信號,高有效
output spi_tx_rdy;        //SPI數據發送完成標誌位,高有效
input spi_rx_en;        //SPI數據接收使能信號,高有效
output spi_rx_rdy;        //SPI數據接收完成標誌位,高有效
input[7:0] spi_tx_db;    //SPI數據發送寄存器
output[7:0] spi_rx_db;    //SPI數據接收寄存器


//模擬SPI的時序模式爲CPOL=1, CPHA=1,模擬速率爲25Mbit

//-------------------------------------------------
//SPI時序控制計數器,全部SPI時序由該計數器值控制
reg[4:0] cnt8;    //SPI時序控制計數器,計數範圍在0-18

always @(posedge clk or negedge rst_n)
    if(!rst_n) cnt8 <= 5'd0;
    else if(spi_tx_en || spi_rx_en) begin
            if(cnt8 < 5'd18)cnt8 <= cnt8+1'b1;    //SPI工做使能
            else ;    //計數到18中止,等待撤銷spi使能
        end
    else cnt8 <= 5'd0;    //SPI關閉,計數中止

//-------------------------------------------------
//SPI時鐘信號產生
reg spi_clkr;    //SPI時鐘信號,由主機產生

always @(posedge clk or negedge rst_n)
    if(!rst_n) spi_clkr <= 1'b1;
    else if(cnt8 > 5'd1 && cnt8 < 5'd18) spi_clkr <= ~spi_clkr;    //在cnt8處於2-17時SPI時鐘有效翻轉

assign spi_clk = spi_clkr;

//-------------------------------------------------
//SPI主機輸出數據控制
reg spi_mosir;    //SPI主機輸出從機輸入數據信號

always @(posedge clk or negedge rst_n)
    if(!rst_n) spi_mosir <= 1'b1;
    else if(spi_tx_en) begin
            case(cnt8[4:1])        //主機發送8bit數據
                4'd1: spi_mosir <= spi_tx_db[7];    //發送bit7
                4'd2: spi_mosir <= spi_tx_db[6];    //發送bit6
                4'd3: spi_mosir <= spi_tx_db[5];    //發送bit5
                4'd4: spi_mosir <= spi_tx_db[4];    //發送bit4
                4'd5: spi_mosir <= spi_tx_db[3];    //發送bit3
                4'd6: spi_mosir <= spi_tx_db[2];    //發送bit2
                4'd7: spi_mosir <= spi_tx_db[1];    //發送bit1
                4'd8: spi_mosir <= spi_tx_db[0];    //發送bit0
                default: spi_mosir <= 1'b1;    //spi_mosi沒有輸出時應保持高電平
                endcase
        end
    else spi_mosir <= 1'b1;    //spi_mosi沒有輸出時應保持高電平

assign spi_mosi = spi_mosir;

//-------------------------------------------------
//SPI主機輸入數據控制
reg[7:0] spi_rx_dbr;    //SPI主機輸入從機輸出數據總線寄存器

always @(posedge clk or negedge rst_n)
    if(!rst_n) spi_rx_dbr <= 8'hff;
    else if(spi_rx_en) begin
            case(cnt8)        //主機接收並鎖存8bit數據
                5'd3: spi_rx_dbr[7] <= spi_miso;    //接收bit7
                5'd5: spi_rx_dbr[6] <= spi_miso;    //接收bit6
                5'd7: spi_rx_dbr[5] <= spi_miso;    //接收bit5
                5'd9: spi_rx_dbr[4] <= spi_miso;    //接收bit4
                5'd11: spi_rx_dbr[3] <= spi_miso;    //接收bit3
                5'd13: spi_rx_dbr[2] <= spi_miso;    //接收bit2
                5'd15: spi_rx_dbr[1] <= spi_miso;    //接收bit1
                5'd17: spi_rx_dbr[0] <= spi_miso;    //接收bit0
                default: ;
                endcase
        end

assign spi_rx_db = spi_rx_dbr;

//-------------------------------------------------
//SPI數據發送完成標誌位,高有效
assign spi_tx_rdy = (cnt8 == 5'd18)/* & spi_tx_en)*/;

//-------------------------------------------------
//SPI數據接收完成標誌位,高有效
assign spi_rx_rdy = (cnt8 == 5'd18)/* & spi_rx_en)*/;


endmodule

2.使用狀態機的SPI master(來源網絡)spa

module spi_master
(
    input                       sys_clk,
    input                       rst,
    output                      nCS,       //chip select (SPI mode)
    output                      DCLK,      //spi clock
    output                      MOSI,      //spi master data output
    input                       MISO,      //spi master input
    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
/****************end*************************/
 
/****************狀態機的具體過程*************/
always@(*)
begin
    case(state)
        IDLE:
            if(wr_req == 1'b1)
                next_state <= DCLK_IDLE;
            else
                next_state <= IDLE;
        DCLK_IDLE:
            //half a SPI clock cycle produces a clock edge//半個SPI時鐘週期產生時鐘邊沿
            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//一個SPI字節,總共有16個時鐘邊沿
            if(clk_edge_cnt == 5'd15)
                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)                        /*在空閒狀態以前,SCK一直保持CPOL的極性*/               
        DCLK_reg <= 1'b0;
    else if(state == IDLE)
        DCLK_reg <= CPOL;
    else if(state == DCLK_EDGE)    /*邊緣檢測時,反轉SCK*/
        DCLK_reg <= ~DCLK_reg;//SPI clock edge
end
/****************end*****************************/
 
//SPI clock wait counter                /*一個計數器*/
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 clock edge counter
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 data output                    /*這裏就是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)          /*兩種方式,取決於CPHA*/
            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 data input
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}; /*MISO輸入,而後進行移位*/
        else if(CPHA == 1'b1 && (clk_edge_cnt[0] == 1'b1))
            MISO_shift <= {MISO_shift[6:0],MISO};
end
endmodule

第二個例子實現了較爲全面的spi主機功能,能夠設置SPI相位和極性,有較高的參考價值code

以上兩個源代碼可供你們參考blog

相關文章
相關標籤/搜索