FPGA实现I2C通信方案
目录
总体系统架构图
I2C控制器模块划分
关键时序图
完整写操作时序(写24C02 EEPROM)
状态机定义
时钟生成(100kHz)
测试方案
测试用例
总体系统架构图
I2C控制器模块划分
-
时钟分频模块:生成I2C时钟
-
主状态机:控制通信流程
-
移位寄存器:数据串并转换
-
ACK检测:响应检测逻辑
-
仲裁逻辑:多主竞争处理
-
三态控制:双向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) ↓
3. 读操作时序
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) beginsda_in <= sda;scl_in <= scl;
end
状态机片段(写操作)
case(state)IDLE:if (start) beginscl_oe <= 1;sda_oe <= 1;state <= START;endSTART:if (scl_high) beginsda_oe <= 0; // 产生下降沿shift_reg <= {slv_addr, 1'b0}; // 地址+写bit_cnt <= 7;state <= ADDR;endADDR:if (scl_fall) beginif (bit_cnt > 0) beginshift_reg <= {shift_reg[6:0], 1'b0};bit_cnt <= bit_cnt - 1;end else beginsda_oe <= 0; // 释放SDAstate <= ACK_ADDR;endendACK_ADDR:if (scl_rise) beginif (!sda_in) begin // 检测ACKshift_reg <= tx_data;bit_cnt <= 7;state <= WR_DATA;end else beginstate <= STOP; // NACK处理endend...
endcase
时钟生成(100kHz)
// 系统时钟50MHz -> I2C 100kHz
parameter DIV_CNT = 250; // 50e6 / (100e3 * 2) = 250always @(posedge sys_clk or negedge rst_n) beginif (!rst_n) beginclk_cnt <= 0;scl_clk <= 1;end else beginif (clk_cnt == DIV_CNT-1) beginclk_cnt <= 0;scl_clk <= ~scl_clk; // 翻转SCLend else beginclk_cnt <= clk_cnt + 1;endend
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 | ↓ | ... |+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
测试用例
-
EEPROM写测试:写入地址0x10,数据0xAA
-
EEPROM读测试:读取地址0x10
-
错误测试:
-
无效地址(NACK检测)
-
总线竞争仲裁
-
时钟拉伸测试
-