4. I2C总线协议基础:物理层、时序与EDID映射
各位同学,大家好。今天我们来聊聊I2C总线。说实话,在嵌入式显示系统里,I2C就像一条看不见的“数据脐带”,连接着显示器和主机。EDID数据就是通过这条总线,从显示器传输到显卡的。搞不懂I2C,你就没法真正理解EDID是怎么读出来的。
4.1 I2C物理层:两条线,一个世界
I2C总线,说白了就两根线:SCL(时钟线)和SDA(数据线)。嗯,这里要注意,这两根线都是开漏输出,必须接上拉电阻。我刚开始做硬件时,就吃过这个亏——忘记加上拉电阻,结果总线一直拉低,死活通信不上。
核心要点:I2C是同步、半双工通信协议。主机产生时钟,从机响应数据。所有设备共享两条总线,通过地址区分。
为什么用开漏?你想想看,如果多个设备同时输出高低电平,那不就短路了吗?开漏结构允许“线与”逻辑,多个设备可以安全地共享总线。这是I2C的精妙之处。
4.2 I2C协议层:起始、停止与数据有效性
I2C的时序其实不复杂,但有几个关键点必须记住。我个人习惯把I2C通信比作“打电话”:
- 起始条件(Start):SCL高电平时,SDA从高变低。相当于“拨号开始”。
- 停止条件(Stop):SCL高电平时,SDA从低变高。相当于“挂断电话”。
- 数据有效性:SCL高电平时,SDA必须稳定。数据只能在SCL低电平时变化。
这里有个坑,我曾经遇到过:有些便宜的显示器,起始条件保持时间不够,导致MCU识别不到起始信号。解决办法是适当延长SCL低电平时间,给从机一点“反应时间”。
避坑指南:我曾经调试一块EDID读取程序,发现读回来的数据全是0xFF。查了半天,原来是起始条件后没有等待足够的时间,从机还没准备好就发了地址。记住:起始条件后,至少要等待4.7μs(标准模式)再发送地址字节。
4.3 I2C读写时序:从地址到数据
I2C的读写时序,我建议你记住这个“三步走”模式:
- 发送从机地址 + 读写位(7位地址 + 1位R/W)
- 等待从机应答(ACK/NACK)
- 传输数据字节(每字节后跟应答位)
对于EDID读取,典型的操作是“复合格式”:先写地址指针,再读数据。具体时序如下:
// EDID读取时序(伪代码)
// 步骤1:起始条件
I2C_Start();
// 步骤2:发送写地址(0xA0,即0x50左移1位)
I2C_SendByte(0xA0); // 7位地址0x50 + 写位0
I2C_WaitAck();
// 步骤3:发送EDID块偏移地址(例如0x00)
I2C_SendByte(0x00); // 从0x00开始读取
I2C_WaitAck();
// 步骤4:重复起始条件(Restart)
I2C_Start();
// 步骤5:发送读地址(0xA1,即0x50左移1位 + 读位1)
I2C_SendByte(0xA1);
I2C_WaitAck();
// 步骤6:读取128字节EDID数据
for(i = 0; i < 128; i++) {
data[i] = I2C_ReadByte();
if(i < 127) I2C_SendAck(); // 非最后字节发ACK
else I2C_SendNack(); // 最后字节发NACK
}
// 步骤7:停止条件
I2C_Stop();
个人经验:我建议你在发送完地址指针后,使用“重复起始条件”而不是先发停止再发起始。这样可以保证总线操作的原子性,避免其他主机中途插入。很多显示器的I2C控制器对这种方式支持更好。
4.4 I2C地址与EDID的映射关系
EDID数据存储在显示器的EEPROM中,这个EEPROM的I2C地址是0x50(7位地址)。为什么是这个地址?这是VESA标准规定的,所有符合标准的显示器都必须使用这个地址。
但这里有个细节:EDID有128字节,而一个I2C地址只能访问256字节。对于支持HDMI 1.4+或DisplayPort的显示器,可能需要读取多个EDID块(每个块128字节)。这时候就需要使用段指针(Segment Pointer)。
| EDID块 | I2C从机地址 | 偏移地址范围 | 说明 |
|---|---|---|---|
| 块0(Base Block) | 0x50 | 0x00 - 0x7F | 128字节,必须存在 |
| 块1(Extension Block) | 0x50 | 0x80 - 0xFF | 通过段指针0x01访问 |
| 块2+ | 0x50 | 0x00 - 0x7F | 通过段指针0x02+访问 |
你可能会问:为什么块1的偏移地址是0x80?其实,EDID的EEPROM内部地址是线性连续的。块0占用0x00-0x7F,块1占用0x80-0xFF。但有些显示器的EEPROM只有256字节,所以块2及以后的块需要通过段指针来切换“页面”。
关键映射关系:
- I2C地址0x50 → EDID EEPROM(主通道)
- I2C地址0x30 → DDC/CI(显示器控制通道,可选)
- 段指针寄存器 → 位于I2C地址0x60(通过0x50写入)
我记得有一次调试4K显示器的EDID,发现只能读到块0的数据,块1全是0xFF。后来才发现,读取块1之前必须先通过I2C地址0x60写入段指针值。这个细节在VESA标准里写得很隐晦,很多工程师都会在这里卡住。
4.5 知识体系总览
下面这张图,是我自己总结的I2C与EDID映射关系的核心逻辑。你把它理解了,整个章节就通了。
这张图里,我特意把段指针单独画出来了。很多初学者以为EDID只有一个I2C地址0x50就够了,其实对于多块EDID,段指针是绕不开的。你想想看,如果显示器支持4K分辨率,它至少需要两个EDID块(一个基础块,一个扩展块),这时候就必须用段指针来切换。
实用技巧:我建议你在读取EDID时,先尝试读取块0的第126字节(0x7E),这个字节表示扩展块的数量。如果值为0,说明只有块0;如果值大于0,就需要通过段指针读取后续块。这样可以避免不必要的I2C通信。
好了,关于I2C总线协议和EDID的映射关系,我们就讲到这里。记住:I2C是EDID读取的“物理通道”,理解它的时序和地址映射,是后续实战的基础。下一节我们会实际动手,用逻辑分析仪抓取I2C波形,看看EDID数据到底是怎么在总线上流动的。
可可精品资料库