问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 37 人浏览分享

开启左侧

HDMI-IP项目-主讲输入输出联合上板调试(EDID音频升级)-连载十四

[复制链接]
37 0
本帖最后由 UT发布 于 2025-5-12 16:37 编辑

1概述

前面的课程,我们完成了音频数据的提取,恢复出了我们需要的24bit O_audio_left_data24bit O_audio_right_dataO_audio_valid音频有效数据。那我们接下来就可以分别输出音频数据和视频数据进入HDMI TX模块中,完成音视频数据的输出。

2工程建立

将之前编写的模块代码进行组合,完成HDMI完整输入输出的demo

640?wx_fmt=png&from=appmsg
3 EDID音频升级
3.1发现问题

Demo编译完成后,上板进行测试,发现问题,电脑的声音界面无法找到我们开发板模拟的显示器。经过检查发现,之前编写的EDID 存储编码存在问题,仅仅能读取到128位,无法完成预期的256位。

结合代码发现常规的寄存器地址一般都是7位,所以最多只能从0计数到127。结合信号发生器抓取数据发现,当电脑显卡端读取到128位的EDID信息后,会主动发送NACK停止IIC总线,然后会restart重新读取一遍EDID信息,此时,发送后续的129~256位的数据即可。

640?wx_fmt=png&from=appmsg
3.2升级EDID代码

代码内容仅仅需要进行一个小修改即可,主要是增加了一个r_edid_restart信号去控制第二次读取的bram输出数据。主要的逻辑部分不做变化,下面贴出完整代码。

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

在实际的上板验证过程中,还发现一个问题,有的显卡的输出格式并不是RGB格式,位深也不是8位,此时需要通过显卡进行修改。

640?wx_fmt=png&from=appmsg
AMD的显卡驱动:
640?wx_fmt=png&from=appmsg
NVIDA的显卡驱动:
640?wx_fmt=png&from=appmsg
4 测试结果
640?wx_fmt=png&from=appmsg



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

303

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

  • 微信公众平台

  • 扫描访问手机版