FPGA实现I2C通信方案

目录

总体系统架构图

I2C控制器模块划分

关键时序图

[完整写操作时序(写24C02 EEPROM)](#完整写操作时序(写24C02 EEPROM))

状态机定义

时钟生成(100kHz)

测试方案

测试用例


总体系统架构图

I2C控制器模块划分
  1. 时钟分频模块:生成I2C时钟

  2. 主状态机:控制通信流程

  3. 移位寄存器:数据串并转换

  4. ACK检测:响应检测逻辑

  5. 仲裁逻辑:多主竞争处理

  6. 三态控制:双向IO管理

关键时序图

SCL /‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\_______

SDA __/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\

复制代码
 起始SDA↓ while SCL=H     结束 ↑ SDA↑ while SCL=H
完整写操作时序(写24C02 EEPROM)

Start 地址+W ACK 数据 ACK Stop

SCL /‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\

| | A6 A5 A4 A3 A2 A1 A0 W | | D7 D6 ... D0 | | |

SDA __/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\

| S | 1 0 1 0 0 0 0 0 | ACK | 0 1 ... 1 | ACK | P |

↑ 从机地址(0x50) + 写(0) ↓ 数据(0x5F) ↓

  1. 读操作时序

Start 地址+R ACK 数据 NACK Stop

SCL /‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\

| | A6 A5 A4 A3 A2 A1 A0 R | | D7 D6 ... D0 | | |

SDA __/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\

| S | 1 0 1 0 0 0 0 1 | ACK | 1 0 ... 1 | NACK| P |

↑ 从机地址(0x50) + 读(1) ↓ 数据(0xA5) ↑(主机释放)

Verilog实现核心代码

状态机定义
复制代码
localparam [3:0]
  IDLE      = 4'd0,
  START     = 4'd1,
  ADDR      = 4'd2,
  ACK_ADDR  = 4'd3,
  WR_DATA   = 4'd4,
  ACK_WR    = 4'd5,
  RD_DATA   = 4'd6,
  ACK_RD    = 4'd7,
  STOP      = 4'd8,
  RESTART   = 4'd9;

三态控制逻辑

复制代码
// SDA双向控制
assign sda_out = (sda_oe) ? shift_reg[7] : 1'bz;
assign sda = sda_out;

// SCL时钟控制
assign scl_out = (scl_oe) ? scl_clk : 1'bz;
assign scl = scl_out;

// 输入采样
always @(posedge sys_clk) begin
  sda_in <= sda;
  scl_in <= scl;
end

状态机片段(写操作)

复制代码
case(state)
  IDLE:
    if (start) begin
      scl_oe <= 1;
      sda_oe <= 1;
      state <= START;
    end
  
  START:
    if (scl_high) begin
      sda_oe <= 0;  // 产生下降沿
      shift_reg <= {slv_addr, 1'b0}; // 地址+写
      bit_cnt <= 7;
      state <= ADDR;
    end
  
  ADDR:
    if (scl_fall) begin
      if (bit_cnt > 0) begin
        shift_reg <= {shift_reg[6:0], 1'b0};
        bit_cnt <= bit_cnt - 1;
      end else begin
        sda_oe <= 0; // 释放SDA
        state <= ACK_ADDR;
      end
    end
  
  ACK_ADDR:
    if (scl_rise) begin
      if (!sda_in) begin // 检测ACK
        shift_reg <= tx_data;
        bit_cnt <= 7;
        state <= WR_DATA;
      end else begin
        state <= STOP; // NACK处理
      end
    end
  ...
endcase
时钟生成(100kHz)
复制代码
// 系统时钟50MHz -> I2C 100kHz
parameter DIV_CNT = 250; // 50e6 / (100e3 * 2) = 250

always @(posedge sys_clk or negedge rst_n) begin
  if (!rst_n) begin
    clk_cnt <= 0;
    scl_clk <= 1;
  end else begin
    if (clk_cnt == DIV_CNT-1) begin
      clk_cnt <= 0;
      scl_clk <= ~scl_clk; // 翻转SCL
    end else begin
      clk_cnt <= clk_cnt + 1;
    end
  end
end

// 时钟边沿检测
assign scl_rise = (scl_in_prev == 0) && (scl_in == 1);
assign scl_fall = (scl_in_prev == 1) && (scl_in == 0);

测试方案

逻辑分析仪捕获波形

复制代码
        +--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
SCL     | START | BIT7| BIT6| BIT5| BIT4| BIT3| BIT2| BIT1| BIT0| ACK | ... |
        +--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
SDA     |   ↓   | A6  | A5  | A4  | A3  | A2  | A1  | A0  | W/R |  ↓  | ... |
        +--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
测试用例
  1. EEPROM写测试:写入地址0x10,数据0xAA

  2. EEPROM读测试:读取地址0x10

  3. 错误测试:

    • 无效地址(NACK检测)

    • 总线竞争仲裁

    • 时钟拉伸测试