请选择 进入手机版 | 继续访问电脑版
[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA_SDK高级篇连载-06单摄像头采集显示方案(VDMA)

文档创建者:FPGA课程
浏览次数:139
最后更新:2024-09-30
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 1_SDK应用方案(仅旗舰型号) » 2-SDK高级应用方案
本帖最后由 FPGA课程 于 2024-9-30 11:21 编辑

​软件版本: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概述
VDMA这个IP的核心用法主要用于视频图像数据的传输。本文中将演示利用VDMA实现摄像头数据的采集,以及通过VDMA从DDR中获取图像数据,并且在HDMI显示屏上进行显示。
本文虽然讲解内容以HDMI输出说明,但是也提供了RGB方式和LVDS方式显示。关于液晶屏的详细使用也可以阅读前文。
关于液晶屏的RGB和LVDS的系接线请阅读“附录1” 。
关于OV5640摄像头介绍请阅读“附录1”。
关于VDMA IP的使用请阅读“附录2”
本文实验目的:
1:了解OV5640主要的参数以及寄存器设置
2:掌握I2C方式和SCCB方式配置摄像头寄存器方法
3:掌握2个VDMA的使用和配置
4:掌握2个VDMA之间如何进行帧同步,如何实现3帧图形缓存,确保图形不撕裂
2系统框图
1:采用EMIO扩展PSI2C
f002b7f49a86480f92aed3b3757b897a.jpg
2:采用AXI-GPIO实现SCCB
927d9e2dd581467193cf06e35a9fa51e.jpg
3硬件电路分析
硬件接口和子卡模块请阅读“附录1”
配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。
4搭建SOC系统工程
4.1PL图形化编程
系统输出部分的搭建请,阅读“04视频图形显示方案(VDMA)”一文中“5.4搭建SOC系统工程”这一章节,本文增加VDMA输入部分。下面给出完成的工程,并且就新增加的输入部分加以介绍。
1:采用EMIO I2C接口方案初始化摄像头
双击ZYNQ IP核,勾选I2C0通道,并且把I2C的IO映射到EMIO上,如下图。
20f088a1e2e747de8d3a12672af18eff.jpg
以下为完成后以EMIO IIC初始化摄像头的工程
e6bb4193c5904f0c82990e06cc942a86.jpg ​​
2:采用SSCB接口方案初始化摄像头
df79d2453ad24f958939dd932ea5e759.jpg ​​
高亮部分为方案增加的IP CORE:
uiSensorRGB565 IP Core:
该IP为Milianke自定义IP完成OV5640摄像头数据从RGB565转为RGB888,具体请阅读 “附录3”
uivideo2axism IP Core:
该IP为Milianke自定义IP完成RGB接口数据流转为AXI-Stream数据流, 具体请阅读 “附录3” 。
axi_vdma_1 IP Core:
该ip是XILINX官方IP,在这里用于输入帧的管理,关于VDMA IP的使用请阅读“附录2” 。
gpio_rstn IP Core:
该IP为XILINX AXI-GPIO总线IP用于扩展PL GPIO,在本方案中SDK控制该IP完成摄像头部分的IP复位。具体请阅读 “附录2”
gpio_sccb IP Core:
该IP为XILINX AXI-GPIO总线IP用于扩展PL GPIO,在本方案中SDK控制该IP完成摄像头部分的寄存器初始化。具体请阅读 “附录2”
4.2设置地址分配
以sccb方式初始化摄像头的地址空间截图,EMIO IIC的没有gpio_sccb AXI-GPIO
96f16c49d8f24abdab6e34cba31c1944.jpg
4.3添加PIN约束
1:选中PROJECT  MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。
ece276387d624172b5f70199635db501.jpg
2:打开提供例程,复制约束文件中的管脚约束到XDC文件,或者查看原理图,自行添加管脚约束,并保存。
以下是添加配套工程路径下已经提供的pin脚文件。配套工程的pin脚约束文件在uisrc/04_pin路径
4.4编译并导出平台文件
1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。
2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。
3:生成Bit文件。
4:导出到硬件: FileàExport HardwareàInclude bitstream
5:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
730564c7ead144f9a43e9a788f699869.jpg
5搭建Vitis-sdk工程
创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。
5.1创建SDK Platform工程
eaad9880aee546de960786ff28bfd729.jpg
5.2创建cam_5640 APP测试工程
0bdbadf96a3e4c57abd73f26bb084fc5.jpg
6程序分析
6.1主程序分析
在5640_test.c主函数中,分配了3个缓冲区,用于存储3帧图像。通过AXI-GPIO完成对输入uiSensorRGB565 IP和uivideo2axism IP的复位,确保数据传输前FIFO清空。ov5640_init(&I2cInst1)函数通过I2C接口初始化寄存器.
  1. /********************MILIANKE**************************
  2. *Company : MiLianKe Electronic Technology Co., Ltd.
  3. *WebSite:https://www.milianke.com
  4. *TechWeb:https://www.uisrc.com
  5. *tmall-shop:https://milianke.tmall.com
  6. *jd-shop:https://milianke.jd.com
  7. *taobao-shop1: https://milianke.taobao.com
  8. *Create Date: 2021/10/15
  9. *File Name: 5640_test.c
  10. *Description:
  11. *Declaration:
  12. *The reference demo provided by Milianke is only used for learning.
  13. *We cannot ensure that the demo itself is free of bugs, so users
  14. *should be responsible for the technical problems and consequences
  15. *caused by the use of their own products.
  16. *Copyright: Copyright (c) MiLianKe
  17. *All rights reserved.
  18. *Revision: 1.0
  19. ****************************************************/
  20. #include "xil_exception.h"
  21. #include "xil_printf.h"
  22. #include "xil_cache.h"
  23. #include "vdma_pl.h"
  24. #include "xgpio.h"
  25. #include "HDMIdma_intr.h"
  26. #include "sccb_iic.h"
  27. #include "ov5640_cfg.h"
  28. #include "sys_intr.h"
  29. #define BUF_BASE_SIZE   0x10000000
  30. #define BUF_RANG_SIZE   0x1000000
  31. #define BUF1_ADDR       BUF_BASE_SIZE + BUF_RANG_SIZE*0
  32. #define BUF2_ADDR       BUF_BASE_SIZE + BUF_RANG_SIZE*1
  33. #define BUF3_ADDR       BUF_BASE_SIZE + BUF_RANG_SIZE*2
  34. #define VIDEO_OUT_HSIZE  1280*4
  35. #define VIDEO_OUT_STRIDE 1280*4
  36. #define VIDEO_OUT_VSIZE  720
  37. #define IMG_SIZE VIDEO_OUT_HSIZE*VIDEO_OUT_VSIZE
  38. #define CAM0_AXI_VDMA_ID XPAR_AXI_VDMA_1_DEVICE_ID
  39. #define CAM0_AXI_VDMA_INTR XPAR_FABRIC_AXI_VDMA_1_S2MM_INTROUT_INTR
  40. u8 *CAM0_DBUF[IMG_SIZE];
  41. u8 *VIDEOOUT_DBUF[IMG_SIZE];
  42. XGpio rstn_5640;
  43. XGpio sscb_cam0;
  44. extern XScuGic Intc;
  45. extern Run_Config RunCfg;
  46. extern XAxiVdma video0_in;
  47. extern XAxiVdma_DmaSetup video0_in_WriteCfg;
  48. extern XHDMIDma HDMIDma;
  49. extern XHDMIPsu HDMIPsu;
  50. extern volatile  int  rfram_cnt;
  51. extern volatile  int  rfram_error;
  52. extern volatile  int  wframe1_cnt;
  53. extern volatile  int  wframe1_error;
  54. extern volatile  int  wfram1_grap_done;
  55. extern volatile  int  grap_button;
  56. extern volatile  int  delay_frame;
  57. void init_intr_sys(void)
  58. {
  59.     Init_Intr_System(&Intc);//initialize global interrupt source
  60.     Video1_in_SetupIntrSystem(&Intc,&video0_in,CAM0_AXI_VDMA_INTR);
  61.     XAxiVdma_IntrEnable(&video0_in, XAXIVDMA_IXR_ALL_MASK, XAXIVDMA_WRITE);//enable vdma s2mm channel interrupt
  62.     HDMIdma_init(&RunCfg ,&Intc);//setup HDMI channel
  63.     HDMIdma_Setup_Intr_System(&RunCfg);//enable HDMI channel
  64.     Setup_Intr_Exception(&Intc);//enable global interrupt source
  65. }
  66. int main()
  67. {
  68.     Xil_DCacheDisable();
  69.     Xil_ICacheDisable();
  70. video_buffer_init(VIDEOOUT_DBUF[0]);
  71. //定义视频输入缓存地址
  72.         VIDEOOUT_DBUF[0] = BUF1_ADDR;
  73.         VIDEOOUT_DBUF[1] = BUF2_ADDR;
  74.         VIDEOOUT_DBUF[2] = BUF3_ADDR;
  75. //通过memset函数清零缓冲区
  76.         memset(VIDEOOUT_DBUF[0], 0x00, 1280*720*4);
  77.         memset(VIDEOOUT_DBUF[1], 0x00, 1280*720*4);
  78.         memset(VIDEOOUT_DBUF[2], 0x00, 1280*720*4);
  79. //通过memset函数清零缓冲区确保数据刷入缓冲区
  80.     Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[0], IMG_SIZE);
  81.     Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[1], IMG_SIZE);
  82.     Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[2], IMG_SIZE);
  83. //初始化AXI-GPIO用于复位
  84.         XGpio_Initialize(&rstn_5640, XPAR_GPIO_RSTN_DEVICE_ID);
  85.         XGpio_SetDataDirection(&rstn_5640, 1, 0x0);
  86.         XGpio_DiscreteWrite(&rstn_5640, 1, 0x0);
  87. //使用sccb方式初始化摄像头
  88.         sccb_gpio_init(&sscb_cam1,XPAR_GPIO_SCCB_DEVICE_ID);
  89.         sleep(1);
  90. //初始化摄像头
  91.         ov5640_init(sscb_cam1,1280,720);
  92.         sleep(1);
  93. //设置VDMA输入帧缓存地址
  94.         video0_in_WriteCfg.FrameStoreStartAddr[0] = (INTPTR)VIDEOOUT_DBUF[0];
  95.         video0_in_WriteCfg.FrameStoreStartAddr[1] = (INTPTR)VIDEOOUT_DBUF[1];
  96.         video0_in_WriteCfg.FrameStoreStartAddr[2] = (INTPTR)VIDEOOUT_DBUF[2];
  97. //5640复位完成
  98.         XGpio_DiscreteWrite(&rstn_5640, 1, 0x1);
  99. //初始化输入视频1VDMA通道,和输出视频VDMA通道
  100.         Video1_S2MMSetup(CAM1_AXI_VDMA_ID, &video1_in, video1_in_WriteCfg , 720 , 1280*4 , 1280*4);
  101. //启动VDMA
  102.         XAxiVdma_DmaStart(&video1_in, XAXIVDMA_WRITE);
  103.     while(1)
  104.     {
  105.          if(wfram1_grap_done == 1)//wait vmda s2mm write channel intrrupt
  106.          {
  107.             video_buffer_update((u8*)VIDEOOUT_DBUF[(wframe1_cnt+1)%3],&RunCfg);
  108.             wfram1_grap_done =0;//clear for next
  109.          }
  110.     }
  111.     return XST_SUCCESS;
  112. }
复制代码

6.2OV5640关键参数
1:OV5640分辨率设置
在初始化摄像头的寄存器参数中,通过设置DVP图像数据输出窗口 (0x3808, 0x3809), (0x380A, 0x380B)寄存器设置图像输出的分辨率为1280*720P。
8f0f936755d94a58b193af5d1ed37a17.jpg
87da9efff93e47eda156d2c2142a1e71.jpg
再看(0x380c, 0x380d)=1380, (0x380e, 0x380f)=740寄存器设置了一帧图像的水平大小和垂直大小.
a79b53656c8742a187a5d5426c3c467f.jpg
2:OV5640PCLK时钟的设置
本文前面有详细的介绍,这里读者如果要修改PCLK时钟,一般修改0x3035寄存器既可以。
671f2428413c4e4fad23a5eb621765e0.jpg
6.3VDMA初始化
本文的demo主要目的除了实现OV5640摄像头的采集,更重要的是进一步掌握VDMA多缓存的Circle模式使用。以下框图中展示了我们利用VDMA开辟的3个缓存地址空间,利用VDMA的帧控制器管理DDR的内存地址。为了确保读写不冲突,设置S2MM为Gen Master,设置MM2S为Gen Slave,并且设置帧延迟1帧,这样,如下图所示当S2MM操作buf0的内存地址的时候,MM2S会操作buf2的内存地址,延迟于Master 1帧。这样就能确保读写内存不冲突了。
c53615e7f645478c85275d08495efa1f.jpg
1:VDMA写DDR操作初始化
399fe34db5254f14af3cd128c1791a95.jpg
以上关键设置中:EnableCircularBuf、EnableSync参数决定了多缓存的park工作方式
关键参数涉及的寄存器位如下(关于VDMA的寄存器介绍请阅读“附录2”):
MM2S_VDMACR. Circular_Park=0 设置park模式,VDMA不能自动完成帧循环切换,需要用户自定义设置中断才能完成图像同步。因为最后输出为HDMI输出,并没有VDMA参与,所以无法自动管理中断,所以只能使用park模式同步。
2:SCCB驱动代码
关于sccb可与阅读“附录1”中关于OV5640摄像头模块的介绍
  1. /********************MILIANKE**************************
  2. *Company:Liyang Milian Electronic Technology Co., Ltd
  3. *Technical forum:www.uisrc.com
  4. *Taobao: https://milianke.taobao.com
  5. *Create Date: 2020/12/01
  6. *Module Name:sscb_iic
  7. *Copyright: Copyright (c) milianke
  8. *Revision: 1.1
  9. *Description:
  10. ****************************************************/
  11. #include "sleep.h"
  12. #include "xgpio.h"
  13. #include "sccb_iic.h"
  14. #define CAM_OV5640           0x78
  15. #define AXI_GPIO_BIT0  0x1
  16. #define AXI_GPIO_BIT1  0x2
  17. void SCL_HIGH(XGpio sscb_gpio)  {XGpio_DiscreteSet(&sscb_gpio,1,AXI_GPIO_BIT0);  }
  18. void SCL_LOW (XGpio sscb_gpio)  {XGpio_DiscreteClear(&sscb_gpio,1,AXI_GPIO_BIT0);}
  19. void SDA_HIGH(XGpio sscb_gpio)  {XGpio_DiscreteSet(&sscb_gpio,1,AXI_GPIO_BIT1);  }
  20. void SDA_LOW (XGpio sscb_gpio)  {XGpio_DiscreteClear(&sscb_gpio,1,AXI_GPIO_BIT1);}
  21. void sccb_gpio_init(XGpio *psscb_gpio , u16 DeviceId)
  22. {
  23.         XGpio_Initialize(psscb_gpio, DeviceId);
  24.         XGpio_SetDataDirection(psscb_gpio, 1, 0x0);
  25.         XGpio_DiscreteWrite(psscb_gpio, 1, 0x3);
  26. }
  27. void sccb_start(XGpio sscb_gpio)
  28. {
  29.                 SCL_HIGH(sscb_gpio);
  30.                 SDA_HIGH(sscb_gpio);
  31.                 usleep(10);
  32.                 SDA_LOW(sscb_gpio);
  33.                 usleep(10);
  34.                 SCL_LOW(sscb_gpio);
  35.                 usleep(10);
  36. }
  37. void sccb_end(XGpio sscb_gpio)
  38. {
  39.                 SDA_LOW(sscb_gpio);
  40.                 usleep(10);
  41.                 SCL_HIGH(sscb_gpio);
  42.                 usleep(10);
  43.                 SDA_HIGH(sscb_gpio);
  44.                 usleep(10);
  45. }
  46. void sccb_sendbyte(XGpio sscb_gpio, u8 value )
  47. {
  48.         //并行数据转串行输出,串行数据输出的顺序为先高位再低位
  49.         u8  i=0;
  50.         for(i=0; i<8; i++)
  51.         {
  52.                 if(value & 0x80 )
  53.                 SDA_HIGH(sscb_gpio);
  54.                 else
  55.                 SDA_LOW(sscb_gpio);
  56.                 usleep(10);
  57.                 SCL_HIGH(sscb_gpio);
  58.                 usleep(10);
  59.                 SCL_LOW(sscb_gpio);
  60.                 usleep(10);
  61.                 value<<=1;
  62.         }
  63.         //第9位,Don’t Care
  64.         SDA_LOW(sscb_gpio);
  65.         usleep(10);
  66.         SCL_HIGH(sscb_gpio);
  67.         usleep(10);
  68.         SCL_LOW(sscb_gpio);
  69.         usleep(10);
  70. }
  71. void write_i2c(XGpio sscb_gpio ,u16 addr,u8 value)
  72. {
  73.         u8 buf[3];
  74.         buf[0] = addr >>8;
  75.         buf[1] = addr;
  76.         buf[2] = value;
  77.         sccb_start(sscb_gpio);
  78.         sccb_sendbyte(sscb_gpio,CAM_OV5640);
  79.         sccb_sendbyte(sscb_gpio,buf[0]);
  80.         sccb_sendbyte(sscb_gpio,buf[1]);
  81.         sccb_sendbyte(sscb_gpio,buf[2]);
  82.         sccb_end(sscb_gpio);
  83.         usleep(2000);
  84. }
复制代码

7OV5640单路HDMI演示
本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
7.1硬件准备
0400e47123e34fc386dba7c9058ebede.jpg
7.2实验结果
c2e6475574d546ac8ad51bd3d3283686.jpg
本方案路径下也提供了基于 CEPX3 实现的双目采集方案











0b2c5c3e09234221a410d49a3e3dfac6.jpg
0fc0911a7225476cb481ec4a42ac4129.jpg
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则