IIC通信协议

目录

一、IIC简介

二、IIC传输方式

 三、IIC时序

3.1 IIC整体时序

3.2 IIC数据传输和应答

3.3 器件地址

3.4 寄存器地址

四、IIC读写

4.1 IIC单次写时序

4.2 IIC连续写时序(页写时序)

4.3 IIC单次读时序

4.4 IIC连续读时序

五、IIC控制器设计


一、IIC简介

IIC即Inter-Integrated Circuit(集成电路总线),是一种双向、半双工、二线制串行通信总线。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。

二、IIC传输方式

IIC总线由数据线SDA时钟线SCL构成通信线路,可用于收发数据。

IIC中控制SCL的电平高低变换的为主设备,主要产生时钟、起始信号和停止信号

IIC从设备主要进行地址、停止位检测

IIC支持多主控, 任何一个能够进行发送和接收的设备都可以成为主设备,但同时只能有一个主控。

主设备和从设备之间以8bit即一个字节(先发送字节的最高位)进行双向数据传送,各种被控器件均并联在总线上,通过器件地址识别(每个器件都有唯一的地址识别)。

IIC在物理上由SDASCL上拉电阻组成。

由于IIC器件一般采用开漏结构与总线相连(为了避免通信设备和空闲设备之间出现总线信号混乱),所以当总线空闲时,SCL和SDA均需接上拉电阻处于高电平状态

 三、IIC时序

3.1 IIC整体时序

在IIC器件开始通信(传输数据)之前,串行时钟线SCL和串行数据线SDA 上拉处于高电平状态,此时IIC总线处于空闲状态

如果主机开始传输数据,只需在SCL为高电平时将SDA线拉低,产生一个起始信号,从机检测到起始信号后,准备接收数据(主机可以向从机写数据,也可以读取从机输出的数据,数据的传输由双向数据线(SDA) 完成)。

当数据传输完成,主机产生一个停止信号,告诉从机数据传输结束,停止信号的产生是在SCL为高电平时,SDA 从低电平跳变到高电平,从机检测到停止信号后,停止接收数据,总线再次处于空闲状态。

总结:

总线空闲状态:SDA 为高电平,SCL 为高电平
IIC协议起始位:SCL为高电平时,SDA 出现下降沿,产生一个起始位
IIC协议结束位:SCL为高电平时,SDA 出现上升沿,产生一个结束位

3.2 IIC数据传输和应答

在起始信号之后,主机开始发送传输的数据:在串行时钟线SCL为低电平状态时,SDA允许改变传输的数据位,在 SCL 为高电平状态时,SDA 要求保持稳定,相当于一个时钟周期传输 1bit 数据,经过 8个时钟周期后,传输了 8bit数据,即一个字节。第8个时钟周期末,主机释放SDA以使从机应答,在第 9个时钟周期,从机将 SDA 拉低以应答;如果第 9 个时钟周期,SCL 为高电平时,SDA 未被检测到为低电平,视为非应答,表明此次数据传输失败,主机可以决定是否放弃写入或者重新发起写入。第 9个时钟周期末,从机释放 SDA 以使主机继续传输数据,如果主机发送停止信号,此次传输结束。

总结:

数据传输时SCL为低电平时,SDA允许改变;SCL为高电平时,SDA保持稳定

3.3 器件地址

主机往总线上发送地址,所有的从机都能接收到主机发出的地址,然后每个从机都将主机发出的地址与自己的地址比较,如果匹配上了,这个从机就会向总线发出一个响应信号。主机收到响应信号后,与从机建立通信兵向总线上发送数据。如果主机没有收到响应信号,则表示寻址失败。

通常情况下,主从器件的角色是确定的,也就是说从机一直工作在从机模式。每个IIC器件都有一个器件地址,有些器件的器件地址是固定的,而有些IIC器件的器件地址由个固定部分和一个可编程的部分构成,这是因为很可能在一个系统中有几个同样的器件,器件地址的可编程能最大数量的使这些器件连接到IIC总线上。

进行数据传输时,主机首先向总线上发出开始信号,对应开始位S,然后按照从高到低的位序发送器件地址,一般为7bit,第8bit位为读写控制位R/W,该位为0时表示主机对从机进行写操作,当该位为1时表示主机对从机进行读操作,然后接收从机响应。

3.4 寄存器地址

某些器件内部会有一些可供读写的寄存器或存储器,要对某个器件的存储单元进行读写时,就需要指定存储单元的地址即字地址,然后再向该地址写入内容。

该地址为一个或两个字节长度,在主机收到从机返回的器件响应后由主机发出,具体长度由器件内部的存储单元的数量决定,当存储单元数量不超过一个字节所能表示的最大数量(2^8=256)时,用一个字节表示,超过一个字节所能表示的最大数量时,就需要用两个字节来表示。

1.单字节字地址

 2.双字节字地址

四、IIC读写

写时序可概括为:start + 器件地址 + 写命令(0) + 字地址 + 数据 (+ 数据 + ... ) + stop

读时序可概括为:start + 器件地址 + 写命令(0) + 字地址 + start + 器件地址 + 读命令(1) + 接收从机的数据 + 主机非应答(1) + stop

4.1 IIC单次写时序

单次写入数据过程如下

主机拉低SDA发送起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机读取从机应答信号;
读取应答信号成功,主机传输1字节字寄存器地址数据;
主机读取从机应答信号;
读取应答信号成功,对于1字节寄存器地址器件,主机传输待写入的数据;对于两字节字地址器件,传输地址数据低字节,主机读取从机应答信号,读取应答信号成功,主机传输待写入的数据;
主机读取从机应答信号;
读取应答信号成功,主机产生 STOP 位,终止传输。

4.2 IIC连续写时序(页写时序)

连续写(也称页写,IIC连续写时序仅部分器件支持) 是主机连续写多个字节数据到从机,连续多字节写操作也是分为1字节地址段器件和2字节地址段器件的写操作。

连续写入多字节数据过程如下:

主机拉低SDA发送起始信号;
主机传输器件地址字节,其中最低位为0,表明为写操作;
主机读取从机应答信号;
读取应答信号成功,主机传输1字节地址数据;
主机读取从机应答信号;
读取应答信号成功,对于1字节地址段器件,传输待写入的第1个数据;对于两字节地址段器件,传输低字节地址数据,主机读取从机应答信号,读取应答信号成功,主机传输待写入的数据;
主机读取从机应答信号;
读取应答信号成功,传输待写入的下一个数据;
直到写完N个数据,主机读取从机应答信号;
读取应答信号成功后,主机产生 STOP 位,终止传输。

4.3 IIC单次读时序

读时序发送完器件地址和字地址后又发送起始信号和器件地址,第一次发送器件地址后面的读写控制位为“0”,第二次发送器件地址时后面的读写控制位为“1”。

因为需要使从机内的存储单元地址指针指向想要读取的存储单元地址处,所以首先发送了一次Dummy Write也就是虚写操作,并不是真的要写数据,而是通过这种虚写操作使地址指针指向字地址,等从机应答后,就可以以当前地址读的方式读数据。

单次读数据过程如下:


主机拉低SDA发送起始信号;
主机传输器件地址字节,其中最低位为0,表明为写操作:
主机取从机应答信号;
读取应答信号成功,主机传输1字节字地址数据;
主机读取从机应答信号;
读取应答信号成功,对于1字节地址段器件,无此步骤;对于两字节地址段器件,主机传输低字节地址数据,主机读取从机应答信号,读取应答信号成功;
主机发起起始信号;
主机传输器件地址字节,其中最低位为1,表明为读操作;
主机读取从机应答信号;
读取应答信号成功,主机读取SDA总线上的一个字节的数据;
产生无应答1信号(无需设置为输出高电平,因为总线会被自动拉高);
主机产生STOP位,终止传输。

4.4 IIC连续读时序

连续读将单次读中的主机非应答改为主机应答,表示继续读取数据,直到数据读完发送主机非应答信号。

五、IIC控制器设计

状态机的状态跳转总共有 8个状态,一开始状态机处于空闲状态idle,当IIC触发执行信号触发 (i2c exec=1)时,状态机进入发送控制命令状态sladdr;发送完控制命令后就发送字地址。通过bit_ctl 信号判断是单字节还是双字节字地址。对于双字节的字地址先发送高8位即第一个字节,发送完高8位后进入发送 8 位字地址状态addr8也就是发送双字节地地址的低 8 位;对于单字节的字地址直接进入发送 8 位字地址状态addr8。发送完字地址后,根据读写判断标志来判断是读操作还是写操作。如果是写 (wr_flag=0) 就进入写数据状态data_wr,开始发送数据;如果是读 (wr_flag=1)就进入发送器件地址读状态addr_rd 发送器件地址,此状态结束后就进入读数据状态data_rd 接受数据。读或写数据结束后就进入结束IIC操作状态done并发送结束信号,此时,IIC总线再次进入空闲状态idle。

参考自正点原子

module i2c_dri
    #(
      parameter   SLAVE_ADDR = 7'b1010000   ,  //从机器件地址
      parameter   CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率
      parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率
    )
   (                                                         
    input                clk        ,  //时钟  
    input                rst_n      ,  //复位,低电平有效
                                         
    //IIC control               
    input                i2c_exec   ,  //I2C触发执行信号
    input                bit_ctrl   ,  //字地址位控制,为1时字地址16位、为0时字地址8位
    input                i2c_rh_wl  ,  //I2C读写控制信号
    input        [15:0]  i2c_addr   ,  //I2C器件字地址
    input        [ 7:0]  i2c_data_w ,  //I2C要写的数据
    inout                sda        ,  //I2C的SDA信号	 
	 
	 //IIC output
    output  reg  [ 7:0]  i2c_data_r ,  //I2C读出的数据
    output  reg          i2c_done   ,  //I2C一次操作完成
    output  reg          i2c_ack    ,  //I2C应答标志 0:应答 1:未应答
    output  reg          scl        ,  //I2C的SCL时钟信号
                                
    //user interface                   
    output  reg          dri_clk       //驱动I2C操作的驱动时钟
     );

//localparam define
localparam  idle     = 8'b0000_0001; //空闲状态
localparam  sladdr   = 8'b0000_0010; //发送器件地址(slave address)
localparam  addr16   = 8'b0000_0100; //发送字地址高8位
localparam  addr8    = 8'b0000_1000; //发送字地址低8位
localparam  data_wr  = 8'b0001_0000; //写数据(8bit)
localparam  addr_rd  = 8'b0010_0000; //发送器件地址
localparam  data_rd  = 8'b0100_0000; //读数据(8bit)
localparam  stop     = 8'b1000_0000; //结束I2C操作

//reg/wire define
reg            sda_dir   ; //I2C数据(SDA)方向控制
wire           sda_in    ; //SDA输入信号
reg            sda_out   ; //SDA输出信号
reg            st_done   ; //状态结束
reg            wr_flag   ; //读写标志
reg    [ 6:0]  cnt       ; //计数
reg    [ 7:0]  cur_state ; //状态机当前状态
reg    [ 7:0]  next_state; //状态机下一状态
reg    [15:0]  addr_t    ; //字地址临时寄存
reg    [ 7:0]  data_r    ; //读取的数据
reg    [ 7:0]  data_wr_t ; //I2C需写的数据的临时寄存
reg    [ 9:0]  clk_cnt   ; //分频时钟计数
wire   [ 8:0]  clk_divide; //模块驱动时钟的分频系数



//*****************************************************
//**                    main code
//*****************************************************

//SDA控制
assign  sda     = sda_dir?sda_out : 1'bz;                  //SDA信号处于数据输出或高阻态
assign  sda_in  = sda ;                                    //SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;          //模块驱动时钟的分频系数

//时钟信号SCL通过计数器方法产生,生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
//将输入时钟clk分频为clk_divide倍频率,并输出到dri_clk信号上。这样可以产生一个驱动I2C操作的时钟信号,其频率是I2C_SCL时钟频率的clk_divide倍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        dri_clk <=  1'b0;
        clk_cnt <= 10'd0;
    end
    else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;                               //在每个分频周期结束时,切换 dri_clk 的电平状态(从高到低或从低到高),实现分频时钟的频率。
    end
    else
        clk_cnt <= clk_cnt + 1'b1;
end

//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= idle;
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = idle;                       //一开始处于空闲状态
    case(cur_state)
        idle: begin
           if(i2c_exec) begin                //当IIC触发执行信号i2c_exec
               next_state = sladdr;          //状态机进入发送从机器件地址
           end
           else
               next_state = idle;
        end
        sladdr: begin
            if(st_done) begin                //如果上一状态结束
                if(bit_ctrl)                 //判断是16位还是8位字地址
                   next_state = addr16;      //双字节地址就进入高8位字地址状态
                else
                   next_state = addr8 ;      //单字节地址就进入低8位字地址状态
            end
            else
                next_state = sladdr;
        end
        addr16: begin
            if(st_done) begin                
                next_state = addr8;          //发完高8位再进入低8位字地址状态
            end
            else begin
                next_state = addr16;
            end
        end
        addr8: begin                         //8位字地址
            if(st_done) begin
                if(wr_flag==1'b0)            //读写判断
                    next_state = data_wr;    //wr_flag为0则进入写数据状态
                else
                    next_state = addr_rd;    //wr_flag为1则进入读数据状态
            end
            else begin
                next_state = addr8;
            end
        end
        data_wr: begin                       //写数据(8 bit)
            if(st_done)
                next_state = stop;           //写完就停止
            else
                next_state = data_wr;
        end
        addr_rd: begin                       
            if(st_done) begin
                next_state = data_rd;        //发送器件地址读状态结束,进入读数据状态
            end
            else begin
                next_state = addr_rd;
            end
        end
        data_rd: begin                       //读取数据(8 bit)
            if(st_done)
                next_state = stop;           //读完就停止
            else
                next_state = data_rd;
        end
        stop: begin
            if(st_done)
                next_state = idle;           //结束I2C操作,进入空闲状态
            else
                next_state = stop ;
        end
        default: next_state= idle;
    endcase
end

//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n) begin                                    //复位初始化
        scl       <= 1'b1;
        sda_out   <= 1'b1;
        sda_dir   <= 1'b1;                          
        i2c_done  <= 1'b0;                          
        i2c_ack   <= 1'b0;                          
        cnt       <= 1'b0;                          
        st_done   <= 1'b0;                          
        data_r    <= 1'b0;                          
        i2c_data_r<= 1'b0;                          
        wr_flag   <= 1'b0;                          
        addr_t    <= 1'b0;                          
        data_wr_t <= 1'b0;                          
    end                                              
    else begin                                       
        st_done <= 1'b0 ;                            
        cnt     <= cnt +1'b1 ;                       
        case(cur_state)                              
             idle: begin                                //空闲状态
                scl     <= 1'b1;                        
                sda_out <= 1'b1;                        
                sda_dir <= 1'b1;                        
                i2c_done<= 1'b0;                        
                cnt     <= 7'b0;                        
                if(i2c_exec) begin                      //当IIC触发执行信号i2c_exec             
                    wr_flag   <= i2c_rh_wl ;            //i2c_rh_wl读写控制信号赋值给读写标志      
                    addr_t    <= i2c_addr  ;            //i2c_addr器件字地址赋值临时寄存
                    data_wr_t <= i2c_data_w;            //I2C需写的数据赋值给临时寄存
                    i2c_ack <= 1'b0;                    //I2C应答标志置为未应答   
                end                                     
            end
            //通过计数器控制SCL的高低电平,SCL高电平时SDA保持稳定,SCL低电平时SDA允许变化				
            sladdr: begin                               //写地址(器件地址和字地址)
                case(cnt)                            
                    7'd1 : sda_out <= 1'b0;             //开始I2C
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6];    //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b0;             //0:写
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;             
                        sda_out <= 1'b1;                         
                    end                              
                    7'd37: scl     <= 1'b1;            
                    7'd38: begin                       //从机应答 
                        st_done <= 1'b1;
                        if(sda_in == 1'b1)             //高电平表示未应答
                            i2c_ack <= 1'b1;           //拉高应答标志位     
                    end                                          
                    7'd39: begin                     
                        scl <= 1'b0;                   //SCL拉低
                        cnt <= 1'b0;                   //计数器清零
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            addr16: begin                         
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1 ;            
                        sda_out <= addr_t[15];       //传送字地址高8位
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[14];    
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[13];    
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[12];    
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[11];    
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[10];    
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[9];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[8];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;             
                        sda_out <= 1'b1;   
                    end                              
                    7'd33: scl  <= 1'b1;             
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end        
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            addr8: begin                          
                case(cnt)                            
                    7'd0: begin                      
                       sda_dir <= 1'b1 ;             
                       sda_out <= addr_t[7];         //字地址低8位
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[6];     
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[5];     
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[4];     
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[3];     
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[2];     
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[1];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[0];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;         
                        sda_out <= 1'b1;                    
                    end                              
                    7'd33: scl     <= 1'b1;          
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            data_wr: begin                           //写数据(8 bit)
                case(cnt)                            
                    7'd0: begin                      
                        sda_out <= data_wr_t[7];     //I2C写8位数据
                        sda_dir <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= data_wr_t[6];  
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= data_wr_t[5];  
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= data_wr_t[4];  
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= data_wr_t[3];  
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= data_wr_t[2];  
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= data_wr_t[1];  
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= data_wr_t[0];  
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;           
                        sda_out <= 1'b1;                              
                    end                              
                    7'd33: scl <= 1'b1;              
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end          
                    7'd35: begin                     
                        scl  <= 1'b0;                
                        cnt  <= 1'b0;                
                    end                              
                    default  :  ;                    
                endcase                              
            end                                      
            addr_rd: begin                           //写地址以进行读数据
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd2 : sda_out <= 1'b0;          //重新开始
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b1;          //1:读
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;            
                        sda_out <= 1'b1;                    
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default : ;
                endcase
            end
            data_rd: begin                        //读取数据(8 bit)
                case(cnt)
                    7'd0: sda_dir <= 1'b0;
                    7'd1: begin
                        data_r[7] <= sda_in;
                        scl       <= 1'b1;
                    end
                    7'd3: scl  <= 1'b0;
                    7'd5: begin
                        data_r[6] <= sda_in ;
                        scl       <= 1'b1   ;
                    end
                    7'd7: scl  <= 1'b0;
                    7'd9: begin
                        data_r[5] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd11: scl  <= 1'b0;
                    7'd13: begin
                        data_r[4] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd15: scl  <= 1'b0;
                    7'd17: begin
                        data_r[3] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd19: scl  <= 1'b0;
                    7'd21: begin
                        data_r[2] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd23: scl  <= 1'b0;
                    7'd25: begin
                        data_r[1] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd27: scl  <= 1'b0;
                    7'd29: begin
                        data_r[0] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd31: scl  <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;          //非应答
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                        i2c_data_r <= data_r;
                    end
                    default  :  ;
                endcase
            end
            stop: begin                           //结束I2C操作
                case(cnt)
                    7'd0: begin
                        sda_dir <= 1'b1;             //结束I2C
                        sda_out <= 1'b0;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1;
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 1'b0;
                        i2c_done <= 1'b1;            //向上层模块传递I2C结束信号
                    end
                    default  : ;
                endcase
            end
        endcase
    end
end

endmodule

小梅哥给出了另一种写法,注释有空再写

module i2c_control(
	Clk,
	Rst_n,
	
	wrreg_req,
	rdreg_req,
	addr,
	addr_mode,
	wrdata,
	rddata,
	device_id,
	RW_Done,
	
	ack,
	
	i2c_sclk,
	i2c_sdat
);

	input Clk;
	input Rst_n;
	
	input wrreg_req;
	input rdreg_req;
	input [15:0]addr;
	input addr_mode;
	input [7:0]wrdata;
	output reg[7:0]rddata;
	input [7:0]device_id;
	output reg RW_Done;
	
	output reg ack;

	output i2c_sclk;
	inout i2c_sdat;
	
	reg [5:0]Cmd;
	reg [7:0]Tx_DATA;
	wire Trans_Done;
	wire ack_o;
	reg Go;
	wire [15:0] reg_addr;
	
	assign reg_addr = addr_mode?addr:{addr[7:0],addr[15:8]};
	
	wire [7:0]Rx_DATA;
	
	localparam 
		WR   = 6'b000001,   //写请求
		STA  = 6'b000010,   //起始位请求
		RD   = 6'b000100,   //读请求
		STO  = 6'b001000,   //停止位请求
		ACK  = 6'b010000,   //应答位请求
		NACK = 6'b100000;   //无应答请求
	
	i2c_bit_shift i2c_bit_shift(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.Cmd(Cmd),
		.Go(Go),
		.Rx_DATA(Rx_DATA),
		.Tx_DATA(Tx_DATA),
		.Trans_Done(Trans_Done),
		.ack_o(ack_o),
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
	
	reg [6:0]state;
	reg [7:0]cnt;
	
	localparam
		IDLE         = 7'b0000001,   //空闲状态
		WR_REG       = 7'b0000010,   //写寄存器状态
		WAIT_WR_DONE = 7'b0000100,   //等待写寄存器完成状态
		WR_REG_DONE  = 7'b0001000,   //写寄存器完成状态
		RD_REG       = 7'b0010000,   //读寄存器状态
		WAIT_RD_DONE = 7'b0100000,   //等待读寄存器完成状态
		RD_REG_DONE  = 7'b1000000;   //读寄存器完成状态
	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		Cmd <= 6'd0;
		Tx_DATA <= 8'd0;
		Go <= 1'b0;
		rddata <= 0;
		state <= IDLE;
		ack <= 0;
	end
	else begin
		case(state)
			IDLE:
				begin
					cnt <= 0;
					ack <= 0;
					RW_Done <= 1'b0;					
					if(wrreg_req)
						state <= WR_REG;
					else if(rdreg_req)
						state <= RD_REG;
					else
						state <= IDLE;
				end
			
			WR_REG:
				begin
					state <= WAIT_WR_DONE;
					case(cnt)
						0:write_byte(WR | STA, device_id);
						1:write_byte(WR, reg_addr[15:8]);
						2:write_byte(WR, reg_addr[7:0]);
						3:write_byte(WR | STO, wrdata);
						default:;
					endcase
				end
			
			WAIT_WR_DONE:
				begin
					Go <= 1'b0; 
					if(Trans_Done)begin
						ack <= ack | ack_o;
						case(cnt)
							0: begin cnt <= 1; state <= WR_REG;end
							1: 
								begin 
									state <= WR_REG;
									if(addr_mode)
										cnt <= 2; 
									else
										cnt <= 3;
								end
									
							2: begin
									cnt <= 3;
									state <= WR_REG;
								end
							3:state <= WR_REG_DONE;
							default:state <= IDLE;
						endcase
					end
				end
			
			WR_REG_DONE:
				begin
					RW_Done <= 1'b1;
					state <= IDLE;
				end
				
			RD_REG:
				begin
					state <= WAIT_RD_DONE;
					case(cnt)
						0:write_byte(WR | STA, device_id);
						1:write_byte(WR, reg_addr[15:8]);
						2:write_byte(WR, reg_addr[7:0]);
						3:write_byte(WR | STA, device_id | 8'd1);
						4:read_byte(RD | NACK | STO);
						default:;
					endcase
				end
				
			WAIT_RD_DONE:
				begin
					Go <= 1'b0; 
					if(Trans_Done)begin
						if(cnt <= 3)
							ack <= ack | ack_o;
						case(cnt)
							0: begin cnt <= 1; state <= RD_REG;end
							1: 
								begin 
									state <= RD_REG;
									if(addr_mode)
										cnt <= 2; 
									else
										cnt <= 3;
								end
									
							2: begin
									cnt <= 3;
									state <= RD_REG;
								end
							3:begin
									cnt <= 4;
									state <= RD_REG;
								end
							4:state <= RD_REG_DONE;
							default:state <= IDLE;
						endcase
					end
				end
				
			RD_REG_DONE:
				begin
					RW_Done <= 1'b1;
					rddata <= Rx_DATA;
					state <= IDLE;				
				end
			default:state <= IDLE;
		endcase
	end
	
	task read_byte;
		input [5:0]Ctrl_Cmd;
		begin
			Cmd <= Ctrl_Cmd;
			Go <= 1'b1; 
		end
	endtask
	
	task write_byte;
		input [5:0]Ctrl_Cmd;
		input [7:0]Wr_Byte_Data;
		begin
			Cmd <= Ctrl_Cmd;
			Tx_DATA <= Wr_Byte_Data;
			Go <= 1'b1; 
		end
	endtask

endmodule
module i2c_bit_shift(
	Clk,
	Rst_n,
	
	Cmd,
	Go,
	Rx_DATA,
	Tx_DATA,
	Trans_Done,
	ack_o,
	i2c_sclk,
	i2c_sdat
);
	input Clk;
	input Rst_n;
	
	input [5:0]Cmd;
	input Go;
	output reg[7:0]Rx_DATA;
	input [7:0]Tx_DATA;
	output reg Trans_Done;
	output reg ack_o;
	output reg i2c_sclk;
	inout i2c_sdat;
	
	reg i2c_sdat_o;

	//系统时钟采用50MHz
	parameter SYS_CLOCK = 50_000_000;
	//SCL总线时钟采用400kHz
	parameter SCL_CLOCK = 400_000;
	//产生时钟SCL计数器最大值
	localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK/4 - 1;
	
	reg i2c_sdat_oe;
	
	localparam 
		WR   = 6'b000001,   //写请求
		STA  = 6'b000010,   //起始位请求
		RD   = 6'b000100,   //读请求
		STO  = 6'b001000,   //停止位请求
		ACK  = 6'b010000,   //应答位请求
		NACK = 6'b100000;   //无应答请求
		
	reg [19:0]div_cnt;
	reg en_div_cnt;
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		div_cnt <= 20'd0;
	else if(en_div_cnt)begin
		if(div_cnt < SCL_CNT_M)
			div_cnt <= div_cnt + 1'b1;
		else
			div_cnt <= 0;
	end
	else
		div_cnt <= 0;

	wire sclk_plus = div_cnt == SCL_CNT_M;
	
	//assign i2c_sdat = i2c_sdat_oe?i2c_sdat_o:1'bz;
	assign i2c_sdat = !i2c_sdat_o && i2c_sdat_oe ? 1'b0:1'bz;
		
	reg [7:0]state;
	
	localparam
		IDLE      = 8'b00000001,   //空闲状态
		GEN_STA   = 8'b00000010,   //产生起始信号
		WR_DATA   = 8'b00000100,   //写数据状态
		RD_DATA   = 8'b00001000,   //读数据状态
		CHECK_ACK = 8'b00010000,   //检测应答状态
		GEN_ACK   = 8'b00100000,   //产生应答状态
		GEN_STO   = 8'b01000000;   //产生停止信号
		
	reg [4:0]cnt;
		
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		Rx_DATA <= 0;
		i2c_sdat_oe <= 1'd0;
		en_div_cnt <= 1'b0;
		i2c_sdat_o <= 1'd1;
		Trans_Done <= 1'b0;
		ack_o <= 0;
		state <= IDLE;
		cnt <= 0;
	end
	else begin
		case(state)
			IDLE:
				begin
					Trans_Done <= 1'b0;
					i2c_sdat_oe <= 1'd1;
					if(Go)begin
						en_div_cnt <= 1'b1;
						if(Cmd & STA)
							state <= GEN_STA;
						else if(Cmd & WR)
							state <= WR_DATA;
						else if(Cmd & RD)
							state <= RD_DATA;
						else
							state <= IDLE;
					end
					else begin
						en_div_cnt <= 1'b0;
						state <= IDLE;
					end
				end
				
			GEN_STA:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0:begin i2c_sdat_o <= 1; i2c_sdat_oe <= 1'd1;end
							1:begin i2c_sclk <= 1;end
							2:begin i2c_sdat_o <= 0; i2c_sclk <= 1;end
							3:begin i2c_sclk <= 0;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							if(Cmd & WR)
								state <= WR_DATA;
							else if(Cmd & RD)
								state <= RD_DATA;
						end
					end
				end
				
			WR_DATA:
				begin
					if(sclk_plus)begin
						if(cnt == 31)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0,4,8,12,16,20,24,28:begin i2c_sdat_o <= Tx_DATA[7-cnt[4:2]]; i2c_sdat_oe <= 1'd1;end	//set data;
							1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end	//sclk posedge
							2,6,10,14,18,22,26,30:begin i2c_sclk <= 1;end	//sclk keep high
							3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end	//sclk negedge
/*							
							0 :begin i2c_sdat_o <= Tx_DATA[7];end
							1 :begin i2c_sclk <= 1;end	//sclk posedge
							2 :begin i2c_sclk <= 1;end	//sclk keep high
							3 :begin i2c_sclk <= 0;end	//sclk negedge
							
							4 :begin i2c_sdat_o <= Tx_DATA[6];end
							5 :begin i2c_sclk <= 1;end	//sclk posedge
							6 :begin i2c_sclk <= 1;end	//sclk keep high
							7 :begin i2c_sclk <= 0;end	//sclk negedge
							
							8 :begin i2c_sdat_o <= Tx_DATA[5];end
							9 :begin i2c_sclk <= 1;end	//sclk posedge
							10:begin i2c_sclk <= 1;end	//sclk keep high
							11:begin i2c_sclk <= 0;end	//sclk negedge
							
							12:begin i2c_sdat_o <= Tx_DATA[4];end
							13:begin i2c_sclk <= 1;end	//sclk posedge
							14:begin i2c_sclk <= 1;end	//sclk keep high
							15:begin i2c_sclk <= 0;end	//sclk negedge
							
							16:begin i2c_sdat_o <= Tx_DATA[3];end
							17:begin i2c_sclk <= 1;end	//sclk posedge
							18:begin i2c_sclk <= 1;end	//sclk keep high
							19:begin i2c_sclk <= 0;end	//sclk negedge
							
							20:begin i2c_sdat_o <= Tx_DATA[2];end
							21:begin i2c_sclk <= 1;end	//sclk posedge
							22:begin i2c_sclk <= 1;end	//sclk keep high
							23:begin i2c_sclk <= 0;end	//sclk negedge	
							
							24:begin i2c_sdat_o <= Tx_DATA[1];end
							25:begin i2c_sclk <= 1;end	//sclk posedge
							26:begin i2c_sclk <= 1;end	//sclk keep high
							27:begin i2c_sclk <= 0;end	//sclk negedge	
							
							28:begin i2c_sdat_o <= Tx_DATA[0];end
							29:begin i2c_sclk <= 1;end	//sclk posedge
							30:begin i2c_sclk <= 1;end	//sclk keep high
							31:begin i2c_sclk <= 0;end	//sclk negedge
*/							
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 31)begin
							state <= CHECK_ACK;
						end
					end
				end
				
			RD_DATA:
				begin
					if(sclk_plus)begin
						if(cnt == 31)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0,4,8,12,16,20,24,28:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end	//set data;
							1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end	//sclk posedge
							2,6,10,14,18,22,26,30:begin i2c_sclk <= 1; Rx_DATA <= {Rx_DATA[6:0],i2c_sdat};end	//sclk keep high
							3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end	//sclk negedge						
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 31)begin
							state <= GEN_ACK;
						end
					end
				end
			
			CHECK_ACK:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end
							1:begin i2c_sclk <= 1;end
							2:begin ack_o <= i2c_sdat; i2c_sclk <= 1;end
							3:begin i2c_sclk <= 0;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							if(Cmd & STO)
								state <= GEN_STO;
							else begin
								state <= IDLE;
								Trans_Done <= 1'b1;
							end								
						end
					end
				end
			
			GEN_ACK:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0:begin 
									i2c_sdat_oe <= 1'd1;
									i2c_sclk <= 0;
									if(Cmd & ACK)
										i2c_sdat_o <= 1'b0;
									else if(Cmd & NACK)
										i2c_sdat_o <= 1'b1;
								end
							1:begin i2c_sclk <= 1;end
							2:begin i2c_sclk <= 1;end
							3:begin i2c_sclk <= 0;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							if(Cmd & STO)
								state <= GEN_STO;
							else begin
								state <= IDLE;
								Trans_Done <= 1'b1;
							end
						end
					end
				end
			
			GEN_STO:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0:begin i2c_sdat_o <= 0; i2c_sdat_oe <= 1'd1;end
							1:begin i2c_sclk <= 1;end
							2:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
							3:begin i2c_sclk <= 1;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							Trans_Done <= 1'b1;
							state <= IDLE;
						end
					end
				end
			default:state <= IDLE;
		endcase
	end
	
endmodule

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值