本帖最后由 FPGA课程 于 2024-9-27 16:24 编辑
软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述PS自带2个I2C控制器,但是很多情况下我们需要更多的I2C控制器。这时候通过AXI-IIC IP可以非常方便实现更多I2C控制的扩展。 本文我们将讲解AXI-IIC控制器的实用,并且通过读写EEPROM演示这个I2C控制器的应用。 由于在“I2C通信及读写EEPROM实验”中已经介绍过I2C协议,以及EEPROM的读写时序,所以本文不再重复介绍。如果读者是初学者,而且不熟悉相关协议,可以阅读下“I2C通信及读写EEPROM实验”这篇文章。 本文实验目的:
1:通过阅读pg090-axi-iic.pdf熟悉AXI-IIC控制器的硬件资源(配套工程的soc_prj/06_doc路径)
2:通过VIVADO搭建AXI-IIC的SOC工程,通过按键模拟输入、LED模拟输出
3:使用VITIS-SDK编写AXI-IIC测试程序,并完成对EEPROM的读写操作
2系统框图
3AXI-IIC IP概述AXI-IIC控制器的功能构架图如下:
3.1特性-符合行业标准I2C协议 -通过axis4 - lite接口访问 -支持I2C主或从模式 -支持多主机操作 -软件可选择ACK位 -仲裁丢失中断并自动从主模式切换从模式 -寻址地址识别中断并自动从主模式到从模式 -起始位和停止位的产生和侦测 -重复起始位的产生 -ACK的产生和侦测 -总线忙检测 -支持1MHZ 、400KHZ 、100KHZ 工作时钟 -7位或10位寻址 -寻址使能和禁止 - 16字节深度的FIFO -节流功能 -动态生成启动和停止 3.2寄存器概述
4硬件电路分析本实验只测试EEPROM
5搭建SOC系统工程详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。 5.1SOC系统工程
1:中断设置
2:设置GP Master接口
3:设置复位输出
4:设置PL时钟
5:AXI-IIC IP
5.2设置AXI外设地址分配只要添加的AXI总线外设都要正确分配地址,这一步不能遗漏。
5.3编译并导出平台文件以下步骤简写,有不清楚的看第一篇文章。 1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。 2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。 3:添加配套工程路径下uisrc/04_pin/fpga_pin.xdc约束文件 4:生成Bit文件。 5:导出到硬件: FileàExport HardwareàInclude bitstream 6:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
6搭建Vitis-sdk工程创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 6.1创建SDK Platform工程
右击soc_base编译,编译的时间可能会有点长 6.2创建APP工程
7程序分析
7.1eeprom_test.c主程序- #include "pli2c_intr.h"
- #include "sys_intr.h"
- #include "sleep.h"
- extern XScuGic Intc;
- extern XIic I2cInst0;
- I2C_ADDR8 *I2C_WINST_8 = (void*) 0x08000000;//设置写数据起始地址
- I2C_ADDR8 *I2C_RINST_8 = (void*) 0x08100000;//设置读数据起始地址
- void init_intr_sys(void)
- {
- Init_Intr_System(&Intc);
- i2cpl_init(&I2cInst0,IIC_DEVICE_ID0);
- i2cpl_setup_intr(&Intc,&I2cInst0,IIC_INT_VEC_ID0);
- Setup_Intr_Exception(&Intc);
- }
- int main(void)
- {
- u32 i=0;
- u32 j=0;
- init_intr_sys();//初始化中断
- while(1)
- {
- //24c02 2kbits write
- for(i=0; i<32; i++)//连续写入256Bytes 每次写入8Bytes 共写32次
- {
- I2C_WINST_8->reg_addr[0]=i*8;
- for(j=0; j<8; j++)
- I2C_WINST_8->reg_buf[j] = (j + i*8);
- i2cpl_wr8(&I2cInst0,I2C_WINST_8,8);
- usleep(4000);
- }
- //读出数据并且比较数据是否正确
- I2C_RINST_8->reg_addr[0]=0;
- i2cpl_rd8(&I2cInst0,I2C_RINST_8,256);
- for(i=0;i<256;i++)//compare write buffer and read buffer
- if(i!=I2C_RINST_8->reg_buf[i])
- xil_printf("error addr%d=%d\r\n",i,I2C_RINST_8->reg_buf[i]);
- xil_printf("i2c test successfully!\r\n");
- sleep(1);
- }
- return 0;
复制代码
测试程序中先往EEPROM里面写入256字节。但是这里需要注意,24C02 EEPROM的页大小是16字节,一般来说,I2C控制器可以支持任意长度的写入。但是,经过实际测试,如果每次写入超过9个字节(1个地址+8个有效数据)就会出错。这应该是AXI-IIC裸机驱动的bug。目前本人分析源码后,还没有找到bug所在。因此暂时通过规避每次写入大于9字节的情况发生。 7.2pli2c_intr.c程序- #include "pli2c_intr.h"
- #include "sys_intr.h"
- static void SendHandler(XIic *InstancePtr);
- static void ReceiveHandler(XIic *InstancePtr,int ByteCount);
- static void StatusHandler(XIic *InstancePtr, int Event);
- volatile u8 TransmitComplete; /* Flag to check completion of Transmission */
- volatile u8 ReceiveComplete; /* Flag to check completion of Reception */
- static void SendHandler(XIic *InstancePtr)
- {
- TransmitComplete = 0;
- }
- static void ReceiveHandler(XIic *InstancePtr,int ByteCount)
- {
- ReceiveComplete = 0;
- }
- static void StatusHandler(XIic *InstancePtr, int Event)
- {
- }
- //这个函数用于写数据到EEPROM
- int i2cpl_wr8(XIic *I2cInstancePtr,I2C_ADDR8 *MsgPtr , u16 byte_cnt)
- {
- int Status;
- TransmitComplete = 1;
- I2cInstancePtr->Stats.TxErrors = 0;
- Status = XIic_Start(I2cInstancePtr); //启动I2C控制器
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- Status = XIic_MasterSend(I2cInstancePtr, MsgPtr, byte_cnt + 1); //发送数据
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- while ((TransmitComplete) || (XIic_IsIicBusy(I2cInstancePtr) == TRUE)) ; //等待发送完成
- Status = XIic_Stop(I2cInstancePtr); //停止I2C控制器
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- return XST_SUCCESS;
- }
- //从EEPROM读数据
- int i2cpl_rd8(XIic *I2cInstancePtr, I2C_ADDR8 *MsgPtr , u16 byte_cnt)
- {
- int Status;
- ReceiveComplete = 1;
- I2C_ADDR8 rd_addr;
- rd_addr.reg_addr[0]= MsgPtr->reg_addr[0];
- i2cpl_wr8(I2cInstancePtr,&rd_addr,0); //首先设置EEPROM寄存器的地址
- Status = XIic_Start(I2cInstancePtr); //启动I2C控制器
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- Status = XIic_MasterRecv(I2cInstancePtr, MsgPtr->reg_buf, byte_cnt); //接收数据
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- while ((ReceiveComplete) || (XIic_IsIicBusy(I2cInstancePtr) == TRUE)) ; //等待数据接收完成
- Status = XIic_Stop(I2cInstancePtr); //停止I2C控制器
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- return XST_SUCCESS;
- }
- int i2cpl_init(XIic *I2cInstancePtr,u16 DeviceId)
- {
- int Status;
- XIic_Config *ConfigPtr; /* Pointer to configuration data */
- ConfigPtr = XIic_LookupConfig(DeviceId);
- if (ConfigPtr == NULL) {
- return XST_FAILURE;
- }
- Status = XIic_CfgInitialize(I2cInstancePtr, ConfigPtr,ConfigPtr->BaseAddress);
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- Status = XIic_SetAddress(I2cInstancePtr, XII_ADDR_TO_SEND_TYPE,0x50);//设置SLAVE器件地址,0x50是24LC02芯片地址
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- }
- int i2cpl_setup_intr(XScuGic *XScuGicPtr, XIic *I2cInstancePtr, u16 I2cIntrId)
- {
- int Status;
- XScuGic_SetPriorityTriggerType(XScuGicPtr, I2cIntrId,0xA0, 0x3);// 设置PL中断
- //设置中断回调函数
- Status = XScuGic_Connect(XScuGicPtr, I2cIntrId,(Xil_InterruptHandler)XIic_InterruptHandler,I2cInstancePtr);
- if (Status != XST_SUCCESS) {
- return Status;
- }
- XIic_SetSendHandler(I2cInstancePtr, I2cInstancePtr,(XIic_Handler) SendHandler); //设置发送中断回调函数
- XIic_SetRecvHandler(I2cInstancePtr, I2cInstancePtr,(XIic_Handler) ReceiveHandler); //设置接收中断回调函数
- XIic_SetStatusHandler(I2cInstancePtr, I2cInstancePtr,(XIic_StatusHandler) StatusHandler); //设置状态回调函数
- XScuGic_Enable(XScuGicPtr, I2cIntrId); //使能中断
- return XST_SUCCESS;
- }
复制代码
以上程序中,我拿关键的函数部分进行分析。 1:XScuGic_SetPriorityTriggerType(XScuGicPtr, I2cIntrId,0xA0,0x3)函数这个函数用于设置PL中断的触发方式, 参数I2cIntrId=121代表了PL的第一个中断。 参数0xA0代表了中断的优先级,这个值越小代表拥有越高的优先级 参数0x3代表输入的PL信号上升沿触发中断 读者如果感兴趣可以继续追踪一下这个函数,看下如何设置中断寄存器的配置。 2:XIic_SetAddress(I2cInstancePtr, XII_ADDR_TO_SEND_TYPE,0x50)函数这个函数用于设置从机地址, 参数XII_ADDR_TO_SEND_TYPE用于设置时7bit地址,还是10bit地址 参数0x50就是24C04的从机地址。当然这个地址根据硬件设计不同,或者芯片型号不一样是变化的。 3:i2cpl_wr8(XIic *I2cInstancePtr,I2C_ADDR8 *MsgPtr , u16 byte_cnt)函数这个函数用于写数据到EEPROM。但是byte_cnt目前最大值是8。暂时还没能找到XILINX驱动的bug所以我们暂时应用程序中需要规避这个问题。
XIic_MasterSend负责具体的发送数据,读者如果想进一步分析这个可以看下XIic_MasterSend函数中的具体代码。 4:int i2cpl_rd8(XIic *I2cInstancePtr,I2C_ADDR8 *MsgPtr,u16 byte_cnt)函数这个函数负责读取EEPROM的数据。读操作没有bug可以实现任意长度读数据。为了实现读数据首先需要设置EEPROM的寄存器地址,通过写1个字节的地址到EEPROM,然后再开始读。 具体的读数据通过XIic_MasterRecv函数实现,读者如果想进一步了解这个函数的具体代码,可以配合寄存器说明自行阅读分析。 8方案演示
8.1硬件准备
本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON(注意新版本的 MLK_H3_CZ08-7100-MZ7100FC),支持 JTAG 模式,对于老版本的核心板,JTAG 调试的时候 一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
8.2实验结果对24C02 EEROM写入256个测试数据,然后读出对比
|