1. 读取显示器EDID基本信息

读取显示器EDID基本信息 学习卡片

任务说明

本案例需要编写一个Linux用户空间程序,通过I2C总线读取显示器DDC通道中的EDID数据(128字节),并解析出厂商识别码、产品ID、基本显示参数(最大行/列分辨率、屏幕尺寸、信号类型等)。程序将直接操作I2C设备节点,不依赖额外库,适用于嵌入式Linux或标准Linux系统。

EDID读取原理

显示器通过I2C地址0x50(DDC2B接口)暴露EDID数据。读取时需先向地址0x00写入偏移量,然后连续读取128字节。解析规则遵循EDID 1.3/1.4规范:厂商识别码位于字节8-9(压缩为3个ASCII字符),产品ID位于字节10-11(小端序),基本参数位于字节21-25。

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

#define EDID_SIZE 128
#define DDC_ADDR 0x50

/* 解析厂商ID:字节8-9,每5位对应一个字母 */
void parse_manufacturer(const unsigned char *edid, char *out) {
    int val = (edid[8] << 8) | edid[9];
    out[0] = 'A' + ((val >> 10) & 0x1F) - 1;
    out[1] = 'A' + ((val >> 5) & 0x1F) - 1;
    out[2] = 'A' + (val & 0x1F) - 1;
    out[3] = '\0';
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "用法: %s /dev/i2c-N\n", argv[0]);
        return 1;
    }

    int fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        perror("打开I2C设备失败");
        return 1;
    }

    if (ioctl(fd, I2C_SLAVE, DDC_ADDR) < 0) {
        perror("设置I2C从机地址失败");
        close(fd);
        return 1;
    }

    unsigned char edid[EDID_SIZE];
    unsigned char offset = 0;

    /* 写入EDID起始偏移 */
    if (write(fd, &offset, 1) != 1) {
        perror("写入偏移失败");
        close(fd);
        return 1;
    }

    /* 读取128字节EDID */
    if (read(fd, edid, EDID_SIZE) != EDID_SIZE) {
        perror("读取EDID失败");
        close(fd);
        return 1;
    }
    close(fd);

    /* 校验头:00 FF FF FF FF FF FF 00 */
    if (memcmp(edid, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", 8) != 0) {
        fprintf(stderr, "EDID头校验失败,数据无效\n");
        return 1;
    }

    /* 解析厂商ID */
    char mfr[4];
    parse_manufacturer(edid, mfr);

    /* 产品ID(小端序) */
    int product_id = edid[10] | (edid[11] << 8);

    /* 基本参数 */
    int edid_version = edid[18];
    int edid_revision = edid[19];
    int max_h = edid[21];   /* 最大水平尺寸(厘米) */
    int max_v = edid[22];   /* 最大垂直尺寸(厘米) */
    int gamma = edid[23];   /* 伽马值(需除以100) */
    int video_input = edid[20];

    /* 显示类型判断 */
    const char *input_type;
    if ((video_input & 0x80) == 0) {
        input_type = "数字信号";
    } else {
        input_type = "模拟信号";
    }

    /* 输出解析结果 */
    printf("========== EDID基本信息 ==========\n");
    printf("厂商: %s\n", mfr);
    printf("产品ID: 0x%04X (%d)\n", product_id, product_id);
    printf("EDID版本: %d.%d\n", edid_version, edid_revision);
    printf("信号类型: %s\n", input_type);
    printf("最大水平尺寸: %d cm\n", max_h);
    printf("最大垂直尺寸: %d cm\n", max_v);
    printf("伽马值: %.2f\n", gamma / 100.0f);
    printf("===================================\n");

    return 0;
}

编译与运行

# 编译
gcc -o edid_read edid_read.c

# 运行(假设I2C总线为1)
sudo ./edid_read /dev/i2c-1

验证方法

将程序连接至真实显示器对应的I2C总线(通常为显卡DDC通道),运行后观察输出。判定标准:

  • 程序能输出8字节EDID头校验通过后的解析信息;
  • 厂商字段显示为3个大写字母(如DEL为戴尔、ACR为宏碁);
  • 产品ID与显示器规格书或系统日志(dmesg)中记录的ID一致;
  • 最大水平/垂直尺寸与显示器标称尺寸(英寸换算为厘米)相符。

若I2C总线号不确定,可通过i2cdetect -l列出所有总线,再使用i2cdetect -y <总线号>扫描地址0x50确认显示器所在总线。

EDID读取I2C时序与数据布局 I2C读取时序 主机写偏移(0x00) 重复起始 读128字节数据 停止 从机地址: 0x50 (DDC2B) EDID 128字节数据布局 头(8B) 厂商+产品 字节8-11 基本参数 字节20-25 其他扩展数据 校验(1B) 字节0-7 字节8-11 字节20-25 字节127 关键解析字段 厂商ID (8-9) 产品ID (10-11) 最大尺寸 (21-22) 伽马值 (23) 信号类型 (20) 厂商ID编码: 每5位对应一个字母 (1=A, 2=B, ..., 26=Z)

判定标准

程序执行后若输出与显示器实际规格一致,且无I2C通信错误,即视为通过。若读取失败,检查:

  • I2C总线号是否正确(可用i2cdetect扫描确认);
  • 当前用户是否有/dev/i2c-N的读写权限(需root或加入i2c组);
  • 显示器是否处于正常工作状态(非待机/休眠)。