资讯详情

EDID 128字节字段解析与HDMI扩展块生成校验

📅 2026/11/3 22:42:44 | 华诺云谱 👁 阅读
EDID 128字节字段解析与HDMI扩展块生成校验
简介本资源是一份面向显示驱动开发、硬件调试与显示器固件工程师的EDID编程规范文档聚焦扩展显示识别数据EDID这一显示终端与信号源之间的信息交换结构。内容从数据结构层面拆解128字节基本块可扩展至256字节依次说明第零区域文件头固定值、生产厂家与产品识别、EDID版本号、基本显示参数与特性、色彩特性、确定显示时序、标准时序识别、详细时序描述块、显示终端名称与行场频范围限制、扩展标志及Checksum校验并结合V1.3实例给出各地址段的填写取值与默认值约定。压缩包共1个文件为doc格式体积约493KB适合作为编写或核对EDID数据表时的查阅依据。已有562人学习该资源可用于理解显示器识别失败、分辨率协商异常等问题背后的字段成因并掌握时序参数与校验和的正确写法。1. 一块 128 字节的“身份证”EDID 为什么总在最后一公里出事样机点亮画面出来了分辨率却怎么也上不到 1080p换根线、换个信号源又能正常了。这种看起来没道理的故障最后查出来的原因常常不是硬件而是显示器里那 128 字节的 EDID 里某个字段写错了。EDID扩展显示识别数据通过 DDC 通道把显示终端的识别信息和显示能力送到信号源信号源据此决定送什么时序过来。这份 EDID 编程规范把 V1.3 的字段按地址逐段列了出来基础块 128 字节扩展块再加 128 字节输入类型、gamma、三段时序、四个描述符、两个 Checksum连每个字节该填什么示例值都有。它适合正在写 EDID 的固件和驱动工程师也适合做兼容性测试、需要手工改一份 EDID 去复现问题的人。规范本身不是代码框架是一张查表用的细则。2. 拆开 Block 0128 字节里每一段在管什么基础块的布局是有规律的越靠前的字段越固定越靠后越依赖产品本身的显示能力。把地址段和用途对上号后面写代码就是照着表填。2.1 00h–13h固定文件头、厂商码与版本号00h–07h 这 8 个字节没有商量余地必须是00 FF FF FF FF FF FF 00解析器靠它确认“这是一块 EDID”写错一位整份数据就被当成无效。08h–09h 是厂商名称编码方式是把三个字母各映射成 1–26 的 5bit 数值再拼成一个 16bit 整数按大端写进文件。10h–0Bh 是产品代码0Ch–0Fh 是产品序列号规范里建议这两段为通用性起见留默认值实际量产时产品代码通常还是要区分机型。10h 存生产周数11h 存年份但年份是相对 1990 的偏移2007 年就写0x11。12h–13h 是版本号V1.3 写0x01 0x03。厂商码手算很容易翻车。我按 5bit 规则反解过某份规范里给出的示例得到的字母和它旁边的注释文字对不上说明这种字段只适合让脚本算。下面这段编解码函数我一直在用def encode_vendor_id(code: str) - bytes: 三个字母 - 2 字节大端厂商码如 ABC - b\x08\x83 code code.upper() assert len(code) 3 and code.isalpha(), 厂商码必须是 3 个字母 v 0 for ch in code: v (v 5) | (ord(ch) - ord(A) 1) # A1 ... Z26 return v.to_bytes(2, big) def decode_vendor_id(raw: bytes) - str: 反向校验把 EDID 里的 2 字节还原成字母用来回读比对 v int.from_bytes(raw, big) return .join(chr(((v s) 0x1F) ord(A) - 1) for s in (10, 5, 0)) print(encode_vendor_id(ABC).hex()) # 0883 print(decode_vendor_id(bytes.fromhex(0883))) # ABC参数说明v是 16bit 容器左移 5 位再或入下一个字母最后两个字节按大端输出decode里取的位移量 10、5、0 分别对应第 1、2、3 个字母。厂商码在 EDID 里是大端而产品代码和序列号是小端这个不一致必须记住后面第 5 章会专门讲它。2.2 14h–22h输入类型、尺寸、gamma 与色彩特性14h 是基本输入信号定义模拟输入默认0x0E数字输入默认0x80。这个字节在 V1.3 和 V1.4 里语义不同V1.3 只要求置 bit7 表示数字低位的含义留给厂商V1.4 把低位拆成了色深和接口类型。如果一份 EDID 要同时被老信号源和新信号源读按规范给0x80最省事但要注意有些主控会去看低位判断色深这时得按 V1.4 补全。15h、16h 是屏幕最大水平和垂直尺寸单位厘米。规范里说这两段一般不用、默认0x00可一旦填了信号源可能用它算 DPI 来做缩放填错比不填更糟。17h 是 gamma编码公式是round(gamma × 100) - 1002.2 算出 120即0x78如果产品 gamma 未知V1.3 里只能填一个近似值拿不准就填 2.2。18h 是支持的显示特性默认0xEA。def gamma_to_byte(gamma: float) - int: 2.2 - 0x78范围约 1.00-3.54 v int(round(gamma * 100)) - 100 assert 0 v 254, gamma 超出可编码范围 return v def byte_to_gamma(b: int) - float: return (b 100) / 100.019h–22h 共 10 个字节是色彩特性放红、绿、蓝、白的色坐标低位和高位一般用默认值、色温 9300K。这段只有在做色彩管理或广色域面板时才需要动改动前先把公式列清楚每个坐标是 10bit分成低 2 位和高 8 位两处存放写错顺序会导致颜色偏得离谱却又不会触发任何校验错误属于最难查的一类。2.3 23h–35hEstablished 与 Standard 两段时序的填法23h–25h 是确定的显示时序三个字节按位表示“支持哪些经典模式”比如 640×48060、800×60060、1024×76860 都在这张位图里。规范示例给了0xBF 0xEE 0x00注释里逐个标出对应模式。这一段的好处是无需算参数查表置位即可代价是只能表达上世纪那批固定模式。26h–35h 是标准的显示时序识别16 字节装 8 组每组 2 字节不用的组填01 01。编码规则是第一字节Hactive / 8 - 31第二字节 bit7–bit6 存宽高比00 是 16:1001 是 4:310 是 5:411 是 16:9bit5–bit0 存刷新率减 60。规范里举的例子是 640×48085Hz640/8-3149得0x31宽高比 4:3 对应0185-6025得0x19合起来第二字节0x59。from math import gcd def std_timing(h: int, v: int, refresh: int) - bytes: assert h % 8 0, 水平像素必须是 8 的倍数 b1 h // 8 - 31 assert 0 b1 255, 水平像素超出该字段表达范围 g gcd(h, v) ratio {(16, 10): 0, (4, 3): 1, (5, 4): 2, (16, 9): 3}.get((h // g, v // g)) assert ratio is not None, 宽高比只能是 16:10 / 4:3 / 5:4 / 16:9 assert 60 refresh 123, 刷新率只支持 60-123Hz return bytes([b1, (ratio 6) | (refresh - 60)]) print(std_timing(640, 480, 85).hex()) # 3159这段代码的价值在于两个断言Hactive最大只能到(25531)×8 2288所以 1920 能塞进去、2560 塞不进去宽高比只能是四种约定值21:9 这类比例在这段里根本表达不了只能走详细时序描述。很多人第一次写 EDID 会把 1920×1080 往标准时序里塞算出来b1 209看着没报错解析器读出来的却是另一个分辨率。2.4 36h–7Dh四个 18 字节描述符的分工从 36h 到 7Dh 共 72 字节切成 4 个 18 字节的描述符。前两个通常是详细时序描述第三、第四个按规范放显示终端名称标识头00 00 00 FC 00和行场频范围限制标识头00 00 00 FD 00。判断一个描述符是时序还是文本看它前两个字节像素时钟非 0 就是详细时序像素时钟为 0 再看第 4 个字节的标签。详细时序的 18 字节是整份规范里位域最碎的地方除了像素时钟按小端存其余字段都是“低 8 位单独放、高位拼在一个字节的高低半字节里”。以规范里的 1920×1080i 为例像素时钟 74.25MHz 写成01 1D行有效 1920 写成80加高位的7场有效 540 写成1C加高位的2第 11 个字节还要把行场同步偏移和宽度的第 9、10 位挤在一起。手工填这四个块出错概率接近 100%。3. 用脚本把字段表压成 256 字节一份能跑通的生成流程规范给的是“地址—取值—含义”三列直接照着填文件不现实。我的做法是先把它翻译成一份配置字典再用固定顺序的函数拼字节最后统一回读校验。这样改一个模式只动配置不动代码。3.1 先落地字段表把规范翻译成字典配置只保留真正会变的字段厂商码、产品代码、生产信息、输入类型、gamma、三段时序、四个描述符。写死的常量文件头、版本号直接放函数里避免有人误改。CFG { vendor: ABC, product: 0x0760, serial: 0x00000001, week: 7, year: 2007, # 实际年份写入时减 1990 video_input: 0x80, # 数字输入 hsize_cm: 70, vsize_cm: 39, gamma: 2.2, features: 0xEA, chroma: bytes.fromhex(de58a3544c99260f4547), # 10 字节色彩特性 established: bytes.fromhex(bfee00), # 3 字节确定时序位图 std_timings: b.join([ std_timing(640, 480, 75), std_timing(800, 600, 72), std_timing(800, 600, 75), std_timing(1024, 768, 70), std_timing(1024, 768, 75), std_timing(800, 600, 85), b\x01\x01, b\x01\x01, # 空位统一填 01 01 ]), ext_count: 1, # 有扩展块就写 1否则 0 }参数说明year用真实年份减 1990 的动作放到打包函数里避免配置里出现两个年份口径std_timings必须正好 16 字节少一组就会把后面描述符的位置整体前移而 Checksum 依然能算对问题会拖到解析阶段才暴露。3.2 DTD 的位域打包18 字节里藏了 4 个高位拼接下面这个函数把一段时序的参数压成 18 字节参数直接用“有效像素 消隐 同步偏移 同步宽度”的口径和大多数时序表一致不用先换算成总数。def pack_dtd(pclk_khz, hactive, hblank, vactive, vblank, hfp, hsw, vfp, vsw, hmm, vmm, interlaceFalse, hpol1, vpol1) - bytes: pc int(round(pclk_khz / 10.0)) # 单位 10kHz assert 0 pc 0xFFFF b bytearray(18) b[0] pc 0xFF # 像素时钟是小端低字节在前 b[1] (pc 8) 0xFF b[2] hactive 0xFF b[3] hblank 0xFF b[4] ((hactive 8) 4) | ((hblank 8) 0x0F) b[5] vactive 0xFF b[6] vblank 0xFF b[7] ((vactive 8) 4) | ((vblank 8) 0x0F) b[8] hfp 0xFF # 行同步偏移 前肩 b[9] hsw 0xFF b[10] ((vfp 0x0F) 4) | (vsw 0x0F) b[11] (((hfp 8) 0x03) 6) | (((hsw 8) 0x03) 4) \ | (((vfp 4) 0x03) 2) | ((vsw 4) 0x03) b[12] hmm 0xFF b[13] vmm 0xFF b[14] ((hmm 8) 4) | ((vmm 8) 0x0F) b[15] 0 # 水平边框一般 0 b[16] 0 # 垂直边框 flags 0x18 # bit4-3 11数字分离同步 if interlace: flags | 0x80 flags | (vpol 1) 2 flags | (hpol 1) 1 b[17] flags return bytes(b) # 1920x1080i60行 2200 总长、场 562 总行 dtd pack_dtd(74250, 1920, 280, 540, 22, 88, 44, 2, 5, 708, 398, interlaceTrue) print(dtd.hex()) # 011d8018711c1620582c2500c48e2100009e参数说明pclk_khz传千赫内部除以 10 换成规范要求的 10kHz 单位hblank和vblank是消隐总量必须等于“前肩 同步宽度 后肩”函数不替你校验这个关系写错就会得到一份 Checksum 合格但画面偏移的 EDIDvactive在隔行模式下填的是场有效行数1080i 要填 540不是 1080flags里0x18表示数字分离同步再按极性置 bit2 和 bit1。上面这组参数算出来的 18 字节和规范示例逐字节一致可以拿它当回归用例。名称和范围限制两个描述符没有位域但填充规则容易记混def pack_name(name: str) - bytes: b bytearray(18) b[0:5] bytes([0x00, 0x00, 0x00, 0xFC, 0x00]) raw name.encode(ascii)[:13] b[5:5 len(raw)] raw b[5 len(raw)] 0x0A # 结束符紧跟在名称之后 for i in range(6 len(raw), 18): b[i] 0x20 # 剩余空间补空格 return bytes(b) def pack_range(vmin, vmax, hmin, hmax, max_pclk_mhz) - bytes: b bytearray(18) b[0:5] bytes([0x00, 0x00, 0x00, 0xFD, 0x00]) b[5], b[6], b[7], b[8] vmin, vmax, hmin, hmax b[9] int(round(max_pclk_mhz / 10)) # 单位 10MHz b[10] 0x00 # 默认 GTF 支持方式 b[11] 0x0A for i in range(12, 18): b[i] 0x20 return bytes(b)0x0A的位置是这两段最常见的错误名称长度不足 13 时结束符应该紧跟名称剩下补0x20如果先把 13 字节全补成空格再把最后一个字节改成0x0A解析出来的名称会带一串多余空格某些信号源会因此匹配不上预设的机型配置。3.3 组块、算 Checksum、回读验证四个描述符凑齐 72 字节后按地址顺序一次性写入54到125再处理扩展标志和校验和。def build_block0(cfg) - bytearray: b bytearray(128) b[0:8] bytes([0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00]) b[8:10] encode_vendor_id(cfg[vendor]) b[10:12] cfg[product].to_bytes(2, little) # 产品代码小端 b[12:16] cfg[serial].to_bytes(4, little) # 序列号小端 b[16] cfg[week] b[17] cfg[year] - 1990 b[18:20] bytes([0x01, 0x03]) b[20] cfg[video_input] b[21] cfg[hsize_cm] b[22] cfg[vsize_cm] b[23] gamma_to_byte(cfg[gamma]) b[24] cfg[features] b[25:35] cfg[chroma] b[35:38] cfg[established] b[38:54] cfg[std_timings] desc b.join([ pack_dtd(74250, 1920, 280, 540, 22, 88, 44, 2, 5, 708, 398, interlaceTrue), pack_dtd(27000, 720, 138, 480, 45, 16, 62, 9, 6, 531, 398), pack_name(ABC HDMI TV), pack_range(59, 61, 15, 46, 80), ]) assert len(desc) 72 b[54:126] desc b[126] cfg[ext_count] b[127] (-sum(b[:127])) 0xFF # 校验和 让 128 字节求和为 0 return b blk build_block0(CFG) assert sum(blk) 0xFF 0 open(display_block0.bin, wb).write(blk)参数说明b[10:12]和b[12:16]用little是有意为之产品代码与序列号在 EDID 里是小端跟厂商码相反b[127]那一行先对前 127 字节求和、取负、再与0xFF相与等价于(256 - sum) % 256注意必须取模直接写256 - sum在求余为 0 时会得到 256最后的断言是整份数据的唯一强校验写完一定要跑。回读用现成的解析工具最省事edid-decode display_block0.bin它会把每个字段翻译成人能读的形式包括厂商码、版本、四个描述符的时序参数和校验结果。生成的字段和你配置里的值逐条对一遍对不上的地方就是位域拼错的位置。4. 扩展块怎么写VGA/DVI 与 HDMI 是两条不同的路128 字节基础块只够描述“显示器是什么”音箱、HDMI 通道、额外的 4:3 和 16:9 模式要靠扩展块。规范把扩展块分成两种写法接口只支持 VGA 或 DVI 的走简单结构支持 HDMI 的走 CEA-861B 结构。选错分支不会报错但信号源读不到音频能力或者拿不到 HDMI 厂商信息表现就是“插上没声音”“HDR 起不来”。4.1 02h 开头之后revision、DTD offset 与能力标志扩展块第一个字节固定是0x02表示 CEA-861 扩展第二个字节是修订版本VGA/DVI 分支写0x01HDMI 分支写0x03。第三个字节是关键它叫 DTD offset指向“详细时序从这里开始”同时也隐含了前面数据块集合的长度。第四个字节放能力标志bit7 欠扫描、bit6 基本音频、bit5 YCbCr 4:4:4、bit4 YCbCr 4:2:2低 4 位是详细时序的个数。VGA/DVI 分支的结构非常简单02 01 04 00之后紧跟一个 18 字节的详细时序通常是 1280×720p6016h 到 7Eh 全部填 07Fh 一个 Checksum。这种写法适合只走模拟或 DVI 的机型没有数据块集合的概念。HDMI 分支则必须在 04h 到d-1之间塞满各种数据块d就是 DTD offset也就是 HDMI 支持音频信息标志的起始地址。4.2 Data Block Collectiontag、长度与四类块的排布每个数据块的第一个字节是“标签 长度”高 3 位是 tag code1 是音频、2 是视频、3 是厂商描述、4 是喇叭安置低 5 位是这个块的字节数不含这个头字节本身。这个“不含头字节”的约定是错位的高发区写多写少一个字节后面所有块的位置全偏。def video_block(vics) - bytes: # tag2每个 VIC 占 1 字节bit7 置 1 表示该模式是接收端首选 return bytes([(2 5) | len(vics)]) bytes(vics) def audio_block(sads) - bytes: # tag1每个 SAD 固定 3 字节 return bytes([(1 5) | (3 * len(sads))]) b.join(sads) def sad_lpcm(channels, rates0x07, depths0x07) - bytes: b1 (1 3) | ((channels - 1) 0x07) # 格式码 1 LPCM低 3 位是通道数-1 return bytes([b1, rates, depths]) def speaker_block(mask: int) - bytes: # tag4固定 3 字节位图bit0 前左右、bit1 低频、其余按规范表顺序 return bytes([(4 5) | 3, mask 0xFF, (mask 8) 0xFF, (mask 16) 0xFF])参数说明vics里放的是视频模式确认码0x85是 1080i60、0x41是 720p60、0x11是 576p50、0x02是 480p60、0x01是 640×480p60选码之前先确认目标机型的面板真的支持sad_lpcm的rates里 bit0 对应 32kHz、bit1 对应 44.1kHz、bit2 对应 48kHzdepths里 bit0 对应 16bit、bit1 对应 20bit、bit2 对应 24bit0x07表示三档全支持。4.3 HDMI VSDB 与 Supports_AI位置依赖长度字节HDMI 厂商描述块VSDB是整份扩展块里最需要小心的地方。它的 payload 前三字节是注册标识03 0C 00低位在前接着两字节是 HDMI 通道的物理地址每个通道占 4bit再往后是可选字节Supports_AI 就在这一串可选字节里。def hdmi_vsdb(pa: int, supports_aiTrue, max_tmds_mhzNone) - bytes: p bytearray() p bytes([0x03, 0x0C, 0x00]) # 24bit 注册标识低位在前 p bytes([(pa 8) 0xFF, pa 0xFF]) # A.B.C.D 各 4bit如 0x1000 - 1.0.0.0 if supports_ai: p bytes([0x80]) # Supports_AI能收 ACP/ISRC 包 if max_tmds_mhz: p bytes([max_tmds_mhz // 5]) # 单位 5MHz return bytes([(3 5) | len(p)]) bytes(p)参数说明pa是物理地址0x1000表示 1.0.0.0双 HDMI本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑