SPI以及IIC的verilog實現以及兩者之間的對比


一、SPI是一種常用的串行通信接口,與UART不同的地方在於。SPI可以同時掛多個從機,但是UART只能點對點的傳輸數據,此外SPI有四條線實現數據的傳輸,而UART采用的是2條實現串行數據的傳輸

 

1.SPI的主從機的接口模型

master和slave在時鍾的上升沿采樣,下降沿發送數據。數據從最高位(MSB)開始發送。)

 

 

用3條通訊總線和1條片選線。

  • MOSI:Master Output Slave Input,顧名思義,即主設備輸出/從設備輸入。數據從主機輸出到從機,主機發送數據。
  • MISO:Master Iutput Slave Onput,主設備輸入/從設備輸出,數據由從機輸出到主機,主機接收數據。
  • SCK:即時鍾信號線,用於通訊同步。該信號由主機產生,其支持的最高通訊速率為fpclk/2,即所掛載總線速率的一半。如SPI2掛載在APB1總線上,則其最高速率為36MHz / 2 = 18MHz。類似木桶效應,兩個設備之間通訊時,通訊速率受限於較低速的設備。
  • NSS:即片選信號線,用於選擇通訊的從設備,也可用CS表示。每個從設備都有一條獨立的NSS信號線,主機通過將某個設備的NSS線置低電平來選擇與之通訊的從設備。所以SPI通訊以NSS線電平置低為起始信號,以NSS線電平被拉高為停止信號。

2.SPI如何使用,以及對應的有幾種配置模式(相位、極性)
SPI配置模式分類根據的是時鍾信號空閑狀態。、以及上升沿采樣還是下降沿采樣,
CPOL=0表示的是時鍾空閑的時候為低電平,反之是高電平
CPHA=0表示的是時鍾信號的第一個邊沿是采樣沿
CPHA=1表示的是時鍾信號的第二個邊沿是采樣沿
對應的時序圖如下:

 

CPOL、CPHA

  • CPOL:即在沒有數據傳輸時,時鍾的空閑狀態的電平。
  • CPHA:即數據的采樣時刻。

有一點需要注意的是,主機和從機需要工作在相同的模式下才能正常通訊。

3.起始、停止信號(轉於知乎)

如上圖,編號1和6即為起始和停止信號的發生區域。NSS電平由高變低,則產生起始信號;NSS電平由低變高,則產生停止信號。從機檢測到自己的NSS線電平被置低,則開始與主機進行通訊;反之,檢測到NSS電平被拉高,則停止通訊。

4.數據有效性

MOSI和MISO線在SCK的每個時鍾周期傳輸一位數據,開發者可以自行設置MSB或LSB先行,不過需要保證兩個通訊設備都使用同樣的協定。從圖16-1看到,在SCK的上升沿和下降沿時進行觸發和采樣。

SPI有四種通訊模式,在SCK上升沿觸發,下降沿采樣只是其中一種模式。四種模式的主要區別便是總線空閑時SCK的狀態及數據采樣時刻。這涉及到“時鍾極性CPOL”和“時鍾相位CPHA”,由CPOL和CPHA的組合而產生了四種的通訊模式。

5.SPI的verilog實現:結合實際的應用場景對該通信協議進行分析:在一個網絡通信模型中,可以將基帶部分作為主控,RF部分作為受控部分,把SPI接口作兩者之間傳輸數據的接口,它完成的主要工作是

(1)將從base band接收到的16位的並行數據,轉換為RF所能接收的串行數據,並將該數據根據SPI協議送給RF。

(2)產生RF所需的時鍾信號SCLK,使能信號CSB。

(3)接收從RF傳回的串行數據,並將其轉換為並行數據。

(4)將base band發送的數據,與RF返回的數據進行比較,並把比較結果傳給base band。

module Serial2Parallel_Master #(
    parameter SCLK_DIVIDER = 8'd0 //Sclk Freq = Clk/2 / (SCLK_DIVIDER + 1)
    )(
    input rst_n,
    input clk,
    input sDataRd,
    input [15:0] pDataWr,
    output dataCS,
    output dataSclk,
    output sDataWr,
    output [15:0] pDataRd
);

            
    // counter,used to generate dataSclk signal
    reg dataCS_reg;
    reg dataSclk_reg;
    reg[7:0] Count1;
    always @(posedge clk or negedge rst_n)
        if(!rst_n)
            Count1 <= 8'd0;
        else if(Count1 == SCLK_DIVIDER)
            Count1 <= 8'd0;
        else
            Count1 <= Count1 + 1'b1;
            
    // generate CS and Sclk sequence        
    reg [5:0] i;//Step number 
    always @(posedge clk or negedge rst_n)
        if(!rst_n)begin
            i <= 6'd0;
            dataSclk_reg <= 1'b1;//Sclk high at reset
            dataCS_reg <= 1'b1;     //CS high at reset
        end
        else begin
            case(i)
                //pull down CS at the beginning
                6'd0:
                if(Count1 == SCLK_DIVIDER) begin  
                    i <= i + 1'b1;
                    dataSclk_reg <= 1'b1;
                    dataCS_reg <= 1'b0;    
                end
                else;       
                //generate 1st to 17th Sclk falling edge
                6'd1,6'd3,6'd5,6'd7,6'd9,6'd11,6'd13,6'd15,6'd17,6'd19,6'd21,6'd23,6'd25,5'd27,6'd29,6'd31,6'd33:
                if(Count1 == SCLK_DIVIDER) begin
                    dataSclk_reg <= 1'b0;
                    dataCS_reg <= 1'b0;
                    i <= i + 1'b1;
                end
                else;
                //generate 1st to 16th Sclk rising edge
                6'd2,6'd4,6'd6,6'd8,6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32:
                if(Count1 == SCLK_DIVIDER) begin
                    dataSclk_reg <= 1'b1;
                    dataCS_reg <= 1'b0;    
                    i <= i + 1'b1;
                end
                else; 
                6'd34://CS and Sclk go high
                if(Count1 == SCLK_DIVIDER) begin
                    dataSclk_reg <= 1'b1;
                    dataCS_reg <= 1'b1;
                    i <= i + 1'b1;
                end
                else;
                6'd35://CS keep high, Sclk go low
                if(Count1 == SCLK_DIVIDER) begin
                    dataSclk_reg <= 1'b0;
                    dataCS_reg <= 1'b1;
                    i <= 6'd0;
                end
                else ;
                default ;
            endcase
        end ;
        
            
    // - receive and send SPI data    
    reg sDataWr_reg;
    reg [15:0] pDataRd_reg;
    reg rxDone_reg;
    reg [5:0] j;   
    always @(negedge dataSclk or negedge rst_n)
        if(!rst_n) begin
            j <= 6'd0;
            sDataWr_reg <= 1'b0;
            pDataRd_reg <= 16'd0;
            rxDone_reg <= 1'b0;
        end
        // - CS high,clear j & AD data
        else if(dataCS) begin 
            j <= 6'd0;
            sDataWr_reg <= 1'b0;
            pDataRd_reg <= 16'd0;
            rxDone_reg <= 1'b0;
        end
        else begin 
            // - first falling of Sclk, send MSB of send data
            if(j == 6'd0) begin    
                j <= j + 1'b1;
                sDataWr_reg <= pDataWr[15];//send data 
                pDataRd_reg <= 16'd0;//receive data clear
                rxDone_reg <= 1'b0;
            end
            
            // - 2nd to 16th falling of Sclk
            else if(j <= 6'd15) begin    
                j <= j + 1'b1;
                sDataWr_reg <= pDataWr[15-j];//send data 
                pDataRd_reg[16-j] <= sDataRd;//receive data
                rxDone_reg <= 1'b0;
            end
            
            // - at 17th falling of sclk_fbk, CS is still low, receive LSB of receive data
            else if(j == 6'd16) begin 
                j <= j + 1'b1;
                sDataWr_reg <= 1'b0;//send data clear
                pDataRd_reg[0] <= sDataRd;//receive data
                rxDone_reg <= 1'b1;//receive done
            end             
            else begin
                j <= j;
                sDataWr_reg <= sDataWr_reg;
                pDataRd_reg <= pDataRd_reg;
                rxDone_reg <= rxDone_reg;
            end 
        end
    // - data latch for pDataRd
    reg [15:0] pDataRd_l;
    always @(posedge clk or negedge rst_n)
        if(!rst_n)
            pDataRd_l <= 16'd0;
        else if(rxDone_reg) begin
            pDataRd_l <= pDataRd_reg;
        end
        else begin
            pDataRd_l <= pDataRd_l;
        end       
    // - delay sDataWr for 1 main clk(10ns)
    reg sDataWr_dly;
    always @(posedge clk or negedge rst_n)
        if(!rst_n)
            sDataWr_dly <= 1'b0;
        else if(sDataWr_reg) begin
            sDataWr_dly <= 1'b1;
        end
        else begin
            sDataWr_dly <= 1'b0;
        end        
    // - output assignment
    assign dataCS = dataCS_reg;
    assign dataSclk = dataSclk_reg;
    assign sDataWr = sDataWr_dly;
    assign pDataRd = pDataRd_l;
endmodule
Serial2Parallel_Master
module Serial2Parallel_Slave (

    input rst_n,
    input clk,
     
    input dataCS,
    input dataSclk,
     
    input sDataRd,
    input [15:0] pDataWr,

    output sDataWr,
    output [15:0] pDataRd
);
            
    // - SPI read and write
    reg sDataWr_reg;
    reg [15:0] pDataRd_reg;
    reg rxDone_reg;
    reg [5:0] j;//operation steps
    always @(negedge dataSclk or negedge rst_n)
        if(!rst_n) begin
            j <= 6'd0;
            sDataWr_reg <= 1'b0;
            pDataRd_reg <= 16'd0;
            rxDone_reg <= 1'b0;
        end
        // - CS high,clear j & AD data
        else if(dataCS) begin 
            j <= 6'd0;
            sDataWr_reg <= 1'b0;
            pDataRd_reg <= 16'd0;
            rxDone_reg <= 1'b0;
        end
        else begin 
            // - first falling of Sclk, send MSB of send data
            if(j == 6'd0) begin    
                j <= j + 1'b1;
                sDataWr_reg <= pDataWr[15];//send data 
                pDataRd_reg <= 16'd0;//receive data clear
                rxDone_reg <= 1'b0;
            end
            
            // - 2nd to 16th falling of Sclk
            else if(j <= 6'd15) begin    
                j <= j + 1'b1;
                sDataWr_reg <= pDataWr[15-j];//send data 
                pDataRd_reg[16-j] <= sDataRd;//receive data
                rxDone_reg <= 1'b0;
            end
            
            // - at 17th falling of sclk_fbk, CS is still low, receive LSB of receive data
            else if(j == 6'd16) begin 
                j <= j + 1'b1;
                sDataWr_reg <= 1'b0;//send data clear
                pDataRd_reg[0] <= sDataRd;//receive data
                rxDone_reg <= 1'b1;//receive done
            end             
            else begin
                j <= j;
                sDataWr_reg <= sDataWr_reg;
                pDataRd_reg <= pDataRd_reg;
                rxDone_reg <= rxDone_reg;
            end 
        end

    // - data latch for pDataRd
    reg [15:0] pDataRd_l;
    always @(posedge dataCS or negedge rst_n)
        if(!rst_n)
            pDataRd_l <= 16'd0;
        else if(rxDone_reg) begin
            pDataRd_l <= pDataRd_reg;
        end
        else begin
            pDataRd_l <= pDataRd_l;
        end       
    // - output assignment
    assign sDataWr = sDataWr_reg;
    assign pDataRd = pDataRd_l;
 
endmodule
Serial2Parallel_Slave

二、IIC通信

IIC的通信模式示意圖:

 

IIC的verilog實現

`timescale 1ns / 1ps

module IIC_AD(
            clk,rst_n,
            scl,sda);


input clk;        // 50MHz
input rst_n;    //��λ�źţ�����Ч
output scl;        // 24C02��ʱ�Ӷ˿�
inout  sda;        // 24C02�����ݶ˿�
 
reg[2:0] cnt;    // cnt=0:scl上升沿,cnt=1:scl高電平中間,cnt=2:scl下降沿,cnt=3:scl低電平中間
reg[9:0] cnt_delay;    //500循環計數,產生iic所需要的時鍾
reg scl_r=1;        //時鍾脈沖寄存器

always @ (posedge clk or negedge rst_n)
    if(!rst_n) 
    cnt_delay <= 9'd0;
    else if(cnt_delay == 9'd499) 
    cnt_delay <= 9'd0;    //計數到10us為scl的周期,即100KHz 
    else 
    cnt_delay <= cnt_delay+1'b1;    

always @ (posedge clk or negedge rst_n) 
begin
    if(!rst_n) cnt <= 3'd5;
    else 
      begin
       case (cnt_delay)
            9'd124:    cnt <= 3'd1;    //cnt=1:scl高電平中間,用於數據采樣 
            9'd249:    cnt <= 3'd2;    //cnt=2:scl下降沿
            9'd280:    cnt <= 3'd3;    //cnt=3:scl低電平中間,用於數據變化
            9'd499:    cnt <= 3'd0;    //cnt=0:scl上升沿
            default: cnt <= 3'd5;
        endcase
      end
end
`define SCL_POS        (cnt==3'd0)        
`define SCL_HIG        (cnt==3'd1)        
`define SCL_NEG        (cnt==3'd2)        
`define SCL_LOW        (cnt==3'd3)        


always @ (posedge clk or negedge rst_n)
    if(!rst_n) 
       scl_r <= 1'b1;
    else if(cnt==3'd0) 
       scl_r <= 1'b1;    //scl上升沿
       else if(cnt==3'd2) 
          scl_r <= 1'b0;    //scl下降沿

assign scl = scl_r;    //產生iic所需要的時鍾
//---------------------------------------------
reg[3:0] num;
reg [7:0] db_r; //在IIC上傳送的數據寄存器   
reg sda_link;   //輸出數據sda信號inout方向控制  0z-c,1c-z
reg sda_r=1; //輸出數據寄存器

//需要寫入24C02的地址和數據 
parameter    DEVICE_WRITE=8'b0101_1000;//被尋址器件地址(寫操作)
parameter   BYTE_ADDR=8'b0000_0011;//寫入/讀出EEPROM的地址寄存器
parameter   WRITE_DATA=8'b0001_1011;//寫入EEPROM的數據

reg [3:0] cstate;//狀態寄存器

parameter     START = 4'd0;//狀態機的進行步驟編號
parameter     ADD1     = 4'd1;
parameter     ACK1     = 4'd2;
parameter     ADD2     = 4'd3;
parameter     ACK2     = 4'd4;
parameter     DATA     = 4'd5;
parameter     ACK3     = 4'd6;
parameter     STOP1 = 4'd7;
//---------------------------------------------
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
          num <= 3'd0;
            sda_link <= 1'b1;
            sda_r <= 1'b1;
           cstate <= START;
        end

    else begin              
      case (cstate)            
            START: begin
                    if(`SCL_HIG) begin        //scl為高電平期間
                        sda_link <= 1'b1;    //確定數據傳輸方向,數據線sda為output
                        db_r <= DEVICE_WRITE;
                        sda_r <= 1'b0;        //拉低數據線sda,產生起始位信號
                        cstate <= ADD1;
                        num <= 4'd0;        //num計數清零
                        end
                    else 
                        cstate <= START; //等待scl高電平中間位置到來 等待數據開始傳輸
                end
            ADD1:    begin
                    if(`SCL_LOW) begin
                            if(num == 4'd8) 
                            begin    
                                    num <= 4'd0;            //num清零
                                    sda_r <= 1'b1;      //提高數據線sda,開始數據變化
                                    sda_link <= 1'b0;    //確定數據傳輸方向,數據線sda為input
                                    cstate <= ACK1;
                            end
                            else begin
                                    cstate <= ADD1;
                                    num <= num+1'b1;
                                    case (num)
                              4'd0: sda_r <= db_r[7];
                                        4'd1: sda_r <= db_r[6];
                                        4'd2: sda_r <= db_r[5];
                                        4'd3: sda_r <= db_r[4];
                                        4'd4: sda_r <= db_r[3];
                                        4'd5: sda_r <= db_r[2];
                                        4'd6: sda_r <= db_r[1];
                                        4'd7: sda_r <= db_r[0];
                                       default: ;
                                    endcase
                          end
                            end
                else cstate <= ADD1;
             end                     
         ACK1:    begin
                        if(/*!sda*/`SCL_NEG) //注:24C01/02/04/08/16器件可以不考慮應答位 
                        begin 
                                cstate <= ADD2;    //從機響應信號
                                db_r <= BYTE_ADDR;    // 1地址    
                        end  
                        else 
                                cstate <= ACK1;        //等待從機響應
                    end
         ADD2:    begin
                    if(`SCL_LOW) 
                    begin
                            if(num == 4'd8) begin    
                                    sda_link <= 1'b0;
                                    sda_r <= 1'b1;
                                    num <= 4'd0;            
                                    cstate <= ACK2;
                            end
                            else begin
                                    sda_link <= 1'b1;//sda作為output  
                                    cstate <= ADD2;
                                    num <= num+1'b1;
                                    case (num)
                              4'd0: sda_r <= db_r[7];
                                        4'd1: sda_r <= db_r[6];
                                        4'd2: sda_r <= db_r[5];
                                        4'd3: sda_r <= db_r[4];
                                        4'd4: sda_r <= db_r[3];
                                        4'd5: sda_r <= db_r[2];
                                        4'd6: sda_r <= db_r[1];
                                        4'd7: sda_r <= db_r[0];
                                       default: ;
                                        endcase
                        end
                            end
                else cstate <= ADD2;
             end                    
         ACK2:    begin
                    if(/*!sda*/`SCL_NEG) begin        //從機響應操作
                                cstate <= DATA;     //寫操作
                                db_r <= WRITE_DATA;    //寫入數據    
                                
                        end    
                    else cstate <= ACK2;    //等待從機響應
                end
         DATA:    begin
                if(`SCL_LOW) 
                    begin
                            if(num == 4'd8) begin    
                                    sda_link <= 1'b0;
                                    sda_r <= 1'b1;
                                    num <= 4'd0;                                        
                                    cstate <= ACK3;
                            end
                            else begin
                                    sda_link <= 1'b1;
                                    cstate <= DATA;
                                    num <= num+1'b1;
                                    case (num)
                              4'd0: sda_r <= db_r[7];
                                        4'd1: sda_r <= db_r[6];
                                        4'd2: sda_r <= db_r[5];
                                        4'd3: sda_r <= db_r[4];
                                        4'd4: sda_r <= db_r[3];
                                        4'd5: sda_r <= db_r[2];
                                        4'd6: sda_r <= db_r[1];
                                        4'd7: sda_r <= db_r[0];
                                       default: ;
                                        endcase
                        end
                            end
                else cstate <= DATA;
             end                    
         ACK3: begin
                    if(/*!sda*/`SCL_NEG) begin
                        sda_r <= 1'b0;                        //拉低數據線sda,產生停止信號
                        sda_link <= 1'b1;//sda作為output 
                        cstate <= STOP1;                        
                        end
                    else cstate <= ACK3;
                end
            STOP1: begin
                     if(`SCL_HIG) begin
                            sda_link <= 1'b1;
                            sda_r <= 1'b1;                       //拉低數據線sda,產生停止信號
                            cstate <= START;
                        end
                    else cstate <= STOP1;
                end
            default: cstate <= START;
            endcase

            end
end
assign sda = sda_link ? sda_r:1'bz;

endmodule 
View Code

IIC的關鍵詞:兩線和低速,該總線采用開漏的結構可以很好地實現數據的雙向傳輸,也就是說在要用到sda或者scl線的時候,可以通過內部的NMOS下拉為零,否則上拉為高電平

 

 

還需要注意的就是開始和結束條件,以及IIC協議的要求:

scl為高的器件,sda必須保持穩定,sda變化相對於scl變高有建立時間的要求,而sda變化相對於scl變低有保持時間的要求。scl低電平期間,數據sda才可以發生變化。而這里的建立保持時間是微秒級別的,所以IIC的速度慢,1MHz左右。從器件不適合高速數字邏輯單路。

然后具體的執行順序為:

 

s1:Start+器件地址+應答信號+要發送的數據+應答信號。

通過scl和sda兩條線的控制來實現數據的傳輸,其中的sda信號線是inout,因為作為串行數據傳輸線,它不僅要傳輸上位機的數據到下位機,此外還需要下位機發送一個響應到上位機去,所以實際上需要它是inout。

IIC通過器件的地址來區分從機,而SPI主要是通過作為主機的CS來區分從機的編碼。

三、關於兩者的對比:

首先是SPI的優點在於:

1.利於硬件實現,不需要多個器件地址,只用到4根數據線,封裝簡單

2.全雙工傳輸,可以同時發送和接收數據

3.三態輸出端口,相對IIC的開漏輸出,抗干擾能力強,傳輸穩定

4.傳輸數據的速度在幾百MHz遠遠高於IIC的幾十Mhz

5.輸入輸出的比特數沒有限制

缺點

1.信號線多,而且隨着從器件的個數增加,芯片選擇線會增加

2.傳輸過程沒有確認信號,不知道從器件的接收情況。

3.沒有校驗機制,傳輸錯誤不會有提示。

 IIC的優點:

控制線少,結構簡單

IIC的缺點

傳輸速度慢而且麻煩。


免責聲明!

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



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