FPGA自学9——IIC总线操作EEPROM

1、IIC总线简介

        IIC是集成电路总线,是一种两线式的串行总线,由SDA数据线SCL时钟线构成的半双工通信方式。

  •        标准模式:100kbit / s
  •        快速模式:400kbit / s
  •        高速模式:3.4Mbit / s

 1.1 、IIC 总线的时序、通信协议

        IIC设备:闲置---->开始信号---->发送地址/应答---->发送数据/应答---->停止信号

  •         当数据线SDA时钟线SCL 都是高电平时,IIC总总线上的所有设备都是空闲状态
  •         当空闲状态数据线SDA数据线从高电平到低电平跳变时,被定义开始信号
  •         当时钟线SCL为 高电平、  数据线SDA 为低电平时,数 数据线SDA 据线从低到高的跳变被定义停止信号

                                       

                           开始信号                                                                       停止信号

           发送地址和 发送数据的过程:

        数据传输是在一个完整的时钟脉冲中进行的。在时钟线SCL 高电平的过程中,数据线SDA必须保持稳定不变,否者会被认为是控制信号。

        时钟线SCL 高电平是,数据线SDA低电平,那么当时钟线SCL降为低电平时,IIC设备收到的就是bit 0。同理 时钟线SCL 高电平是,数据线SDA高电平,那么当时钟线SCL升为高电平时,IIC设备收到的就是bit 1.

         每次传输的数据都是8位的,为确保每一个数据都被接收到,接收设备收到一个完整的数据后,会反馈一个应答信号,表示操作成功。

 下图是写IIC写操作EEPROM的时序图 

 2、FPGA程序设计

        使用芯片信号为AT24C64的EEPROM,在0x00地址写入0x55,在0xA0地址写入 0xAA,在0xB0地址写入0xA5;

        写完之后再从该地址读出数据进行对比,相等则LED常亮,否则LED闪烁。

      2.1、 程序模块划分:

2.2、 状态机

3、IIC底层驱动

module iic_div 
    #(
        parameter   SLAVE_ADDR = 7'b1010000   ,  //EEPROM从机地址
        parameter   CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率
        parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率
    )
    
    (
    input   clk ,   //本模块输入时钟
    input   rst_n,  //复位信号
    
    input   i2c_exec,   //IIC触发执行信号,本模块采集到此变量的高电平才会进行一次操作
    input   bit_ctrl,   //字地址位控制(16bit/8bit)   
    input   i2c_rh_wl,  //I2C读写控制信号  0:写操作   1:读操作              
    
    input        [15:0]  i2c_addr   ,  //I2C器件内地址     制定操作的地址,对于本实验就是只要EEPROM的要读写的地址
    input        [ 7:0]  i2c_data_w ,  //I2C写入的数据     向IIC从机写入的数据存储在这里
    output  reg  [ 7:0]  i2c_data_r ,  //I2C读出的数据     从IIC从机读出的数据存储在这里           
    
    output  reg          i2c_done   ,  //I2C一次操作完成    1:向上层模块传递I2C结束信号    
    output  reg          i2c_ack    ,  //I2C应答标志        0:应答 1:未应答    
    output  reg          scl        ,  //I2C的SCL时钟信号
    inout                sda        ,  //I2C的SDA信号
    
    output  reg          dri_clk       //驱动I2C操作的驱动时钟 本模块输入时钟clk分频,因为IIC的速率较低,
    //从机时钟使用的就是这个时钟
     );
//状态机、对应图中的执行步骤
localparam  st_idle     = 8'b0000_0001; //空闲状态

localparam  st_sladdr   = 8'b0000_0010; //发送器件地址(slave address)

localparam  st_addr16   = 8'b0000_0100; //发送16位字地址

localparam  st_addr8    = 8'b0000_1000; //发送8位字地址

localparam  st_data_wr  = 8'b0001_0000; //写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000; //发送器件地址读
localparam  st_data_rd  = 8'b0100_0000; //读数据(8 bit)
localparam  st_stop     = 8'b1000_0000; //结束I2C操作

//reg define
reg            sda_dir   ; //I2C数据(SDA)方向控制  sda_dir表示IIC数据方向,当为1时表示主机(FPGA)输出信号,为0时FPGA输出高组态,表示释放控制权
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 define
wire          sda_in     ; //SDA输入信号
wire   [8:0]  clk_divide ; //模块驱动时钟的分频系数

//SDA三态门控制
assign  sda     = sda_dir ?  sda_out : 1'bz;        //SDA数据输出或高阻  变量 sda是硬件连线
assign  sda_in  = sda ;                             //SDA数据输入

assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;   //模块驱动时钟的分频系数    >> 2'd2右移两位===除以4
//Sda数据线的数据 只能在scl时钟线的低电平时间内进行改变
//SDA数据线改变在SCL时钟线低电平的中间位置改变,那么驱动时钟dri_clk则需要是scl时钟线的4倍频,则dri_clk的值是CLK_FREQ/I2C_FREQ的4倍
//则分频系数则是 CLK_FREQ/I2C_FREQ/4



//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作,典型时钟分频电路,只适用于偶数分频
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_divide[8:1]:舍去clk_divide的最低位 相当于除以2 
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;    //计数到分频系数的一半则翻转一次IIC驱动时钟
    end
    else
        clk_cnt <= clk_cnt + 1'b1; //分频时钟计数 自增
end  
//假如clk_divide=10,则(clk_divide/2)-1=4, 当clk_cnt从0计数到4,dri_clk驱动信号翻转一次
//则一个分频系数10,dri_clk翻转了两次


//(三段式状态机)——————状态跳转 同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin //驱动时钟已经是dri_clk了 
    if(!rst_n)
        cur_state <= st_idle; //复位键按下,则当前状态为空闲状态
    else
        cur_state <= next_state; // 当状态跳转到下一个状态
end

//(三段式状态机)————下一个状态的判断   组合逻辑  判断状态转移条件
always @(*) begin 
     next_state = st_idle;//状态机下一状态  为 空闲状态
     //下面的代码没有更新next_state 则下次状态就为空闲状态
      case(cur_state) 
        st_idle:begin    //当前状态是空闲状态
            if(i2c_exec) //i2c_exec :IIC触发信号 高电平触发,由外部模块输入
                next_state=st_sladdr;//发送器件地址(slave address)
            else
                next_state=st_idle;//状态保持  
        end//当前是空闲状态下,检测到触发电平,进入发送  "发送器件地址(slave address)" 状态
        
        st_sladdr:begin//当前状态是 "发送器件地址(slave address)" 状态
            if(st_done)begin    //上个状态结束
                if(bit_ctrl)    //bit_ctrl:16位器件地址 还是8位器件地址  有外部模块输入
                    next_state=st_addr16;
                else
                    next_state=st_addr8;//跳过  st_addr16 状态
            end  
            else
               next_state = st_sladdr;  //状态保持  
        end        
        
        st_addr16:begin
            if(st_done) //上个状态结束
                next_state=st_addr8;
            else
                next_state=st_addr16;//状态保持 
        end
        
        st_addr8:begin
            if(st_done)begin    //上个状态结束
                if(wr_flag==1'b0)               //读写判断
                    next_state=st_data_wr;//写数据(8 bit)
                else
                 	next_state = st_addr_rd;    //发送器件地址读
            end
            else
                next_state=st_addr8;
        end
        
        st_data_wr: begin       //写数据(8 bit)
          if(st_done)   //上个状态结束
                next_state = st_stop;//结束I2C操作
            else
                next_state = st_data_wr;//状态保持 
        end
        st_addr_rd: begin                       //写地址以进行读数据
            if(st_done) begin
                next_state = st_data_rd;
            end
            else begin
                next_state = st_addr_rd;
            end
        end
        st_data_rd:begin//读数据(8 bit)
            if(st_done) //上个状态结束
                next_state=st_stop;//结束I2C操作
            else
                next_state=st_data_rd;//状态保持
        end
        
        st_stop: begin //结束I2C操作
            if(st_done) //上个状态结束
                next_state = st_idle; //空闲状态
            else
                next_state = st_stop;//状态保持
        end
       default: next_state= st_idle;//上面都不满足,下个状态为空闲状态
     endcase
end

//时序逻辑---描述状态输出
//时序逻辑---描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n) begin
      scl       <= 1'b1;//I2C的SCL时钟信号       本模块输出信号
      sda_out   <= 1'b1;//SDA输出信号           本模块输出信号
      sda_dir   <= 1'b1;//I2C数据(SDA)方向控制  sda_dir表示IIC数据方向,当为1时表示主机(FPGA)输出信号,为0时FPGA输出高组态,表示释放控制权                          
      i2c_done  <= 1'b0;//I2C一次操作完成      本模块输出信号                     
      i2c_ack   <= 1'b0;//I2C应答标志          本模块输出信号                         
      cnt       <= 1'b0;//计数                       
      st_done   <= 1'b0;//状态结束                          
      data_r    <= 1'b0;//读取的数据                        
      i2c_data_r<= 1'b0;//I2C读出的数据           本模块输出信号                     
      wr_flag   <= 1'b0;//写标志                         
      addr_t    <= 1'b0;//地址                         
      data_wr_t <= 1'b0;//I2C需写的数据的临时寄存                          
    end 
    else    begin
        st_done <= 1'b0 ;         //下面不更新这里一直是0                         
        cnt     <= cnt +1'b1 ;    //由下面操作清零   
        case(cur_state) //当前状态
            st_idle: begin  //当前状态是空闲状态
                scl=1'b1;//I2C的SCL时钟信号  拉高
                sda_out <= 1'b1; //SDA输出信号                     
                sda_dir <= 1'b1; //主机(FPGA)输出信号
                i2c_done<= 1'b0;                     
                cnt     <= 7'b0;  
                if(i2c_exec)begin //i2c_exec :IIC触发信号 高电平触发,由外部模块输入
                    wr_flag<=i2c_rh_wl;//写标志 =I2C读写控制信号i2c_rh_wl  由外部模块输入 
                    //由外部模块控制是读操作 还是写操作
                    
                    addr_t<=i2c_addr;//i2c_addr I2C器件内地址 由外部模块输入
                    //这一步是选择IIC器件地址     
                    
                    data_wr_t <= i2c_data_w;//i2c_data_w: I2C要写的数据  由外部模块输入
                    //缓存外部模块发来的数据
                end
            end
           st_sladdr:begin   //写地址(器件地址和字地址)
               case(cnt) 
               7'd1 :sda_out<=1'b0;
               7'd3 :scl<=1'b0;
               7'd4 :sda_out<=SLAVE_ADDR[6];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd5 :scl<=1'b1;  //SCL时钟线拉高
               7'd7 :scl<=1'b0;  //延时一个时钟后:SCL时钟线拉低
               7'd8 :sda_out<=SLAVE_ADDR[5];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd9 :scl<=1'b1;
               7'd11:scl<=1'b0;
               7'd12:sda_out<=SLAVE_ADDR[4];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd13:scl<=1'b1;
               7'd15:scl<=1'b0;
               7'd16:sda_out<=SLAVE_ADDR[3];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd17:scl<=1'b1;
               7'd19:scl<=1'b0;
               7'd20:sda_out<=SLAVE_ADDR[2];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd21:scl<=1'b1;
               7'd23:scl<=1'b0;
               7'd24:sda_out<=SLAVE_ADDR[1];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd25:scl<=1'b1;
               7'd27:scl<=1'b0;
               7'd28:sda_out<=SLAVE_ADDR[0];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               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控制权   
                  sda_out <= 1'b1;   ///数据线拉高
               end
               7'd37: scl     <= 1'b1;   
               7'd38: begin                 //从机应答 
               st_done <= 1'b1;             //操作完成,由上个组合逻辑切换下个状态(next_state),
                   if(sda_in == 1'b1)      //高电平表示未应答
                   i2c_ack <= 1'b1;        //拉高应答标志位
               end
               7'd39: begin                     
                  scl <= 1'b0;   //再由第一个状态机时序逻辑 切换当前状态(cur_state),所以这里滞后一个周期              
                  cnt <= 1'b0;    //清空计数              
               end 
                 default :  ;   
             endcase            
           end 
          st_addr16: begin                         
           case(cnt)                            
            7'd0 : begin                     
                sda_dir <= 1'b1 ;               //主机(FPGA)输出信号             
                sda_out <= addr_t[15];          //传送字地址 高八位
             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控制权            
                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 
        st_addr8: begin                          
          case(cnt)                            
            7'd0: begin                      
                sda_dir <= 1'b1 ;    //主机(FPGA)输出信号         
                sda_out <= addr_t[7];          //传送字地址 低八位
            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控制权     
                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    
        st_data_wr: begin                        //写数据(8 bit)
          case(cnt)                            
            7'd0: begin                      
                sda_out <= data_wr_t[7];     //I2C写8位数据 data_wr_t存储着要写入的数据
                sda_dir <= 1'b1;             //主机(FPGA)输出信号           
            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控制权       
                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 
        st_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  
        st_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      
        st_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

3.1、三态门

         数据线SDA是双向的,为了避免主机、从机同时操作数据线,FPGA内部使用三态门结构避免此事件发送。

 3.2、分频电路/代码

assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;   
//模块驱动时钟的分频系数    >> 2'd2右移两位===除以4

//Sda数据线的数据 只能在scl时钟线的低电平时间内进行改变
//SDA数据线改变在SCL时钟线低电平的中间位置改变,
//那么驱动时钟dri_clk则需要是scl时钟线的4倍频,
//则dri_clk的值是CLK_FREQ/I2C_FREQ的4倍
//则分频系数则是 CLK_FREQ/I2C_FREQ/4

//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作,典型时钟分频电路,只适用于偶数分频
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_divide[8:1]:舍去clk_divide的最低位 相当于除以2 
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;    //计数到分频系数的一半则翻转一次IIC驱动时钟
    end
    else
        clk_cnt <= clk_cnt + 1'b1; //分频时钟计数 自增
end  
//假如clk_divide=10,则(clk_divide/2)-1=4, 当clk_cnt从0计数到4,dri_clk驱动信号翻转一次
//则一个分频系数10,dri_clk翻转了两次

上面代码中:

  • CLK_FREQ     模块输入的时钟频率(顶层模块提供给此模块的时钟)是宏定义
  • I2C_FREQ       IIC_SCL的时钟频率(根据外设设置的IIC时钟频率)是宏定义
  • clk_divide       模块驱动时钟的分频系数 ,是本模块内部变量
  • clk_cnt              分频时钟计数,是本模块内部变量
  • dri_clk               驱动I2C操作的驱动时钟,是本模块的输出信号,从机设备使用这个时钟

        驱动时钟 比SCL时钟快多少倍方便操作呢?因为SDA数据线 只能在SCL时钟线的低电平时间内进行改变。

        SDA数据线改变在SCL时钟线低电平的中间位置改变,那么驱动时钟dri_clk则需要是scl时钟线的4倍频

        则分频系数为       ( CLK_FREQ        I2C_FREQ)        /        4

                                       " 输入时钟" /  "IIC时钟频率" /4

3.3、三段式状态机——状态跳转

时序逻辑

//(三段式状态机)——————状态跳转 同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin //驱动时钟已经是dri_clk了 
    if(!rst_n)
        cur_state <= st_idle; //复位键按下,则当前状态为空闲状态
    else
        cur_state <= next_state; // 当状态跳转到下一个状态
end

        这个always块的输入时钟已经是dri_clk(分频而得)

3.4、三段式状态机————下一个状态的判断 

组合逻辑 

always @(*) begin 
     next_state = st_idle;//状态机下一状态  为 空闲状态
     //下面的代码没有更新next_state 则下次状态就为空闲状态
      case(cur_state) 
        st_idle:begin    //当前状态是空闲状态
            if(i2c_exec) //i2c_exec :IIC触发信号 高电平触发,由外部模块输入
                next_state=st_sladdr;//发送器件地址(slave address)
            else
                next_state=st_idle;//状态保持  
        end//当前是空闲状态下,检测到触发电平,进入发送  "发送器件地址(slave address)" 状态
        
        st_sladdr:begin//当前状态是 "发送器件地址(slave address)" 状态
            if(st_done)begin    //上个状态结束
                if(bit_ctrl)    //bit_ctrl:16位器件地址 还是8位器件地址  有外部模块输入
                    next_state=st_addr16;
                else
                    next_state=st_addr8;//跳过  st_addr16 状态
            end  
            else
               next_state = st_sladdr;  //状态保持  
        end        
        
        st_addr16:begin
            if(st_done) //上个状态结束
                next_state=st_addr8;
            else
                next_state=st_addr16;//状态保持 
        end
        
        st_addr8:begin
            if(st_done)begin    //上个状态结束
                if(wr_flag==1'b0)               //读写判断
                    next_state=st_data_wr;//写数据(8 bit)
                else
                 	next_state = st_addr_rd;    //发送器件地址读
            end
            else
                next_state=st_addr8;
        end
        
        st_data_wr: begin       //写数据(8 bit)
          if(st_done)   //上个状态结束
                next_state = st_stop;//结束I2C操作
            else
                next_state = st_data_wr;//状态保持 
        end
        st_addr_rd: begin                       //写地址以进行读数据
            if(st_done) begin
                next_state = st_data_rd;
            end
            else begin
                next_state = st_addr_rd;
            end
        end
        st_data_rd:begin//读数据(8 bit)
            if(st_done) //上个状态结束
                next_state=st_stop;//结束I2C操作
            else
                next_state=st_data_rd;//状态保持
        end
        
        st_stop: begin //结束I2C操作
            if(st_done) //上个状态结束
                next_state = st_idle; //空闲状态
            else
                next_state = st_stop;//状态保持
        end
       default: next_state= st_idle;//上面都不满足,下个状态为空闲状态
     endcase
end

根据当前状态判断下一个状态是什么。

i2c_exec:        IIC触发信号 高电平触发,由外部模块输入

bit_ctrl:             6位器件地址 还是8位器件地址  由外部模块输入

st_done:        状态结束,由下一个时序状态控制,强调 每个模块都是并行执行

wr_flag:       读写判断 ,本模块的由下一个时序状态控制

3.4、三段式状态机————描述状态输出

时序逻辑


//时序逻辑---描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n) begin
      scl       <= 1'b1;//I2C的SCL时钟信号       本模块输出信号
      sda_out   <= 1'b1;//SDA输出信号           本模块输出信号
      sda_dir   <= 1'b1;//I2C数据(SDA)方向控制  sda_dir表示IIC数据方向,当为1时表示主机(FPGA)输出信号,为0时FPGA输出高组态,表示释放控制权                          
      i2c_done  <= 1'b0;//I2C一次操作完成      本模块输出信号                     
      i2c_ack   <= 1'b0;//I2C应答标志          本模块输出信号                         
      cnt       <= 1'b0;//计数                       
      st_done   <= 1'b0;//状态结束                          
      data_r    <= 1'b0;//读取的数据                        
      i2c_data_r<= 1'b0;//I2C读出的数据           本模块输出信号                     
      wr_flag   <= 1'b0;//写标志                         
      addr_t    <= 1'b0;//地址                         
      data_wr_t <= 1'b0;//I2C需写的数据的临时寄存                          
    end 
    else    begin
        st_done <= 1'b0 ;         //下面不更新这里一直是0                         
        cnt     <= cnt +1'b1 ;    //由下面操作清零   
        case(cur_state) //当前状态
            st_idle: begin  //当前状态是空闲状态
                scl=1'b1;//I2C的SCL时钟信号  拉高
                sda_out <= 1'b1; //SDA输出信号                     
                sda_dir <= 1'b1; //主机(FPGA)输出信号
                i2c_done<= 1'b0;                     
                cnt     <= 7'b0;  
                if(i2c_exec)begin //i2c_exec :IIC触发信号 高电平触发,由外部模块输入
                    wr_flag<=i2c_rh_wl;//写标志 =I2C读写控制信号i2c_rh_wl  由外部模块输入 
                    //由外部模块控制是读操作 还是写操作
                    
                    addr_t<=i2c_addr;//i2c_addr I2C器件内地址 由外部模块输入
                    //这一步是选择IIC器件地址     
                    
                    data_wr_t <= i2c_data_w;//i2c_data_w: I2C要写的数据  由外部模块输入
                    //缓存外部模块发来的数据
                end
            end
           st_sladdr:begin   //写地址(器件地址和字地址)
               case(cnt) 
               7'd1 :sda_out<=1'b0;
               7'd3 :scl<=1'b0;
               7'd4 :sda_out<=SLAVE_ADDR[6];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd5 :scl<=1'b1;  //SCL时钟线拉高
               7'd7 :scl<=1'b0;  //延时一个时钟后:SCL时钟线拉低
               7'd8 :sda_out<=SLAVE_ADDR[5];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd9 :scl<=1'b1;
               7'd11:scl<=1'b0;
               7'd12:sda_out<=SLAVE_ADDR[4];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd13:scl<=1'b1;
               7'd15:scl<=1'b0;
               7'd16:sda_out<=SLAVE_ADDR[3];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd17:scl<=1'b1;
               7'd19:scl<=1'b0;
               7'd20:sda_out<=SLAVE_ADDR[2];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd21:scl<=1'b1;
               7'd23:scl<=1'b0;
               7'd24:sda_out<=SLAVE_ADDR[1];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd25:scl<=1'b1;
               7'd27:scl<=1'b0;
               7'd28:sda_out<=SLAVE_ADDR[0];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               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控制权   
                  sda_out <= 1'b1;   ///数据线拉高
               end
               7'd37: scl     <= 1'b1;   
               7'd38: begin                 //从机应答 
               st_done <= 1'b1;             //操作完成,由上个组合逻辑切换下个状态(next_state),
                   if(sda_in == 1'b1)      //高电平表示未应答
                   i2c_ack <= 1'b1;        //拉高应答标志位
               end
               7'd39: begin                     
                  scl <= 1'b0;   //再由第一个状态机时序逻辑 切换当前状态(cur_state),所以这里滞后一个周期              
                  cnt <= 1'b0;    //清空计数              
               end 
                 default :  ;   
             endcase            
           end 
          st_addr16: begin                         
           case(cnt)                            
            7'd0 : begin                     
                sda_dir <= 1'b1 ;               //主机(FPGA)输出信号             
                sda_out <= addr_t[15];          //传送字地址 高八位
             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控制权            
                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 
        st_addr8: begin                          
          case(cnt)                            
            7'd0: begin                      
                sda_dir <= 1'b1 ;    //主机(FPGA)输出信号         
                sda_out <= addr_t[7];          //传送字地址 低八位
            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控制权     
                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    
        st_data_wr: begin                        //写数据(8 bit)
          case(cnt)                            
            7'd0: begin                      
                sda_out <= data_wr_t[7];     //I2C写8位数据 data_wr_t存储着要写入的数据
                sda_dir <= 1'b1;             //主机(FPGA)输出信号           
            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控制权       
                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 
        st_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  
        st_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      
        st_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

        这个模块描述每个状态的动作,用cut计数,控制SDA 和 SCL两根线完成IIC总线通信,下面详细描述下一下代码实际产生的波形:

st_sladdr:begin   //写地址(器件地址和字地址)
               case(cnt) 
               7'd1 :sda_out<=1'b0;
               7'd3 :scl<=1'b0;
               7'd4 :sda_out<=SLAVE_ADDR[6];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd5 :scl<=1'b1;  //SCL时钟线拉高
               7'd7 :scl<=1'b0;  //延时一个时钟后:SCL时钟线拉低
               7'd8 :sda_out<=SLAVE_ADDR[5];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd9 :scl<=1'b1;
               7'd11:scl<=1'b0;
               7'd12:sda_out<=SLAVE_ADDR[4];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd13:scl<=1'b1;
               7'd15:scl<=1'b0;
               7'd16:sda_out<=SLAVE_ADDR[3];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd17:scl<=1'b1;
               7'd19:scl<=1'b0;
               7'd20:sda_out<=SLAVE_ADDR[2];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd21:scl<=1'b1;
               7'd23:scl<=1'b0;
               7'd24:sda_out<=SLAVE_ADDR[1];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               7'd25:scl<=1'b1;
               7'd27:scl<=1'b0;
               7'd28:sda_out<=SLAVE_ADDR[0];//传输器件地址  SLAVE_ADDR(1010000)是从机地址 由外部模块提供
               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控制权   
                  sda_out <= 1'b1;   ///数据线拉高
               end
               7'd37: scl     <= 1'b1;   
               7'd38: begin                 //从机应答 
               st_done <= 1'b1;             //操作完成,由上个组合逻辑切换下个状态(next_state),
                   if(sda_in == 1'b1)      //高电平表示未应答
                   i2c_ack <= 1'b1;        //拉高应答标志位
               end
               7'd39: begin                     
                  scl <= 1'b0;   //再由第一个状态机时序逻辑 切换当前状态(cur_state),所以这里滞后一个周期              
                  cnt <= 1'b0;    //清空计数              
               end 
                 default :  ;   
             endcase            
           end 

 

 完成了传输:10100000地址的操作。

 后面的各个模块都是这个操作完成的。

猜你喜欢

转载自blog.csdn.net/qq_34301282/article/details/120901134