开源github链接:bxinquan/zynq_cam_isp_demo: 基于verilog实现了ISP图像处理IP
国内Gitee链接:zynq_cam_isp: 开源ISP项目
基于以上开源链接移植项目到正点原子领航者Zynq7020开发板,并对该项目的Vivddo工程进行架构详解,后续会讲解ISP算法对应源码
Vivado版本(2023.2)
Zynq通用视频处理框架介绍
这个可以参考知乎文章:ZYNQ视频图像处理系统(下) - 知乎
贴出里面的一张图
Vivado工程搭建
创建vivado工程,选择对应的器件
在Vivado中导入开源的IP核目录(开源链接中下载)
创建一个BlockDesign,添加ZYNQ7 Processsing System,并按照以下设置PS端
若需要烧录到QSPI Flash中就勾选以下,注意必须勾选Feedback Clock选项,否则会烧录到Flash Timeout失败。
SD卡也勾选上,稍后可以将程序放在SD卡并使用SD卡启动,在Vitis中也可以挂载SD卡保存相应的RAW/RGB图像。
UART用于向串口输出相关调试信息
EMIO和MIO用于映射PL端和PS端的按键以及LED
PL配置两个时钟系统:低速时钟用于AXI-Lite总线寄存器配置,高速时钟用于ISP等其他IP的基准时钟(经过不同分频/倍频提供给相应IP核)
正点原子DDR使用的型号为MT41J256M16 RE-125
勾选中断相关
另外注意:Bnak0 的电平为3.3V ,bank1的电平为1.8V
添加相关IP并连线,最终的BlcokDesign如下
完整框架图可以在这个链接下载查看:【免费】开源ISPVivado框架PDF资源-CSDN文库
由于使用了正点原子开发板,因此需要修改开源项目中对应的管脚约束XDC文件,修改的XDC文件内容如下:
#----------------------摄像头接口的时钟---------------------------
#96M
create_clock -period 10.416 -name cam_pclk [get_ports cam_pclk]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_IBUF]
# set_clock_groups -async -group [get_clocks cam_pclk] \
# -group [get_clocks clk_fpga_0] -group [get_clocks clk_fpga_1] \
# -group [get_clocks camif_xclk_base_clk_wiz_0_0] \
# -group [get_clocks isp_pclk_base_clk_wiz_0_0] \
# -group [get_clocks lcd_clk_base_clk_wiz_0_0] \
# -group [get_clocks dvi_clk_base_clk_wiz_1_0_1]set_clock_groups -async -group [get_clocks cam_pclk]#----------------------摄像头接口---------------------------
#camera 采用的是DVP接口
set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVCMOS33} [get_ports cam_rst_n]
set_property -dict {PACKAGE_PIN V15 IOSTANDARD LVCMOS33} [get_ports cam_pwdn]set_property -dict {PACKAGE_PIN W14 IOSTANDARD LVCMOS33} [get_ports cam_pclk]
set_property -dict {PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports cam_vsync]
set_property -dict {PACKAGE_PIN T12 IOSTANDARD LVCMOS33} [get_ports cam_active_video]set_property -dict {PACKAGE_PIN R14 IOSTANDARD LVCMOS33} [get_ports cam_data[0]]
set_property -dict {PACKAGE_PIN U13 IOSTANDARD LVCMOS33} [get_ports cam_data[1]]
set_property -dict {PACKAGE_PIN V13 IOSTANDARD LVCMOS33} [get_ports cam_data[2]]
set_property -dict {PACKAGE_PIN U15 IOSTANDARD LVCMOS33} [get_ports cam_data[3]]
set_property -dict {PACKAGE_PIN U14 IOSTANDARD LVCMOS33} [get_ports cam_data[4]]
set_property -dict {PACKAGE_PIN W13 IOSTANDARD LVCMOS33} [get_ports cam_data[5]]
set_property -dict {PACKAGE_PIN V12 IOSTANDARD LVCMOS33} [get_ports cam_data[6]]
set_property -dict {PACKAGE_PIN Y14 IOSTANDARD LVCMOS33} [get_ports cam_data[7]]#camera xclk 实际中该约束不起作用,OV5640模组已经板载了24M有源晶振
set_property -dict {PACKAGE_PIN U19 IOSTANDARD LVCMOS33} [get_ports cam_xclk]#iic for camera
set_property -dict {PACKAGE_PIN T10 IOSTANDARD LVCMOS33} [get_ports iic_scl_io]
set_property -dict {PACKAGE_PIN T11 IOSTANDARD LVCMOS33} [get_ports iic_sda_io] set_property PULLUP true [get_ports iic_scl_io]
set_property PULLUP true [get_ports iic_sda_io]#----------------------GPIO EMIO接口---------------------------
#PL_RESET
set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[0]}]
#PL_KEY0
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[1]}]
#PL_KEY1
set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[2]}]
#PL_LED0
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[3]}]
#PL_LED1
set_property -dict {PACKAGE_PIN L15 IOSTANDARD LVCMOS33} [get_ports {GPIO_EMIO_tri_io[4]}]#----------------------LCD接口---------------------------
set_property -dict {PACKAGE_PIN W18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[0]}]
set_property -dict {PACKAGE_PIN W19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[1]}]
set_property -dict {PACKAGE_PIN R16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[2]}]
set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[3]}]
set_property -dict {PACKAGE_PIN W20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[4]}]
set_property -dict {PACKAGE_PIN V20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[5]}]
set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[6]}]
set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[7]}]
set_property -dict {PACKAGE_PIN V17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[8]}]
set_property -dict {PACKAGE_PIN V18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[9]}]
set_property -dict {PACKAGE_PIN T17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[10]}]
set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[11]}]
set_property -dict {PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[12]}]
set_property -dict {PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[13]}]
set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[14]}]
set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[15]}]
set_property -dict {PACKAGE_PIN V16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[16]}]
set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[17]}]
set_property -dict {PACKAGE_PIN T14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[18]}]
set_property -dict {PACKAGE_PIN T15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[19]}]
set_property -dict {PACKAGE_PIN Y17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[20]}]
set_property -dict {PACKAGE_PIN Y16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[21]}]
set_property -dict {PACKAGE_PIN T16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[22]}]
set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[23]}]set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33} [get_ports lcd_hs]
set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports lcd_vs]
set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports lcd_de]
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33} [get_ports lcd_bl]
set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports lcd_clk]#----------------------HDMI接口---------------------------
set_property -dict {PACKAGE_PIN J18 IOSTANDARD TMDS_33 } [get_ports TMDS_tmds_clk_p]
set_property -dict {IOSTANDARD TMDS_33 } [get_ports TMDS_tmds_clk_n]set_property -dict {IOSTANDARD TMDS_33 PACKAGE_PIN G19} [get_ports {TMDS_tmds_data_p[0]}]
set_property -dict {IOSTANDARD TMDS_33 } [get_ports {TMDS_tmds_data_n[0]}]
set_property -dict {IOSTANDARD TMDS_33 PACKAGE_PIN K19} [get_ports {TMDS_tmds_data_p[1]}]
set_property -dict {IOSTANDARD TMDS_33 } [get_ports {TMDS_tmds_data_n[1]}]
set_property -dict {IOSTANDARD TMDS_33 PACKAGE_PIN J20} [get_ports {TMDS_tmds_data_p[2]}]
set_property -dict {IOSTANDARD TMDS_33 } [get_ports {TMDS_tmds_data_n[2]}]set_property -dict {PACKAGE_PIN L19 IOSTANDARD LVCMOS33} [get_ports tmds_oen]
在正点原子带的ov5640模块中已经有一个24MHz的有源晶振了,因此cam_xclk可以省略。 正点原子ov5640模块中将摄像头的xclk引脚直接接到24Mhz晶振上了,需要不同的pclk直接配置该模块内部的相关寄存器即可。(后面嵌入式vitis端搭建会讲)
摄像头的配置可参考:摄像头配置——OV5640配置输出RAW格式-CSDN博客
Vaivado 工程讲解
数据输入通路
在Vitis中配置Ov5640的输出为DVP格式,OV5640 提供了一个 10 位的 DVP 接口(支持 8 位接发)。ATK-MC5640 模块采用 8 位 DVP 连接的方式
在配置成RAW格式输出时,RAW格式实际是以RAW10输出的,所以低两位数据会被截断(对于后续ISP的处理确实会有影响,因为低两位的数据直接丢失了)
cam接口为8位宽的RAW数据,cam_pclk为摄像头输出的像素时钟,在例程里,像素时钟配置为了96MHz,对应摄像头输出的帧率(2592x1944的分辨率)大致在17帧左右。OV5640的输出时序如下
Ov5640图像传感器的数据输出(通过 D[9:0]),是在 PCLK、VSYNC、HREF(HSYNV)
的控制下进行的。,图像数据在 HREF 为高的时候输出,当 HREF 变高后,每一个 PCLK
时钟,输出一个 8 位或 10 位的数据,ATK-MC5640 模块采用 8 位,所以每个 PCLK 输出 1 个字节图像数据,且在 RGB/YUV 输出格式下(565格式),每个像素数据需要两个 PCLK 时钟,在 RAW输出格式下,每个像素数据需要一个 PCLK 时钟。
xil_camif这个IP是一个接收并传递RAW数据的IP核,通过配置该IP可以选择后续数据是来自于摄像头还是该IP生成的彩条。(后续会讲解这个IP的源码仿真以及打包封装)
RAW数据VDMA暂存通路
在进入ISP前,先将RAW帧通过VDMA缓存到DDR中,ISP随后通过VDMA再读取数据进行处理,例程中isp_pclk设置为了120MHz,稍微大于摄像头输入的像素时钟频率,但是由于VDMA的存在,可以保证时序同步。VDMA使用了多缓冲机制(例程配置了3帧),将写入和读取解耦,写端写完会将该缓冲帧标记为可读,读端读完会将对应的帧标记为可写,读写之间不会冲突。由于输入的帧率低于ISP处理的帧率,因此可以想象一段时间后,ISP读帧会拿到与上一帧相同位置的帧进行处理输出。
ISP处理与后处理通路
ISP通过VDMA从DDR中取出一帧数据开始处理(例程中分辨率设置为了2592x1944),处理完后分为了两路数据,一路用于后续处理输出到HDMI,另一路用于输出到LCD。由于两者输出的分辨率是不同的,DVI输出为1280x720@60FPS,而LCD输出为800x480@60FPS,所以Xil_vip_v1.0这个IP会对视频进行相应的缩放裁剪以达到输出的尺寸(在嵌入式Vitis中对该IP进行配置以裁剪成两种输出尺寸),由于显示输出帧率快于ISP和xil_vip处理输出的帧率,因此在输出到显示器之前都需要先将帧缓冲到VDMA中,将输入与输出解耦。以下对VDMA的同步机制进行解释。
以1280×720 @ 60 FPS的输出情况进行解释:
写缓冲器:输入路径(120MHz 的isp_pclk时钟)将1280x720的帧数据写入一个缓冲器。
读缓冲器:输出路径(基于 1280×720 @ 60 FPS 的时钟)从缓冲器中读取帧数据。
假设使用 3 个缓冲区(A、B、C):
1、初始状态:
缓冲区 A:写缓冲区(输入通路正在写)。
缓冲区 B:可供读取(已写入完成)。
缓冲区 C:空闲。
2、输入路径完成写入 A,切换到 C。
3、输出路径读取 B,完成后切换到 A。
4、循环以上过程,确保输入和输出互不干扰。
总之,写入和读取操作不会发生在同一个缓冲区上。但是由于输出帧率大于输入帧率,因此输出过程有时候会重复读取上一帧已经读取过的帧。VDMA中每帧的平均被显示的次数:约为 60/17 ≈ 3.5 次。
HDMI输出通路
在HDMI输出端中,通过VDMA从DDR中获取对应尺寸的输出通过DVI_Transmitter输出到HDMI中。因为DDR中对于该输出保存的帧尺寸为1280x720,因此对axs_to_video这个IP的配置也要相应改成如下:
LCD输出通路
同理LCD端输出也需要配置axis_to_video为相应的参数(LCD参数的配置参考正点原子对于LCD屏的描述),IP配置如下:
其他注意
开启LCD的背光lcd_bl、cam_pwdn为掉电使能(高有效),正常工作情况下需要保持该引脚一直为低电平。cam_rst_n保持为高电平,一直工作。
实际效果
在正点原子的领航者zynq7020实际效果如下:
该开源项目的Vivado工程讲解结束!敬请期待后续关于该开源项目的嵌入式vitis讲解、IP核代码讲解、ISP算法讲解.....