本帖最后由 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
2:采用AXI-GPIO实现SCCB
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上,如下图。
以下为完成后以EMIO IIC初始化摄像头的工程
2:采用SSCB接口方案初始化摄像头
高亮部分为方案增加的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
4.3添加PIN约束1:选中PROJECT MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。
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平台。
5搭建Vitis-sdk工程创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 5.1创建SDK Platform工程
5.2创建cam_5640 APP测试工程
6程序分析
6.1主程序分析在5640_test.c主函数中,分配了3个缓冲区,用于存储3帧图像。通过AXI-GPIO完成对输入uiSensorRGB565 IP和uivideo2axism IP的复位,确保数据传输前FIFO清空。ov5640_init(&I2cInst1)函数通过I2C接口初始化寄存器. - /********************MILIANKE**************************
- *Company : MiLianKe Electronic Technology Co., Ltd.
- *WebSite:https://www.milianke.com
- *TechWeb:https://www.uisrc.com
- *tmall-shop:https://milianke.tmall.com
- *jd-shop:https://milianke.jd.com
- *taobao-shop1: https://milianke.taobao.com
- *Create Date: 2021/10/15
- *File Name: 5640_test.c
- *Description:
- *Declaration:
- *The reference demo provided by Milianke is only used for learning.
- *We cannot ensure that the demo itself is free of bugs, so users
- *should be responsible for the technical problems and consequences
- *caused by the use of their own products.
- *Copyright: Copyright (c) MiLianKe
- *All rights reserved.
- *Revision: 1.0
- ****************************************************/
- #include "xil_exception.h"
- #include "xil_printf.h"
- #include "xil_cache.h"
- #include "vdma_pl.h"
- #include "xgpio.h"
- #include "HDMIdma_intr.h"
- #include "sccb_iic.h"
- #include "ov5640_cfg.h"
- #include "sys_intr.h"
- #define BUF_BASE_SIZE 0x10000000
- #define BUF_RANG_SIZE 0x1000000
- #define BUF1_ADDR BUF_BASE_SIZE + BUF_RANG_SIZE*0
- #define BUF2_ADDR BUF_BASE_SIZE + BUF_RANG_SIZE*1
- #define BUF3_ADDR BUF_BASE_SIZE + BUF_RANG_SIZE*2
- #define VIDEO_OUT_HSIZE 1280*4
- #define VIDEO_OUT_STRIDE 1280*4
- #define VIDEO_OUT_VSIZE 720
- #define IMG_SIZE VIDEO_OUT_HSIZE*VIDEO_OUT_VSIZE
- #define CAM0_AXI_VDMA_ID XPAR_AXI_VDMA_1_DEVICE_ID
- #define CAM0_AXI_VDMA_INTR XPAR_FABRIC_AXI_VDMA_1_S2MM_INTROUT_INTR
- u8 *CAM0_DBUF[IMG_SIZE];
- u8 *VIDEOOUT_DBUF[IMG_SIZE];
- XGpio rstn_5640;
- XGpio sscb_cam0;
- extern XScuGic Intc;
- extern Run_Config RunCfg;
- extern XAxiVdma video0_in;
- extern XAxiVdma_DmaSetup video0_in_WriteCfg;
- extern XHDMIDma HDMIDma;
- extern XHDMIPsu HDMIPsu;
- extern volatile int rfram_cnt;
- extern volatile int rfram_error;
- extern volatile int wframe1_cnt;
- extern volatile int wframe1_error;
- extern volatile int wfram1_grap_done;
- extern volatile int grap_button;
- extern volatile int delay_frame;
- void init_intr_sys(void)
- {
- Init_Intr_System(&Intc);//initialize global interrupt source
- Video1_in_SetupIntrSystem(&Intc,&video0_in,CAM0_AXI_VDMA_INTR);
- XAxiVdma_IntrEnable(&video0_in, XAXIVDMA_IXR_ALL_MASK, XAXIVDMA_WRITE);//enable vdma s2mm channel interrupt
- HDMIdma_init(&RunCfg ,&Intc);//setup HDMI channel
- HDMIdma_Setup_Intr_System(&RunCfg);//enable HDMI channel
- Setup_Intr_Exception(&Intc);//enable global interrupt source
- }
- int main()
- {
- Xil_DCacheDisable();
- Xil_ICacheDisable();
- video_buffer_init(VIDEOOUT_DBUF[0]);
- //定义视频输入缓存地址
- VIDEOOUT_DBUF[0] = BUF1_ADDR;
- VIDEOOUT_DBUF[1] = BUF2_ADDR;
- VIDEOOUT_DBUF[2] = BUF3_ADDR;
- //通过memset函数清零缓冲区
- memset(VIDEOOUT_DBUF[0], 0x00, 1280*720*4);
- memset(VIDEOOUT_DBUF[1], 0x00, 1280*720*4);
- memset(VIDEOOUT_DBUF[2], 0x00, 1280*720*4);
- //通过memset函数清零缓冲区确保数据刷入缓冲区
- Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[0], IMG_SIZE);
- Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[1], IMG_SIZE);
- Xil_DCacheFlushRange((INTPTR)VIDEOOUT_DBUF[2], IMG_SIZE);
- //初始化AXI-GPIO用于复位
- XGpio_Initialize(&rstn_5640, XPAR_GPIO_RSTN_DEVICE_ID);
- XGpio_SetDataDirection(&rstn_5640, 1, 0x0);
- XGpio_DiscreteWrite(&rstn_5640, 1, 0x0);
- //使用sccb方式初始化摄像头
- sccb_gpio_init(&sscb_cam1,XPAR_GPIO_SCCB_DEVICE_ID);
- sleep(1);
- //初始化摄像头
- ov5640_init(sscb_cam1,1280,720);
- sleep(1);
- //设置VDMA输入帧缓存地址
- video0_in_WriteCfg.FrameStoreStartAddr[0] = (INTPTR)VIDEOOUT_DBUF[0];
- video0_in_WriteCfg.FrameStoreStartAddr[1] = (INTPTR)VIDEOOUT_DBUF[1];
- video0_in_WriteCfg.FrameStoreStartAddr[2] = (INTPTR)VIDEOOUT_DBUF[2];
- //5640复位完成
- XGpio_DiscreteWrite(&rstn_5640, 1, 0x1);
- //初始化输入视频1VDMA通道,和输出视频VDMA通道
- Video1_S2MMSetup(CAM1_AXI_VDMA_ID, &video1_in, video1_in_WriteCfg , 720 , 1280*4 , 1280*4);
- //启动VDMA
- XAxiVdma_DmaStart(&video1_in, XAXIVDMA_WRITE);
- while(1)
- {
- if(wfram1_grap_done == 1)//wait vmda s2mm write channel intrrupt
- {
- video_buffer_update((u8*)VIDEOOUT_DBUF[(wframe1_cnt+1)%3],&RunCfg);
- wfram1_grap_done =0;//clear for next
- }
- }
- return XST_SUCCESS;
- }
复制代码
6.2OV5640关键参数
1:OV5640分辨率设置在初始化摄像头的寄存器参数中,通过设置DVP图像数据输出窗口 (0x3808, 0x3809), (0x380A, 0x380B)寄存器设置图像输出的分辨率为1280*720P。
再看(0x380c, 0x380d)=1380, (0x380e, 0x380f)=740寄存器设置了一帧图像的水平大小和垂直大小.
2:OV5640PCLK时钟的设置本文前面有详细的介绍,这里读者如果要修改PCLK时钟,一般修改0x3035寄存器既可以。
6.3VDMA初始化本文的demo主要目的除了实现OV5640摄像头的采集,更重要的是进一步掌握VDMA多缓存的Circle模式使用。以下框图中展示了我们利用VDMA开辟的3个缓存地址空间,利用VDMA的帧控制器管理DDR的内存地址。为了确保读写不冲突,设置S2MM为Gen Master,设置MM2S为Gen Slave,并且设置帧延迟1帧,这样,如下图所示当S2MM操作buf0的内存地址的时候,MM2S会操作buf2的内存地址,延迟于Master 1帧。这样就能确保读写内存不冲突了。
1:VDMA写DDR操作初始化
以上关键设置中:EnableCircularBuf、EnableSync参数决定了多缓存的park工作方式 关键参数涉及的寄存器位如下(关于VDMA的寄存器介绍请阅读“附录2”): MM2S_VDMACR. Circular_Park=0 设置park模式,VDMA不能自动完成帧循环切换,需要用户自定义设置中断才能完成图像同步。因为最后输出为HDMI输出,并没有VDMA参与,所以无法自动管理中断,所以只能使用park模式同步。 2:SCCB驱动代码关于sccb可与阅读“附录1”中关于OV5640摄像头模块的介绍 - /********************MILIANKE**************************
- *Company:Liyang Milian Electronic Technology Co., Ltd
- *Technical forum:www.uisrc.com
- *Taobao: https://milianke.taobao.com
- *Create Date: 2020/12/01
- *Module Name:sscb_iic
- *Copyright: Copyright (c) milianke
- *Revision: 1.1
- *Description:
- ****************************************************/
- #include "sleep.h"
- #include "xgpio.h"
- #include "sccb_iic.h"
- #define CAM_OV5640 0x78
- #define AXI_GPIO_BIT0 0x1
- #define AXI_GPIO_BIT1 0x2
- void SCL_HIGH(XGpio sscb_gpio) {XGpio_DiscreteSet(&sscb_gpio,1,AXI_GPIO_BIT0); }
- void SCL_LOW (XGpio sscb_gpio) {XGpio_DiscreteClear(&sscb_gpio,1,AXI_GPIO_BIT0);}
- void SDA_HIGH(XGpio sscb_gpio) {XGpio_DiscreteSet(&sscb_gpio,1,AXI_GPIO_BIT1); }
- void SDA_LOW (XGpio sscb_gpio) {XGpio_DiscreteClear(&sscb_gpio,1,AXI_GPIO_BIT1);}
- void sccb_gpio_init(XGpio *psscb_gpio , u16 DeviceId)
- {
- XGpio_Initialize(psscb_gpio, DeviceId);
- XGpio_SetDataDirection(psscb_gpio, 1, 0x0);
- XGpio_DiscreteWrite(psscb_gpio, 1, 0x3);
- }
- void sccb_start(XGpio sscb_gpio)
- {
- SCL_HIGH(sscb_gpio);
- SDA_HIGH(sscb_gpio);
- usleep(10);
- SDA_LOW(sscb_gpio);
- usleep(10);
- SCL_LOW(sscb_gpio);
- usleep(10);
- }
- void sccb_end(XGpio sscb_gpio)
- {
- SDA_LOW(sscb_gpio);
- usleep(10);
- SCL_HIGH(sscb_gpio);
- usleep(10);
- SDA_HIGH(sscb_gpio);
- usleep(10);
- }
- void sccb_sendbyte(XGpio sscb_gpio, u8 value )
- {
- //并行数据转串行输出,串行数据输出的顺序为先高位再低位
- u8 i=0;
- for(i=0; i<8; i++)
- {
- if(value & 0x80 )
- SDA_HIGH(sscb_gpio);
- else
- SDA_LOW(sscb_gpio);
- usleep(10);
- SCL_HIGH(sscb_gpio);
- usleep(10);
- SCL_LOW(sscb_gpio);
- usleep(10);
- value<<=1;
- }
- //第9位,Don’t Care
- SDA_LOW(sscb_gpio);
- usleep(10);
- SCL_HIGH(sscb_gpio);
- usleep(10);
- SCL_LOW(sscb_gpio);
- usleep(10);
- }
- void write_i2c(XGpio sscb_gpio ,u16 addr,u8 value)
- {
- u8 buf[3];
- buf[0] = addr >>8;
- buf[1] = addr;
- buf[2] = value;
- sccb_start(sscb_gpio);
- sccb_sendbyte(sscb_gpio,CAM_OV5640);
- sccb_sendbyte(sscb_gpio,buf[0]);
- sccb_sendbyte(sscb_gpio,buf[1]);
- sccb_sendbyte(sscb_gpio,buf[2]);
- sccb_end(sscb_gpio);
- usleep(2000);
- }
复制代码
7OV5640单路HDMI演示本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF) 7.1硬件准备
7.2实验结果
本方案路径下也提供了基于 CEPX3 实现的双目采集方案
|