本帖最后由 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 版本差异
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.vedid_ddc_dve.v模块定义了一个用于处理EDID(Extended Display Identification Data,扩展显示识别数据)通过I2C协议传输的模块edid_ddc_dve。它主要实现了与外部设备进行I2C通信的功能,并根据接收到的数据执行相应的操作。 4.1关键功能点I2C地址检测: 比较接收到的地址与预设的设备地址(I2C_ADDR),如果匹配则进入下一状态。 数据移位寄存器: 接收数据并将其从串行转换为并行格式。 计数器管理: 包括位计数器(跟踪当前处理的位)、SCL低电平中心点计数器等,以确保正确的I2C时序。 SDA线控制: 根据当前状态和计数器值决定何时驱动SDA线(输入或输出) 4.2状态机流转图4.3状态机状态介绍4.3.1状态机状态定义
- // --- 状态机定义 ---
- parameter IDLE = 4'd0; // 空闲状态
- parameter ADDR_MATCH = 4'd1; // 地址匹配状态
- parameter ACK_HOLD = 4'd2; // ACK应答保持状态
- parameter ADD_DATA = 4'd3; // 写子地址状态
- parameter ADD_ACK_HOLD = 4'd4; // 子地址ACK应答保持状态
- parameter READ_DATA = 4'd5; // 读数据状态
- 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状态机代码
- module edid_ddc_dve #(
- parameter I_CLK = 50_000_000 // 系统时钟频率,默认为50MHz
- ) (
- input wire I_clk, // 系统时钟
- input wire I_rstn, // 复位信号
- input wire I_adv_scl, // I2C时钟线
- inout wire IO_adv_sda // I2C数据线
- );
-
- // --- 参数定义 ---
- parameter I2C_ADDR = 7'h50; // EDID设备地址
- parameter I2C_CLK_DVI = I_CLK / 400_000; //模式IIC传输速率为100K,取4分之一周期
- // --- 状态机定义 ---
- parameter IDLE = 4'd0; // 空闲状态
- parameter ADDR_MATCH = 4'd1; // 地址匹配状态
- parameter ACK_HOLD = 4'd2; // ACK应答保持状态
- parameter ADD_DATA = 4'd3; // 写子地址状态
- parameter ADD_ACK_HOLD = 4'd4; // 子地址ACK应答保持状态
- parameter READ_DATA = 4'd5; // 读数据状态
- parameter WAIT_ACK = 4'd6; // 等待ACK/NACK状态
-
- // --- 内部信号 ---
- reg [8:0] addr_ptr; // 当前读取/写入地址指针
- reg [3:0] state; // 状态机状态
- reg [3:0] bit_cnt; // 位计数器(0-7)
- reg [7:0] shift_reg; // 移位寄存器
- reg [7:0] r_sda_cunt; //sda中心变换计数器
- reg [7:0] r_ack_cunt; //ack中心检测计数器
- reg sda_out; // SDA输出值
- reg sda_oe; // SDA输出使能
- reg r_adv_scl; // 上一个时钟周期的SCL值
- reg r_adv_sda; // 上一个时钟周期的SDA值
- reg rr_adv_scl; // 上一个时钟周期的SCL值
- reg rr_adv_sda; // 上一个时钟周期的SDA值
- reg r_add_flag; //器件地址通过,开始子地址校验,整个过程中只会校验一次器件地址
- reg r_start_begin; //器件地址通过,开始子地址校验,整个过程中只会校验一次器件地址
- // reg r_wr_flag; //区分读还是写标志,0是写,1是读
- reg r_ack_charge_flag; //ack/Nack判断信号
-
- wire [7:0] mem; // EDID存储器(128/256字节)
- wire w_scl_ps; // SCL正跳变检测
- wire w_scl_ns; // SCL负跳变检测
- wire start_cond; // 起始条件检测
- wire w_cunt_flag; //需要数据计数
- wire w_shift_flag; //需要数据计数
-
- // --- SDA线控制 ---
- assign IO_adv_sda = sda_oe ? sda_out : 1'bz; // 根据sda_oe控制SDA输出或高阻态
-
- // --- SCL跳变检测 ---
- assign w_scl_ps = (r_adv_scl & ~rr_adv_scl); // SCL正跳变检测
- assign w_scl_ns = (~r_adv_scl & rr_adv_scl); // SCL负跳变检测
-
- // --- 起始条件检测 ---
- assign start_cond = (rr_adv_sda && !r_adv_sda)&& r_adv_scl ; // 起始条件:SDA下降沿且SCL为高电平
- assign repeat_start_cond = (!r_adv_sda && IO_adv_sda) && I_adv_scl; // 重复起始条件:SDA上升沿且SCL为高电平
- assign w_cunt_flag = state == ADDR_MATCH | state == ADD_DATA|state == READ_DATA|state == ACK_HOLD|state == ADD_ACK_HOLD;//设备地址检测时需要计数,子地址写入时也需要检测
- assign w_shift_flag = state == ADDR_MATCH | state == ADD_DATA;
-
- // --- SCL和SDA值保存 ---
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- r_adv_scl <= 'b0;
- r_adv_sda <= 'b0;
- end
- r_adv_scl <= I_adv_scl; // 保存上一个时钟周期的SCL值
- r_adv_sda <= IO_adv_sda; // 保存上一个时钟周期的SDA值
- rr_adv_scl <= r_adv_scl; // 保存上一个时钟周期的SCL值
- rr_adv_sda <= r_adv_sda; // 保存上一个时钟周期的SDA值
- end
-
- // --- 主状态机 ---
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- state <= IDLE; // 复位状态下进入空闲状态
- end else begin
- case (state)
- IDLE: begin
- if (r_start_begin && w_scl_ns) begin // 检测到起始条件
- state <= ADDR_MATCH; // 进入地址匹配状态
- end else state <= IDLE; // 保持空闲状态
- end
-
- ADDR_MATCH: begin
- if (bit_cnt == 8) begin
- if (shift_reg[7:1] == I2C_ADDR) begin // 检查设备地址是否匹配
- state <= ACK_HOLD; // ACK从应答状态
- end else begin
- state <= IDLE; // 地址不匹配,回到空闲状态
- end
- end else state <= ADDR_MATCH; // 未循环采样完成,重复进入地址匹配状态
- end
-
- ACK_HOLD: begin
- if (bit_cnt == 4'd9 &&r_sda_cunt == I2C_CLK_DVI && shift_reg[0]==0) begin //释放SDA线,ACK发送完毕
- state <= ADD_DATA;
- end else if (bit_cnt == 4'd9 && r_sda_cunt == I2C_CLK_DVI && shift_reg[0] == 1) begin
- state <= READ_DATA;
- end else begin
- state <= ACK_HOLD;
- end
- end
-
- ADD_DATA: begin // 写子地址(通常是0x00)
- if (w_scl_ps && bit_cnt == 8) begin // 子地址接收完成
- state <= ADD_ACK_HOLD; // 读取完子地址,进入子地址ACK响应
- end else state <= ADD_DATA; // 未循环采样完成,重复进入地址匹配状态
- end
-
- ADD_ACK_HOLD: begin // 子地址应答ACK
- if (bit_cnt == 4'd9 && r_sda_cunt == I2C_CLK_DVI) begin //释放SDA线,ACK发送完毕
- state <= IDLE;
- end else begin
- state <= ADD_ACK_HOLD;
- end
- end
-
- READ_DATA: begin
- if (bit_cnt == 8 && r_sda_cunt == I2C_CLK_DVI) begin //记录8个SCL上升沿
- state <= WAIT_ACK; // 进入ACK/NACK状态
- end else state <= READ_DATA;
- end
-
- WAIT_ACK: begin
- if (~r_ack_charge_flag && w_scl_ns) begin // 在SCL正跳变时检查ACK/NACK
- state <= READ_DATA; // 继续读取数据
- end else if (r_ack_charge_flag && w_scl_ns) begin // 主设备发送NACK,结束传输
- state <= IDLE; // 回到空闲状态
- end else state <= WAIT_ACK;
- end
-
- default: state <= IDLE; // 默认情况下回到空闲状态
- endcase
- end
- end
-
- //开始标志位检测
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- r_start_begin <= 'b0;
- end else if (start_cond) begin //检测到开始信号
- r_start_begin <= 'b1; //拉高代表检测开始信号标志,等待SCL下降沿
- end else if (w_scl_ns) //检测到scl下降沿,拉低r_start_begin
- r_start_begin <= 'b0;
- else r_start_begin <= r_start_begin;
- end
-
- //计数器,高电平计数,记8位有效数据
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- bit_cnt <= 'b0; // 初始化位计数器为0
- end else if (w_cunt_flag && w_scl_ps && bit_cnt < 9) begin
- bit_cnt <= bit_cnt + 1; // 增加位计数器
- end else if (state == IDLE | state == WAIT_ACK) begin
- bit_cnt <= 'b0; // 位计数器复位
- end else if (w_scl_ps && bit_cnt == 9) begin
- bit_cnt <= 'b1; // 增加位计数器
- end else begin
- bit_cnt <= bit_cnt; // 位计数器清零
- end
- end
-
- //位移寄存器,将接收到的数据并转串
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- shift_reg <= 'b0; // 清空移位寄存器
- end else if (w_shift_flag && w_scl_ps && bit_cnt <= 8) begin
- shift_reg <= {shift_reg[6:0], r_adv_sda}; // 在SCL正跳变时接收地址的每一位
- 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
- shift_reg <= mem; // 在SCL下降沿时,修改待发送数据
- end else if (state == READ_DATA && w_scl_ns && bit_cnt <= 8) begin
- shift_reg <= {shift_reg[6:0], 1'b0}; // 在SCL下降沿时,修改待发送数据
- end else if (state == IDLE) begin
- shift_reg <= 'b0; // 清空移位寄存器
- end else begin
- shift_reg <= shift_reg; // 位计数器清零
- end
- end
-
- //子地址赋值
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- addr_ptr <= 'b0; // 默认状态关闭SDA输出使能
- end else if (state == ADD_DATA && w_scl_ps && bit_cnt == 8) begin //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
- addr_ptr <= shift_reg; // 保持SDA输出使能
- end else if (state == WAIT_ACK && w_scl_ps) begin //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
- addr_ptr <= addr_ptr + 1; // 保持SDA输出使能
- end else addr_ptr <= addr_ptr;
- end
-
- //r_add_flag信号用来区分接收到的是器件地址还是子地址
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- r_add_flag <= 'b0;
- end else if (state == ADD_DATA) begin
- r_add_flag <= 'b1;
- end else begin
- r_add_flag <= r_add_flag;
- end
- end
-
- //sda_oe控制SDA线输出
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- sda_oe <= 0; // 默认状态关闭SDA输出使能
- end else if (bit_cnt == 4'd8&&r_sda_cunt == I2C_CLK_DVI) begin //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
- sda_oe <= 1; // 保持SDA输出使能
- end else if ((bit_cnt == 4'd9&&r_sda_cunt == I2C_CLK_DVI)|state == IDLE|state == WAIT_ACK) begin //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
- sda_oe <= 0; // 默认状态关闭SDA输出使能
- end else if (state == READ_DATA) begin //输出slave读取的数据
- sda_oe <= 1; // 保持SDA输出使能
- end else begin // (第9周期结束)
- sda_oe <= sda_oe; // 释放SDA线
- end
- end
-
- //sda_out输出ram中的数据,并转串,同时还负责发送ACK信号
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- sda_out <= 0; // 默认状态关闭SDA输出使能
- end else if ((state==ACK_HOLD)|(state==ADD_ACK_HOLD)) begin //检测到地址匹配,准备输出在下一个时钟上升沿(进入第9个时钟周期)开始输出ACK应答信号
- sda_out <= 0; // 保持SDA输出使能
- end else if (state == READ_DATA) begin //输出slave读取的数据
- sda_out <= shift_reg[7]; // 保持SDA输出使能
- end else begin // (第9周期结束)
- sda_out <= 0; // 释放SDA线
- end
- end
-
- //SCL低电平中心点计数器
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- r_sda_cunt <= 'b0;
- end else if (~r_adv_scl) begin
- r_sda_cunt <= r_sda_cunt + 1;
- end else if (r_adv_scl) begin
- r_sda_cunt <= 'b0;
- end else begin
- r_sda_cunt <= r_sda_cunt;
- end
- end
-
- //ACK检测计数器
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- r_ack_cunt <= 'b0;
- end else if (state == WAIT_ACK) begin
- r_ack_cunt <= r_ack_cunt + 1;
- end else begin
- r_ack_cunt <= 'b0;
- end
- end
- //等待主机应答信号,SCL高电平中心点采样
- always @(posedge I_clk or negedge I_rstn) begin
- if (!I_rstn) begin
- r_ack_charge_flag <= 'b0;
- end else if (state == WAIT_ACK && r_ack_cunt == 8'hff && r_adv_sda == 0) begin
- r_ack_charge_flag <= 'b0;
- end else if (state == WAIT_ACK && r_ack_cunt == 8'hff && r_adv_sda == 1) begin
- r_ack_charge_flag <= 'b1;
- end else begin
- r_ack_charge_flag <= r_ack_charge_flag;
- end
- end
- // --- Block RAM实例化 ---
- blk_mem_gen_1 u0_blk_mem_gen_1 (
- .clka (I_clk), // BRAM时钟
- .wea (1'b0), // 写使能(始终为0,表示只读)
- .addra(addr_ptr), // BRAM地址
- .dina (8'b0), // 输入数据(始终为0,表示只读)
- .douta(mem) // 输出数据
- );
- endmodule
复制代码
4.5 BRAM使用
使用BRAM IP存储EDID数据信息,方便访问数据。 添加IP 宽度设置为8,深度设置为256,因为EDID信息有256个字节。当然如果你仅需要配置128个字节的EDID信息,将深度修改为128即可。 勾选本地文件,然后找到本地配置好EDID信息的COE文件的位置 4.6 EDID文件获取方式 EDID文件可以直接通过Phoenix.exe软件获取目前你显示器上的信息,然后修改为赛灵思IP内能使用的.coe文件即可,也可以直接使用米联客提供的.coe文件。 首先我们打开Phoenix.exe软件,从Tools中选择EDID获取 选择任意显示器,因为大部分显示器都支持720P 60Hz的数据传输。 选择完成后,该显示器的信息就显示在软件中,我们直接选择导出。 选择路径后,就生成了一个.hex文件,该文件就是你选择的显示器的EDID文件信息,修改为.coe文件后,即可导入工程使用。 |