ZYNQ IIC驱动EEPROM:从硬件配置到SDK代码的避坑实践 1. ZYNQ IIC驱动EEPROM的硬件配置第一次用ZYNQ的IIC控制器驱动EEPROM时我在硬件配置环节踩了不少坑。最典型的就是EMIO引脚映射问题——明明在Vivado里勾选了IIC0控制器下载程序后却发现设备根本检测不到。后来才发现ZYNQ的IIC控制器需要通过EMIO引出到PL端再通过XDC约束文件绑定到具体引脚。具体操作时先在Block Design中添加ZYNQ7 Processing System核双击进入配置界面。在Peripheral I/O Pins选项卡中找到I2C 0勾选EMIO选项。这时候回到Diagram界面会发现ZYNQ核上多出了I2C0的接口。右键选择Make External生成外部引脚。关键点来了必须根据开发板原理图确认EEPROM连接的FPGA引脚编号。比如我的板子上SCL接T19引脚SDA接U19引脚。在XDC约束文件中要这样写set_property PACKAGE_PIN T19 [get_ports IIC0_scl_io] set_property IOSTANDARD LVCMOS33 [get_ports IIC0_scl_io] set_property PACKAGE_PIN U19 [get_ports IIC0_sda_io] set_property IOSTANDARD LVCMOS33 [get_ports IIC0_sda_io]特别注意电压标准必须与EEPROM的工作电压一致通常是3.3V。有次我忘记设置IOSTANDARD结果读出来的全是乱码调试了半天才发现是电平不匹配。2. SDK中的IIC控制器初始化硬件配置完成后在Vitis中新建工程时很多人会直接套用官方例程的XIicPs代码但有几个参数特别容易出错首先是设备ID的定义。在xparameters.h中搜索IIC控制器基地址时要注意区分PS7_I2C_0和PS7_I2C_1。如果硬件用的是IIC0但代码里误写成IIC1的ID初始化时会直接返回XST_FAILURE。正确的初始化代码应该包含三个关键步骤XIicPs_Config *Config XIicPs_LookupConfig(IIC_DEVICE_ID); if (NULL Config) { xil_printf(IIC Config Failed\r\n); return XST_FAILURE; } Status XIicPs_CfgInitialize(IicInstance, Config, Config-BaseAddress); Status XIicPs_SelfTest(IicInstance); // 硬件自检 XIicPs_SetSClk(IicInstance, 100000); // 设置100kHz标准速率实测发现时钟频率设置过高会导致EEPROM无响应。AT24C系列通常支持400kHz但稳妥起见建议先用100kHz测试。我曾遇到过设置为400kHz时连续写入多个字节会丢失数据的情况。3. EEPROM读写的数据处理最坑的就是地址偏移问题。ZYNQ的XIicPs库要求读写缓冲区时第一个字节必须是EEPROM的内部存储地址。比如要读取地址0x00的数据代码要这样写u8 WriteBuffer[2] {0x00, 0x00}; // 第一位是EEPROM地址 u8 ReadBuffer[1]; XIicPs_MasterSendPolled(IicInstance, WriteBuffer, 1, 0x50); // 先发送地址 XIicPs_MasterRecvPolled(IicInstance, ReadBuffer, 1, 0x50); // 再读取数据而写入操作更要注意缓冲区构造u8 WriteBuffer[3] {0x00, 0xAB, 0xCD}; // 格式地址数据1数据2 XIicPs_MasterSendPolled(IicInstance, WriteBuffer, 3, 0x50);我曾经因为没在第一位放地址直接发送数据导致写入的位置完全错乱。后来用逻辑分析仪抓波形才发现EEPROM把第一个数据字节当成了存储地址。4. 实际调试中的问题排查当IIC通讯异常时建议按以下步骤排查先用示波器检查SCL和SDA波形。正常的IIC信号应该是SCL有规律的方波SDA在SCL高电平期间保持稳定。如果看到SDA始终为高可能是设备地址错误或EEPROM未上电。在SDK中打印XIicPs的自检结果。如果SelfTest失败大概率是硬件配置有问题比如引脚映射错误或时钟未使能。注意延时。连续读写操作之间要加至少5ms延时AT24C04的页写入周期典型值为5ms。我有次连续写入不带延时结果第二批数据全部丢失。地址对齐问题。AT24C04的页大小为16字节如果跨页写入需要在代码中分段处理。例如写入32字节数据时for(int i0; i2; i){ WriteBuffer[0] i*16; // 每16字节更新一次起始地址 memcpy(WriteBuffer[1], Data[i*16], 16); XIicPs_MasterSendPolled(IicInstance, WriteBuffer, 17, 0x50); usleep(5000); // 等待写入完成 }最终调试通过的完整代码应该包含错误重试机制。比如读写失败时自动重试3次int retry 0; while(retry 3){ Status XIicPs_MasterSendPolled(IicInstance, buf, len, addr); if(Status XST_SUCCESS) break; retry; usleep(1000); } if(retry 3) xil_printf(IIC Operation Failed!\r\n);