EDID解析:显示器识别、DDC读取与HDMI扩展块实践
简介这份文档面向显示器、显卡与嵌入式显示驱动方向的硬件及固件工程师系统梳理扩展显示识别数据EDID的编程规范帮助解决显示器与信号源之间识别失败、分辨率与刷新率不匹配等兼容性问题。全文以128字节基本数据块为线索逐段说明文件头、生产厂家与产品识别、版本号、基本显示参数与特性、色彩特性、确定时序与标准时序识别、四组详细时序描述块、显示终端名称及行场频范围限制、扩展标志与CheckSum的地址分配和填写要点并给出V1.3常用默认值、gamma换算与标准时序字节编码等示例。资源包共1个doc文件约493KB篇幅紧凑便于随查随用。已有562人学习适合需要动手修改EDID、排查显示异常或入门DDC与EDID协议的读者。1. 显示器一插就“认识”EDID 究竟是什么把一台陌生的显示器接到主机上屏幕亮起来的那一瞬间系统其实已经把它摸清底细了多大尺寸、原生分辨率是多少、支持哪些刷新率、有没有内置喇叭。整个过程不用装驱动不用联网查询甚至不用你点一下确认。之所以能这样是因为显示器里存着一小段自述——扩展显示识别数据英文全称 Extended Display Identification Data缩写 EDID。它的本质是一段结构化字节藏在显示终端里通过显示数据通道DDC被主机读走。打个比方它像贴在显示器背后的铭牌只不过这张铭牌是写给机器看的显卡照着铭牌上的参数调输出才不会把一路 1080p 的信号硬塞给一块只认 720p 的屏。读懂它很多“黑屏、无信号、分辨率不对”的玄学问题就能找到根。2. 为什么非得有这块数据EDID 存在的三个理由2.1 显示器不会说话只能靠一段字节自报家门信号发生器和显示终端之间天生是不对等的主机有算力、有内存、能跑复杂的协商逻辑而显示器这边往往只有一颗很小的控制芯片。指望显示器主动“告诉”主机自己能干什么最经济的办法就是让它老老实实存一段固定格式的字节主机来读。这里要分清两个东西。DDC 是那条通道EDID 是通道里跑的货。通道本身是单向的、低速的主机按固定地址去“点名”显示器把预先烧录好的 128 字节吐出来。正因为是预烧录的它不能讨价还价显示器出厂时写成什么样接上任何主机读到的就是什么样。这个设计有个好处——确定性。主机读完 EDID就知道该发什么、不该发什么不用试错。也有个代价——写错了没法自愈。一块 EDID 写错的屏接到 A 主机上可能凑合能用接到 B 主机上就直接无信号而屏本身硬件是好的。很多产线上“这块屏点不亮”的争论最后都追到那 128 个字节里的某一位上。所以 EDID 不是“附加信息”而是设备之间建立信任的契约。契约写错了硬件再好也握不上手。2.2 没有 EDID 会发生什么一次“盲开”的翻车设想一个极端场景主机读不到任何 EDID 数据。这时候它没法知道对面是什么只能退回默认策略——按自己认为最保守的模式发信号比如某个老旧的 640×480 或 1024×768。结果通常是两种要么屏上显示“输入信号超出范围”要么黑屏但背光亮着。这就是为什么排查显示问题时“换一根线试试”往往有效——线里有一根 DDC 引脚接触不良时 EDID 读不全主机看到的是残缺甚至全零的数据。有些系统读到全零的 EDID 会自己造一份虚拟的顶上屏幕也就“能用但不对”有些系统则直接放弃。同一块屏在不同主机上表现不一致多半卡在这一步。EDID 缺失或损坏的典型症状还有分辨率被锁死在低位且改不上去、刷新率只能选 60Hz、显示器型号显示成“通用即插即用监视器”。这些症状本身不是故障而是主机在信息不足时的自保行为。理解了这一层就不会一上来就怀疑显卡坏了。2.3 为什么是 128 字节又为什么能扩到 256 字节128 这个数字不是随便定的。它对应 DDC 通道上一次可寻址空间的大小一个字节的偏移量能寻到 256 个位置实际协议里用一半也就是 128 字节正好凑成一个完整的数据块。所以 EDID 的基本结构就是一块 128 字节从头到尾地址 00h 到 7Fh最后一位留给校验。但 128 字节装不下后来的一切。早期的显示需求简单几个常见分辨率、一个 gamma、若干色度坐标够了。等 HDMI 进来要声明支持哪些音频格式、几个声道、喇叭怎么摆、厂商私有扩展128 字节立刻不够用。于是规范给了扩展的口子7Eh 这个位置叫扩展标志写 00h 表示到此为止写 01h 表示后面还挂着一块 128 字节的扩展数据总数就成了 256 字节。提示扩展标志只是“有没有下一块”的开关具体挂的是哪种扩展要看扩展块开头的第一个字节。这个设计很像老式文件系统的链式分配第一块管好自己的事最后一位指向下一块。好处是向后兼容老设备读到扩展标志为 00h 就停下不会因为不认识后面的内容而崩溃坏处是扩展块和基本块必须成对维护改了一边忘了另一边校验就会挂。后面会反复看到EDID 里所有“翻车”几乎都源于这种成对关系没对齐。3. 128 字节里装了什么把基本数据块一段段拆开看3.1 首尾两端文件头与 Checksum 的分工00h 到 07h 这 8 个字节是固定死的00 FF FF FF FF FF FF 00。它没有任何信息量作用只有一个——让读取方确认“我确实对齐到了一个 EDID 块的开头”。如果解析程序读到的前 8 个字节不是这个序列说明偏移错了或者数据坏了后面全都别信。7Fh 这最后 1 个字节是校验和。算法很朴素把 00h 到 7Eh 这 127 个字节的数值全部加起来再加上校验和字节结果对 256 取模必须等于 0。换句话说校验和是用来“补齐”的前面加起来缺多少它就填多少。def edid_checksum(block): 传入 128 字节的基本块或扩展块返回正确的校验和字节 return (-sum(block[:127])) 0xFF # 取负数再按位与等价于对 256 取模后补齐 def check_block(block): return sum(block[:127]) block[127] 0xFF 0上面两个函数里block[:127]是前 127 个字节block[127]是校验位。-sum(...) 0xFF这行的含义是先求总和取负数再截断到 8 位得到的就是让总和归零的那个值。第二个函数做反向验证 0xFF 0保证结果落在 0 到 255 之间再判断。校验和是整个规范里最容易被忽略、也最容易出错的一环。它不看内容对不对只看字节加法对不对。这意味着你把 gamma 写成 2.4 而实际屏幕是 2.2校验和照样能通过但只要少改一个字节忘了重算主机就可能整块丢弃。很多“明明照着模板改的却不生效”问题就出在这里。3.2 08h 到 13h厂商识别、序列号与版本号08h 和 09h 两个字节放厂商代码采用的是 5 位压缩编码把三个字母塞进 16 个比特里。10h 是生产周11h 是生产年份——注意年份是偏移量从 1990 起算所以写 11h 表示 2007 年。这两个字段本质上是溯源信息出问题时能定位到批次。12h 和 13h 是版本号和修订号常见组合是 01h、03h就是 EDID 1.3也有 01h、04h。这个字段决定了后面很多字节的解释方式因为不同版本的字段定义有差异。写扩展块之前务必先确认基本块声明的是哪个版本否则同样的字节在不同版本下含义不同解析出来就是错的。0Ah 到 0Fh 是产品代码和序列号规范里允许填默认值。默认值的好处是通用坏处是同一批机器在系统里显示成同一个名字做批量管理时会分不清谁是谁。这一段的取舍取决于使用场景面向零售就填真值面向内部测试填默认值反而省事。3.3 14h 到 18h输入类型、尺寸与 gamma14h 是基本输入信号定义也是全文最容易被误写的字节之一。模拟输入和数字输入的取值完全不同模拟默认 0Eh数字默认 80h。最低位还承载接口细节比如是否支持某种数字接口规范所以实际写出来常见 81h 而不是 80h。15h 和 16h 是屏幕最大水平和垂直尺寸单位是厘米。允许填 0表示“不详”。填 0 不会导致故障但主机就没法推算像素密度某些系统会因此把界面缩放设得很奇怪。17h 是 gamma算法是「gamma 值 × 100 − 100」。gamma 2.2 对应 120也就是 78h。这个公式看着简单实际手算时最容易把 2.2 写成 220 再减 100 得 120看着对其实中间少乘了——正确写法是 2.2 × 100 220220 − 100 120。数值上碰巧一致但如果 gamma 是 1.8就该是 80写成 180 − 100 80 也对所以这条公式其实不容易错真正容易错的是把结果当十进制直接填进去而忘了转十六进制。18h 是支持的显示特性用位标志描述电源管理、色彩空间等能力默认 EAh 是一个常见起点。这一位写错通常不会黑屏但会让系统误判屏幕支持某类省电模式出现“休眠后唤不醒”的怪现象。3.4 23h 到 35h三种表达时序的方式EDID 描述显示能力用了三套并行机制从粗到细。23h 到 25h 是“确定的显示时序”三个字节每个比特对应一组业界通用的老模式。它相当于一份复选框支持就置 1不支持就置 0。写法简单缺点是只能覆盖那几十种预定义模式超出范围就无能为力。26h 到 35h 是“标准的显示时序识别”16 个字节能放 8 组每组 2 字节。它的编码方式很有代表性值得单独拆开第一个字节是「水平有效像素 ÷ 8 − 31」第二个字节高两位表示宽高比低六位表示「刷新率 − 60」。位段含义编码方式byte1水平有效像素H_active / 8 − 31byte2 bit7-6宽高比0016:10014:3105:41116:9byte2 bit5-0刷新率实际刷新率 − 60举个例子640×480 85Hz640 ÷ 8 − 31 49即 31h宽高比 4:3 对应 01放在高两位是 40h85 − 60 25即 19h两者相或得 59h。所以这两个字节写 31h、59h。from math import gcd def encode_standard_timing(h_active, v_active, refresh_hz): b1 h_active // 8 - 31 assert 0 b1 255, 水平像素超出该字段表达范围 g gcd(h_active, v_active) ratio (h_active // g, v_active // g) ratio_code {(16, 10): 0b00, (4, 3): 0b01, (5, 4): 0b10, (16, 9): 0b11}[ratio] b2 (ratio_code 6) | (refresh_hz - 60) return b1, b2这段代码做了三件事把水平像素压缩进一个字节用最大公约数把分辨率约成最简比再查表得到两位宽高比码把刷新率减去基准 60 后放进低六位。参数说明h_active和v_active是像素数refresh_hz是整数刷新率。注意减 60 之后必须是 0 到 63 之间的正整数低于 60Hz 的模式无法用这套机制表达只能走详细时序。36h 往后是“详细的时序描述”每个块 18 字节能精确描述点频、消隐、同步偏移、同步宽度、图像物理尺寸。它是三套机制里唯一能表达任意自定义模式的代价是占地方——128 字节里只有 4 个位置。3.5 5Ah 到 7Dh描述符块与“点频为 0”的暗号36h 到 7Dh 这 72 字节是四个 18 字节的槽位但槽位不一定都放时序。规范约定如果某个槽位的点频字段为 0它就不是时序块而是“显示终端描述符”。描述符靠第 4 个字节的标签区分类型常见的有两种。5Ah 到 6Bh 通常是显示终端名称标识头固定为 00 00 00 FC 00后面跟 ASCII 字符以 0Ah 结尾没填满的位置补 20h空格。6Ch 到 7Dh 通常是行场频范围限制标识头是 00 00 00 FD 00后面依次是最低场频、最高场频、最低行频、最高行频、最大点频。这两个块的实用价值常被低估。行场频范围写宽了主机可能发出屏幕实际跟不上的信号导致间歇性失步写窄了一些本来能显示的模式会被系统直接过滤掉。名称则直接影响用户在系统设置里看到什么写成乱码虽然不影响显示但用起来很别扭。4. 扩到 256 字节以后CEA-861 扩展块怎么表达 HDMI 能力4.1 扩展块的开头02h 和它的版本号当 7Eh 写成 01h后面就跟着第二块 128 字节。这块的开头几个字节定义了它的“身份”。第一个字节固定写 02h表示这是一个 CEA 扩展第二个字节写修订版本号01h 对应较早的修订03h 对应较新的修订。两种修订的写法差别不小。修订 01h 的结构很朴素00h 是 02h01h 是 01h02h 是 04h03h 是 00h然后 04h 到 15h 放一条 18 字节的详细时序剩下的全部填 00h最后 7Fh 放校验和。这种写法只够声明“我还多支持一个模式”适合只有 VGA 或 DVI 接口的设备。修订 03h 的结构复杂得多02h 这个位置不再是固定值而是变成了一个指针变量记作 d指向“音频信息标志”的地址。03h 到 (d−1)h 是一串“数据块”每个数据块开头一个字节声明标签和长度后面跟内容。d 之后才是若干条详细时序。注意修订号写 01h 还是 03h决定了 02h 这个字节是常量还是指针。同一个位置两种含义这是扩展块里最容易读错的地方。4.2 03h 的数据块集合视频、音频、喇叭、厂商修订 03h 的 03h 字节本身也是信息高 4 位是 DTV 功能标志分别表示是否支持欠扫描、是否支持基本声音格式、是否支持两种色度采样格式低 4 位是紧随其后的详细时序块数量。从 04h 开始进入数据块区每个块用第一个字节编码「标签」和「长度」。标签 1 是音频数据块2 是视频数据块3 是厂商描述数据块4 是喇叭安置数据块。这种「标签 长度 内容」的结构很像网络协议里的 TLV好处是可以任选、可以乱序、可以以后加新类型解析方跳过不认识的块即可。视频数据块里放的是视频模式确认码每个码一个字节比如 85h 表示 1920×1080i 60Hz 16:960h 表示 1280×720p 60Hz11h 表示 720×576p 50Hz。最高位是 Native 标志置 1 表示这是接收端的最佳显示模式主机应该优先选它。这个标志很实用一块屏可能支持十几种模式但只有一种是不缩放最清晰的标出来系统就会优先用。音频数据块分压缩和非压缩两类。非压缩那类描述 Linear PCM 的能力包含最大声道数、支持的采样率位图、支持的位深位图。压缩那类描述格式代码、最大声道数和最大比特率。规范里有个硬性要求如果声明支持某种压缩格式最大比特率必须写到该格式规定的值不能随便填。这条约束常被忽略结果就是协商通过了但实际出声异常。厂商描述数据块是给厂商留的自定义空间长度可变头部一般是厂商自己的注册标识。这一块内容对通用主机不可见属于「懂的都懂」的部分。4.3 一个完整的 HDMI 扩展块长什么样把上面这些串起来一块典型的 HDMI 扩展数据大致是这样组织的开头 02h、03h 表明身份02h 位置写 1Dh 表示音频信息标志从 1Dh 开始03h 写 53h表示支持基本声音格式、支持某种色度采样、后面跟 3 条详细时序04h 写 47h表示接下来是一个长度为 7 的视频数据块然后 7 个视频模式码依次排开。再往后是音频数据块开头一个字节声明标签和长度接着描述 PCM 的能力再接着描述压缩格式的能力。然后是喇叭安置数据块用位标志说明左右声道、中置、低音等是否存在。再往后是厂商描述块里面包含注册标识和通道编号。最后一块区域是详细时序位置由前面的 d 值和数据块总长度算出来通常能放 4 条 18 字节的时序。这里有个地域差异值得记一笔面向不同市场的机型选的场频基准不同有的以 60Hz 为主有的以 50Hz 为主还有的两者都要。选错了不会立刻坏但会出现「某些片源播放时轻微抖动」这类难查的问题。4.4 Supports_AI一个 0x80 背后的责任在厂商描述块之后通常有一个单独的位置放着「HDMI 支持音频信息标志」规范里记作 Supports_AI。它的值如果是 0x80含义是这条通道支持音频信息处理能够接收并处理带内容保护标记的音频数据包。这个标志不是随便写的。一旦置位接收端就必须承担相应义务当收到的数据包提示内容受保护时凡是带数字音频输出口的设备必须把这些输出口静音。这是一条硬性行为约束写进规范是为了防止受保护的音频被悄悄转发出去。所以这一位的取舍不是「写了更好看」而是「写了就得做到」。有些产品为了兼容性测试好看把这一位置 1但固件里没实现对应的静音逻辑短期测不出问题长期就是隐患。判断该不该写正确顺序是先看固件有没有这套处理流程再看要不要声明。5. 四组常见易混概念EDID、DDC、CEA-861 和 DisplayID 的区别5.1 EDID 与 DDC一个是货一个是车易混点很多人把这两个词当同义词用说「读一下 DDC 看看屏幕型号」。为什么混它们总是成对出现读 EDID 必须走 DDC写文档时图省事就拿通道名代指内容。怎么区分DDC 是物理层和传输协议管的是「怎么把字节搬过去」EDID 是数据结构管的是「这些字节按什么格式排」。类比一下DDC 是快递线路EDID 是包裹里那张写满参数的清单。线路断了清单再好也送不到清单写错了线路通着也读不出正确结果。排查时先看线路换线、看接触再看内容校验和、字段顺序不能反。5.2 EDID 与 CEA-861一个是容器一个是内容规范易混点看到扩展块开头是 02h就以为「EDID 2.0 来了」。为什么混CEA-861 的内容确实挂在 EDID 的扩展块里两者物理上连在一起。怎么区分EDID 定义的是「块怎么分、校验怎么算、基本参数放哪」它是容器规范CEA-861 定义的是「视频模式码怎么编、音频能力怎么描述」它是内容规范。容器可以装别的内容内容也可以装在别处。所以看到 02h 应该理解为「这块扩展里装的是 CEA 定义的东西」而不是「这是另一个版本的 EDID」。同理扩展块里出现 01h 修订和 03h 修订说的是 CEA 规范的修订不是 EDID 的版本。5.3 EDID 1.3 与 1.4不是版本号越大越高级易混点以为 1.4 全面优于 1.3能升就升。为什么混软件世界习惯了版本递增看到低版本就想更新。怎么区分版本号决定的是字段的解释规则不是能力高低。1.4 引入了一些新字段和新的描述符类型但同时也改变了部分位置的含义。如果后端解析工具只认 1.3 的布局硬写成 1.4 反而会解析错乱。选哪个版本取决于整条链路上所有环节都支持到哪一版取交集而不是取最大值。这一点和很多「向下兼容」的直觉相反EDID 更像是「按版本解释」而不是「高版本包含低版本」。5.4 详细时序与标准时序精确描述和快速标签易混点既然详细时序最精确为什么还要用标准时序那 16 个字节为什么混两者都能表达分辨率看起来功能重叠。怎么区分标准时序是压缩编码两个字节搞定一组模式代价是刷新率必须不低于 60Hz、宽高比只能是那四种、水平像素受公式限制。详细时序是完整描述18 个字节能表达任意点频、任意消隐、任意同步极性代价是占地方。实际写法是「常见模式用标准时序批量覆盖核心模式用详细时序精确描述」。如果只写详细时序能表达的模式数量太少只写标准时序一些非标模式根本写不进去。两者是互补不是替代。5.5 文件头与校验和都固定作用完全不同易混点把开头那 8 个字节也当成某种校验。为什么混两者都是固定值或固定算法看起来都属于「格式要求」。怎么区分文件头是身份标记作用是对齐告诉读取方「从这里开始是一个 EDID 块」它不参与任何运算。校验和是完整性检查作用是对账覆盖前面 127 个字节任何一个字节改动都必须重算。前者错了说明位置不对后者错了说明内容被改过或写漏了。排查时文件头对但校验和不对多半是手工改字段忘了重算文件头不对那压根不是 EDID 数据。6. 什么时候该自己写 EDID一份边界判断与踩坑清单先说结论绝大多数情况下不该手写。EDID 应该由方案商或屏厂提供或者用工具从一份验证过的模板生成。手写适用的场景其实很窄——原型阶段需要快速验证某个自定义时序能不能点亮或者手上只有一份参考数据、需要按新屏幕的物理尺寸和时序做小范围改写。真正需要判断的是「改哪一段」。可以按下面的顺序过一遍需求该动的位置不该动的位置换了一块不同尺寸的屏15h、16h 物理尺寸详细时序里的图像宽高文件头、扩展标志屏幕实际 gamma 不是 2.217h19h–22h 色度坐标只支持数字输入14h23h–25h 已确定时序新增一个非标分辨率36h 起的详细时序槽位26h–35h 标准时序区要声明 HDMI 音频能力扩展块 03h 起的数据块基本块 7Eh 之外的内容只是型号显示不对5Ah 起的名称描述符08h–09h 厂商代码这张表背后是一条经验越是靠前的字段影响面越大越不该轻易动。厂商代码写错系统里认不出设备物理尺寸写错只影响界面缩放。改之前先想清楚「这个字段错了会牵连什么」。再说什么时候别碰。三种情况建议直接放弃手改一是整条链路里有你不掌握的解析环节比如某个中间设备会缓存并二次转发 EDID你改了源头的值中间那层还拿着旧的二是产品要过兼容性认证任何自造字段都可能成为测试不通过的原因三是屏幕本身没有提供准确的时序参数文档靠猜填出来的详细时序能点亮是运气稳定运行是奢望。我自己有过一次典型的翻车为了让一块非标屏在某个主机上跑出想要的刷新率直接在标准时序区手写了两字节校验和是算对了但忘了宽高比字段只支持四种取值填进去的组合落到了保留值上。结果在测试机上一切正常换到另一台机器上直接无信号。后来把那段改成详细时序描述问题消失。从那以后我每次动 EDID都强制走一遍固定流程先读出原始 128 字节存一份改完用脚本重算校验和再逐字段打印出来和预期比对最后才烧进去。这套流程多花十分钟省下的往往是半天。希望这些能帮到你少走一点弯路。本文还有配套的精品资源点击获取