• [Craftor原创]基于Verilog的I2C总线驱动设计


    摘要:

    此版本的设计中,笔者将协议里对总线的操作细分为4个,即起始(Start)、写(Write)、读(Read)、停止(Stop),并给对应的操作编码:起始(1000)、写(0100)、读(0010)、停止(0001)。每次读写操作中也包括了一次应答操作。上层模块需要操作总线时,仅需要按照芯片操作步骤给出命令,即可以对任意支持I2C总线的外设进行操作。

    此版本中主机不具有应答能力,在对24C02芯片进行操作时,可以进行任意随机读写,但不可进行连续读,这个问题将会在下一版中解决。

    设计步骤:

    1、双向I/O口的设计

    I2C总线的SCL和SDA信号都是双向端口,在操作的时候与一般的单向端口有一定差别,在设计时需要特别注意。

    I/O的定义如下:

    inout wire scl
    inout wire sda
    
    

    当需要进行输出操作时,需将使能信号scl_en/sda_en拉高,并给scl_out/sda_out赋相应的值;输入操作时,拉低scl_en/sda_en,读取scl_in/sda_in信号即可。

    reg scl_en, scl_r, scl_out;
    reg sda_en, sda_r, sda_out;
    
    wire scl_in;
    wire sda_in;
    
    assign scl = scl_r;
    assign sda = sda_r;
    assign scl_in = scl;
    assign sda_in = sda;
    
    always@(scl_en or scl_out) begin
      if (scl_en)
        scl_r <= scl_out;
      else
        scl_r <= 1'bZ;
    end
    
    always@(sda_en or sda_out) begin
      if (sda_en)
        sda_r <= sda_out;
      else
        sda_r <= 1'bZ;
    end
    

    2、时钟的设计

    此设计中采用1MHz的输入时钟,进行4分频,总线工作的250KHz的频率下。

    在1MHz的输入时钟下,对一个2bit的计数器进行循环计数,产生0、1、2、3四个节拍,在对SCL和SDA进行操作时都基于这4个节拍,如果需要在SCL和SDA间产生相位差,就相当容易。因为在写操作时,需要先送数据,再送时钟,而读操作时,需要写给时钟,再读数据。

    reg [1:0] bit = 0;
    always@(posedge sys_clk) bit <= bit + 1’b1; 

    3、起始信号(Start, 4’b1000)的产生

    在SCL高电平时,SDA上产生一个下降沿,即是起始信号。

    always@(posedge sys_clk) begin
      case (start_state)
      0:
        begin
          if ((cmd == 4'b1000) && (!cmd_busy))
            begin
              scl_en <= 1;
              scl_out <= 1;
              sda_en <= 1;
              sda_out <= 1;
              start_state <= 1;
              start_busy <= 1;
            end
          else
            start_busy <= 0;
        end
      1: if (bit == 3) start_state <= 2;
      2:
        begin
          case (bit)
          0: sda_out <= 0;
          1: scl_out <= 0;
          3: 
            begin
              start_state <= 0;
              start_busy <= 0;
            end
          endcase
        end
      endcase
    end

    4、停止信号(Stop, 4’b0001)的产生

    在SCL高电平时,SDA上产生一个上升沿,即是停止信号。

    always@(posedge sys_clk) begin
      case (stop_state)
      0:
        begin
          if ((cmd == 4'b0001) && (!cmd_busy))
            begin
              scl_en <= 1;
              scl_out <= 1;
              sda_en <= 1;
              sda_out <= 0;
              stop_state <= 1;
              stop_busy <= 1;
            end
          else
            stop_busy <= 0;
        end
      1: if (bit ==3 ) stop_state <= 2;
      2:
        case (bit)
        0: sda_out <= 1;
        3: 
          begin
            stop_state <= 0;
            stop_busy <= 0;
            scl_en <= 0;
            scl_out <= 1'bZ;
            sda_en <= 0;
            sda_out <= 1'bZ;
          end
        endcase
      endcase
    end

    5、写操作(Write, 4’b0100)的产生

    always@(posedge sys_clk) begin
      case (wr_state)
      0:
        begin
          if ((cmd == 4'b0100) && (!cmd_busy))
            begin
              wr_cnt <= 0;
              wr_state <= 1;
              sda_en <= 1;
              sda_out <= dout[7];
              scl_en <= 1;
              scl_out <= 0;
              wr_busy <= 1;
            end
          else
            wr_busy <= 0;
        end
      1: if (bit == 3) wr_state <= 2;
      2:
        begin
          case (bit)
          0:
            case(wr_cnt)
            0,1,2,3,4,5,6,7:
              begin
                sda_out <= dout[7-wr_cnt];
                wr_cnt <= wr_cnt + 1;
              end
            endcase
          1: scl_out <= ~scl_out;
          3:
            begin
              scl_out <= ~scl_out;
              if (wr_cnt == 8)
                begin
                  wr_cnt <= 0;
                  wr_state <= 3;
                  sda_en <= 0;
                  wr_busy <= 0;
                  wr_ack <= 1;
                end
            end
          endcase
        end
      3:
        if (bit == 3)
          begin
            wr_ack <= 2;
            wr_state <= 0;
            sda_en <= 0;
            sda_out <= 1'bZ;
          end
      endcase
    end

    6、读操作(Read, 4’b0010)的产生

    always@(posedge sys_clk) begin
      case (rd_state)
      0:
        begin
          if ((cmd == 4'b0010) && (!cmd_busy))
            begin
              rd_cnt <= 0;
              sda_en <= 0;
              rd_state <= 1;
              scl_en <= 1;
              scl_out <= 0;
              rd_busy <= 1;
            end
          else
            rd_busy <= 0;
        end
      1: if (bit == 3) rd_state <= 2;
      2:
        begin
          case (bit)
          0,2: scl_out <= ~scl_out;
          1:
            begin
              case (rd_cnt)
              0,1,2,3,4,5,6,7:
                begin
                  din[7-rd_cnt] <= sda_in;
                  rd_cnt <= rd_cnt + 1;
                end
              endcase
            end
          3:
            if (rd_cnt == 8)
              begin
                rd_cnt <= 0;
                rd_state <= 3;
                rd_busy <= 0;
                rd_ack <= 1;
              end
          endcase
        end
      3:
        if (bit == 3)
          begin
            rd_ack <= 0;
            rd_state <= 0;
          end
      endcase
    end

    7、应答信号(Acknowledge)接收

    应答信号会在读写进程里自动接收,不需要另外操作。

    always@(posedge sys_clk)
    if ((bit == 1)&&(ack_en == 1)) ack <= sda_in;

    8、设计整合

    很显然,如果简单地将上面各个操作的代码放在一个Verilog文件里,肯定是综合不过去的,因为每个进程都要对SCL和SDA进行操作。所以笔者又将各操作整合到了一个状态机里,这样就不会出现对SCL和SDA多重操作的情况。

    ->完整的代码见附件

    9、仿真与测试

    仿真:起始、写、读、停止信号的产生,都在如下的时序图上。

    wave

    测试:在Altera的EP2C8Q208C8上测试通过。

    10、附件:

    I2C总线驱动,V1.0

    http://code.craftor.org/20160801/i2c_interface_v1.v

    对24C02读写的测试程序

    http://code.craftor.org/20160801/ctrl_24C02.v

    用到的分频程序

    http://code.craftor.org/20160801/clk_div.v

    写在最后:

    文中及文中提到的代码和设计全为Craftor原创,转载请保留作者信息。

    文中代码仅供交流和学习使用,不得用于任何商业用途。

  • 相关阅读:
    eclipse 构建 jpa project 所需的用户库(vendor: EclipseLink)
    <mvc:resources mapping="/xxx/**" location="/xxx/"/>无效,可能和Controller的URL模式有关
    面向对象设计的基本原则
    MySql数据库时区异常,java.sql.SQLException: The server time zone value '?й???׼ʱ?' is unrecognized or represents more than one time zone.
    elasticsearch kibana + 分词器安装详细步骤
    neo4j企业版集群搭建
    Elasticsearchdump 数据导入/导出
    Hive环境搭建和SparkSql整合
    Hadoop 集群搭建和维护文档
    HBase 安装snappy压缩软件以及相关编码配置
  • 原文地址:https://www.cnblogs.com/craftor/p/1834780.html
Copyright © 2020-2023  润新知