软件版本: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接口初始化寄存器. #include "xil_exception.h"
#include "xil_printf.h"
#include "xil_cache.h"
#include "sleep.h"
#include "vdma_pl.h"
#include "xgpio.h"
#include "sccb_iic.h"
#include "ov5640_cfg.h"
#define CAM1_AXI_VDMA_ID XPAR_AXI_VDMA_1_DEVICE_ID
#define VIDEO_OUT_VDMA_ID XPAR_AXI_VDMA_0_DEVICE_ID
#define BUF_BASE_SIZE 0x08000000
#define BUF_RANG_SIZE 0x800000
#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//定义图像缓存地址
XGpio rstn_5640;
XGpio sscb_cam1;
XGpio sscb_cam2;
extern XAxiVdma video1_in,video_out;
XAxiVdma_DmaSetup video1_in_WriteCfg;
XAxiVdma_DmaSetup video_out_ReadCfg;
int main()
{
UINTPTR VIDEO1_IN_BUF_ADDR[3];
UINTPTR VIDEO_OUT_BUF_ADDR[3];
//定义视频输入缓存地址
VIDEO1_IN_BUF_ADDR[0] = BUF1_ADDR;
VIDEO1_IN_BUF_ADDR[1] = BUF2_ADDR;
VIDEO1_IN_BUF_ADDR[2] = BUF3_ADDR;
//定义视频输出缓存地址
VIDEO_OUT_BUF_ADDR[0] = BUF1_ADDR;
VIDEO_OUT_BUF_ADDR[1] = BUF2_ADDR;
VIDEO_OUT_BUF_ADDR[2] = BUF3_ADDR;
//通过memset函数清零缓冲区
memset(VIDEO_OUT_BUF_ADDR[0], 0x00, 1280*720*4);
memset(VIDEO_OUT_BUF_ADDR[1], 0x00, 1280*720*4);
memset(VIDEO_OUT_BUF_ADDR[2], 0x00, 1280*720*4);
//初始化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输入帧缓存地址
video1_in_WriteCfg.FrameStoreStartAddr[0] = VIDEO1_IN_BUF_ADDR[0];
video1_in_WriteCfg.FrameStoreStartAddr[1] = VIDEO1_IN_BUF_ADDR[1];
video1_in_WriteCfg.FrameStoreStartAddr[2] = VIDEO1_IN_BUF_ADDR[2];
//设置VDMA输出帧缓存地址
video_out_ReadCfg.FrameStoreStartAddr[0] = VIDEO_OUT_BUF_ADDR[0];
video_out_ReadCfg.FrameStoreStartAddr[1] = VIDEO_OUT_BUF_ADDR[1];
video_out_ReadCfg.FrameStoreStartAddr[2] = VIDEO_OUT_BUF_ADDR[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);
Video_Out_MM2SSetup(VIDEO_OUT_VDMA_ID, &video_out, video_out_ReadCfg , 720 , 1280*4 , 1280*4);
//启动VDMA
XAxiVdma_DmaStart(&video1_in, XAXIVDMA_WRITE);
XAxiVdma_DmaStart(&video_out, XAXIVDMA_READ);
while(1);
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摄像头模块的介绍 #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 实现的双目采集方案
|