EDID 数据结构解析与编辑工具:校验和计算、厂商/设备名编解码、物理地址读写、颜色与时序信息提取
本程序实现对 Extended Display Identification Data (EDID) 二进制文件的完整解析与交互式编辑功能,涵盖数据校验、制造商ID编解码、显示器名称读写、物理地址设置、颜色坐标解析、时序信息展示等核心知识点。所有代码保留原始变量名与函数名,每条代码均添加详细注释,并附带理想运行结果示例。
🧩 一、头文件与辅助函数
c
#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字节块的校验和
c
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字节)
c
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字母字符串
c
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位制造商代码
c
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~26
int 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字符串
c
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块提取显示器名称
c
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块
c
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格式)
c
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. 设置物理地址到扩展块
c
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文件到内存
c
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. 将内存数据写回文件
c
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)
c
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. 打印十六进制数据(调试用)
c
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全部信息(核心解析函数)
c
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;
// RED
tmp1 = ((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);
// GREEN
tmp1 = ((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);
// BLUE
tmp1 = ((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);
// WHITE
tmp1 = ((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
🖥️ 九、主函数:交互式菜单
c
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、9
buf[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兼容性测试的实用参考实现。