[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA_SDK入门篇连载-19 PL AXI-IIC实验

文档创建者:FPGA课程
浏览次数:135
最后更新:2024-09-27
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 1_SDK应用方案(仅旗舰型号) » 1-SDK基础入门方案
本帖最后由 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系统框图
81636bc85cbe47f6b00519ae9e6a724d.jpg
3AXI-IIC IP概述
AXI-IIC控制器的功能构架图如下:
34ea1d2d39db48ee913465fe609f8829.jpg
3.1特性
-符合行业标准I2C协议
-通过axis4 - lite接口访问
-支持I2C主或从模式
-支持多主机操作
-软件可选择ACK位
-仲裁丢失中断并自动从主模式切换从模式
-寻址地址识别中断并自动从主模式到从模式
-起始位和停止位的产生和侦测
-重复起始位的产生
-ACK的产生和侦测
-总线忙检测
-支持1MHZ 、400KHZ 、100KHZ 工作时钟
-7位或10位寻址
-寻址使能和禁止
- 16字节深度的FIFO
-节流功能
-动态生成启动和停止
3.2寄存器概述
2e2ba59cab3741eaa975c74bae2f0ba9.jpg
4硬件电路分析
本实验只测试EEPROM
7a38f3aefb5f451cb4de7473231895ed.jpg
5搭建SOC系统工程
详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。
5.1SOC系统工程
c77a6e74e8164cf1915a0c0bd6d50ea7.jpg
1:中断设置
01a1d9e782ca460c8594cab7e34d28aa.jpg
2:设置GP Master接口
fa6b3eb2fb034780a9ec66fd5cffc22e.jpg
3:设置复位输出
9b3a6d3e83c94eabadfac7d10037a76e.jpg
4:设置PL时钟
9cefec4a73854c998d5403c1ec0144a9.jpg
5:AXI-IIC IP
0d2d0f9dc5da45b0aab2c797c2977bce.jpg
5.2设置AXI外设地址分配
只要添加的AXI总线外设都要正确分配地址,这一步不能遗漏。
eb4609c9a5a3430f8be4393d38d8faee.jpg
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平台。
1611f76b40dd4816b7330eb807344fd9.jpg
6搭建Vitis-sdk工程
创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。
6.1创建SDK Platform工程
40328f3f7a4c4a5e81e9e11dc1f872c1.jpg
右击soc_base编译,编译的时间可能会有点长
6.2创建APP工程
159401af58ed4a65a4201750076daeed.jpg
7程序分析
7.1eeprom_test.c主程序
  1. #include "pli2c_intr.h"
  2. #include "sys_intr.h"
  3. #include "sleep.h"

  4. extern XScuGic Intc;
  5. extern XIic  I2cInst0;

  6. I2C_ADDR8 *I2C_WINST_8 = (void*) 0x08000000;//设置写数据起始地址
  7. I2C_ADDR8 *I2C_RINST_8 = (void*) 0x08100000;//设置读数据起始地址

  8. void init_intr_sys(void)
  9. {
  10.         Init_Intr_System(&Intc);
  11.         i2cpl_init(&I2cInst0,IIC_DEVICE_ID0);
  12.         i2cpl_setup_intr(&Intc,&I2cInst0,IIC_INT_VEC_ID0);
  13.         Setup_Intr_Exception(&Intc);
  14. }

  15. int main(void)
  16. {

  17. u32 i=0;
  18. u32 j=0;

  19. init_intr_sys();//初始化中断
  20. while(1)
  21. {
  22. //24c02 2kbits write
  23.                 for(i=0; i<32; i++)//连续写入256Bytes 每次写入8Bytes 共写32次
  24.                 {
  25.                         I2C_WINST_8->reg_addr[0]=i*8;
  26.                         for(j=0; j<8; j++)
  27.                                 I2C_WINST_8->reg_buf[j] = (j + i*8);
  28.                         i2cpl_wr8(&I2cInst0,I2C_WINST_8,8);
  29.                         usleep(4000);
  30.                 }
  31. //读出数据并且比较数据是否正确
  32.                 I2C_RINST_8->reg_addr[0]=0;
  33.                 i2cpl_rd8(&I2cInst0,I2C_RINST_8,256);

  34.                 for(i=0;i<256;i++)//compare write buffer and read buffer
  35.                         if(i!=I2C_RINST_8->reg_buf[i])
  36.                                 xil_printf("error addr%d=%d\r\n",i,I2C_RINST_8->reg_buf[i]);
  37.                 xil_printf("i2c test successfully!\r\n");
  38.                 sleep(1);
  39. }
  40. return 0;
复制代码

测试程序中先往EEPROM里面写入256字节。但是这里需要注意,24C02 EEPROM的页大小是16字节,一般来说,I2C控制器可以支持任意长度的写入。但是,经过实际测试,如果每次写入超过9个字节(1个地址+8个有效数据)就会出错。这应该是AXI-IIC裸机驱动的bug。目前本人分析源码后,还没有找到bug所在。因此暂时通过规避每次写入大于9字节的情况发生。
7.2pli2c_intr.c程序
  1. #include "pli2c_intr.h"
  2. #include "sys_intr.h"

  3. static void SendHandler(XIic *InstancePtr);
  4. static void ReceiveHandler(XIic *InstancePtr,int ByteCount);
  5. static void StatusHandler(XIic *InstancePtr, int Event);

  6. volatile u8 TransmitComplete;   /* Flag to check completion of Transmission */
  7. volatile u8 ReceiveComplete;    /* Flag to check completion of Reception */

  8. static void SendHandler(XIic *InstancePtr)
  9. {
  10.     TransmitComplete = 0;
  11. }

  12. static void ReceiveHandler(XIic *InstancePtr,int ByteCount)
  13. {
  14.     ReceiveComplete = 0;
  15. }

  16. static void StatusHandler(XIic *InstancePtr, int Event)
  17. {

  18. }
  19. //这个函数用于写数据到EEPROM
  20. int i2cpl_wr8(XIic *I2cInstancePtr,I2C_ADDR8 *MsgPtr , u16 byte_cnt)
  21. {
  22.     int Status;

  23.     TransmitComplete = 1;
  24.     I2cInstancePtr->Stats.TxErrors = 0;

  25.     Status = XIic_Start(I2cInstancePtr); //启动I2C控制器
  26.     if (Status != XST_SUCCESS) {
  27.         return XST_FAILURE;
  28.     }

  29.     Status = XIic_MasterSend(I2cInstancePtr, MsgPtr, byte_cnt + 1); //发送数据
  30.     if (Status != XST_SUCCESS) {
  31.         return XST_FAILURE;
  32.     }

  33.     while ((TransmitComplete) || (XIic_IsIicBusy(I2cInstancePtr) == TRUE)) ; //等待发送完成

  34.     Status = XIic_Stop(I2cInstancePtr); //停止I2C控制器
  35.     if (Status != XST_SUCCESS) {
  36.         return XST_FAILURE;
  37.     }

  38.     return XST_SUCCESS;
  39. }
  40. //从EEPROM读数据
  41. int i2cpl_rd8(XIic *I2cInstancePtr, I2C_ADDR8 *MsgPtr , u16 byte_cnt)
  42. {
  43.     int Status;
  44.     ReceiveComplete = 1;

  45.     I2C_ADDR8 rd_addr;
  46.     rd_addr.reg_addr[0]= MsgPtr->reg_addr[0];

  47.     i2cpl_wr8(I2cInstancePtr,&rd_addr,0); //首先设置EEPROM寄存器的地址

  48.     Status = XIic_Start(I2cInstancePtr); //启动I2C控制器
  49.     if (Status != XST_SUCCESS) {
  50.         return XST_FAILURE;
  51.     }

  52.     Status = XIic_MasterRecv(I2cInstancePtr, MsgPtr->reg_buf, byte_cnt); //接收数据
  53.     if (Status != XST_SUCCESS) {
  54.         return XST_FAILURE;
  55.     }

  56.     while ((ReceiveComplete) || (XIic_IsIicBusy(I2cInstancePtr) == TRUE)) ; //等待数据接收完成

  57.     Status = XIic_Stop(I2cInstancePtr); //停止I2C控制器
  58.     if (Status != XST_SUCCESS) {
  59.         return XST_FAILURE;
  60.     }

  61.     return XST_SUCCESS;
  62. }

  63. int i2cpl_init(XIic *I2cInstancePtr,u16 DeviceId)
  64. {
  65.     int Status;
  66.     XIic_Config *ConfigPtr; /* Pointer to configuration data */

  67.     ConfigPtr = XIic_LookupConfig(DeviceId);
  68.     if (ConfigPtr == NULL) {
  69.         return XST_FAILURE;
  70.     }

  71.     Status = XIic_CfgInitialize(I2cInstancePtr, ConfigPtr,ConfigPtr->BaseAddress);
  72.     if (Status != XST_SUCCESS) {
  73.         return XST_FAILURE;
  74.     }

  75.     Status = XIic_SetAddress(I2cInstancePtr, XII_ADDR_TO_SEND_TYPE,0x50);//设置SLAVE器件地址,0x50是24LC02芯片地址
  76.     if (Status != XST_SUCCESS) {
  77.         return XST_FAILURE;
  78.     }
  79. }

  80. int i2cpl_setup_intr(XScuGic *XScuGicPtr, XIic *I2cInstancePtr, u16 I2cIntrId)
  81. {
  82.     int Status;

  83.     XScuGic_SetPriorityTriggerType(XScuGicPtr, I2cIntrId,0xA0, 0x3);// 设置PL中断
  84. //设置中断回调函数
  85.     Status = XScuGic_Connect(XScuGicPtr, I2cIntrId,(Xil_InterruptHandler)XIic_InterruptHandler,I2cInstancePtr);
  86.     if (Status != XST_SUCCESS) {
  87.         return Status;
  88.     }

  89.     XIic_SetSendHandler(I2cInstancePtr, I2cInstancePtr,(XIic_Handler) SendHandler); //设置发送中断回调函数
  90.     XIic_SetRecvHandler(I2cInstancePtr, I2cInstancePtr,(XIic_Handler) ReceiveHandler); //设置接收中断回调函数
  91.     XIic_SetStatusHandler(I2cInstancePtr, I2cInstancePtr,(XIic_StatusHandler) StatusHandler); //设置状态回调函数

  92.     XScuGic_Enable(XScuGicPtr, I2cIntrId); //使能中断

  93.     return XST_SUCCESS;
  94. }
复制代码

以上程序中,我拿关键的函数部分进行分析。
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所以我们暂时应用程序中需要规避这个问题。
e4e9f1e686c84b4c84a31290485f4870.jpg
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)
f3b10d3e92bb49b5a87daa851d6b2c4a.jpg
8.2实验结果
对24C02 EEROM写入256个测试数据,然后读出对比
a45d524bef274e38ba2dcdb52bbca338.jpg













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

本版积分规则