EDID 数据结构解析与编辑工具:校验和计算、厂商/设备名编解码、物理地址读写、颜色与时序信息提取

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兼容性测试的实用参考实现。

相关推荐
Pluchon2 小时前
硅基计划3.0 Map类&Set类
java·开发语言·数据结构·算法·哈希算法·散列表
重生之我是Java开发战士4 小时前
【数据结构】Java集合框架:List与ArrayList
java·数据结构·list
爱干饭的boy4 小时前
手写Spring底层机制的实现【初始化IOC容器+依赖注入+BeanPostProcesson机制+AOP】
java·数据结构·后端·算法·spring
躲在云朵里`4 小时前
Redis深度解析:核心数据结构、线程模型与高频面试题
数据结构·数据库·redis
wangwangblog6 小时前
LLVM 数据结构简介
开发语言·数据结构·c++
平平无奇。。。9 小时前
解密完全二叉树顺序存储之堆结构
c语言·数据结构·visual studio
ゞ 正在缓冲99%…10 小时前
leetcode142.环形链表II
数据结构·链表
岑梓铭10 小时前
《考研408数据结构》第一章复习笔记
数据结构·笔记·考研·408