[X]关闭

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

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

​ 软件版本: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概述
本方案在前面方案的基础上增加一路视频输入,实现2路视频采集后在HDMI显示器上显示。
关于FEP-GPIO-CEPX3扩展卡介绍请阅读“附录1”。
关于OV5640摄像头介绍请阅读“附录1”。
关于VDMA IP的使用请阅读“附录2”
本文实验目的:
1:掌握OV5640摄像头寄存器输出分辨率的修改方法
2:掌握多个VDMA同时使用的同步解决方法
3:掌握VDMA视频格式中Stride参数的使用
4:掌握VDMA与HDMI之间之间如何进行帧同步,如何实现3帧图形缓存,确保图形不撕裂。
2系统框图
17fd8a6902b54d788c3419c73fbd9ccb.jpg
3硬件电路分析
硬件接口和子卡模块请阅读“附录1”
配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。
4搭建SOC系统工程
4.1PL图形化编程
系统输出部分的搭建请,阅读“04视频图形显示方案(VDMA)”一文中“5.4搭建SOC系统工程”这一章节,本文增加VDMA输入部分。下面给出完成的工程,并且就新增加的输入部分加以介绍。
99ac684b9ad74bdd850ec073acb91fd7.jpg
上图中高亮部是VDMA的帧同步计数器,由于是使用HDMI输出,为了防止图像撕裂,所以我们使用park模式下,将收到的摄像头数据三次缓存,这样可以确保视频不出现撕裂,CAM1中的VDMA IP和输出视频的VDMA0 IP 都设置成slave模式,而CAM0中的VDMA设置为Master模式,后面SDK中的VDMA寄存器配置也需要做响应的设置。
另外,我们对CAM0和CAM1的IP在BD图像模块中做了层级封装,这样可以让复杂的BD图像设计,看起来更加简洁,如下图所示。
605d2e644e434183997931f12764422a.jpg ​​

71344612f42b4e6b9438dcd6c3327383.jpg ​​
这个技巧如下,选中需要层级封装的IP,右击选择Create Hierarchy
3d397809276148cb9735da9d2c603e19.jpg
1:CAM0中VDMA IP设置
64f06f1e307e44b4a41e02986b6b2963.jpg
以下设置CAM0/VDMA 为主模式
6c4339a3ba824aadbca4510fbbd03b49.jpg
2:CAM1中VDMA IP设置
79cc68955dd04384a772cdc02c999c3e.jpg
以下设置CAM1/VDMA 为从模式,帧同步跟随主模式的CAM0/VDMA
0fbb4b37b7824f17bd2578620b20bbe3.jpg
3:视频输出 VDMA IP 设置

a6afcf3302dd46188c59d0aa0b54ddd7.jpg
以下设置视频输出 VDMA 为从模式,帧同步跟随主模式的 CAM0/VDMA
fa6ec162aae0426995f97a2e3d60eb19.jpg
4.2设置地址分配
以sccb方式初始化摄像头的地址空间截图
d633f33c36674bf39a2db55a449f6fba.jpg
4.3添加PIN约束
1:选中PROJECT  MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。
19fc8d94af25486c926de7118c63ed62.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平台。
3126b8df30ba419596a727b13ae901df.jpg
5搭建Vitis-sdk工程
创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。
5.1创建SDK Platform工程
6fe972fa151a418ca0d300e00155b3eb.jpg
5.2创建camx2_5640 APP测试工程
e20a2e13384f46d7b435d911b7c348a4.jpg
6程序分析
6.1主程序分析
  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. //内存地址空间定义
  30. #define BUF_BASE_SIZE   0x08000000
  31. #define BUF_RANG_SIZE          0x800000
  32. #define BUF1_ADDR                BUF_BASE_SIZE + BUF_RANG_SIZE*0
  33. #define BUF2_ADDR                BUF_BASE_SIZE + BUF_RANG_SIZE*1
  34. #define BUF3_ADDR                BUF_BASE_SIZE + BUF_RANG_SIZE*2
  35. //设置输出视频参数
  36. #define VIDEO_OUT_HSIZE         1280*4
  37. #define VIDEO_OUT_STRIDE  1280*4
  38. #define VIDEO_OUT_VSIZE         720
  39. #define IMG_SIZE VIDEO_OUT_HSIZE*VIDEO_OUT_VSIZE
  40. //设置输入视频0参数
  41. #define VIDEO0_IN_HSIZE         640*4
  42. #define VIDEO0_IN_STRIDE  1280*4
  43. #define VIDEO0_IN_VSIZE         480
  44. //设置输入视频1参数
  45. #define VIDEO1_IN_HSIZE         640*4
  46. #define VIDEO1_IN_STRIDE 1280*4
  47. #define VIDEO1_IN_VSIZE         480
  48. #define VIDEO1_OFFSET   (640+1280*240)*4
  49. #define CAM0_AXI_VDMA_ID XPAR_CAM0_AXI_VDMA_1_DEVICE_ID
  50. #define CAM1_AXI_VDMA_ID XPAR_CAM1_AXI_VDMA_1_DEVICE_ID
  51. #define CAM0_AXI_VDMA_INTR XPAR_FABRIC_CAM0_AXI_VDMA_1_S2MM_INTROUT_INTR
  52. #define CAM1_AXI_VDMA_INTR XPAR_FABRIC_CAM1_AXI_VDMA_1_S2MM_INTROUT_INTR
  53. u8 *CAM0_DBUF[IMG_SIZE];
  54. u8 *CAM1_DBUF[IMG_SIZE];
  55. u8 *VIDEOOUT_DBUF[IMG_SIZE];
  56. XGpio rstn_5640;
  57. XGpio sscb_cam0;
  58. XGpio sscb_cam1;
  59. extern XScuGic Intc;
  60. extern Run_Config RunCfg;
  61. extern XAxiVdma video0_in,video1_in;
  62. extern XAxiVdma_DmaSetup video0_in_WriteCfg,video1_in_WriteCfg;
  63. extern XHDMIDma HDMIDma;
  64. extern XHDMIPsu HDMIPsu;
  65. extern volatile  int  rfram_cnt;
  66. extern volatile  int  rfram_error;
  67. extern volatile  int  wframe1_cnt;
  68. extern volatile  int  wframe1_error;
  69. extern volatile  int  wfram1_grap_done;
  70. extern volatile  int  grap_button;
  71. extern volatile  int  delay_frame;
  72. void init_intr_sys(void)
  73. {
  74.         Init_Intr_System(&Intc);//initialize global interrupt source
  75.         Video1_in_SetupIntrSystem(&Intc,&video0_in,CAM0_AXI_VDMA_INTR);
  76.         Video2_in_SetupIntrSystem(&Intc,&video1_in,CAM1_AXI_VDMA_INTR);
  77.         XAxiVdma_IntrEnable(&video0_in, XAXIVDMA_IXR_ALL_MASK, XAXIVDMA_WRITE);//enable vdma s2mm channel interrupt
  78.         XAxiVdma_IntrEnable(&video1_in, XAXIVDMA_IXR_ALL_MASK, XAXIVDMA_WRITE);//enable vdma s2mm channel interrupt
  79.         HDMIdma_init(&RunCfg ,&Intc);//setup HDMI channel
  80.         HDMIdma_Setup_Intr_System(&RunCfg);//enable HDMI channel
  81.         Setup_Intr_Exception(&Intc);//enable global interrupt source
  82. }
  83. int main()
  84. {
  85.         Xil_DCacheDisable();
  86.         Xil_ICacheDisable();
  87.         video_buffer_init(CAM0_DBUF[0]);
  88. //设置摄像头0的三个缓存地址
  89.         CAM0_DBUF[0] = (u8*)BUF1_ADDR;
  90.         CAM0_DBUF[1] = (u8*)BUF2_ADDR;
  91.         CAM0_DBUF[2] = (u8*)BUF3_ADDR;
  92.         CAM1_DBUF[0] = (u8*)(BUF1_ADDR + VIDEO1_OFFSET);
  93.         CAM1_DBUF[1] = (u8*)(BUF2_ADDR + VIDEO1_OFFSET);
  94.         CAM1_DBUF[2] = (u8*)(BUF3_ADDR + VIDEO1_OFFSET);
  95. //设置摄像头1的三个缓存地址
  96.         VIDEOOUT_DBUF[0] = (u8*)BUF1_ADDR;
  97.         VIDEOOUT_DBUF[1] = (u8*)BUF2_ADDR;
  98.         VIDEOOUT_DBUF[2] = (u8*)BUF3_ADDR;
  99. //通过memset函数设置缓存初值
  100.         memset(VIDEOOUT_DBUF[0], 0x00, IMG_SIZE);
  101.         memset(VIDEOOUT_DBUF[1], 0x00, IMG_SIZE);
  102.         memset(VIDEOOUT_DBUF[2], 0x00, IMG_SIZE);
  103. //通过Xil_DcacheFlushRang确保把cache中初始化数据都刷入到DDR中
  104.         Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[0], IMG_SIZE);
  105.         Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[1], IMG_SIZE);
  106.         Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[2], IMG_SIZE);
  107. //初始化AXI-GPIO 并且复位摄像头
  108.         XGpio_Initialize(&rstn_5640, XPAR_GPIO_RSTN_DEVICE_ID);
  109.         XGpio_SetDataDirection(&rstn_5640, 1, 0x0);
  110.         XGpio_DiscreteWrite(&rstn_5640, 1, 0x0);
  111. //初始化用于sccb的AXI-GPIO
  112.         sccb_gpio_init(&sscb_cam0,XPAR_CAM0_GPIO_SCCB_DEVICE_ID);
  113.         sccb_gpio_init(&sscb_cam1,XPAR_CAM1_GPIO_SCCB_DEVICE_ID);
  114. //通过sccb分别对摄像头0和摄像头1进行初始化
  115.         ov5640_init(sscb_cam0,640,480,0x46,0x07);
  116.         ov5640_init(sscb_cam1,640,480,0x40,0x01);
  117. //设置摄像头0的VDMA的视频缓存地址
  118.         video0_in_WriteCfg.FrameStoreStartAddr[0] = (UINTPTR)CAM0_DBUF[0];
  119.         video0_in_WriteCfg.FrameStoreStartAddr[1] = (UINTPTR)CAM0_DBUF[1];
  120.         video0_in_WriteCfg.FrameStoreStartAddr[2] = (UINTPTR)CAM0_DBUF[2];
  121. //设置摄像头1的VDMA的视频缓存地址
  122.         video1_in_WriteCfg.FrameStoreStartAddr[0] = (UINTPTR)CAM1_DBUF[0];
  123.         video1_in_WriteCfg.FrameStoreStartAddr[1] = (UINTPTR)CAM1_DBUF[1];
  124.         video1_in_WriteCfg.FrameStoreStartAddr[2] = (UINTPTR)CAM1_DBUF[2];
  125. //完成复位
  126.         XGpio_DiscreteWrite(&rstn_5640, 1, 0x1);
  127. //初始化并且设置摄像头0的VDMA
  128.         Video1_S2MMSetup(CAM0_AXI_VDMA_ID, &video0_in, video0_in_WriteCfg , 480 , 640*4 ,  1280*4);
  129. //初始化并且设置摄像头1的VDMA
  130.         Video2_S2MMSetup(CAM1_AXI_VDMA_ID, &video1_in, video1_in_WriteCfg , 480 , 640*4 ,  1280*4);
  131.         XAxiVdma_DmaStart(&video0_in, XAXIVDMA_WRITE); //启动摄像头0的VDMA传输方向为PL到PS
  132.         XAxiVdma_DmaStart(&video1_in, XAXIVDMA_WRITE); //启动摄像头1的VDMA传输方向为PL到PS
  133.     while(1)
  134.     {
  135.              if(wfram1_grap_done == 1)//park模式手动控制中断,等待中断产生             {
  136.                      video_buffer_update((u8*)CAM0_DBUF[(wframe1_cnt+1)%3],&RunCfg);
  137.                      wfram1_grap_done =0;//清除中断
  138.              }
  139.     }    return XST_SUCCESS;
  140. }
复制代码

1:地址空间的分配
  1. #define BUF_BASE_SIZE   0x08000000
  2. #define BUF_RANG_SIZE          0x800000
  3. #define BUF1_ADDR                BUF_BASE_SIZE + BUF_RANG_SIZE*0
  4. #define BUF2_ADDR                BUF_BASE_SIZE + BUF_RANG_SIZE*1
  5. #define BUF3_ADDR                BUF_BASE_SIZE + BUF_RANG_SIZE*2
  6. #define VIDEO1_OFFSET   (640+1280*240)*4
复制代码

为了保存2路视频图像,我们需要为2路视频图像分别设置2个缓存地址空间,同时未来能让2个图像显示在一个屏幕上,我们还需要对2路图像缓存地址做合理分配,其中Stride值在这里就大大有用了。
地址为0x08000000=128MB,每个缓存的空间大小为0x800000=8MB
2:多路视频同屏显示原理
a8eeaa2cb6a449448bc472b382c19823.jpg
为了把2个图像显示到1个显示器,首先得搞清楚以下关系:
hsize:每1行图像实际在内存中占用的有效空间,以32bit表示一个像素的时候占用内存大小为hsize*4
hstride:用于设置每行图像第一个像素的地址,以32bit 表示一个像素的时候h_cnt* hstride*4
vsize:有效的行
因此很容易得出cam0的每行第一个像素的地址也是h_cnt* hstride*4
同理如果我们需要把cam1在hsize和vsize空间的任何位置显示,我们只要关心cam1每一行图像第一个像素的地址,可以用以下公式h_cnt* hstride*4+offset
比如我们这里背景输出到显示器的分辨率为1280*720,cam1的分辨率是640*480需要移动上图的右下脚,以下是对2路视频通路的地址设置和1路背景输出的地址设置:
  1. CAM0_DBUF[0] = (u8*)BUF1_ADDR;
  2.         CAM0_DBUF[1] = (u8*)BUF2_ADDR;
  3.         CAM0_DBUF[2] = (u8*)BUF3_ADDR;
  4.         CAM1_DBUF[0] = (u8*)(BUF1_ADDR + VIDEO1_OFFSET);
  5.         CAM1_DBUF[1] = (u8*)(BUF2_ADDR + VIDEO1_OFFSET);
  6.         CAM1_DBUF[2] = (u8*)(BUF3_ADDR + VIDEO1_OFFSET);
  7.         VIDEOOUT_DBUF[0] = (u8*)BUF1_ADDR;
  8.         VIDEOOUT_DBUF[1] = (u8*)BUF2_ADDR;
  9.         VIDEOOUT_DBUF[2] = (u8*)BUF3_ADDR;
复制代码

6.2 VDMA寄存器的参数配置
因为接收端为PD接口,并不使用VDMA,所以并不能使用circle模式自行控制中断,为了确保视频图像输出的不撕裂,使用Park模式:
CAM0为video1通道,是主模式
08bd808f98a54f308cbf1e175c430ff5.jpg
CAM1为video1通道,是从模式,CAM0和CAM2的帧缓存设置一致,但是实际上由于CAM0,CAM1,并不是同步相机,CAM1所以可能存在1帧的延迟。
0d0c5bf588ec4b839e4f72ce07a1f8a3.jpg
VideoOut 通道,是从模式,和 CAM0 通道同步,由于 CAM1 可能延迟 1 帧于 CAM0,所以如果设置延迟 1 帧,可能和 正在写入的 CAM1 通道内存重叠,导致 CAM1 图像撕裂,因此最佳设置是延迟 2 帧。
ca2982fe20ff4771a7937979e4097321.jpg
6.3:OV5640的镜像参数
本文中的摄像头模块采用了最新的FEP-CEPX3-CARD模块,该模块2个摄像头是对称放置,为了让2个图像都是同一方向,需要把其中的一路图像设置镜像
  1. ov5640_init(sscb_cam0,640,480,0x46,0x07);
  2.         ov5640_init(sscb_cam1,640,480,0x40,0x01);
复制代码

我们可以简单了解下OV5640和镜像相关的2个寄存器:
82ba3fbdc59f4b5c8e84c0bc936d828d.jpg
5aec49af47074c6b8fd2d658aa55870c.jpg
234abe4101d24327bbaa3b3b767bf2cf.jpg
以看出来,只是两个寄存器:0x3820控制上下翻转,0x3821控制左右翻转。
7双目采集方案演示
本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
7.1硬件准备
需要注意这里的 MLK-H3-CZ08-7100FC,扩展卡默认使用 1.8V IO 配的也是 1.8V 的子卡和摄像头(下图中TF 卡本实 验没有用到)
989939a1f5f84202adf2ea07288a6b69.jpg
7.2实验结果
a70a243d9be143618b5a61a8aeed2554.jpg
本方案路径下也提供了开发板自带的 CEP 接口摄像头采集的 demo











1ebd5c6f361c4bc79dbe787d214592d0.jpg
557f90acb1af4a4e885147f7272cec6d.jpg
a80e6de4addb43248bcd32046d2c6485.jpg
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则