• 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 的连接, 模块例化有利于简化每一个模块的代码,易于维护和修改。

     

  • 相关阅读:
    Boot.ini
    CCP4 SET
    Install GTK+ GLIB
    C head file
    Changes in Python
    ubuntu
    错误: 配置节中设置 validateRequest=false 可以禁用请求验证
    c++中冒号(:)和双冒号(::)的用法
    C++学习之类和结构体
    C++中双冒号的作用
  • 原文地址:https://www.cnblogs.com/caiya/p/12986991.html
Copyright © 2020-2023  润新知