1. 项目概述
在HDMI接口开发中,EDID(Extended Display Identification Data)的传输是一个关键环节。上一章节我们已经完成了EDID数据的hex文件生成和ROM写入,本章将重点讲解如何通过I²C协议将这些数据从ROM传输到HDMI接收端。为了深入理解I²C通信的时序细节,我们选择先模拟主机发送I²C请求的方式,通过仿真观察完整的传输过程。
这个实验基于Xilinx Artix-7系列FPGA平台,系统时钟频率为50MHz。我们将使用Verilog HDL实现I²C主控制器,重点解决以下几个技术难点:
- 精确的时钟分频控制,满足I²C标准时序要求
- 状态机的设计与实现,完整覆盖I²C协议的各种状态
- 双向数据线(SDA)的可靠控制机制
- 完整的读写时序实现,包括起始条件、停止条件和重复起始条件
2. I²C协议基础与AT24CM01时序分析
2.1 I²C协议核心要点
I²C(Inter-Integrated Circuit)是一种同步、多主从架构的串行通信总线,由Philips公司开发,主要特点包括:
- 仅需两条线:串行数据线(SDA)和串行时钟线(SCL)
- 每个设备都有唯一的7位或10位地址
- 支持多主从架构,通过冲突检测和仲裁机制解决总线竞争
- 标准模式(100kHz)、快速模式(400kHz)和高速模式(3.4MHz)等多种速度等级
在我们的HDMI EDID传输场景中,使用标准模式(100kHz)即可满足需求。根据I²C规范,关键时序参数如下:
| 参数 | 符号 | 标准模式最小值 | 单位 |
|---|---|---|---|
| SCL时钟频率 | fSCL | 0 | 100 |
| SCL高电平时间 | tHIGH | 4.0 | - |
| SCL低电平时间 | tLOW | 4.7 | - |
| 起始条件保持时间 | tHD;STA | 4.0 | - |
| 停止条件建立时间 | tSU;STO | 4.0 | - |
2.2 AT24CM01 EEPROM读取时序
AT24CM01是常见的I²C接口EEPROM,其顺序读取时序如图1所示。顺序读取可以由随机地址读取发起,当主设备接收到一个数据字节后,它用ACK信号来响应。只要EEPROM接收到ACK,主设备就会继续递增寄存器地址,自动读取下一个寄存器的数据。当主设备发送NACK(非应答)信号并产生STOP条件时,顺序读取结束。
关键操作流程:
- 主设备发送起始条件(START)
- 发送从设备地址(7位) + 写标志(0)
- 发送要读取的寄存器地址(16位)
- 发送重复起始条件(Repeated START)
- 发送从设备地址(7位) + 读标志(1)
- 连续读取数据,每次接收一个字节后发送ACK
- 最后一个字节接收后发送NACK
- 发送停止条件(STOP)
3. I²C主控制器设计与实现
3.1 时钟分频设计
系统时钟为50MHz(周期20ns),需要分频产生满足I²C时序要求的SCL时钟。我们设计一个10位计数器sclk_cnt(0-1023),在状态机不为空闲时开始计数:
verilog复制reg [9:0] sclk_cnt;
always @(posedge clk or posedge rst) begin
if(rst)
sclk_cnt <= 10'd0;
else if(state != IDLE)
sclk_cnt <= (sclk_cnt == 10'd1023) ? 10'd0 : sclk_cnt + 1'b1;
else
sclk_cnt <= 10'd0;
end
// SCL生成逻辑
assign scl = (sclk_cnt <= 10'd511) ? 1'b1 : 1'b0;
这样产生的SCL周期为1024×20ns=20.48μs,频率约48.8kHz,高低电平时间均为10.24μs,完全满足标准模式时序要求。
3.2 状态机设计
I²C主控制器采用有限状态机(FSM)实现,主要状态包括:
- IDLE:空闲状态,等待传输开始
- START:产生起始条件
- SLAVE_ADDR_WR:发送从设备地址(写)
- WR_ADDR:写入寄存器地址
- SCL_START_T:准备重复起始条件
- REPEATED_START:产生重复起始条件
- SLAVE_ADDR_RD:发送从设备地址(读)
- RD_DATA:读取数据
- STOP:产生停止条件
状态转移图如下:
code复制[IDLE] -> [START] -> [SLAVE_ADDR_WR] -> [WR_ADDR]
-> [SCL_START_T] -> [REPEATED_START] -> [SLAVE_ADDR_RD]
-> [RD_DATA] -> [STOP] -> [IDLE]
3.3 双向SDA接口实现
SDA是双向数据线,需要特殊处理:
verilog复制// SDA输入缓冲
wire sda_in;
buf sda_buf(sda_in, sda);
// SDA输出控制
reg sda_out;
reg sda_oe; // 输出使能
assign sda = (sda_oe) ? sda_out : 1'bz;
// SDA输出使能控制逻辑
always @(*) begin
case(state)
START: sda_oe = (sclk_cnt < 512);
SLAVE_ADDR_WR: sda_oe = (iic_scl_cnt <= 7);
WR_ADDR: sda_oe = (iic_scl_cnt <= 7);
REPEATED_START:sda_oe = (sclk_cnt >= 512);
SLAVE_ADDR_RD: sda_oe = (iic_scl_cnt <= 7);
RD_DATA: sda_oe = (iic_scl_cnt == 8);
STOP: sda_oe = 1'b1;
default: sda_oe = 1'b0;
endcase
end
3.4 起始条件实现
起始条件定义为:SCL为高时,SDA由高变低。需要满足tHD;STA ≥ 4.0μs的保持时间。
verilog复制// 起始条件生成
always @(posedge clk or posedge rst) begin
if(rst) begin
sda_out <= 1'b1;
end else begin
case(state)
IDLE: sda_out <= 1'b1;
START: begin
if(sclk_cnt < 256)
sda_out <= 1'b1;
else if(sclk_cnt < 512)
sda_out <= 1'b0;
else
sda_out <= 1'b0;
end
// 其他状态...
endcase
end
end
4. 关键时序实现细节
4.1 器件地址传输
AT24CM01的器件地址为0xA0(写)/0xA1(读)。在SLAVE_ADDR_WR状态,我们需要移位发送7位地址+1位读写标志:
verilog复制reg [7:0] slave_addr_wr_shift = 8'b10100000; // 0xA0
always @(posedge clk) begin
if(state == SLAVE_ADDR_WR && scl_l_flag && iic_scl_cnt <= 7) begin
sda_out <= slave_addr_wr_shift[7];
slave_addr_wr_shift <= {slave_addr_wr_shift[6:0], 1'b0};
end
end
4.2 重复起始条件
在写入寄存器地址后,需要产生重复起始条件来切换为读模式:
verilog复制always @(posedge clk) begin
case(state)
SCL_START_T: begin
if(sclk_cnt == 10'd1023) begin
state <= REPEATED_START;
sda_out <= 1'b0;
end
end
REPEATED_START: begin
if(sclk_cnt == 10'd511) begin
state <= SLAVE_ADDR_RD;
slave_addr_rd_shift <= 8'b10100001; // 0xA1
end
end
// 其他状态...
endcase
end
4.3 数据读取与ACK/NACK
在RD_DATA状态,主设备每接收一个字节后需要发送ACK,最后一个字节发送NACK:
verilog复制reg [7:0] rd_slave_data;
reg [2:0] bit_cnt;
reg [7:0] rd_data_cnt;
always @(posedge clk) begin
if(state == RD_DATA) begin
if(scl_h_flag && iic_scl_cnt <= 7) begin
rd_slave_data <= {rd_slave_data[6:0], sda_in};
bit_cnt <= bit_cnt + 1'b1;
end
if(iic_scl_cnt == 8 && scl_l_flag) begin
if(rd_data_cnt == 8'd127) begin // 最后一个字节
sda_out <= 1'b1; // NACK
state <= STOP;
end else begin
sda_out <= 1'b0; // ACK
rd_data_cnt <= rd_data_cnt + 1'b1;
end
end
end
end
5. 仿真与调试技巧
5.1 仿真波形分析要点
在ModelSim等仿真工具中观察时序时,需要特别关注以下关键点:
- 起始条件:SCL为高时,SDA的下降沿是否清晰,tHD;STA是否≥4.0μs
- 数据有效性:SDA数据变化必须在SCL低电平期间完成,在SCL高电平时保持稳定
- ACK响应:每个字节传输后的第9个时钟周期,SDA是否被从设备拉低
- 重复起始条件:SCL高电平期间,SDA的下降沿时序
- 停止条件���SCL高电平期间,SDA的上升沿时序
5.2 常见问题排查
-
从设备无响应:
- 检查器件地址是否正确(AT24CM01为0xA0/0xA1)
- 确认上拉电阻值合适(通常4.7kΩ)
- 检查电源电压是否在器件工作范围内
-
ACK信号异常:
- 确认从设备地址和寄存器地址正确
- 检查总线是否有冲突(多主设备情况)
- 验证时序参数是否满足从设备要求
-
数据错误:
- 检查SCL/SDA信号质量,是否存在过冲/振铃
- 确认时钟频率不超过从设备支持的最大值
- 验证双向SDA接口实现是否正确
5.3 实际调试建议
- 使用逻辑分析仪捕获实际波形,与仿真结果对比
- 在代码中添加调试输出,实时显示状态机状态和关键计数器值
- 对于时序敏感部分,可以适当增加margin(如将4.0μs的要求设计为5.0μs)
- 分阶段验证:先验证起始条件,再验证地址传输,最后验证数据读写
6. 扩展与优化
6.1 性能优化方向
- 动态时钟调整:根据实际需要动态调整SCL频率,在初始化阶段使用较低频率,正常传输时提高频率
- DMA支持:添加DMA接口,减少CPU干预,提高大数据量传输效率
- 错误检测与恢复:添加超时检测、总线忙检测等机制,提高可靠性
6.2 多设备支持
当前设计针对AT24CM01优化,可以通过以下方式扩展为通用I²C主控制器:
- 参数化从设备地址
- 添加寄存器配置接口,支持灵活设置时序参数
- 实现完整的I²C协议状态机,支持所有标准操作
6.3 与EDID传输的集成
完成I²C主控制器验证后,下一步将:
- 将EDID数据从ROM中读出
- 按照DDC/CI标准封装数据帧
- 实现完整的HDMI EDID传输流程
- 添加热插拔检测(HPD)支持
在实际项目中,我发现I²C时序的精确控制对系统稳定性至关重要。特别是在HDMI应用中,显示设备对EDID读取时序有严格要求。建议在正式使用前,使用多种品牌的显示设备进行兼容性测试,确保在各种情况下都能可靠工作。
