EDID 数据结构解析与编辑工具:校验和计算、厂商/设备名编解码、物理地址读写、颜色与时序信息提取
EDID 数据结构解析与编辑工具:校验和计算、厂商/设备名编解码、物理地址读写、颜色与时序信息提取
本程序实现对 Extended Display Identification Data (EDID) 二进制文件的完整解析与交互式编辑功能,涵盖数据校验、制造商ID编解码、显示器名称读写、物理地址设置、颜色坐标解析、时序信息展示等核心知识点。所有代码保留原始变量名与函数名,每条代码均添加详细注释,并附带理想运行结果示例。
🧩 一、头文件与辅助函数
#include <ctype.h> // 提供 toupper() 函数,用于字符转大写
#include <math.h> // 提供 fabs() 函数(虽未使用,保留原结构)
#include <stdint.h> // 提供 uint8_t, uint16_t, uint32_t 等标准整型
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 提供 malloc(), free(), exit()
#include <string.h> // 提供字符串与内存操作函数:strlen, memcpy, memset, memcmp// 理想运行结果:无直接输出,为后续函数提供基础支持
🔢 二、校验和计算与修复
1. 计算128字节块的校验和
static uint8_t calc_checksum(const uint8_t *blk)
{uint32_t sum = 0; // 初始化累加器for (int i = 0; i < 128; i++) // 遍历全部128字节{sum = sum + blk[i]; // 累加每一字节}return (uint8_t)(sum & 0xFF); // 返回低8位作为校验和
}
// 理想运行结果:输入有效EDID块,返回如 0xA3
2. 修复块的校验和(修改第127字节)
static void fix_checksum(uint8_t *blk)
{uint32_t sum = 0; // 初始化累加器for (int i = 0; i < 127; i++) // 仅累加前127字节{sum = sum + blk[i]; // 累加}blk[127] = (uint8_t)((256 - (sum & 0xFF)) & 0xFF); // 第128字节设为补码校验值
}
// 理想运行结果:修改 blk[127] 使整个块校验和为0
🏭 三、制造商ID编解码
1. 解码16位制造商代码为3字母字符串
static void decode_mfg_id(uint16_t code, char out[4])
{int c1 = ((code >> 10) & 0x1F); // 提取高5位(bit14~10)int c2 = ((code >> 5) & 0x1F); // 提取中5位(bit9~5)int c3 = (code & 0x1F); // 提取低5位(bit4~0)out[0] = 'A' + c1 - 1; // 映射为 'A'~'Z'(1→A, 26→Z)out[1] = 'A' + c2 - 1;out[2] = 'A' + c3 - 1;out[3] = '\0'; // 字符串结束符
}
// 理想运行结果:输入 0x23A0 → 输出 "SAM"
2. 编码3字母字符串为16位制造商代码
static int encode_mfg_id(const char *three, uint16_t *out)
{if (strlen(three) != 3) // 必须为3字符{return -1; // 长度错误}int c1 = toupper(three[0]) - 'A' + 1; // 转大写,映射为1~26int c2 = toupper(three[1]) - 'A' + 1;int c3 = toupper(three[2]) - 'A' + 1;if (c1 < 1 || c1 > 26 || c2 < 1 || c2 > 26 || c3 < 1 || c3 > 26){return -1; // 超出A~Z范围}*out = (c1 << 10) | (c2 << 5) | c3; // 组合成16位码return 0; // 成功
}
// 理想运行结果:输入 "SAM" → 输出 0x23A0,返回0
📝 四、安全ASCII转换与显示器名称操作
1. 安全转换二进制数据为可打印ASCII字符串
static const char *safe_ascii(const uint8_t *buf, int len, char *out, int outsz)
{int n = len < outsz - 1 ? len : outsz - 1; // 防止缓冲区溢出for (int i = 0; i < n; ++i){out[i] = (buf[i] >= 0x20 && buf[i] <= 0x7E) ? buf[i] : ' '; // 非打印字符转空格}out[n] = '\0'; // 结束符for (int i = n - 1; i >= 0; --i) // 去除末尾空白符{if (out[i] == ' ' || out[i] == '\n' || out[i] == '\r' || out[i] == '\t'){out[i] = '\0'; // 截断}else{break; // 遇到非空白字符停止}}return out;
}
// 理想运行结果:输入 {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x00} → 输出 "Hello"
2. 从EDID块提取显示器名称
static const char *get_monitor_name(const uint8_t *blk, char out[32])
{for (int i = 0; i < 4; ++i) // 搜索4个描述符块{int off = 54 + i * 18; // 每块18字节,从54字节开始const uint8_t *d = blk + off; // 定位到当前块if (d[0] == 0 && d[1] == 0 && d[3] == 0xFC) // 检查是否为显示器名称描述符{char tmp[16];safe_ascii(d + 5, 13, tmp, sizeof(tmp)); // 从d[5]开始取13字节转字符串snprintf(out, 32, "%s", tmp); // 复制到输出缓冲区return out;}}out[0] = '\0'; // 未找到则返回空串return out;
}
// 理想运行结果:若块中含 "DELL U2720Q" → 返回该字符串
3. 设置显示器名称到EDID块
static void set_monitor_name(uint8_t *blk, const char *name)
{for (int i = 0; i < 4; ++i){int off = 54 + i * 18;uint8_t *d = blk + off;if (d[0] == 0 && d[1] == 0 && d[3] == 0xFC) // 找到名称描述符{memset(d + 5, 0x20, 13); // 用空格填充原名称区size_t n = strlen(name); // 获取新名称长度if (n > 13) // 最多13字符n = 13;memcpy(d + 5, name, n); // 复制新名称d[5 + n] = 0x0A; // 末尾加换行符(非必须,保留原逻辑)return;}}
}
// 理想运行结果:将 "MyMonitor" 写入对应描述符块
📍 五、物理地址读写(CEA扩展块)
1. 从扩展块读取物理地址(A.B.C.D格式)
static int get_physical_address_simple(uint8_t *ext, int *a, int *b, int *c, int *d)
{if (ext == NULL || ext[0] != 0x02) // 必须为CEA扩展块{return -1;}uint8_t hi = ext[0x04]; // 高字节uint8_t lo = ext[0x05]; // 低字节*a = (hi >> 4) & 0x0F; // A: 高4位*b = hi & 0x0F; // B: 低4位*c = (lo >> 4) & 0x0F; // C: 高4位*d = lo & 0x0F; // D: 低4位return 0;
}
// 理想运行结果:若 ext[4]=0xAB, ext[5]=0xCD → a=10, b=11, c=12, d=13
2. 设置物理地址到扩展块
static int set_physical_address_simple(uint8_t *ext, int a, int b, int c, int d)
{if (ext == NULL) // 指针不能为空{return -1;}ext[0x04] = ((a & 0x0F) << 4) | (b & 0x0F); // 组合A(高4位)与B(低4位)ext[0x05] = ((c & 0x0F) << 4) | (d & 0x0F); // 组合C(高4位)与D(低4位)return 0;
}
// 理想运行结果:设置 a=1,b=2,c=3,d=4 → ext[4]=0x12, ext[5]=0x34
💾 六、文件读写函数
1. 读取整个EDID文件到内存
static uint8_t *read_all(const char *path, size_t *out_sz)
{FILE *f = fopen(path, "rb"); // 以二进制只读打开if (f == NULL){perror("fopen fail"); // 打印系统错误return NULL;}fseek(f, 0, SEEK_END); // 定位到文件末尾long sz = ftell(f); // 获取文件大小fseek(f, 0, SEEK_SET); // 回到文件开头if (sz <= 0 || (sz % 128) != 0) // 必须为128倍数{fclose(f);return NULL;}uint8_t *buf = malloc(sz); // 分配内存fread(buf, 1, sz, f); // 读取全部内容fclose(f); // 关闭文件*out_sz = sz; // 返回实际大小return buf;
}
// 理想运行结果:成功读取 "edid.bin"(256字节)→ 返回指针,out_sz=256
2. 将内存数据写回文件
static int write_all(const char *path, const uint8_t *buf, size_t sz)
{FILE *f = fopen(path, "wb"); // 以二进制写入打开if (f == NULL){return -1; // 打开失败}fwrite(buf, 1, sz, f); // 写入全部数据fclose(f); // 关闭文件return 0; // 成功
}
// 理想运行结果:成功写入 → 返回0
🎨 七、颜色坐标解析
1. 将10位色度值转换为浮点坐标(0.0~1.0)
static double get_color_value(unsigned short num)
{double value = 0;double power_of_2 = 1.0 / 1024.0; // 2^(-10),最低位权值for (int i = 0; i < 10; i++) // 遍历10位{if (num & (1 << i)) // 检查第i位是否为1{value += power_of_2; // 累加对应权值}power_of_2 *= 2.0; // 权值左移(×2)}return value;
}
// 理想运行结果:输入 0x200 (0b1000000000) → 返回 0.5000
📊 八、信息展示函数
1. 打印十六进制数据(调试用)
static void print_hex_data(uint8_t *buf, size_t sz)
{printf("load file read dat size =%zu\n", sz); // 打印总字节数for (size_t i = 0; i < sz; i++){if (i % 16 == 0) // 每16字节换行{printf("\n%02zx ", i); // 打印偏移地址}printf("%02x ", buf[i]); // 打印当前字节}printf("\n");
}
// 理想运行结果:
// 00 ff ff ff ff ff ff 00 4c 2d 01 02 ...
2. 展示EDID全部信息(核心解析函数)
static void show_all_info(uint8_t *buf, size_t sz)
{printf("\n--------------Base info------------------\n");// 验证EDID头unsigned char head[] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0};if (0 == memcmp(head, buf, sizeof(head))){printf("this is valid edid\n");}else{printf("this is not valid edid\n");return;}// 校验和检查(基础块)uint32_t sum = 0;for (int i = 0; i < 127; i++){sum += buf[i];}unsigned char check = (256 - (sum % 256)) % 256;if (buf[127] == check){printf("base check 0x%02x\n", buf[127]);}else{printf("base check failure....\n");return;}// 扩展块校验和(如有)if (sz >= 256 && buf[128] == 0x02){sum = 0;for (int i = 0; i < 127; i++){sum += buf[128 + i];}check = (256 - (sum % 256)) % 256;if (buf[255] == check){printf("扩展部分 check 0x%02x\n", buf[255]);}else{printf("ext check failure....\n");return;}}// 制造商信息uint16_t mfg_code = (buf[8] << 8) | buf[9];char mfg[4];decode_mfg_id(mfg_code, mfg);printf("制造商名称 %s\t", mfg);printf("产品代码 %u\t", (buf[10] | (buf[11] << 8)));printf("产品序列号 %u\t", (buf[12] | (buf[13] << 8) | (buf[14] << 16) | (buf[15] << 24)));printf("制造周 %u\t", buf[16]);printf("制造年份 %u\n", buf[17] + 1990);// 版本信息printf("版本号 %u\t修改号 %u\n", buf[18], buf[19]);// 基本显示参数printf("BasicDisplayParametersFeatures\n");if (buf[20] & 0x80){printf("\t 数字信号显示\n");}else{printf("\t 模拟信号显示\n");}printf("\t %ucm * %u cm\n", buf[21], buf[22]);printf("\t Gamma [1.00→3.55] %.2f\n", (buf[23] + 100) / 100.0);// 功能支持if (buf[24] & 0x80)printf("\t 支持Standby");if (buf[24] & 0x40)printf("\t 支持Suspend");if (buf[24] & 0x20)printf("\t 支持Low Power");if (buf[24] & 0x08)printf("\t RGB颜色显示");if (buf[24] & 0x04)printf("\t 非标准sRGB");if (buf[24] & 0x02)printf("\t 第一时序");if (buf[24] & 0x01)printf("\t 支持GTF\n");// 颜色特性printf(" ColorCharacteristic:\n");unsigned short tmp1, tmp2;double result1, result2;// REDtmp1 = ((buf[25] & 0xc0) >> 6) | ((buf[27] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = ((buf[25] & 0x30) >> 4) | ((buf[28] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t RED %.4f %.4f\n", result1, result2);// GREENtmp1 = ((buf[25] & 0x0c) >> 2) | ((buf[29] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = (buf[25] & 0x03) | ((buf[30] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t GREEN %.4f %.4f\n", result1, result2);// BLUEtmp1 = ((buf[26] & 0xc0) >> 6) | ((buf[31] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = ((buf[26] & 0x30) >> 4) | ((buf[32] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t Blue %.4f %.4f\n", result1, result2);// WHITEtmp1 = ((buf[26] & 0x0c) >> 2) | ((buf[33] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = (buf[26] & 0x03) | ((buf[34] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t Whith %.4f %.4f\n", result1, result2);// 已建立时序printf(" Established Timings\n");if (buf[35] & 0x80) printf("\t 720 x 400 @ 70Hz\n");if (buf[35] & 0x40) printf("\t 720 x 400 @ 88Hz\n");if (buf[35] & 0x20) printf("\t 640 x 480 @ 60Hz\n");if (buf[35] & 0x10) printf("\t 640 x 480 @ 67Hz\n");if (buf[35] & 0x08) printf("\t 640 x 480 @ 72Hz\n");if (buf[35] & 0x04) printf("\t 640 x 480 @ 75Hz\n");if (buf[35] & 0x02) printf("\t 800 x 600 @ 56Hz\n");if (buf[35] & 0x01) printf("\t 800 x 600 @ 60Hz\n");if (buf[36] & 0x80) printf("\t 800 x 600 @ 72Hz\n");if (buf[36] & 0x40) printf("\t 800 x 600 @ 75Hz\n");if (buf[36] & 0x20) printf("\t 832 x 624 @ 75H z\n");if (buf[36] & 0x10) printf("\t 1024 x 768 @ 87Hz(I)\n");if (buf[36] & 0x08) printf("\t 1024 x 768 @ 60Hz\n");if (buf[36] & 0x04) printf("\t 1024 x 768 @ 70Hz\n");if (buf[36] & 0x02) printf("\t 1024 x 768 @ 75Hz\n");if (buf[36] & 0x01) printf("\t 1280 x 1024 @ 75Hz\n");if (buf[37] & 0x80) printf("\t 1152 x 870 @ 75Hz\n");// 标准时序(预留,未实现详细解析)printf(" Standard Timing Identification\n");// 详细时序描述(预留)printf(" DetailedTimeDescription\n");// 产品名称char name[32];get_monitor_name(buf, name);printf("ProductName: %s\n", name);// 扩展信息printf("--------------Ext info------------------\n");if (sz >= 256 && buf[128] == 0x02){int a, b, c, d;if (get_physical_address_simple(buf + 128, &a, &b, &c, &d) == 0){printf("物理地址:A:%d B:%d C:%d D:%d\n", a, b, c, d);}}else{printf("设备没有扩展信息,没有物理地址\n");}
}// 理想运行结果:
// --------------Base info------------------
// this is valid edid
// base check 0xa3
// 扩展部分 check 0xb7
// 制造商名称 SAM 产品代码 1234 产品序列号 567890 制造周 25 制造年份 2020
// 版本号 1 修改号 4
// BasicDisplayParametersFeatures
// 数字信号显示
// 60cm * 34 cm
// Gamma [1.00→3.55] 2.20
// 支持Standby 支持Suspend RGB颜色显示 支持GTF
// ColorCharacteristic:
// RED 0.6400 0.3300
// GREEN 0.3000 0.6000
// Blue 0.1500 0.0600
// Whith 0.3127 0.3290
// Established Timings
// 640 x 480 @ 60Hz
// 800 x 600 @ 60Hz
// 1024 x 768 @ 60Hz
// Standard Timing Identification
// DetailedTimeDescription
// ProductName: SAMSUNG U28E590D
// --------------Ext info------------------
// 物理地址:A:0 B:0 C:1 D:0
🖥️ 九、主函数:交互式菜单
int main(int argc, char *argv[])
{char filename[256];// 支持命令行传参或交互输入if (argc > 1){strncpy(filename, argv[1], sizeof(filename) - 1); // 使用命令行参数filename[sizeof(filename) - 1] = '\0';}else{printf("请输入EDID文件名: ");scanf("%255s", filename); // 用户输入}size_t sz;uint8_t *buf = read_all(filename, &sz); // 读取文件if (buf == NULL){printf("fread fail!\n");return 1;}print_hex_data(buf, sz); // 先打印原始数据int run = 1;while (run){printf("\n==== 菜单 ====\n");printf("1.showall\n");printf("2.设置厂商名\n");printf("3.设置产品名\n");printf("4.设置物理地址\n");printf("5.结束\n");printf("请选择:");int choice;scanf("%d", &choice);if (choice == 5){write_all(filename, buf, sz); // 保存修改printf("已保存到%s\n", filename);run = 0;}else if (choice == 1){show_all_info(buf, sz); // 显示全部信息}else if (choice == 2){char new_mfg[8];printf("请输入新的厂家名: ");scanf("%7s", new_mfg);uint16_t code;if (encode_mfg_id(new_mfg, &code) == 0) // 编码成功{buf[8] = code >> 8; // 更新字节8、9buf[9] = code & 0xFF;fix_checksum(buf); // 修复校验和printf("厂家名已更新\n");}else{printf("input fail\n");}}else if (choice == 3){char new_name[32];printf("请输入新的设备名:");scanf("%31s", new_name);set_monitor_name(buf, new_name); // 设置名称fix_checksum(buf); // 修复校验和printf("设备名已更新\n");}else if (choice == 4){int a, b, c, d;printf("请输入物理地址(a.b.c.d):");scanf("%d.%d.%d.%d", &a, &b, &c, &d);if (a < 0 || a > 15 || b < 0 || b > 15 || c < 0 || c > 15 || d < 0 || d > 15){printf("错误: 每个数字必须在 0-15 范围内\n");continue;}if (sz >= 256){if (buf[128] == 0x02){if (set_physical_address_simple(buf + 128, a, b, c, d) == 0){fix_checksum(buf + 128); // 修复扩展块校验和printf("物理地址已更新为 %d.%d.%d.%d\n", a, b, c, d);}else{printf("设置物理地址失败\n");}}else{printf("未找到有效的 CEA 扩展块\n");}}else{printf("EDID 文件太短,无法设置物理地址\n");}}else{printf("无效选择\n");}}free(buf); // 释放内存return 0;
}// 理想运行结果:
// 输入文件名 → 显示菜单 → 选择1显示信息 → 选择2修改厂商 → 输入"SAM" → 成功 → 选择5保存退出
✅ 总结知识点:
- EDID 1.3/1.4 数据结构解析
- 128/256字节块校验和计算与修复
- 3字母厂商ID ↔ 16位编码互转
- 安全ASCII字符串处理
- 显示器名称定位与修改(Descriptor Tag 0xFC)
- CEA扩展块物理地址读写(HDMI/DisplayPort)
- 10位色度坐标浮点转换
- 位域解析:功能支持、时序标志
- 交互式命令行菜单设计
- 二进制文件读写与内存管理
本工具可作为显示器固件调试、EDID注入、HDMI兼容性测试的实用参考实现。