问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 237 人浏览分享

开启左侧

HDMI项目-主讲 EDID实现-连载三

[复制链接]
237 0
本帖最后由 UT发布 于 2025-3-26 13:53 编辑

1EDID的定义与核心作用

EDID(Extended Display Identification Data)是由 视频电子标准协会(VESA) 制定的标准化数据格式,用于显示器与视频源设备(如显卡、机顶盒)之间的通信。其核心功能是自动传递显示器的技术参数,使视频源能够动态调整输出设置,实现即插即用。

1.1 数据结构与内容基础结构:

主块(128字节) :包含制造商信息(EISA ID、产品序列号)、最大分辨率、色域、伽马值、支持的刷新等。

扩展块(128字节) :HDMI等数字接口扩展,包含音频参数(如声道配置、采样率)及高级视频格式(HDR)。

未来可能扩展至256或512字节以支持更高复杂度。

1.2 核心作用

自动配置:视频源读取EDID后自动匹配最佳分辨率、刷新率和色彩设置,避免手动调整。

兼容性保障:在多显示器或混合设备环境中确保不同品牌/型号的显示器协同工作。

性能优化:支持动态调整伽马值、色域等参数以提升显示质量。

热插拔支持:通过DDC(Display Data Channel)通道实现即插即用。

2 EDID的历史与技术演进2.1 发展历程

1994年(EDID 1.0):首个版本发布,基于VGA接口的128字节主块,通过DDC通道传输。

2000年(EDID 1.3):支持数字接口(DVI/HDMI),引入扩展块(E-EDID)。

2006年(EDID 1.4):新增CVT定时、色彩位深支持,但不兼容HDMI。

2007年(DisplayID):VESA推出替代方案,支持可变长度数据结构(最高256字节),适配4K/8K等高分辨率。

2.2 版本差异
640?wx_fmt=jpeg&from=appmsg
3 EDID的数据结构解析3.1主块结构(128字节)

标头(0x00-0x07) :固定值00 FF FF FF FF FF FF 00,标识EDID有效性。

制造商信息(0x08-0x11) :EISA ID(如“DEL”代表戴尔)、产品编码、序列号。

显示参数(0x12-0x19) :最大水平/垂直尺寸、伽马值、电源管理支持。

色域(0x19-0x22) :红/绿/蓝原色坐标,定义色彩空间。

时序描述符(0x36-0x7D) :详细支持的分辨率与刷新率组合。

3.2 扩展块(以HDMI为例)

CEA-861扩展:包含音频描述符(如Dolby Atmos支持)、视频格式(如4K@60Hz)及HDR元数据。

VSDB(Vendor Specific Data Block) :HDMI特有字段,包含物理地址、HDCP版本等。

4 edid_ddc_dve.v

edid_ddc_dve.v模块定义了一个用于处理EDID(Extended Display Identification Data,扩展显示识别数据)通过I2C协议传输的模块edid_ddc_dve。它主要实现了与外部设备进行I2C通信的功能,并根据接收到的数据执行相应的操作。

4.1关键功能点

I2C地址检测: 比较接收到的地址与预设的设备地址(I2C_ADDR),如果匹配则进入下一状态。

数据移位寄存器: 接收数据并将其从串行转换为并行格式。

计数器管理: 包括位计数器(跟踪当前处理的位)、SCL低电平中心点计数器等,以确保正确的I2C时序。

SDA线控制: 根据当前状态和计数器值决定何时驱动SDA线(输入或输出)

4.2状态机流转图
640?wx_fmt=png&from=appmsg
4.3状态机状态介绍4.3.1状态机状态定义
  1.   // --- 状态机定义 ---
  2.   parameter IDLE = 4'd0;  // 空闲状态
  3.   parameter ADDR_MATCH = 4'd1;  // 地址匹配状态
  4.   parameter ACK_HOLD = 4'd2;  // ACK应答保持状态
  5.   parameter ADD_DATA = 4'd3;  // 写子地址状态
  6.   parameter ADD_ACK_HOLD = 4'd4;  // 子地址ACK应答保持状态
  7.   parameter READ_DATA = 4'd5;  // 读数据状态
  8.   parameter WAIT_ACK = 4'd6;  // 等待ACK/NACK状态
复制代码
IDLE空闲状态,等待I2C起始条件(SCL负跳变且SDA下降沿)。一旦检测到起始条件,进入ADDR_MATCH

ADDR_MATCH在此状态下检查接收到的地址是否与预设的设备地址匹配。如果匹配,则根据接收的读写标志决定是进入ACK_HOLD还是直接进入READ_DATA或ADD_DATA。

ACK_HOLD:发送ACK应答后,等待进一步操作。若为写操作,转向ADD_DATA;若为读操作,转向READ_DATA。

ADD_DATA:用于写入子地址(通常是0x00),完成后进入ADD_ACK_HOLD。

ADD_ACK_HOLD:确认子地址写入后的ACK响应,完成后返回IDLE。

READ_DATA:在该状态下读取数据,完成8位数据读取后进入WAIT_ACK。

WAIT_ACK:等待主机的ACK/NACK响应,根据响应决定继续读取数据还是回到IDLE状态。

4.4状态机代码
  1. module edid_ddc_dve #(
  2.     parameter I_CLK = 50_000_000  // 系统时钟频率,默认为50MHz
  3. ) (
  4.     input wire I_clk,      // 系统时钟
  5.     input wire I_rstn,     // 复位信号
  6.     input wire I_adv_scl,  // I2C时钟线
  7.     inout wire IO_adv_sda  // I2C数据线
  8. );
  9.   // --- 参数定义 ---
  10.   parameter I2C_ADDR = 7'h50;  // EDID设备地址
  11.   parameter I2C_CLK_DVI = I_CLK / 400_000;  //模式IIC传输速率为100K,取4分之一周期
  12.   // --- 状态机定义 ---
  13.   parameter IDLE = 4'd0;  // 空闲状态
  14.   parameter ADDR_MATCH = 4'd1;  // 地址匹配状态
  15.   parameter ACK_HOLD = 4'd2;  // ACK应答保持状态
  16.   parameter ADD_DATA = 4'd3;  // 写子地址状态
  17.   parameter ADD_ACK_HOLD = 4'd4;  // 子地址ACK应答保持状态
  18.   parameter READ_DATA = 4'd5;  // 读数据状态
  19.   parameter WAIT_ACK = 4'd6;  // 等待ACK/NACK状态
  20.   // --- 内部信号 ---
  21.   reg [8:0] addr_ptr;  // 当前读取/写入地址指针
  22.   reg [3:0] state;  // 状态机状态
  23.   reg [3:0] bit_cnt;  // 位计数器(0-7)
  24.   reg [7:0] shift_reg;  // 移位寄存器
  25.   reg [7:0] r_sda_cunt;  //sda中心变换计数器
  26.   reg [7:0] r_ack_cunt;  //ack中心检测计数器
  27.   reg sda_out;  // SDA输出值
  28.   reg sda_oe;  // SDA输出使能
  29.   reg r_adv_scl;  // 上一个时钟周期的SCL值
  30.   reg r_adv_sda;  // 上一个时钟周期的SDA值
  31.   reg rr_adv_scl;  // 上一个时钟周期的SCL值
  32.   reg rr_adv_sda;  // 上一个时钟周期的SDA值
  33.   reg r_add_flag;  //器件地址通过,开始子地址校验,整个过程中只会校验一次器件地址
  34.   reg r_start_begin;  //器件地址通过,开始子地址校验,整个过程中只会校验一次器件地址
  35.   // reg r_wr_flag;  //区分读还是写标志,0是写,1是读
  36.   reg r_ack_charge_flag;  //ack/Nack判断信号
  37.   wire [7:0] mem;  // EDID存储器(128/256字节)
  38.   wire w_scl_ps;  // SCL正跳变检测
  39.   wire w_scl_ns;  // SCL负跳变检测
  40.   wire start_cond;  // 起始条件检测
  41.   wire w_cunt_flag;  //需要数据计数
  42.   wire w_shift_flag;  //需要数据计数
  43.   // --- SDA线控制 ---
  44.   assign IO_adv_sda = sda_oe ? sda_out : 1'bz;  // 根据sda_oe控制SDA输出或高阻态
  45.   // --- SCL跳变检测 ---
  46.   assign w_scl_ps = (r_adv_scl & ~rr_adv_scl);  // SCL正跳变检测
  47.   assign w_scl_ns = (~r_adv_scl & rr_adv_scl);  // SCL负跳变检测
  48.   // --- 起始条件检测 ---
  49.   assign start_cond = (rr_adv_sda && !r_adv_sda)&& r_adv_scl ;  // 起始条件:SDA下降沿且SCL为高电平
  50.   assign repeat_start_cond = (!r_adv_sda && IO_adv_sda) && I_adv_scl;  // 重复起始条件:SDA上升沿且SCL为高电平
  51.   assign w_cunt_flag = state == ADDR_MATCH | state == ADD_DATA|state == READ_DATA|state == ACK_HOLD|state == ADD_ACK_HOLD;//设备地址检测时需要计数,子地址写入时也需要检测
  52.   assign w_shift_flag = state == ADDR_MATCH | state == ADD_DATA;
  53.   // --- SCL和SDA值保存 ---
  54.   always @(posedge I_clk or negedge I_rstn) begin
  55.     if (!I_rstn) begin
  56.       r_adv_scl <= 'b0;
  57.       r_adv_sda <= 'b0;
  58.     end
  59.     r_adv_scl  <= I_adv_scl;  // 保存上一个时钟周期的SCL值
  60.     r_adv_sda  <= IO_adv_sda;  // 保存上一个时钟周期的SDA值
  61.     rr_adv_scl <= r_adv_scl;  // 保存上一个时钟周期的SCL值
  62.     rr_adv_sda <= r_adv_sda;  // 保存上一个时钟周期的SDA值
  63.   end
  64.   // --- 主状态机 ---
  65.   always @(posedge I_clk or negedge I_rstn) begin
  66.     if (!I_rstn) begin
  67.       state <= IDLE;  // 复位状态下进入空闲状态
  68.     end else begin
  69.       case (state)
  70.         IDLE: begin
  71.           if (r_start_begin && w_scl_ns) begin  // 检测到起始条件
  72.             state <= ADDR_MATCH;  // 进入地址匹配状态
  73.           end else state <= IDLE;  // 保持空闲状态
  74.         end
  75.         ADDR_MATCH: begin
  76.           if (bit_cnt == 8) begin
  77.             if (shift_reg[7:1] == I2C_ADDR) begin  // 检查设备地址是否匹配
  78.               state <= ACK_HOLD;  // ACK从应答状态
  79.             end else begin
  80.               state <= IDLE;  // 地址不匹配,回到空闲状态
  81.             end
  82.           end else state <= ADDR_MATCH;  // 未循环采样完成,重复进入地址匹配状态  
  83.         end
  84.         ACK_HOLD: begin
  85.           if (bit_cnt == 4'd9 &&r_sda_cunt == I2C_CLK_DVI && shift_reg[0]==0) begin  //释放SDA线,ACK发送完毕
  86.             state <= ADD_DATA;
  87.           end else if (bit_cnt == 4'd9 && r_sda_cunt == I2C_CLK_DVI && shift_reg[0] == 1) begin
  88.             state <= READ_DATA;
  89.           end else begin
  90.             state <= ACK_HOLD;
  91.           end
  92.         end
  93.         ADD_DATA: begin  // 写子地址(通常是0x00)
  94.           if (w_scl_ps && bit_cnt == 8) begin  // 子地址接收完成
  95.             state <= ADD_ACK_HOLD;  // 读取完子地址,进入子地址ACK响应
  96.           end else state <= ADD_DATA;  // 未循环采样完成,重复进入地址匹配状态  
  97.         end
  98.         ADD_ACK_HOLD: begin  // 子地址应答ACK
  99.           if (bit_cnt == 4'd9 && r_sda_cunt == I2C_CLK_DVI) begin  //释放SDA线,ACK发送完毕
  100.             state <= IDLE;
  101.           end else begin
  102.             state <= ADD_ACK_HOLD;
  103.           end
  104.         end
  105.         READ_DATA: begin
  106.           if (bit_cnt == 8 && r_sda_cunt == I2C_CLK_DVI) begin  //记录8个SCL上升沿
  107.             state <= WAIT_ACK;  // 进入ACK/NACK状态
  108.           end else state <= READ_DATA;
  109.         end
  110.         WAIT_ACK: begin
  111.           if (~r_ack_charge_flag && w_scl_ns) begin  // 在SCL正跳变时检查ACK/NACK
  112.             state <= READ_DATA;  // 继续读取数据
  113.           end else if (r_ack_charge_flag && w_scl_ns) begin  // 主设备发送NACK,结束传输
  114.             state <= IDLE;  // 回到空闲状态
  115.           end else state <= WAIT_ACK;
  116.         end
  117.         default: state <= IDLE;  // 默认情况下回到空闲状态
  118.       endcase
  119.     end
  120.   end
  121.   //开始标志位检测
  122.   always @(posedge I_clk or negedge I_rstn) begin
  123.     if (!I_rstn) begin
  124.       r_start_begin <= 'b0;
  125.     end else if (start_cond) begin  //检测到开始信号
  126.       r_start_begin <= 'b1;  //拉高代表检测开始信号标志,等待SCL下降沿
  127.     end else if (w_scl_ns)  //检测到scl下降沿,拉低r_start_begin
  128.       r_start_begin <= 'b0;
  129.     else r_start_begin <= r_start_begin;
  130.   end
  131.   //计数器,高电平计数,记8位有效数据
  132.   always @(posedge I_clk or negedge I_rstn) begin
  133.     if (!I_rstn) begin
  134.       bit_cnt <= 'b0;  // 初始化位计数器为0
  135.     end else if (w_cunt_flag && w_scl_ps && bit_cnt < 9) begin
  136.       bit_cnt <= bit_cnt + 1;  // 增加位计数器
  137.     end else if (state == IDLE | state == WAIT_ACK) begin
  138.       bit_cnt <= 'b0;  // 位计数器复位
  139.     end else if (w_scl_ps && bit_cnt == 9) begin
  140.       bit_cnt <= 'b1;  // 增加位计数器
  141.     end else begin
  142.       bit_cnt <= bit_cnt;  // 位计数器清零
  143.     end
  144.   end
  145.   //位移寄存器,将接收到的数据并转串
  146.   always @(posedge I_clk or negedge I_rstn) begin
  147.     if (!I_rstn) begin
  148.       shift_reg <= 'b0;  // 清空移位寄存器
  149.     end else if (w_shift_flag && w_scl_ps && bit_cnt <= 8) begin
  150.       shift_reg <= {shift_reg[6:0], r_adv_sda};  // 在SCL正跳变时接收地址的每一位
  151.     end else if ((state == ACK_HOLD && shift_reg[0] == 1&&bit_cnt == 4'd9&&r_sda_cunt == I2C_CLK_DVI) | (state == WAIT_ACK && w_scl_ns)) begin
  152.       shift_reg <= mem;  // 在SCL下降沿时,修改待发送数据
  153.     end else if (state == READ_DATA && w_scl_ns && bit_cnt <= 8) begin
  154.       shift_reg <= {shift_reg[6:0], 1'b0};  // 在SCL下降沿时,修改待发送数据
  155.     end else if (state == IDLE) begin
  156.       shift_reg <= 'b0;  // 清空移位寄存器
  157.     end else begin
  158.       shift_reg <= shift_reg;  // 位计数器清零
  159.     end
  160.   end
  161.   //子地址赋值
  162.   always @(posedge I_clk or negedge I_rstn) begin
  163.     if (!I_rstn) begin
  164.       addr_ptr <= 'b0;  // 默认状态关闭SDA输出使能
  165.     end else if (state == ADD_DATA && w_scl_ps && bit_cnt == 8) begin  //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
  166.       addr_ptr <= shift_reg;  // 保持SDA输出使能
  167.     end else if (state == WAIT_ACK && w_scl_ps) begin  //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
  168.       addr_ptr <= addr_ptr + 1;  // 保持SDA输出使能
  169.     end else addr_ptr <= addr_ptr;
  170.   end
  171.   //r_add_flag信号用来区分接收到的是器件地址还是子地址
  172.   always @(posedge I_clk or negedge I_rstn) begin
  173.     if (!I_rstn) begin
  174.       r_add_flag <= 'b0;
  175.     end else if (state == ADD_DATA) begin
  176.       r_add_flag <= 'b1;
  177.     end else begin
  178.       r_add_flag <= r_add_flag;
  179.     end
  180.   end
  181.   //sda_oe控制SDA线输出
  182.   always @(posedge I_clk or negedge I_rstn) begin
  183.     if (!I_rstn) begin
  184.       sda_oe <= 0;  // 默认状态关闭SDA输出使能
  185.     end else if (bit_cnt == 4'd8&&r_sda_cunt == I2C_CLK_DVI) begin  //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
  186.       sda_oe <= 1;  // 保持SDA输出使能  
  187.     end else if ((bit_cnt == 4'd9&&r_sda_cunt == I2C_CLK_DVI)|state == IDLE|state == WAIT_ACK) begin  //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
  188.       sda_oe <= 0;  // 默认状态关闭SDA输出使能
  189.     end else if (state == READ_DATA) begin  //输出slave读取的数据
  190.       sda_oe <= 1;  // 保持SDA输出使能  
  191.     end else begin  // (第9周期结束)
  192.       sda_oe <= sda_oe;  // 释放SDA线
  193.     end
  194.   end
  195.   //sda_out输出ram中的数据,并转串,同时还负责发送ACK信号
  196.   always @(posedge I_clk or negedge I_rstn) begin
  197.     if (!I_rstn) begin
  198.       sda_out <= 0;  // 默认状态关闭SDA输出使能
  199.     end else if ((state==ACK_HOLD)|(state==ADD_ACK_HOLD)) begin  //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
  200.       sda_out <= 0;  // 保持SDA输出使能  
  201.     end else if (state == READ_DATA) begin  //输出slave读取的数据
  202.       sda_out <= shift_reg[7];  // 保持SDA输出使能  
  203.     end else begin  // (第9周期结束)
  204.       sda_out <= 0;  // 释放SDA线
  205.     end
  206.   end
  207.   //SCL低电平中心点计数器
  208.   always @(posedge I_clk or negedge I_rstn) begin
  209.     if (!I_rstn) begin
  210.       r_sda_cunt <= 'b0;
  211.     end else if (~r_adv_scl) begin
  212.       r_sda_cunt <= r_sda_cunt + 1;
  213.     end else if (r_adv_scl) begin
  214.       r_sda_cunt <= 'b0;
  215.     end else begin
  216.       r_sda_cunt <= r_sda_cunt;
  217.     end
  218.   end
  219.   //ACK检测计数器
  220.   always @(posedge I_clk or negedge I_rstn) begin
  221.     if (!I_rstn) begin
  222.       r_ack_cunt <= 'b0;
  223.     end else if (state == WAIT_ACK) begin
  224.       r_ack_cunt <= r_ack_cunt + 1;
  225.     end else begin
  226.       r_ack_cunt <= 'b0;
  227.     end
  228.   end
  229.   //等待主机应答信号,SCL高电平中心点采样
  230.   always @(posedge I_clk or negedge I_rstn) begin
  231.     if (!I_rstn) begin
  232.       r_ack_charge_flag <= 'b0;
  233.     end else if (state == WAIT_ACK && r_ack_cunt == 8'hff && r_adv_sda == 0) begin
  234.       r_ack_charge_flag <= 'b0;
  235.     end else if (state == WAIT_ACK && r_ack_cunt == 8'hff && r_adv_sda == 1) begin
  236.       r_ack_charge_flag <= 'b1;
  237.     end else begin
  238.       r_ack_charge_flag <= r_ack_charge_flag;
  239.     end
  240.   end
  241.   // --- Block RAM实例化 ---
  242.   blk_mem_gen_1 u0_blk_mem_gen_1 (
  243.       .clka (I_clk),     // BRAM时钟
  244.       .wea  (1'b0),      // 写使能(始终为0,表示只读)
  245.       .addra(addr_ptr),  // BRAM地址
  246.       .dina (8'b0),      // 输入数据(始终为0,表示只读)
  247.       .douta(mem)        // 输出数据
  248.   );
  249. endmodule
复制代码
4.5 BRAM使用
  使用BRAM IP存储EDID数据信息,方便访问数据。
  添加IP
image.jpg
  宽度设置为8,深度设置为256,因为EDID信息有256个字节。当然如果你仅需要配置128个字节的EDID信息,将深度修改为128即可。
image.jpg
  勾选本地文件,然后找到本地配置好EDID信息的COE文件的位置
image.jpg
4.6 EDID文件获取方式
         EDID文件可以直接通过Phoenix.exe软件获取目前你显示器上的信息,然后修改为赛灵思IP内能使用的.coe文件即可,也可以直接使用米联客提供的.coe文件。
首先我们打开Phoenix.exe软件,从Tools中选择EDID获取
image.jpg
   选择任意显示器,因为大部分显示器都支持720P 60Hz的数据传输。
image.jpg
        选择完成后,该显示器的信息就显示在软件中,我们直接选择导出。
image.jpg
image.jpg
   选择路径后,就生成了一个.hex文件,该文件就是你选择的显示器的EDID文件信息,修改为.coe文件后,即可导入工程使用。
image.jpg
image.jpg
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

253

主题
精彩推荐
热门资讯
网友晒图
图文推荐

  • 微信公众平台

  • 扫描访问手机版