简介《EDID编程规范》面向显示驱动、嵌入式开发及显示器固件调试人员聚焦扩展显示识别数据从结构理解到实际编写的关键问题。文档以128字节基本块和256字节扩展结构为主线梳理文件头、生产厂家与产品识别、版本号、基本显示参数、色彩特性、确定与标准显示时序、详细时序描述、显示终端名称及行场频范围限制、扩展标志和CheckSum等字段并给出V1.3数据编写要点与示例值便于在EDID烧录、兼容性排查和显示模式适配时快速查阅。资源包共1个doc文件约493KB轻量便于随项目归档。已有562人学习适合需要一份可直接对照字段地址与取值规则的硬件/固件工程师及显示技术学习者。1. 一根转接线黑屏问题却出在显示器里那块 256 字节的 EEPROM一根 DVI 转 HDMI 的转接线接上电视死活不出图。换线、换口、换播放器全都没用很多人第一反应是带宽不够。可把显示器里那块 EEPROM 读出来逐字节看问题常常出在 EDID 编程规范里最不起眼的两个地方像素时钟写成了 74 而不是 7425或者最后一个字节的 Checksum 差了 1。前者让信号源算错了时序后者让整块数据被直接判为无效退回 1024×768 的安全模式。EDID 的反直觉之处在于显示器不是被动地被识别而是主动把自己能吃什么信号预先写在 DDC 通道上让信号源照着做。搞懂这套从 128 字节扩到 256 字节的结构你才能在黑屏、闪屏、分辨率上不去这类玄学问题里第一时间分辨是链路根本没读到还是读到了但解析错了。2. EDID 的分区地图128 字节如何装下一台显示器的身份2.1 DDC 不是协议是一条挂在 I²C 上的只读通道把 EDID 理解成“一个文件”会走偏它更像一个固定地址的只读寄存器区。显示终端上电后那颗小容量 EEPROM 就已经把答案准备好了信号源要做的只是按 I²C 时序把它读出来。这条通道在规范里叫 DDC物理层就是从 VGA 的 DDC 引脚或 HDMI/DVI 的 DDC/CEC 引脚引出的两根线。一次完整的读取是标准的 I²C 随机读主机发出起始条件用写方向地址 0xA0 选中从机7 位地址 0x50送一个字节的子地址 0x00然后重复起始换成读方向 0xA1连续收 128 个字节最后发 STOP。想读扩展块就把子地址换成 0x80再读 128 字节。整个过程中主机不会写任何东西进显示器所以 EDID 一旦烧录错误除了重新烧录或者用软件覆盖没有“运行时纠正”的机会。注意早期还有 DDC1 这种单向方案靠 VSYNC 线把 128 字节一位一位挤出去速率极低且只在特定条件下有效。今天几乎没有设备再走这条路但排查老设备时如果发现读出来全是 0x00 或全 0xFF先确认对方到底支不支持 DDC2B 双向通道。这套设计的代价是显而易见的EDID 的内容必须在上电前就确定无法根据实际接入的信号源动态协商。收益同样明显——信号源在输出第一个像素之前就已经知道对方的分辨率、刷新率、色度和音频能力不需要“试探—失败—回退”这种破坏性握手。显示器厂商、信号源厂商、线材厂商三方只要共同遵守这张地址表互操作就能成立。2.2 前 8 字节文件头一个刻意设计的“反随机”常量第零块的前 8 个字节是固定的00 FF FF FF FF FF FF 00。这个值不是随便挑的它同时排除了两种最常见的故障读法如果 DDC 线断了或者从机没响应主机读到的通常是全 0x00总线被拉低或全 0xFF总线被拉高。文件头一头一尾各放一个 0x00中间六个 0xFF就是为了让这两种情况都能被立刻识别出来而不是把垃圾数据当成合法 EDID 去解析。紧接着 08h–09h 是生产厂家名称用的是三位字母压缩成 15 位的编码。以规范里的例子 PRI 为例字母按 A1 递增P16、R18、I9拼成 16 位是0_10000_10010_01001即 0x4249高字节在前写成42 49。这里藏着一个很容易翻车的细节——厂家 ID 是大端而紧随其后的产品代码 0Ah–0Bh 和序列号 0Ch–0Fh 是小端。同一份数据里两种字节序并存手工拼包时把产品代码高低字节写反信号源读到的就是另一个型号。10h 是生产周11h 是生产年份年份的基准是 1990所以 2007 年写成11h。12h–13h 是版本号和修订号01 03对应 EDID 1.3。这两字节决定了后面所有字段的解释方式1.3 和 1.4 在 0x14 的视频输入定义位、0x7E 的扩展块计数语义上并不完全一致解析器必须先看版本再决定怎么读。2.3 基本参数与色彩特性gamma 那个 0x78 是怎么来的14h 的视频输入定义是一个典型的状态压缩字节。数字输入时最高位置 1取 0x80模拟输入时取 0x0E低几位分别描述信号电平标准、是否带消隐、是否有 SOG 同步。这意味着“这根口是 HDMI 还是 VGA”这件事在第一块的第 20 个字节就确定下来了后面的 CEA 扩展块只是补充能力不负责表达接口类型。15h 和 16h 是屏幕最大水平和垂直尺寸单位厘米。这两个值影响的是信号源如何计算 DPI 和缩放策略很多产线图省事直接填 0x00结果在需要按物理尺寸做缩放的场景下系统只能退回用分辨率猜。17h 是 gamma编码方式为(gamma × 100) - 100。规范里 gamma 2.2 的例子算出 120写成 0x78。这个“减 100”的设计是为了让 1.00 到 3.54 的常用区间刚好落进一个字节代价是精度只有 0.01。如果填 0x00语义是“gamma 未定义由显示端自行决定”并不是 gamma1.0这两个含义在实际设备上表现差别很大。18h 是支持特性规范里默认 0xEA附录的认证样例用的是 0x0A 或 0x0E差别主要在 DPMS 节能位、首选时序模式、显示类型RGB 彩色 / 单色 / 灰阶以及 sRGB 默认色彩空间标志。19h–22h 的 10 个字节是色彩特性红绿蓝白四组色度坐标每组用 2 位小数部分加 8 位整数部分拼成 10 位。规范建议一般直接用默认值色温约 9300K只有在需要精确描述色域时才值得逐位计算。2.4 扩展标志与 Checksum一个字节决定后面还有没有戏7Eh 是扩展标志。填 0x00 表示只有这 128 字节基本块填 0x01 表示后面还有一块 128 字节的扩展数据。这个字段是 1.3 时代“最多两块”的简单表达一旦它非零信号源就会去读子地址 0x80如果扩展块本身写坏了表现出来往往是“基本模式能显示一切到高分辨率就黑屏”。7Fh 是第零块 Checksum规则很朴素00h–7Eh 这 127 个字节加上校验字节按字节累加后低 8 位必须为 0。换句话说校验值等于前 127 字节之和的二进制补码低字节def fix_block_checksum(block: bytearray) - int: 把一个 128 字节的 EDID 块最后一位修成合法校验值。 参数: block: 长度必须为 128 的 bytearrayblock[0x7F] 会被原地覆盖。 返回: 写入的校验字节。 assert len(block) 128, EDID 块必须是 128 字节 # sum(block[:0x7F]) 可能超过 255取负后 0xFF 等价于 256 - (sum % 256) block[0x7F] (-sum(block[:0x7F])) 0xFF return block[0x7F] def verify_block(block: bytes) - bool: return len(block) 128 and sum(block) 0xFF 0fix_block_checksum只做一件事让整块的字节和为 0模 256。verify_block是它的逆运算直接对全 128 字节求和取低 8 位。这段逻辑简单到可以塞进单片机的几行汇编里这正是它被选中的原因——它不需要 CRC 表不需要多项式写入工具用一个累加器就能算完。代价是检错能力弱任意两个字节同时加 1 和减 1校验和依然成立。所以规范里那张 18 项的检查表不能省Checksum 只能挡住“写漏了”“写串了”这类低级错误挡不住字段语义错误。扩展块里的 7Fh 是同样的算法但作用域仅限该块自己的 128 字节两个块的校验互相独立。改完第一块的任何字段都必须重算第一块的校验只改扩展块同理。很多工具图省事只重算一次结果就是另一块静默失效。3. DTD 的 18 字节位域一个视频模式是怎么被拆开又拼回去的3.1 四个字段跨字节存储活动像素为什么要拆成两半第零块的 36h–47h 和 48h–59h 是两个详细的时序描述块每块 18 字节负责精确描述一个视频模式。之所以叫“详细”是因为它能表达任意分辨率和刷新率组合而不是从预定义表里挑。扩展块里还有四个 DTD 位置在 HDMI 场景下通常是 1Eh–65h 这段所以一台设备最多能用六个 DTD 精确描述六个模式。18 字节要装下像素时钟、水平活动/消隐、垂直活动/消隐、四组同步偏移与宽度、物理尺寸、边框和标志位必然要压缩。压缩的手法是把 10 位或 12 位的数值拆成“低 8 位单独一个字节 高 4 位塞进共享字节的高低半字节”。以 1920×1080i 的规范样例为例80 18 71这三个字节里0x80 是水平活动像素低 8 位0x18 是水平消隐低 8 位0x71 的高半字节 7 是水平活动的高 4 位低半字节 1 是水平消隐的高 4 位拼回来就是 0x7801920 和 0x118280。同样的套路用在垂直方向1C 16 20里 0x1C 是垂直活动低 8 位0x16 是垂直消隐低 8 位0x20 的高半字节 2 和低半字节 0 补齐高位得到 540 和 22。这里就是交错模式最容易写错的地方——1080i 的活动线数填的是每场 540 行不是每帧 1080 行填成 1080 会让信号源算出的场频只有实际的一半画面要么抖要么直接不锁。偏移字段位宽说明0–1像素时钟16 位小端单位 10 kHz7425 表示 74.25 MHz2水平活动像素低 8 位高位在偏移 4 的高半字节3水平消隐低 8 位高位在偏移 4 的低半字节4高 4 位组合8 位高半字节水平活动高 4 位低半字节水平消隐高 4 位5–7垂直活动/消隐同上高位组合在偏移 78–9行同步偏移/宽度低 8 位高位在偏移 11 的 bit7–6 与 bit5–410场同步偏移/宽度低 4 位各半高半字节偏移低半字节宽度11同步高 2 位组合8 位bit7–6 行偏移bit5–4 行宽bit3–2 场偏移bit1–0 场宽12–14图像物理尺寸高低位拆分单位毫米偏移 14 高半字节水平高位低半字节垂直高位15–16水平/垂直边框各 8 位通常为 017标志位8 位交错、立体、同步类型、行场极性这张表值得打印出来贴在工位上因为几乎每一次手工改 EDID 出错都发生在偏移 4、7、11、14 这四个“高低位共享字节”上。3.2 同步偏移与宽度极性藏在最后一字节的 bit1/bit0偏移 8 到 11 描述的是同步信号的位置和宽度。行同步偏移 0x5888、行同步宽度 0x2C44这两个值在 1920×1080i 的例子里直接写成低 8 位是因为它们都没超过 255高 2 位为 0 时偏移 11 对应位就是 00。场同步偏移 2、宽度 5 更极端低 4 位就够装所以偏移 10 写成 0x25——高半字节 2 是偏移低半字节 5 是宽度。偏移 17 的标志位决定了信号源用什么方式去“看”这些同步信号。bit7 是交错标志1080i 必须置 1否则信号源会把它当成 1080p 的逐行时序去驱动结果就是垂直方向被压扁或撕裂。bit4–bit3 是同步类型11表示数字分离同步行场各自一根线10表示数字复合同步01表示数字三电平同步00表示模拟。bit2 和 bit1 分别是场同步和行同步的极性1 为正、0 为负。规范样例里的标志位是 0x9E拆开是1001 1110交错1立体00同步类型11场极性1行极性1。而 720×480p 那块的标志位是 0x18拆开是0001 1000非交错数字分离同步行场都是负极性。这两个值如果写反屏幕可能还能出图但画面会偏移或者抖动——这类问题最容易被误判成“线材质量差”实际是极性位填错了。3.3 用 30 行代码把 18 字节反解成人能读的时序手工对着十六进制数位域很容易错位写个反解函数既能把每台样机的 EDID 变成可读表格也能在改完之后立刻自检def parse_dtd(b: bytes) - dict: 把 18 字节详细时序描述还原成可读字段。 参数: b: 长度 18 的 bytes来自第零块 0x36 或扩展块对应偏移。 if len(b) 18: raise ValueError(DTD 必须 18 字节) pixclk_khz (((b[1] 8) | b[0]) * 10) # 单位 10kHz - kHz hact ((b[4] 0xF0) 4) | b[2] # 高 4 位左移 8 再拼低 8 位 hbl ((b[4] 0x0F) 8) | b[3] vact ((b[7] 0xF0) 4) | b[5] vbl ((b[7] 0x0F) 8) | b[6] hoff ((b[11] 0xC0) 2) | b[8] # bit7-6 - bit9-8 hwid ((b[11] 0x30) 4) | b[9] # bit5-4 - bit9-8 voff ((b[11] 0x0C) 2) | (b[10] 4) # bit3-2 - bit5-4 vwid ((b[11] 0x03) 2) | (b[10] 0x0F) htotal, vtotal hact hbl, vact vbl flags b[17] refresh pixclk_khz * 1000 / (htotal * vtotal) if flags 0x80: # 交错时像素时钟按场算 refresh * 2 return { pixel_clock_mhz: pixclk_khz / 1000, h_active: hact, h_blank: hbl, h_total: htotal, v_active: vact, v_blank: vbl, v_total: vtotal, h_sync_offset: hoff, h_sync_width: hwid, v_sync_offset: voff, v_sync_width: vwid, interlaced: bool(flags 0x80), h_polarity: positive if flags 0x02 else negative, v_polarity: positive if flags 0x04 else negative, refresh_hz: round(refresh, 2), }关键在四个移位常量0xF0 4把高半字节移到 bit11–80x0F 8同理0xC0 2把 bit7–6 移到 bit9–80x30 4把 bit5–4 移到 bit9–80x0C 2把 bit3–2 移到 bit5–4。参数里b[10]的高半字节是场偏移、低半字节是场宽这点和偏移 11 的组合顺序是相反的写反了会得到一个偏移宽度互换但总和不变的错误结果肉眼很难发现——总像素数没变刷新率也勉强能算只是同步位置错了。交错模式下刷新率要乘 2是因为像素时钟对应的是场周期而我们要的是帧率。这段判断如果漏掉1080i 会被算成 30Hz。3.4 标准时序表与 Established Timings为什么还要留着这些“老古董”如果 DTD 能精确描述任意模式为什么还要留 23h–25h 的确定时序位图和 26h–35h 的标准时序表答案很现实18 字节一个模式四个 DTD 位置装不下显示器支持的全部模式而信号源在选模式时需要一份“候选清单”。23h–25h 是三个字节的位图每个 bit 对应一个固定的经典模式。规范样例里 0x23 写 0xBF0x24 写 0xEE附录的认证样例则用 0xBF、0xEE、0x00覆盖了 640×480 的 60/67/72/75Hz、800×600 的 56/60/72/75Hz、1024×768 的 60/70/75Hz、832×62475 等。位图的优势是 3 个字节表达 24 个模式代价是模式集合被规范写死想加一个 1366×768 就只能用别的表。26h–35h 的标准时序识别是 16 字节、8 组、每组 2 字节编码方式很有代表性第一个字节存(水平活动像素 / 8) - 31第二个字节高 2 位存宽高比、低 6 位存刷新率 - 60。640×48085Hz 的算法是 640/8-31490x31宽高比 4:3 对应 bit7–6 为 01刷新率 85-60250x19拼成 0x59于是这对字节就是31 59。这套编码的边界条件也值得记水平像素必须能被 8 整除且大于 248否则第一个字节会下溢刷新率必须落在 60 到 123 Hz 之间低于 60 无法表达宽高比只有 16:10、4:3、5:4、16:9 四种2.35:1 这类比例根本描述不了。规范里把不用填的槽位约定为01 01而不是00 00就是因为 0x00 会被解析成 248 像素、60Hz 的合法模式而 0x01 对应的 256 像素同样“不合法但可识别”解析器看到01 01就知道这是空槽。三级结构的分工到这里就清楚了位图负责“大家都认识的经典模式”标准时序负责“常见但不一定进位图的模式”DTD 负责“必须精确描述的主模式和设备原生模式”。这是一个典型的表达力换兼容性的取舍——越靠前的表越省字节、越容易被老设备读懂越靠后的表越精确、越依赖解析器完整实现。4. CEA-861 扩展块HDMI 的能力清单为什么要写成一条链4.1 扩展块头与 d 指针数据块是一条自描述的链当 7Eh 填 0x01信号源会去读 0x80 开始的第二个 128 字节。这个块的头 4 个字节有固定含义00h 是扩展块标签0x02 表示 CEA-86101h 是修订版本只支持 VGA/DVI 时写 0x01 或 0x04支持 HDMI 时规范样例写 0x03 对应 861B02h 是一个叫 d 的指针指向“后面还有东西”的起始地址。从 03h 开始到 d-1 是一串数据块每个数据块自己描述自己第一个字节的高 3 位是标签代码低 5 位是紧跟其后的负载字节数。标签 1 是音频数据块2 是视频数据块3 是厂商描述数据块VSDB4 是喇叭安置数据块0 和 5–7 保留。解析器读到 03h取出标签和长度跳过长度的字节落到下一个数据块的头部如此循环直到 d。这种“标签长度负载”的链式结构和前面那种固定偏移的扁平表是完全不同的设计思路。扁平的 DTD 表要求每个字段位置固定改一个就得整体重排链式结构允许厂商只写自己需要的块不支持的块直接不出现解析器也不会因为读到不认识的标签就崩掉——它只需要按长度跳过。这是向前兼容的标准做法代价是解析器必须真的会“走链”而不能假设视频块一定在 04h。4.2 视频数据块与 VIC一个字节代表一整个分辨率规范样例里 04h 是 0x47拆开是标签 2视频、长度 7。后面 7 个字节85 04 11 02 90 06 14就是 7 个短视频描述符。每个字节的 bit6–0 是视频识别码 VICbit7 是 native 标志表示这是接收端的最佳显示模式。VIC 5 对应 1920×1080i59.94/60Hz 16:9加上 native 标志就是 0x85VIC 4 是 1280×720p600x04VIC 17 是 720×576p500x11VIC 2 是 720×480p60 4:30x02VIC 16 是 640×480p60 4:3加 native 得到 0x90VIC 6 是 720(1440)×480i600x06VIC 20 是 1920×1080i500x14。七个字节描述七种模式平均每个不到一个字节这就是查表编码的威力。native 标志的实际作用比看上去大。当信号源要在多个支持模式里挑一个输出时native 标志是唯一能表达“这是面板原生分辨率”的信号。规范样例里给了两个 native1080i 和 480p这在多制式电视上很常见但严格说 native 应该只有一个。如果所有模式本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?