
简介这是一份面向赛灵思Zynq-7000系列器件的IIC寄存器配置例程硬件平台为米联客MZ7035FA开发板对应芯片型号xc7z045ffg676开发工具采用Vivado 2017.4与SDK。工程核心是利用处理器系统侧的IIC控制器通过EMIO接口把IIC信号引到可编程逻辑端对ADV7611单输入HDMI接收芯片完成寄存器配置从而使Zynq能够接收外部输入的HDMI信号目标分辨率为1920×108060Hz。可编程逻辑部分负责接收并解析视频分辨率参数同时将输入HDMI环出输出以作回环验证处理器系统部分负责IIC配置并通过GPIO拉高done引脚提示配置完成。ADV7611支持HDMI 1.4a规定的强制性3D电视格式以及最高UXGA 60Hz、8位分辨率例程中寄存器配置注释清晰可迁移到同类HDMI桥接芯片调试中。资源包共1577个文件大小45.16MB主要文件类型包括Verilog/VHDL硬件源码、C语言软件代码、XDC约束文件、Tcl脚本以及Vivado/SDK工程文件覆盖工程创建、逻辑设计、约束编译与调试全流程。目前已有1560人学习下载适合希望掌握Zynq软硬件协同、IIC外设配置及HDMI视频通路设计的FPGA工程师作为工程参考。1. ZYNQ配置IIC寄存器驱动ADV7611这步走通HDMI输入就成了一半接手MZ7035FA这块板子时最容易被卡住的地方不是逻辑代码而是ADV7611的IIC配置。这颗HDMI接收芯片的寄存器非常多手册快三百页但真正要动的手脚其实集中在几个页面上。更关键的是ZYNQ这边怎么把IIC总线引出去——用PS的IIC控制器走EMIO还是用PL侧逻辑模拟IIC两条路我都在项目里试过结论很明确只要能调PS的资源就不要自己写IIC时序。PS侧IIC控制器带硬件仲裁、超时检测和FIFOSDK里调Xilinx提供的驱动函数就行省掉一大截调试时间。本文以xc7z045ffg676跑1080p60输入为例把ADV7611寄存器初始化、PL侧回环和分辨率解析三个环节串起来讲适合正在做视频采集或HDMI分发项目的开发者参考。2. ADV7611寄存器寻址与初始化序列构造2.1 IIC地址映射与分页机制ADV7611虽然是单颗芯片但内部寄存器被拆成好几个映射块每个映射块都有自己的IIC从机地址。默认配置下主映射Main Map挂在0x988位地址格式CP映射在0x9AHDMI映射在0x9C。ZYNQ的IIC控制器如果配置成7位地址模式对应的就是0x4C、0x4D、0x4E。很多人第一次写配置程序时只往0x98这个地址写寄存器发现读出来全是0xFF或者写不进去原因就是没有切换MAP页。ADV7611的寄存器寻址是分页的访问HDMI相关寄存器前必须先在主映射的0xFD寄存器里写入目标页号。比如要访问HDMI映射的寄存器就先写0x98、0xFD、0x03后续的读写操作才会落到HDMI页上。#define ADV7611_IIC_ADDR 0x4C // 7-bit地址模式下的主映射 #define REG_MAP_ADDR 0xFD // MAP页选择寄存器 static int adv7611_write_reg(uint8_t map, uint8_t reg, uint8_t val) { uint8_t buf[2] {reg, val}; /* 先切页再写寄存器 */ uint8_t map_sel[2] {REG_MAP_ADDR, map}; XIic_Send(BASEADDR, ADV7611_IIC_ADDR, map_sel, 2, XIIC_STOP); return XIic_Send(BASEADDR, ADV7611_IIC_ADDR, buf, 2, XIIC_STOP); }这段代码先通过主映射的0xFD寄存器切换页面再写入目标寄存器的地址和数据。XIic_Send的最后一个参数XIIC_STOP表示发送完成后产生停止条件这是IIC时序里必须的操作漏掉的话从机可能不认这次传输。如果用的是带FIFO的IIC控制器也可以连续发送多个字节但切页操作建议单独发一次方便跟踪时序异常。2.2 核心寄存器配置表与初始化序列ADV7611上电后默认是不工作的必须写一段初始化序列才能让它正常接收HDMI信号。不同开发板给出的例程序列可能有差别核心的配置项集中在输入格式、EDID RAM和电源管理三块。我整理了一份精简的初始化序列实测在1080p60输入下稳定跑通。映射页寄存器地址写入值作用说明0x00 主映射0x0D0x40禁用时钟输出先静默配置0x00 主映射0x0F0x00关闭HDMI输出避免配置过程中闪屏0x00 主映射0x980x03设置ADI公司ID校验必须匹配0x00 主映射0x9A0xE0设置IIC地址的MSB0x00 主映射0x9C0x30设置IIC地址的LSB0x01 CP映射0x170x41配置色彩空间转换YCbCr转RGB0x01 CP映射0x180x16视频时序相关参数0x01 CP映射0x190x02使能自动输入格式检测0x03 HDMI映射0x000x00HDMI模式使能0x03 HDMI映射0x010x00禁用HDCP纯直通模式0x03 HDMI映射0x020x00通道极性配置0x03 HDMI映射0x070x10深色彩模式关闭8bit输出0x03 HDMI映射0x0A0x80使能HDMI输入0x04 EDID映射0x00~0x7F自定义写入128字节EDID 1.3配置顺序有讲究先配主映射和电源管理再切到CP映射配置视频处理链路HDMI映射的配置放最后因为一旦使能输入芯片就开始解析信号后续寄存器写入可能会被打断。EDID映射建议在芯片处于非工作状态时写入写完后再使能HDMI输入。2.3 为什么需要写EDIDHDMI源端设备比如电脑显卡、摄像机在输出信号之前会通过DDC通道读取显示端的EDID确认支持的分辨率、刷新率和色彩格式。如果EDID没写或者内容不对源端可能直接不输出信号或者输出一个奇怪的分辨率。MZ7035FA板卡上ADV7611的DDC引脚直接连到了HDMI座子所以EDID必须由我们通过IIC写入。EDID 1.3标准长度是128字节其中前16字节是头部和厂商信息必须严格符合规范。项目里如果只是接收固定1080p60的信号源可以简化EDID内容但校验和必须算对否则源端不认。计算方式是前127字节求和取低8位的补码填入第127字节。static void adv7611_edid_fix_checksum(uint8_t *edid) { uint8_t sum 0; /* 前127字节求和目标是让总和为0x00 */ for (int i 0; i 127; i) { sum edid[i]; } edid[127] (uint8_t)(0x00 - sum); }这段校验和逻辑其实是把EDID配置中最后做补码求和这个步骤拆出来了。0x00 - sum的运算等价于取sum的补码这样前127字节加上第127字节的和刚好是0x00符合EDID规范的校验要求。写EDID时建议一页一页连续写因为ADV7611的EDID RAM是双端口一边读一边写可能产生不一致。3. PL端HDMI回环逻辑与分辨率参数解析3.1 硬件管脚与时钟规划ADV7611输出的像素接口是并行RGB或者YCbCr加上行场同步、时钟和数据使能信号。MZ7035FA上ADV7611与FPGA之间的连接走的是PL侧普通IO没有走硬核收发器这点和HDMI的TMDS物理层不同。ADV7611已经把TMDS信号解码成并行数据了FPGA这边只需要处理并行总线和同步信号。常见的接法是把ADV7611的像素时钟PIX_CLK连接到PL的全局时钟引脚数据总线接到普通IO bank。ZYNQ的IO bank电压要注意ADV7611的IO电压通常是1.8V或2.5V必须检查板级原理图确认bank电压匹配否则数据采进来全是乱的。回环验证的思路很简单把ADV7611输出的并行数据和同步信号直接接到HDMI发送端芯片的输入。如果板上HDMI发送端也是并行接口回环路径就是纯逻辑直通不经过DDR缓存。这样做的好处是延迟极低坏处是如果输入分辨率变化输出不做帧率转换会跟着变。3.2 基于FIFO的跨时钟回环实现ADV7611输出的像素时钟和ZYNQ PL侧逻辑的时钟域可能不同步直接打拍拼接大概率出问题。我一般会在回环路径上放一个异步FIFO写入端用ADV7611的像素时钟读出端用HDMI发送芯片的像素时钟。两个时钟频率接近但相位不同步时FIFO能起到缓冲作用但要注意FIFO深度设计太大增加延迟太小容易溢出。module hdmi_loopback_fifo #( parameter DATA_WIDTH 24, parameter FIFO_DEPTH 1024 )( input wire wr_clk, input wire wr_en, input wire [DATA_WIDTH-1:0] wr_data, input wire rd_clk, input wire rd_en, output wire [DATA_WIDTH-1:0] rd_data, output wire fifo_full, output wire fifo_empty ); /* 调用Xilinx FIFO IP核原生模式 */ fifo_generator_i0 fifo_inst ( .rst (1b0), .wr_clk (wr_clk), .rd_clk (rd_clk), .din (wr_data), .wr_en (wr_en), .rd_en (rd_en), .dout (rd_data), .full (fifo_full), .empty (fifo_empty), .wr_rst_busy (), .rd_rst_busy () ); /* 写使能由外部同步信号控制 */ assign wr_en vsync_active hsync_active data_valid; endmodule这里在回环路径上加了同步使能条件写使能只有在行场有效期间才拉高避免消隐期间写入无效数据。FIFO的wr_rst_busy和rd_rst_busy信号没接出来实际使用时建议引到LED调试FIFO复位的释放时序如果不对首帧数据会出现错位。3.3 分辨率参数解析策略PL侧解析分辨率的核心是测量行场同步信号的周期。像素时钟下用计数器统计一个完整行周期内的像素数再统计一个完整场周期内的行数两个数值就能算出分辨率。这个方法不需要查表输入信号变化时自动适应。reg [15:0] h_cnt; reg [15:0] v_cnt; reg h_sync_d; reg v_sync_d; always (posedge pix_clk) begin h_sync_d h_sync; v_sync_d v_sync; /* 行同步上升沿说明新的一行开始 */ if (h_sync !h_sync_d) begin v_cnt v_sync ? v_cnt 1b1 : 16d0; h_cnt 16d1; end else begin h_cnt h_cnt 1b1; end /* 场同步上升沿一帧结束锁存统计结果 */ if (v_sync !v_sync_d) begin reg_h_total h_cnt; reg_v_total v_cnt; end end这段逻辑在行同步上升沿累加行计数器在场同步上升沿锁存结果。注意v_cnt的复位条件用了v_sync的实时值而不是延时值这是为了防止场同步恰好和行同步同时出现时计数出错。锁存出来的reg_h_total和reg_v_total就是实际的行像素数和行数与1080p的1920和1080做比较就能确认输入格式。解析结果可以在SDK里通过AXI GPIO读取也可以直接在ILA里观察。3.4 像素时钟与数据对齐问题ADV7611输出的像素时钟默认是上升沿采样数据但走线长度不同会导致数据和时钟的相位偏移。遇到图像有彩色噪点或左右移动的花屏优先检查数据采样沿。Xilinx的IDELAY原语可以调整单根数据线的延迟但更简单的做法是先看ADV7611的输出格式寄存器设置确认是否把时钟极性反转了。/* 使用IDELAYE2调整单bit延迟每个tap约78ps 200MHz参考时钟 */ IDELAYE2 #( .IDELAY_TYPE(FIXED), .DELAY_SRC (DATAIN), .IDELAY_VALUE(12) ) idelay_d0 ( .IDATAIN(pix_data[0]), .DATAOUT(data_delayed[0]), .C(pix_clk), .CE(1b0), .INC(1b0), .CINVCTRL(1b0), .CNTVALUEIN(8d0), .CNTVALUEOUT(), .LD(1b0), .LDPIPEEN(1b0), .REGRST(1b0) );这段IDELAYE2的配置把数据线延迟了12个tap对应大约936ps。这个值是经验值实际项目中可以用IO约束里的INPUT_DELAY配合时序报告来调整。如果所有数据线都延迟不如直接在约束文件里把PACKAGE_PIN对应的输入延迟统一设置代码里只针对特殊长度的信号做微调。4. SDK工程搭建与软件配置流程4.1 Vivado硬件工程中的关键配置Vivado 2017.4下创建ZYNQ硬件工程PS端配置要打开IIC0或IIC1控制器。MZ7035FA板级原理图上ADV7611的IIC总线接在PS的MIO14和MIO15上对应IIC1控制器。如果板子把IIC引到了EMIO需要在ZYNQ IP配置里把IIC接口改成EMIO并分配PL侧引脚。# ZYNQ PS配置IIC1为MIO14/MIO15 set_property -dict [list \ CONFIG.PCW_USE_IIC1 {1} \ CONFIG.PCW_IIC1_I2C_FREQ {100000} \ ] [get_bd_cells processing_system7_0]这段Tcl命令设置了IIC1的时钟频率为100kHz。ADV7611的IIC最高支持400kHz但PCB走线质量和上拉电阻决定实际能跑多快调试阶段用100kHz更稳。上拉电阻的值对IIC信号质量影响很大MZ7035FA板上用的是2.2kΩ上拉到3.3V如果自己画板建议按总线上设备数量和总线电容来算电容超过100pF就把上拉降到1.5kΩ左右。4.2 BSP与驱动初始化顺序SDK里创建应用工程时BSP默认会把XIic驱动注册进系统。初始化顺序有讲究先复位ADV7611再初始化IIC控制器最后写寄存器序列。芯片不复位直接配置可能因为内部状态机未初始化导致某些寄存器写不进去。#include xiic.h #include xgpiops.h #define IIC_DEVICE_ID XPAR_XIICPS_1_DEVICE_ID #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID #define ADV7611_RST_PIN 78 /* MIO78接到ADV7611复位脚 */ int adv7611_init(void) { XIicPs iic_inst; XGpioPs gpio_inst; XIicPs_Config *iic_cfg; /* 先拉低复位至少保持10ms */ XGpioPs_WritePin(gpio_inst, ADV7611_RST_PIN, 0); usleep(10000); XGpioPs_WritePin(gpio_inst, ADV7611_RST_PIN, 1); usleep(20000); /* PS端IIC控制器初始化 */ iic_cfg XIicPs_LookupConfig(IIC_DEVICE_ID); XIicPs_CfgInitialize(iic_inst, iic_cfg, iic_cfg-BaseAddress); XIicPs_SetSClk(iic_inst, 100000); /* 写入寄存器初始化序列 */ for (int i 0; i init_table_len; i) { adv7611_write_reg(init_table[i].map, init_table[i].reg, init_table[i].val); } return 0; }初始化序列里的init_table和init_table_len来自第2.2节的寄存器配置表建议把配置表独立成数组放在单独的头文件里方便不同分辨率或不同板卡之间切换。复位管脚在MIO78上需要先确认板级原理图别盲写管脚号曾经遇到一块定制板把复位接到了PL侧IO上结果软件怎么复位都没反应。4.3 IIC读回校验与版本确认写完寄存器序列不能直接跑得读回ADV7611的ID寄存器确认芯片响应正常。ID寄存器在任意映射页都能读到写法是先切页到目标映射再直接读0x00或0x01。读回的数据和预期不符时先用示波器看IIC波形确认SDA和SCL上有没有正常的ACK。static int adv7611_verify(void) { uint8_t val 0; /* 切到主映射读芯片ID寄存器0x00 */ adv7611_write_reg(0x00, REG_MAP_ADDR, 0x00); XIic_Recv(BASEADDR, DEV_ADDR, val, 1, XIIC_STOP); xil_printf(ADV7611 ID: 0x%02x\r\n, val); /* 正确ID应为0x00或0x01 */ if (val 0x00 || val 0x01) { return 0; } return -1; }这里读ID用了一次空写来设置寄存器地址再发起读操作。有些IIC控制器支持重复起始条件可以先把寄存器地址发出去然后在同一个传输事务里直接读但Xilinx的XIic驱动里分开调用反而更安全。ID读出来是0x00或0x01对应的都是ADV76110xFF说明芯片没在总线上0x55或其它值说明地址配错了。4.4 done信号与GPIO输出PS侧初始化完成后把结果通过MIO或EMIO输出到PL侧的LED方便观察。这里用GPIO输出拉高作为done信号PL侧可以把done信号接到逻辑里做输入锁定也可以直接驱动LED。MZ7035FA板上有几个用户LED通过MIO连接到PS端直接用XGpioPs驱动即可。/* 配置完成拉高LED */ XGpioPs_SetDirection(gpio_inst, LED_PIN_MASK, 1); XGpioPs_WritePin(gpio_inst, LED_PIN, 1);拉高LED这个操作放在配置序列和读回校验都成功之后不然LED一亮就以为配置成功了实际可能只是部分寄存器写进去了。实际项目里我习惯再加一个心跳闪烁跑一个1s周期的翻转任务看到闪烁说明PS的调度活着纯延时点灯不能证明系统状态。5. 用ILA抓IIC时序反推配置问题配置完发现HDMI没有图像第一件事不是改代码而是抓IIC波形。把ILA的探针挂在SDA和SCL上采样时钟用200MHz触发条件设置成SCL上升沿。这个抓法能看到完整的IIC事务包括起始条件、地址字节、寄存器地址和数据字节。重点看ACK位ADV7611在第九个时钟周期拉低SDA表示ACK如果一直保持高电平说明设备没响应或者地址不对。抓完波形后对着数据手册查每个字节。很多人配置不成功的原因是寄存器写顺序不对比如先使能了HDMI输入再写EDID结果EDID被芯片内部状态机覆盖。ILA里能看到写入顺序时就能定位问题在哪个环节。我常做的验证流程是先用SDK读回ID和几个关键寄存器的值确认IIC通路没问题再用ILA抓HDMI输入侧的数据有效信号看是否有行场同步输出最后才去查图像质量。如果行场同步都没有问题大概率在ADV7611配置或者输入信号本身用示波器量HDMI座子的TMDS信号就能区分。把IIC寄存器配置做成一个通用的初始化数组把EDID写入和寄存器初始化拆成两个独立函数后续换分辨率或者换HDMI芯片时只需要改数组内容不用动整体流程。这套方法在MZ7035FA上验证过跑1080p60输入回环输出稳定也能用到其它带PS侧IIC的ZYNQ平台上。本文还有配套的精品资源点击获取