IIC接口介紹

最近遇到一個BUG,跟IIC通信有關,所以借這個機會總結一下IIC總線協議

1.引腳接口介紹

1.A0,A1,A2爲24LC64的片選信號,IIC總線最多可以掛載8個IIC接口器件,通過對A0,A1,A2尋址,可以實現對不同的EEPROM操作

2.WP爲讀寫使能信號,當WP懸空或者接地,EEPROM可讀可寫,當WP接電源,EEPROM只能讀不能寫。因爲我們要對EEPROM寫,所以這裏WP信號懸空

3.SCL爲時鐘信號線,最高頻率400Khz

4.SDA爲數據線,雙向線(inout),當爲in時,數據通過SDA寫到EEPROM。爲out時,eeprom讀出來的數據通過SDA傳到外面

2.接口時序

   IIC讀寫時序分爲隨機讀寫和頁讀寫,這裏只研究隨機讀寫

   2.1 寫時序

        

  寫操作步驟

  1.發送啓動信號

  2.發送控制字寫(1010_A0A1A2_0 )

  3.EEPROM發送應答信號ACK

  4.發送高字節寫地址

  5.EEPROM發送應答信號ACK

  6.發送低字節寫地址

  7.EEPROM發送應答信號ACK

  8.發送8bit寫數據

  9.EEPROM發送應答信號ACK

  10.發送停止信號

  2.2 讀時序

 讀操作信號  

 1.發送啓動信號

  2.發送控制字寫(1010_A0A1A2_0)

  3.EEPROM發送應答信號ACK

  4.發送高字節讀地址

  5.EEPROM發送應答信號ACK

  6.發送低字節讀地址

  7.EEPROM發送應答信號ACK

  8.發送啓動信號

  9.發送控制字讀(1010_A0A1A2_1)

  10.EEPROM發送應答信號ACK

  11.讀取一個8bit數據

  12..EEPROM發送NO ACK信號

  13.發送停止信號

3.操作步驟解析

  3.1啓動信號

        

       SCL 保持高電平期間 ,如果 SDA 出現由高到低的跳變,代表啓動信號

   3.2控制字

        1010_A0A1A2X,

      1.1010爲EEPROM信號標識,爲一組固定的序列

      2.A0A1A2爲片選信號,由於只有一個flash,所以A0A1A2在這裏全爲0

      3.最後一個bit X,爲0時代表寫,爲1時代表讀。

  3.3地址

       24LC64表示有64Kbit的存儲空間,需要13位地址線尋址。但是IIC是以字節的實行操作的,所以需要13位地址線擴展成16位,高3位隨意填0或者1,習慣填0

  3.4應答信號與非應答信號

       應答信號和非應答信號都是由數據接收方(EEPROM)發出的,當SCL爲高電平時候,如果檢測到SDA爲低電平,說明有應答信號。如果檢測到SDA爲高電平,說明有非應答信號。所以在應答時鐘週期的時候,我們要釋放SDA信號線,讓EEPROM通過SDA發送一個低電平或者高電平過來。

  3.5停止信號

         

        SCL 保持高電平期間 ,如果 SDA 出現由低到高的跳變,代表停止信號

  3.6 數據傳輸

        由於IIC總線協議的啓動和停止信號都是在SCL高電平期間發生跳變,這就決定了其數據只能在SCL低電平期間發生改變,不然會被當做啓動或者停止信號處理。在SCL爲高電平期間,數據必須保持穩定。即在SCL低電平的時候改變數據,高電平的時候採集數據

   4關鍵代碼解析

     4.1狀態機設置

 

     4.2 sda信號線控制

            由於sda是inout型,讀寫都是有這根線控制。所以我們要有一個信號,來指示sda信號線什麼時候寫,什麼時候是讀。

當link_sda信號爲1的時候,指示sda信號寫。這時候我們把需要寫的數據一個bit一個bit的賦給中間變量sda_buf信號,該信號經過sda信號線把數據寫進flash

當link_sda信號爲0的時候,指示sda信號讀。

完整代碼如下

複製代碼

module iic_control(
  input  wire       sclk,
  input  wire       reset,
  input  wire       key_wr,
  input  wire       key_rd,
  
  output reg        scl,
  inout  wire       sda,
  output wire[7:0]  dataout,
  output reg        led
);

  parameter    IDLE           =  14'b00_0000_0000_0000,
               start1         =  14'b00_0000_0000_0001,
               control_byte1  =  14'b00_0000_0000_0010,
               ack1           =  14'b00_0000_0000_0100,
               high_addr_byte =  14'b00_0000_0000_1000,
               ack2           =  14'b00_0000_0001_0000,
               low_addr_byte  =  14'b00_0000_0010_0000,
               ack3           =  14'b00_0000_0100_0000,
               start2         =  14'b00_0000_1000_0000,
               control_byte2  =  14'b00_0001_0000_0000,
               ack4           =  14'b00_0010_0000_0000,
               transfer_data  =  14'b00_0100_0000_0000,
               ack5           =  14'b00_1000_0000_0000,
               no_ack         =  14'b01_0000_0000_0000,
               stop           =  14'b10_0000_0000_0000;
               
  reg[13:0]         state;   
  
  reg[6:0]          cnt;         //分頻計數
  reg               link_sda;    //總線開關
  reg               sda_buf;     //總線數據緩存器
  reg               wr;          //寫使能
  reg               rd;          //讀使能
  
  reg[7:0]          data;
  reg[3:0]          cnt_num;
  reg[7:0]          result;
  
  assign sda=(link_sda)?sda_buf:1'hz;
  assign dataout = result;
  
  [email protected](posedge sclk or negedge reset)
    if(!reset)
      cnt <= 7'd0;
    else if(cnt==7'd124)
      cnt <= 7'd0;
    else 
      cnt <= cnt + 1'b1;     
      
   [email protected](posedge sclk or negedge reset)
     if(!reset)
       scl <= 1'b0;
     else if(cnt==7'd30)
       scl <= 1'b1;
     else if(cnt==8'd93)
       scl <= 1'b0;
  
  [email protected](posedge sclk or negedge reset)
    if(!reset==1)
      begin
        state    <= IDLE;
        link_sda <= 0;
        sda_buf  <= 0;
        data     <= 0;
        cnt_num  <= 4'd0;
        result   <= 8'd0;
        led      <= 1;
      end
    else case(state)
      IDLE: 
        begin
          if(!key_wr)
            wr <= 1;
          if(!key_rd)
            rd <= 1;
          if((wr==1)||(rd==1)&&(!scl==1))
            begin
              state    <= start1;
              link_sda <= 1;
              sda_buf  <= 1;
              data     <= 8'b10100000;  //寫控制字準備
            end 
        end
      start1:
        begin
          if((scl==1)&&(cnt==7'd61))
            begin
              state    <= control_byte1;
              link_sda <= 1;
              sda_buf  <= 0;
            end 
        end
      control_byte1:
        begin
          if((cnt_num<4'd8)&&(cnt==7'd124))
            begin                           
              cnt_num  <= cnt_num + 1;
              sda_buf  <= data[7];
              data     <= {data[6:0],data[7]};
            end
          else if((cnt_num==4'd8)&&(cnt==7'd124))
            begin        
              state    <= ack1; 
              link_sda <= 0;
              cnt_num  <= 0;
            end
        end 
      ack1:
        begin
//          if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
//            begin
//              state    <= high_addr_byte;
//              data     <= 8'b00000000;  //高字節地址準備
//              link_sda <= 1;
//            end
          if((scl==1)&&(cnt==7'd61)) 
            begin
              state    <= high_addr_byte; 
              data     <= 8'b00000000;  //高字節地址準備
//              link_sda <= 1;
            end
        end
      high_addr_byte:
        begin
          if(cnt==7'd124)
            begin
              link_sda <= 1;
            end
          if((cnt_num<4'd8)&&(cnt==7'd124))
            begin 
              cnt_num  <= cnt_num + 1;  
              sda_buf  <= data[7];
              data     <= {data[6:0],data[7]};
            end
          else if((cnt_num==4'd8)&&(cnt==7'd124))
            begin
              state    <= ack2;
              link_sda <= 0;
              cnt_num  <= 0;
            end
        end
      ack2:
        begin
//          if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
//            begin
//              state    <= low_addr_byte;
//              data     <= 8'b00000000;  //低字節地址準備
//              link_sda <= 1;
//            end
         if((scl==1)&&(cnt==7'd61))
           begin
             state    <= low_addr_byte;
             data     <= 8'b00000000;  //低字節地址準備
//             link_sda <= 1;
           end 
         end
      low_addr_byte:
        begin
          if(cnt==7'd124)
            begin
              link_sda <= 1;
            end
          if((cnt_num<4'd8)&&(cnt==7'd124))
            begin
              cnt_num  <= cnt_num + 1;
              sda_buf  <= data[7];
              data     <= {data[6:0],data[7]};
            end
          else if((cnt_num==4'd8)&&(cnt==7'd124))
            begin
              state    <= ack3;
              link_sda <= 0;
              cnt_num  <= 0;
            end 
        end
      ack3:
        begin
//          if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
//            begin
//              link_sda <= 1;          
//              if(wr==1)
//                begin
//                  state    <= transfer_data;
//                  data     <= 8'b10101010;//準備想要寫入的數據
//                end               
//              if(rd==1)
//                begin
//                  state    <= start2;
//                  sda_buf  <= 1;//準備再次發啓動信號
//                end
//            end 
          if((scl==1)&&(cnt==7'd61))
            begin
//              link_sda <= 1;
              if(wr==1)
                begin
                  state    <= transfer_data;
                  data     <= 8'b10101010;//準備想要寫入的數據
                end
              if(rd==1)
                begin
                  state    <= start2;
                  sda_buf  <= 1;//準備再次發啓動信號
                end 
            end             
        end
      start2:
        begin
          if(cnt==7'd124)
            begin
              link_sda <= 1;
            end
          if((scl==1)&&(cnt==7'd61))
            begin
              state    <= control_byte2;              
              sda_buf  <= 0;
              data     <= 8'b10100001;  //讀控制字準備
            end 
        end    
      control_byte2:
        begin
          if((cnt_num<4'd8)&&(cnt==7'd124))
            begin                           
              cnt_num  <= cnt_num + 1;
              sda_buf  <= data[7];
              data     <= {data[6:0],data[7]};
            end
          else if((cnt_num==4'd8)&&(cnt==7'd124))
            begin
              state    <= ack4;
              link_sda <= 0;
              cnt_num  <= 0;
            end
        end
      ack4:
        begin
//          if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
//            begin
//              state    <= transfer_data;
//              data     <= 8'b00000000;  //低字節地址準備
//              link_sda <= 0;
//            end 
          if((scl==1)&&(cnt==7'd61))
            begin
              state    <= transfer_data;
              data     <= 8'b00000000;  //低字節地址準備
//              link_sda <= 0;
            end
        end
      transfer_data:
        begin
          if(wr==1)
            begin
              if(cnt==7'd124)
                begin
                  link_sda <= 1;
                end
              if((cnt_num<4'd8)&&(cnt==7'd124))
                begin                           
                  cnt_num  <= cnt_num + 1;
                  sda_buf  <= data[7];
                  data     <= {data[6:0],data[7]};
                end
              else if((cnt_num==4'd8)&&(cnt==7'd124))
                begin
                  state    <= ack5;
                  link_sda <= 0;
                  cnt_num  <= 0;
                  wr       <= 0;
                  led      <= 0;
                end
            end 
          if(rd==1) 
            begin
              if(cnt==7'd124)
                begin
                  link_sda <= 0;
                end
              if((cnt_num<4'd8)&&(cnt==7'd124))
                begin                           
                  cnt_num  <= cnt_num + 1;
                  result   <= {result[6:0],sda};         
                end
              else if((cnt_num==4'd8)&&(cnt==7'd124))
                begin
                  state    <= no_ack;
                  link_sda <= 1;
                  cnt_num  <= 0;
                  sda_buf  <= 1;
                  rd       <= 0;
                end
            end
        end
      ack5:
        begin
//          if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
//            begin
//              state    <= stop;
//                link_sda <= 1;             
//            end
          if((scl==1)&&(cnt==7'd61))
            begin
              state    <= stop; 
//              link_sda <= 1;
            end
        end
      no_ack:
        begin
          if(cnt==7'd124)
            begin
              state    <= stop;
              link_sda <= 1; 
              sda_buf  <= 0;
            end
        end
      stop:
        begin
          if(cnt==7'd124)
            begin
              link_sda <= 1;
            end
          if((scl==1)&&(cnt==7'd61))
            begin
              sda_buf  <= 1;
              state    <= IDLE; 
            end
        end
      default:state<=0;
    endcase  
          

endmodule

複製代碼

  說明:由於仿真中沒有嵌入EEPROM仿真模型,因此,無法給出ACK應答信號,沒有應答信號,狀態機就沒辦法繼續向下跳轉。所以爲了完成仿真,就在代碼中屏蔽了所有的ACK檢測。在仿真中,只要看本該出現ACK信號的時候,sda信號是不是藍色高組態。如果是高組態就表示仿真沒有問題。在實際工程中,只需要把代碼中屏蔽掉的if語句放開,把對應的if語句(仿真用的)屏蔽掉就可以直接用了