SPI


概述

全雙工與半雙工

全雙工是指收發可以在同一時刻進行,而半雙工是指在同一時刻只能進行一項操作

SPI

SPI是serial peripheral interface(串行外設接口)的縮寫,是一種同步串行通信協議。於1979年,由Motorola公司推出,用於節省自家芯片的PCB空間布局。

特點

主從控制

主機可以輸出時鍾信號和片選信號來控制從設備,從設備只能從主設備處獲取時鍾。、

同步傳輸

主設備根據將要交換的數據來產生相應的時鍾脈沖(clock pulse),時鍾脈沖組成時鍾信號(clock signal),時鍾信號通過時鍾極性(CPOL)和時鍾相位(CPHA)控制主從設備之間何時交換數據、何時對數據進行采樣,來保證數據傳輸同步進行。

缺點

沒有應答機制確認是否接收到數據,所以跟IIC比降低了可靠性

管腳

img

  • MOSI:主設備數據輸出,從設備數據輸入
  • MISO:主設備數據輸入,從設備數據輸出
  • SCLK:主設備產生,發送給從設備
  • CS:從設備使能信號,由主設備控制。

傳輸模式

img

SPI總線共有四種傳輸模式,主要是{CPOL,CPHA}的組合。CPOL代表SPI總線空閑時刻的電平,CPHA規定數據在第幾個邊沿采樣,在第幾個邊沿切換。

比如第一種模式,CPOL=0,CPHA=0,表示空閑時刻為低電平,數據在上升沿采樣,數據在時鍾下降沿切換。

常用的為模式0和模式3,下面為模式0時序圖

img

在空閑時刻,片選信號為高電平,時鍾信號為低電平。在需要傳送數據時,片選信號拉低,MOSI於MISO在時鍾上升沿進行采樣,在時鍾下降沿數據切換。可以看到時鍾上升沿正對數據中間。

img

SPI設備可以理解為一個大的移位寄存器,它在通信過程中不能單純充當發送或接受,而應該是在每個CLK周期內進行1bit數據交換。

代碼實現

spi-master

module spi_master
#(
    parameter   CLK_FRE = 50    ,  // system clk frequence
    parameter   SPI_FRE = 5     ,  // SCLK frequence
    parameter   DATA_WIDTH = 8  ,  // serial bit number
    parameter   CPOL    = 0     ,  // clock polarity 
    parameter   CPHA    = 0        // clock phase
)(
    input       clk             ,
    input       rst_n           ,
    input   [DATA_WIDTH-1:0]    data_tx , //the serial data from master to slave
    input       start           ,
    input       miso            ,
    output      o_sclk            ,
    output  reg mosi            ,
    output  reg cs_n            ,
    output  reg [DATA_WIDTH-1:0] data_rx ,  //the serial data from slave to master
    output  reg finish          
);
    localparam  CYCLE = CLK_FRE / SPI_FRE - 1   ;
    localparam  SHIFT_CNT = clogb2(DATA_WIDTH)  ;
    localparam  SCLK_CNT  = clogb2(CYCLE)       ;

    function integer clogb2 ( input integer bit_depth) ;
        begin
            for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
                bit_depth = bit_depth >> 1;
        end
    endfunction

    localparam  IDLE      = 3'b000   ;
    localparam  LOAD_DATA = 3'b001   ;
    localparam  SEND_DATA = 3'b010   ;
    localparam  DONE      = 3'b100   ;
    reg [2:0]   state,next_state     ;
    reg         sclk                 ;
    reg         sclk_valid           ;
    reg [SCLK_CNT-1:0] sclk_cnt      ;
    reg [SHIFT_CNT-1:0]shift_cnt     ;
    reg [DATA_WIDTH-1:0] data_tx_temp;
    reg         sclk_delay           ;
    wire        sclk_posedge         ;
    wire        sclk_negedge         ;
    reg        data_sample          ;
    reg        data_switch          ;

    //  generate sclk
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk_cnt <= 'd0;
            else if(sclk_valid)
                begin
                    if(sclk_cnt == CYCLE)
                        sclk_cnt <= 'd0 ;
                    else
                        sclk_cnt <= sclk_cnt + 1 ;
                end
            else
                sclk_cnt <= 'd0;
        end

    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk <= CPOL ;
            else if(sclk_valid)
                begin
                    if(sclk_cnt == CYCLE)
                        sclk <= ~sclk ;
                    else
                        sclk <= sclk ;
                end
            else
                sclk <= CPOL ;
        end
    // detect the sclk edge
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk_delay <= CPOL ;
            else if (sclk_valid)
                sclk_delay <= sclk ;
            else
                sclk_delay <= CPOL ;
        end
    assign sclk_posedge = sclk & (!sclk_delay) ;
    assign sclk_negedge = (!sclk) & sclk_delay ;
    
    always @(*)
        begin
           case ({CPOL,CPHA})
                 2'b00   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 2'b01   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b10   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b11   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 default   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
            endcase
        end
    //FSM
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                state <= IDLE ;
            else
                state <= next_state ;
        end
    always @(*)
        begin
            case(state)
                IDLE : next_state = start ? LOAD_DATA : IDLE ;
                LOAD_DATA : next_state = SEND_DATA  ;
                SEND_DATA : next_state = (shift_cnt == DATA_WIDTH) ? DONE : SEND_DATA;
                DONE : next_state = IDLE ;
            endcase
        end
    //the control signal
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                begin
                    sclk_valid  <= 1'b0;
                    data_tx_temp<= 'd0;
                    cs_n        <= 1'b1;
                    shift_cnt   <= 'd0 ;
                    finish      <= 1'b0;
                end
            else
                begin
                    case(state)
                        IDLE : begin
                            sclk_valid   <= 1'b0    ;
                            data_tx_temp <= 'd0     ;
                            cs_n         <= 1'b1    ;
                            shift_cnt    <= 'd0     ;
                            finish       <= 1'b0    ;
                        end
                        LOAD_DATA : begin
                            sclk_valid   <=  1'b0    ;
                            data_tx_temp <= data_tx ;
                            cs_n         <=  1'b0   ;
                            shift_cnt    <=  'd0    ;
                            finish       <= 1'b0    ;
                        end
                        SEND_DATA : begin
                            sclk_valid   <= 1'b1    ;
                            if(data_switch)
                                begin
                                    shift_cnt <= shift_cnt + 1;
                                    data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
                                end
                            else
                                begin
                                    shift_cnt <= shift_cnt ;
                                    data_tx_temp <= data_tx_temp ;
                                end
                        end
                        DONE : begin
                            sclk_valid <= 1'b0;
                            data_tx_temp <= 'd0;
                            cs_n <= 1'b1 ;
                            finish <= 1'b1;
                            shift_cnt <= 'd0 ;
                        end
                    endcase
                end
        end
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                data_rx <= 'd0;
            else if(data_sample)
                data_rx <= {data_rx[DATA_WIDTH-2:0],miso} ;
            else
                data_rx <= data_rx ;
        end

    assign mosi = data_tx_temp[DATA_WIDTH-1];
    assign o_sclk = sclk_delay;
endmodule

仿真結果

思路

作為spi master端,可以先產生sclk,通過檢測sclk的上升沿和下降沿來進行數據采樣和數據切換進行數據傳輸。

在實際應用中spi最為接口信號,可以約定指令格式,分為指令位、讀寫位、數據位,通過spi master端向slave端發送指令。

spi-slave

module spi_slave
#(
    parameter   CLK_FRE = 50        ,
    parameter   SCLK_FRE = 5        ,
    parameter   DATA_WIDTH = 8      ,
    parameter   CPOL = 0            ,
    parameter   CPHA = 0            
)(
    input           clk             ,
    input           rst_n           ,
    input   [DATA_WIDTH-1:0] data_tx,
    input           data_tx_valid   ,
    input           sclk            ,
    input           cs_n            ,
    input           mosi            ,
    output          miso            ,
    output          finish          ,
    output  reg [DATA_WIDTH-1:0] data_rx
);
    localparam  SAMPLE_CNT = clogb2(DATA_WIDTH);
    reg  [DATA_WIDTH-1:0]   data_tx_temp ;
    reg             sclk_delay      ;
    reg             cs_n_delay      ;
    wire            sclk_posedge    ;
    wire            sclk_negedge    ;
    wire            cs_negedge      ;
    reg            data_sample     ;
    reg            data_switch     ;
    reg [SAMPLE_CNT-1:0] sample_cnt ;
    //capture the edge of sclk
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sclk_delay <= CPOL ;
            else if(!cs_n)
                sclk_delay <= sclk ;
            else
                sclk_delay <= CPOL ;
        end
    assign sclk_posedge = sclk & (!sclk_delay) ;
    assign sclk_negedge = (!sclk) & sclk_delay ;
    //capture the edge of cs_n
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                cs_n_delay <= 1'b1 ;
            else
                cs_n_delay <= cs_n ;
        end
    assign cs_negedge = (!cs_n) & cs_n_delay ;

    always @(*)
        begin
           case ({CPOL,CPHA})
                 2'b00   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 2'b01   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b10   : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end     
                 2'b11   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
                 default   : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end     
            endcase
        end
//trans data from slave to master
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                data_tx_temp <= 'd0;
            else if(data_tx_valid)
                data_tx_temp <= data_tx ;
            else if(!cs_n && data_switch)
                data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
            else
                data_tx_temp <= data_tx_temp ;
        end
assign miso = !cs_n ? data_tx_temp[DATA_WIDTH-1] : 1'b0 ;

//sample data from master to slave
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                data_rx <= 'd0;
            else if(!cs_n && data_sample)
                data_rx <= {data_rx[DATA_WIDTH-2:0],mosi} ;
            else 
                data_rx <= data_rx ;
        end
//finish signal
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                sample_cnt <= 'd0;
            else if(!cs_n && data_sample)
                sample_cnt = sample_cnt + 1 ;
            else if(cs_n)
                sample_cnt <= 'd0;
            else
                sample_cnt <= sample_cnt ;
        end
    assign finish = (sample_cnt == DATA_WIDTH) ;

    function integer clogb2 ( input integer bit_depth);
        begin
            for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
                bit_depth = bit_depth >> 1;
        end
    endfunction
endmodule

loop


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM