FPGA--IIC通信


 IIC 的總線協議和時序
IIC 標准速率為 100kbit/s,快速模式 400kbit/s,支持多機通訊, 支持多主控模塊,但同一時刻只允許有一個主控。 由數據線 SDA 和時鍾 SCL 構成串行總線;每個電路和模塊都有唯一的地址。

在這里以 AT24C04 為例說明 I2C 讀寫的基本操作和時序,I2C 設備的操作可分為寫單個存儲字節,寫多個存儲字節,讀單個存儲字節和讀多個存儲字節。 各個操作如下圖所示。

 

 下面對 I2C 總線通信過程中出現的幾種信號狀態和時序進行分析。
①總線空閑狀態
I2C 總線總線的 SDA 和 SCL 兩條信號線同時處於高電平時,規定為總線的空閑狀態。此時各個器件的輸出級場效應管均處在截止狀態,即釋放總線,由兩條信號線各自的上拉電阻把電平拉高。
②啟動信號(Start)
在時鍾線 SCL 保持高電平期間,數據線 SDA 上的電平被拉低(即負跳變),定義為 I2C 總線 總線的啟動信號,它標志着一次數據傳輸的開始。啟動信號是由主控器主動建立的,在建立該信號之前 I2C 總線必須處於空閑狀態,如下圖所示。

 

 ③停止信號(Stop)
在時鍾線 SCL 保持高電平期間,數據線 SDA 被釋放,使得 SDA 返回高電平(即正跳變),稱為 I2C 總線的停止信號,它標志着一次數據傳輸的終止。停止信號也是由主控器主動建立的,建立該信號之后,I2C 總線將返回空閑狀態。

④數據位傳送
在 I2C 總線上傳送的每一位數據都有一個時鍾脈沖相對應(或同步控制),即在 SCL 串行時鍾的配合下,在 SDA 上逐位地串行傳送每一位數據。進行數據傳送時,在 SCL 呈現高電平期間,SDA 上的電平必須保持穩定,低電平為數據 0,高電平為數據 1。只有在 SCL 為低電平期間,才允許 SDA 上的電平改變狀態。

 

 ⑤應答信號(ACK 和 NACK)
I2C 總線上的所有數據都是以 8 位字節傳送的,發送器每發送一個字節,就在時鍾脈沖 9 期間釋放數據線,由接收器反饋一個應答信號。應答信號為低電平時,規定為有效應答位(ACK簡稱應答位),表示接收器已經成功地接收了該字節;應答信號為高電平時,規定為非應答位(NACK),一般表示接收器接收該字節沒有成功。對 於反饋有效應答位 ACK 的要求是,接收器在第 9 個時鍾脈沖之前的低電平期間將 SDA 線拉低,並且確保在該時鍾的高電平期間為穩定的低電平。
如果接收器是主控器,則在它收到最后一個字節后,發送一個 NACK 信號,以通知被控發送器結束數據發送,並釋放 SDA 線,以便主控接收器發送一個停止信號。

 

 1. 編寫 IIC 的通信程序 iic_com.v

module iic_com
(
    input CLK,
     input RSTn,
     
     input [1:0] Start_Sig,             //read or write command
     input [7:0] Addr_Sig,              //eeprom words address
     input [7:0] WrData,                //eeprom write data
     output [7:0] RdData,               //eeprom read data
     output Done_Sig,                   //eeprom read/write finish
     
     output SCL,
     inout SDA
     
);

parameter F100K = 9'd500;              //100Khz的時鍾分頻系數  
              
     
reg [4:0]i;
reg [4:0]Go;
reg [9:0]C1;
reg [7:0]rData;
reg rSCL;
reg rSDA;
reg isAck;
reg isDone;
reg isOut;    
 
assign Done_Sig = isDone;
assign RdData = rData;
assign SCL = rSCL;
assign SDA = isOut ? rSDA : 1'bz;       //SDA數據輸出選擇

//****************************************// 
//*             I2C讀寫處理程序            *// 
//****************************************// 
always @ ( posedge CLK or negedge RSTn )
     if( !RSTn )  begin
            i <= 5'd0;
            Go <= 5'd0;
            C1 <= 9'd0;
            rData <= 8'd0;
            rSCL <= 1'b1;
            rSDA <= 1'b1;
            isAck <= 1'b1;
            isDone <= 1'b0;
            isOut <= 1'b1;
     end
     else if( Start_Sig[0] )                     //I2C 數據寫
         case( i )
                    
            0: // iic Start
             begin
                    isOut <= 1;                         //SDA端口輸出
                    
                    if( C1 == 0 ) rSCL <= 1'b1;
                    else if( C1 == 400 ) rSCL <= 1'b0;       //SCL由高變低
                              
                    if( C1 == 0 ) rSDA <= 1'b1; 
                    else if( C1 == 200 ) rSDA <= 1'b0;        //SDA先由高變低 
                              
                    if( C1 == F100K -1) begin C1 <= 9'd0; i <= i + 1'b1; end
                    else C1 <= C1 + 1'b1;
             end
                      
             1: // Write Device Addr
             begin rData <= {4'b1010, 3'b000, 1'b0}; i <= 5'd7; Go <= i + 1'b1; end         
                 
             2: // Wirte Word Addr
             begin rData <= Addr_Sig; i <= 5'd7; Go <= i + 1'b1; end
                    
             3: // Write Data
             begin rData <= WrData; i <= 5'd7; Go <= i + 1'b1; end
     
             4: //iic Stop
             begin
                isOut <= 1'b1;
                          
                if( C1 == 0 ) rSCL <= 1'b0;
                else if( C1 == 100 ) rSCL <= 1'b1;     //SCL先由低變高       
        
                 if( C1 == 0 ) rSDA <= 1'b0;
                 else if( C1 == 300 ) rSDA <= 1'b1;     //SDA由低變高  
                           
                 if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                 else C1 <= C1 + 1'b1; 
             end
                     
             5:
             begin isDone <= 1'b1; i <= i + 1'b1; end       //寫I2C 結束
                     
             6: 
             begin isDone <= 1'b0; i <= 5'd0; end
                 
             7,8,9,10,11,12,13,14:                         //發送Device Addr/Word Addr/Write Data
             begin
                 isOut <= 1'b1;
                  rSDA <= rData[14-i];                      //高位先發送
                      
                  if( C1 == 0 ) rSCL <= 1'b0;
                 else if( C1 == 100 ) rSCL <= 1'b1;
                  else if( C1 == 300 ) rSCL <= 1'b0; 
                          
                  if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                  else C1 <= C1 + 1'b1;
             end
                     
             15:                                          // waiting for acknowledge
             begin
                 isOut <= 1'b0;                            //SDA端口改為輸入
                 if( C1 == 100 ) isAck <= SDA;
                          
                  if( C1 == 0 ) rSCL <= 1'b0;
                  else if( C1 == 100 ) rSCL <= 1'b1;
                  else if( C1 == 300 ) rSCL <= 1'b0;
                          
                  if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                  else C1 <= C1 + 1'b1; 
             end
                     
             16:
             if( isAck != 0 ) i <= 5'd0;
             else i <= Go; 
                    
              endcase
    
      else if( Start_Sig[1] )                     //I2C 數據讀
            case( i )
                
             0: //iic Start
             begin
                  isOut <= 1;                      //SDA端口輸出
                          
                  if( C1 == 0 ) rSCL <= 1'b1;
                    else if( C1 == 400 ) rSCL <= 1'b0;      //SCL由高變低
                          
                    if( C1 == 0 ) rSDA <= 1'b1; 
                    else if( C1 == 200 ) rSDA <= 1'b0;     //SDA先由高變低 
                          
                    if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                     else C1 <= C1 + 1'b1;
             end
                      
             1: // Write Device Addr
             begin rData <= {4'b1010, 3'b000, 1'b0}; i <= 5'd9; Go <= i + 1'b1; end
                     
             2: // Wirte Word Addr
             begin rData <= Addr_Sig; i <= 5'd9; Go <= i + 1'b1; end
                    
             3: //iic Start again
             begin
                 isOut <= 1'b1;
                          
                 if( C1 == 0 ) rSCL <= 1'b0;
                  else if( C1 == 100 ) rSCL <= 1'b1;
                  else if( C1 == 500 ) rSCL <= 1'b0;                //SCL后變低      
                          
                 if( C1 == 0 ) rSDA <= 1'b0; 
                  else if( C1 == 100 ) rSDA <= 1'b1;
                  else if( C1 == 300 ) rSDA <= 1'b0;                //SDA先變低
                          
                  if( C1 == 600 -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                  else C1 <= C1 + 1'b1;
             end
                     
             4: // Write Device Addr ( Read )
             begin rData <= {4'b1010, 3'b000, 1'b1}; i <= 5'd9; Go <= i + 1'b1; end
                    
             5: // Read Data
             begin rData <= 8'd0; i <= 5'd19; Go <= i + 1'b1; end
                 
             6: //iic Stop
             begin
                 isOut <= 1'b1;
                 if( C1 == 0 ) rSCL <= 1'b0;
                  else if( C1 == 100 ) rSCL <= 1'b1;            //SCL先變高
        
                  if( C1 == 0 ) rSDA <= 1'b0;
                  else if( C1 == 300 ) rSDA <= 1'b1;            //SDA后變高
                           
                  if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                  else C1 <= C1 + 1'b1; 
             end
                     
             7:                                                       //寫I2C 結束
             begin isDone <= 1'b1; i <= i + 1'b1; end
                     
             8: 
             begin isDone <= 1'b0; i <= 5'd0; end
                 
                    
             9,10,11,12,13,14,15,16:                                  //發送Device Addr(write)/Word Addr/Device Addr(read)
             begin
                  isOut <= 1'b1;                          
                    rSDA <= rData[16-i];
                          
                   if( C1 == 0 ) rSCL <= 1'b0;
                    else if( C1 == 100 ) rSCL <= 1'b1;
                    else if( C1 == 300 ) rSCL <= 1'b0; 
                          
                    if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                    else C1 <= C1 + 1'b1;
             end
                   
             17: // waiting for acknowledge
             begin
                  isOut <= 1'b0;                                       //SDA端口改為輸入
                         
                    if( C1 == 200 ) isAck <= SDA;
                          
                    if( C1 == 0 ) rSCL <= 1'b0;
                    else if( C1 == 100 ) rSCL <= 1'b1;
                    else if( C1 == 300 ) rSCL <= 1'b0;
                          
                    if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                    else C1 <= C1 + 1'b1; 
             end
                     
             18:
                  if( isAck != 0 ) i <= 5'd0;
                    else i <= Go;
                     
                     
             19,20,21,22,23,24,25,26: // Read data
             begin
                 isOut <= 1'b0;
                 if( C1 == 200 ) rData[26-i] <= SDA;
                          
                  if( C1 == 0 ) rSCL <= 1'b0;
                  else if( C1 == 100 ) rSCL <= 1'b1;
                  else if( C1 == 300 ) rSCL <= 1'b0; 
                          
                  if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                  else C1 <= C1 + 1'b1;
             end      
                     
             27: // no acknowledge
             begin
                 isOut <= 1'b1;
                      
                  if( C1 == 0 ) rSCL <= 1'b0;
                  else if( C1 == 100 ) rSCL <= 1'b1;
                  else if( C1 == 300 ) rSCL <= 1'b0;
                          
                  if( C1 == F100K -1 ) begin C1 <= 9'd0; i <= Go; end
                  else C1 <= C1 + 1'b1; 
            end
                
            endcase    
        
           
        

    
                
endmodule

Iic_com 讀寫程序分為 IIC 數據讀和 IIC 數據寫,通過狀態機 i 來切換 IIC 的不同狀態,譬如接收到寫命令,狀態機i=0 轉入 Start 狀態,SDA 先變低,再 SCL 變低;狀態機i=1 開 始轉入寫 IIC 從設備地址 0x80; 之后狀態機轉到7開始發送8位的數據,其中狀態機i=7,8,9,10,11,12,13,14 是 IIC 發送8位的數據,然后狀態機進入 i=15 等待 IIC 從設備的應答信號。狀態機 i=16 為判斷是否有應答,如果有的話狀態機轉到 i=2 寫 IIC 的地址,然后狀態機又是重復i=7,8,9,10,11,12,13,14 發送8位的地址和 i=15 等待應答,i=16 判斷應答。最后狀態機 i=3 開始發送 IIC 寫數據。發送完數據 i=4 發送 Stop 信號。IIC 讀的流程跟寫差不多,我就不再重復了,大家具體看程序和程序的說明吧!
2. 編寫頂層文件 eeprom_test.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name:    eeprom_test 
// Function: write and read eeprom using I2C bus
//////////////////////////////////////////////////////////////////////////////////
module eeprom_test
(
    input CLK_50M,
     input RSTn,
     output [3:0]LED,
     
     output SCL,
     inout SDA
);
  
  
wire [7:0] RdData;
wire Done_Sig;

reg [3:0] i;
reg [3:0] rLED;

reg [7:0] rAddr;
reg [7:0] rData;
reg [1:0] isStart;

assign LED = rLED;

/***************************/
/*   EEPROM write and read */
/***************************/      
always @ ( posedge CLK_50M or negedge RSTn )    
     if( !RSTn ) begin
            i <= 4'd0;
            rAddr <= 8'd0;
            rData <= 8'd0;
            isStart <= 2'b00;
         rLED <= 4'b0000;
     end
     else
        case( i )
                
         0:
          if( Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end
          else begin isStart <= 2'b01; rData <= 8'h12; rAddr <= 8'd0; end              //eeprom write 0x12 to EEPROM addr 0
                     
          1:
          if( Done_Sig ) begin isStart <= 2'b00; i <= i + 1'b1; end
          else begin isStart <= 2'b10; rAddr <= 8'd0; end                              //eeprom read data from EEPROM addr 0
                     
          2:
          begin rLED <= RdData[3:0]; end        
        
        endcase    
     
/***************************/
//I2C通信程序//
/***************************/                
iic_com U1
     (
         .CLK         ( CLK_50M ),
          .RSTn        ( RSTn ),
          .Start_Sig   ( isStart ),
          .Addr_Sig    ( rAddr ),
          .WrData      ( rData ),
          .RdData      ( RdData ),
          .Done_Sig    ( Done_Sig ),
         .SCL         ( SCL ),
          .SDA         ( SDA )
);


endmodule

頂層文件實現以下兩部分功能:
1. 上電后寫一個數據到 EEPROM 的地址 0, 再讀出地址 0 的內容。這里我們寫的數據是 0x12,用戶可以自行修改。
2. 例化 iic_com 模塊,verilog 通過模塊調用或稱為模塊實例化的方式來實現 iic_com 子模塊與頂層模塊 eeprom_test 的連接, 模塊例化有利於簡化每一個模塊的代碼,易於維護和修改。

 


免責聲明!

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



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