IIC那些事(四)

博客给出I2C完整示例,提醒仿真模型可从镁光官网获取,自行实现ack不现实。强调状态机绘图要注意逻辑,理清思路后代码实现会更顺利。还提到要提高visio画图能力,而非依赖ps。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IIC那些事(三)

I2C示例

  1. 完结篇,直接给出一个完整的示例,当然代码不全贴了。
  2. 注意仿真的模型是从镁光官网直接拿的,这个自行实现ack很不现实。
  3. 另外注意状态机的绘图要特别注意逻辑,一旦出错很难排查。
  4. 理清逻辑,思路,那么代码实现就是顺其自然的,按照小梅哥的说法叫做,照图施工。

代码实现

  1. demo代码
// iic_sda的输出,如果输出使能打开,则直接输出obuf,否则输出高阻态,表示断开三态缓冲开关
  assign iic_sda = (out_en == 1'b1) ? sda_obuf : 1'bz;
  
  // cnt计数器计数实现scl时钟,频率为100k
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cnt <= 32'd0;
    else
      if (cnt < T_SCL - 1'b1 && cnt_en == 1'b1)
        cnt <= cnt + 1'b1;
      else
        cnt <= 32'd0;
  end
  
  // 利用上述cnt实现iic_scl
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      iic_scl <= 1'b1;
    else
      if (cnt < T_SCL/2)
        iic_scl <= 1'b1;
      else
        iic_scl <= 1'b0;
  end
  
  // scl高电平的中间点标志信号flag_high
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      flag_high <= 1'b0;
    else  
      if (cnt == T_SCL/4 - 1'b1)
        flag_high <= 1'b1;
      else  
        flag_high <= 1'b0;
  end

  // scl低电平的中间点标志信号flag_low
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      flag_low <= 1'b0;
    else
      if (cnt == (T_SCL * 3)/4 - 1'b1)
        flag_low <= 1'b1;
      else
        flag_low <= 1'b0;
  end
  
  // 状态机的处理过程,c_state表示当前状态,n_state表示下一状态
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      c_state <= IDLE;
    else
      c_state <= n_state;
  end
  
  // 状态转移过程
  always @ * begin
    case (c_state)
		// 起始状态,若检测到start_flag信号,则进行START状态
      IDLE        :     begin
        if (start_flag == 1'b1)
          n_state = START;
        else
          n_state = IDLE;
      end
		
		// START状态,若此时检测到flag_high信号,则表示
      START       :     begin
        if (flag_high == 1'b1)
          n_state = CTRL;
        else
          n_state = START;
      end
      
      CTRL        :     begin
        if (flag_low == 1'b1 && drive_cnt == 4'd8)
          n_state = CTRL_ACK;
        else
          n_state = CTRL;
      end
      
      CTRL_ACK    :     begin
        if (flag_high == 1'b1)
          if (iic_sda == 1'b0)
            if (addr_sel == 1'b1)
              n_state = HADDR;
            else
              n_state = LADDR;
          else
            n_state = START;
        else
          n_state = CTRL_ACK;
      end
      
      HADDR       :     begin
        if (flag_low == 1'b1 && drive_cnt == 4'd8)
          n_state = HADDR_ACK;
        else
          n_state = HADDR;
      end
      
      HADDR_ACK   :     begin
        if (flag_high == 1'b1)
          if (iic_sda == 1'b0)
            n_state = LADDR;
          else
            n_state = START;
        else
          n_state = HADDR_ACK;
      end
      
      LADDR       :     begin
        if (flag_low == 1'b1 && drive_cnt == 4'd8)
          n_state = LADDR_ACK;
        else
          n_state = LADDR;
      end
      
      LADDR_ACK   :     begin
        if (flag_high == 1'b1)
          if (iic_sda == 1'b0)
            if (wren == 1'b1)
              n_state = WR;
            else
              n_state = RD_START;
          else
            n_state = START;
        else
          n_state = LADDR_ACK;
      end
      
      WR          :     begin
        if (flag_low == 1'b1 && drive_cnt == 4'd8)
          n_state = WR_ACK;
        else
          n_state = WR;
      end
      
      WR_ACK      :     begin
        if (flag_high == 1'b1)
          if (iic_sda == 1'b0)
            n_state = STOP;
          else
            n_state = START;
        else
          n_state = WR_ACK;
      end
      
      STOP        :     begin
        if (flag_high == 1'b1)
          n_state = IDLE;
        else
          n_state = STOP;
      end
      
      RD_START    :     begin
        if (flag_high == 1'b1)
          n_state = RD_CTRL;
        else
          n_state = RD_START;
      end
      
      RD_CTRL     :     begin
        if (flag_low == 1'b1 && drive_cnt == 4'd8)
          n_state = RD_CTRL_ACK;
        else
          n_state = RD_CTRL;
      end
      
      RD_CTRL_ACK :     begin
        if (flag_high == 1'b1)
          if (iic_sda == 1'b0)
            n_state = RD;
          else
            n_state = START;
        else
          n_state = RD_CTRL_ACK;
      end
      
      RD          :     begin
        if (flag_low == 1'b1 && drive_cnt == 4'd8)
          n_state = NO_ACK;
        else
          n_state = RD;
      end
      
      NO_ACK      :     begin
        if (flag_high == 1'b1)
          n_state = STOP;
        else
          n_state = NO_ACK;
      end
      
      default     :     n_state = IDLE;
    endcase
  end

  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cnt_en <= 1'b0;
    else
      case (c_state)
        IDLE          : cnt_en <= 1'b0;
        CTRL_ACK,
        HADDR_ACK,
        LADDR_ACK,
        WR_ACK,
        RD_CTRL_ACK   : begin
          if (flag_high == 1'b1)
            if (iic_sda == 1'b0)
              cnt_en <= 1'b1;
            else
              cnt_en <= 1'b0;
          else
            cnt_en <= cnt_en;
        end
        default       :   cnt_en <= 1'b1;
      endcase
  end

  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      out_en <= 1'b0;
    else
      case (c_state)
        IDLE          :   out_en <= 1'b0;
        START         :   out_en <= 1'b1;
        CTRL          :   begin
          if (flag_low == 1'b1 && drive_cnt == 4'd8)
            out_en <= 1'b0;
          else
            out_en <= 1'b1;
        end
        CTRL_ACK      :   out_en <= 1'b0;
        HADDR         :   begin
          if (flag_low == 1'b1 && drive_cnt == 4'd8)
            out_en <= 1'b0;
          else
            if (flag_low == 1'b1)
              out_en <= 1'b1;
            else
              out_en <= out_en;
        end
        HADDR_ACK     :   out_en <= 1'b0;
        LADDR         :   begin
          if (flag_low == 1'b1 && drive_cnt == 4'd8)
            out_en <= 1'b0;
          else
            if (flag_low == 1'b1)
              out_en <= 1'b1;
            else
              out_en <= out_en;
        end
        LADDR_ACK     :   out_en <= 1'b0;
        WR            :   begin
          if (flag_low == 1'b1 && drive_cnt == 4'd8)
            out_en <= 1'b0;
          else
            if (flag_low == 1'b1)
              out_en <= 1'b1;
            else
              out_en <= out_en;
        end
        WR_ACK        :   out_en <= 1'b0;
        STOP          :   begin
          if (flag_low == 1'b1)
            out_en <= 1'b1;
          else
            out_en <= out_en;
        end
        RD_START      :   begin
          if (flag_low == 1'b1)
            out_en <= 1'b1;
          else  
            out_en <= out_en;
        end
        
        RD_CTRL       :   begin
          if (flag_low == 1'b1 && drive_cnt == 4'd8)
            out_en <= 1'b0;
          else
            out_en <= 1'b1;
        end
        RD_CTRL_ACK   :   out_en <= 1'b0;
        RD            :   begin
          if (flag_low == 1'b1 && drive_cnt == 4'd8)
            out_en <= 1'b1;
          else
            out_en <= 1'b0;
        end
        
        NO_ACK        :   out_en <= 1'b1;
        
        default       :   out_en <= 1'b0;
      endcase
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      sda_obuf <= 1'b1;
    else
      case (c_state)
        IDLE          :     sda_obuf <= 1'b1;
        START         :     begin
          if (flag_high == 1'b1)
            sda_obuf <= 1'b0;
          else
            sda_obuf <= 1'b1;
        end
        
        CTRL          :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            sda_obuf <= temp[7];
          else
            sda_obuf <= sda_obuf;
        end
        
        CTRL_ACK      :     sda_obuf <= 1'b0;
        HADDR         :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            sda_obuf <= temp[7];
          else
            sda_obuf <= sda_obuf;
        end
        
        HADDR_ACK     :     sda_obuf <= 1'b0;
        LADDR         :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            sda_obuf <= temp[7];
          else
            sda_obuf <= sda_obuf;
        end
        LADDR_ACK     :     sda_obuf <= 1'b0;
        WR            :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            sda_obuf <= temp[7];
          else
            sda_obuf <= sda_obuf;
        end
        WR_ACK        :     sda_obuf <= 1'b0;
        STOP          :     begin
          if (flag_low == 1'b1)
            sda_obuf <= 1'b0;
          else
            if (flag_high == 1'b1)
              sda_obuf <= 1'b1;
            else
              sda_obuf <= sda_obuf;
        end
        RD_START      :     begin
          if (flag_low == 1'b1)
            sda_obuf <= 1'b1;
          else
            if (flag_high == 1'b1)
              sda_obuf <= 1'b0;
            else
              sda_obuf <= sda_obuf;
        end
        RD_CTRL       :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            sda_obuf <= temp[7];
          else
            sda_obuf <= sda_obuf;
        end
        RD_CTRL_ACK   :     sda_obuf <= 1'b0;
        RD            :     begin
          if (flag_low == 1'b1 && drive_cnt == 4'd8)
            sda_obuf <= 1'b1;
          else
            sda_obuf <= sda_obuf;
        end
        NO_ACK        :     sda_obuf <= sda_obuf;
        default       :     sda_obuf <= 1'b1;
      endcase
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      drive_cnt <= 4'd0;
    else
      case (c_state)
        IDLE        :     drive_cnt <= 4'd0;
        START       :     drive_cnt <= 4'd0;
        CTRL        :     begin
          if (flag_low == 1'b1)
            if (drive_cnt < 4'd8)
              drive_cnt <= drive_cnt + 1'b1;
            else
              drive_cnt <= 4'd0;
          else
            drive_cnt <= drive_cnt;
        end
        CTRL_ACK    :     drive_cnt <= 4'd0;
        HADDR       :     begin
          if (flag_low == 1'b1)
            if (drive_cnt < 4'd8)
              drive_cnt <= drive_cnt + 1'b1;
            else
              drive_cnt <= 4'd0;
          else
            drive_cnt <= drive_cnt;
        end
        HADDR_ACK   :     drive_cnt <= 4'd0;
        LADDR       :     begin
          if (flag_low == 1'b1)
            if (drive_cnt < 4'd8)
              drive_cnt <= drive_cnt + 1'b1;
            else
              drive_cnt <= 4'd0;
          else
            drive_cnt <= drive_cnt;
        end
        LADDR_ACK   :     drive_cnt <= 4'd0;
        WR          :     begin
          if (flag_low == 1'b1)
            if (drive_cnt < 4'd8)
              drive_cnt <= drive_cnt + 1'b1;
            else
              drive_cnt <= 4'd0;
          else
            drive_cnt <= drive_cnt;
        end
        WR_ACK      :     drive_cnt <= 4'd0;
        STOP        :     drive_cnt <= 4'd0;
        RD_START    :     drive_cnt <= 4'd0;
        RD_CTRL     :     begin
          if (flag_low == 1'b1)
            if (drive_cnt < 4'd8)
              drive_cnt <= drive_cnt + 1'b1;
            else
              drive_cnt <= 4'd0;
          else
            drive_cnt <= drive_cnt;
        end
        RD_CTRL_ACK :     drive_cnt <= 4'd0;
        RD          :     begin
          if (flag_high == 1'b1 && drive_cnt < 4'd8)
            drive_cnt <= drive_cnt + 1'b1;
          else
            if (flag_low == 1'b1 && drive_cnt == 4'd8)
              drive_cnt <= 4'd0;
            else
              drive_cnt <= drive_cnt;
        end
        NO_ACK      :     drive_cnt <= 4'd0;
        default     :     drive_cnt <= 4'd0;
      endcase
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      temp <= 8'd0;
    else
      case (c_state)
        IDLE          :     temp <= 8'd0;
        START         :     begin
          if (flag_high == 1'b1)
            temp <= {slave_addr, 1'b0};
          else  
            temp <= temp;
        end
        CTRL          :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            temp <= temp << 1'b1;
          else
            temp <= temp;
        end
        CTRL_ACK      :     begin
          if (flag_high == 1'b1 && iic_sda == 1'b0)
            if (addr_sel == 1'b1)
              temp <= addr[15:8];
            else
              temp <= addr[7:0];
          else
            temp <= temp;
        end
        HADDR         :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            temp <= temp << 1'b1;
          else
            temp <= temp;
        end
        HADDR_ACK     :     begin
          if (flag_high == 1'b1 && iic_sda == 1'b0)
            temp <= addr[7:0];
          else
            temp <= temp;
        end
        LADDR         :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            temp <= temp << 1'b1;
          else
            temp <= temp;
        end
        LADDR_ACK     :     begin
          if (flag_high == 1'b1 && iic_sda == 1'b0)
            if (wren == 1'b1)
              temp <= wdata;
            else
              temp <= {slave_addr, 1'b1};
          else
            temp <= temp;
        end
        WR            :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            temp <= temp << 1'b1;
          else
            temp <= temp;
        end
        WR_ACK        :     temp <= 8'd0;
        STOP          :     temp <= 8'd0;
        RD_START      :     temp <= temp;
        RD_CTRL       :     begin
          if (flag_low == 1'b1 && drive_cnt < 4'd8)
            temp <= temp << 1'b1;
          else
            temp <= temp;
        end
        RD_CTRL_ACK   :     temp <= 8'd0;
        RD            :     begin
          if (flag_high == 1'b1 && drive_cnt < 4'd8)
            temp <= {temp[6:0], iic_sda};
          else
            temp <= temp;
        end
        NO_ACK        :     temp <= 8'd0;
        
        default       :     temp <= 8'd0;
      endcase
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      iic_done <= 1'b0;
    else
      if (c_state == STOP && flag_high == 1'b1)
        iic_done <= 1'b1;
      else
        iic_done <= 1'b0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      rdata <= 8'd0;
    else
      if (c_state == RD && flag_low == 1'b1 && drive_cnt == 4'd8)
        rdata <= temp;
      else
        rdata <= rdata;
  end
  1. 仿真展示
    在这里插入图片描述

写在最后

  1. 提高visio画图能力,而不是老用ps搞。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IMMUNIZE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值