基于OV5640的PL以太网视频传输实验
OV5640是OmniVision(豪威科技)公司生产的CMOS图像传感器。OV5640支持更高的分辨率、采集速率,具有更高的图像处理性能,主要应用在手机、数码相机、电脑多媒体等领域。
本章我们将使用DFZU2EG/4EV MPSoC开发板实现对OV5640的数字图像采集,并通过开发板上的PL以太网接口发送给上位机实时显示。
本章分为以下几个章节:
- 简介
- 实验任务
- 硬件设计
- 程序设计
- 下载验证
简介
OV5640是一款1/4英寸单芯片图像传感器,其感光阵列达到2592*1944(即500W像素),能实现最快15fps QSXVGA(2592*1944)或者90fps VGA(640*480)分辨率的图像采集。传感器采用OmniVision推出的OmniBSI(背面照度)技术,使传感器达到更高的性能,如高灵敏度、低串扰和低噪声。传感器内部集成了图像处理的功能,包括自动曝光控制(AEC)、自动白平衡(AWB)等。同时该传感器支持LED补光、MIPI(移动产业处理器接口)输出接口和DVP(数字视频并行)输出接口选择、ISP(图像信号处理)以及AFC(自动聚焦控制)等功能。
OV5640的功能框图如下图所示:
图30.1.1功能框图
由上图可知,时序发生器(timing)控制着感光阵列(image array)、放大器(AMP)、AD转换以及输出外部时序信号(VSYNC、HREF和PCLK),外部时钟XVCLK经过PLL锁相环后输出的时钟作为系统的控制时钟;感光阵列将光信号转化成模拟信号,经过增益放大器之后进入10位AD转换器;AD转换器将模拟信号转化成数字信号,并且经过ISP进行相关图像处理,最终输出所配置格式的10位视频数据流。增益放大器控制以及ISP等都可以通过寄存器(registers)来配置,配置寄存器的接口就是SCCB接口,该接口协议兼容IIC协议。
OV5640使用的是两线式SCCB接口总线,有关SCCB总线的详细介绍可以参考“基于OV7725的PL以太网视频传输实验”中OV7725简介部分。虽然OV5640和OV7725都是采用SCCB接口总线来配置寄存器,但不同的是,OV7725是用8位(1个字节)来表示寄存器地址,而OV5640是用16位(两个字节)表示寄存器地址。
OV5640 SCCB的写传输协议如下图所示:
图30.1.2写传输协议
上图中的ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写:读),OV5640的器件地址为7’h3c,所以在写传输协议中,ID Address(W)= 8’h78(器件地址左移1位,低位补0);Sub-address(H)为高8位寄存器地址,Sub-address(L)为低8位寄存器地址,在OV5640众多寄存器中,有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;Write Data为8位写数据,每一个寄存器地址对应8位的配置数据。
在OV5640正常工作之前,必须先对传感器进行初始化,即通过配置寄存器使其工作在预期的工作模式,以及得到较好画质的图像。因为SCCB的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。OV公司提供了OV5640的软件应用手册(OV5640 Software Application Note,位于开发板所随附的资料“7_硬件资料/4_OV5640资料/OV5640_camera_module_software_application_notes.pdf”),如果某些寄存器不知道如何配置可以参考此手册,下表是本程序用到的关键寄存器的配置说明。
表30.1.1关键寄存器配置说明
地址 (HEX) | 寄存器 | 默认值 (HEX) | 详细说明 |
0x3008 | SYSTEM CTROL0 | 0x02 | Bit[7]:软件复位 Bit[6]:软件电源休眠 |
0x3016 | PAD OUTPUT ENABLE 00 | 0x00 | Bit[1]:闪光灯输出使能 |
0x3017 | PAD OUTPUT ENABLE 01 | 0x00 | 输入/输出控制(0:输入:输出) Bit[7]:FREX输出使能 Bit[6]:VSYNC输出使能 Bit[5]:HREF输出使能 Bit[4]:PCLK输出使能 Bit[3:0]:D[9:6]输出使能 |
0x3018 | PAD OUTPUT ENABLE 02 | 0x00 | 输入/输出控制(0:输入:输出) Bit[7:2]:D[5:0]输出使能 Bit[1]:GPIO1输出使能 Bit[0]:GPIO0输出使能 |
0x3019 | PAD OUTPUT VALUE 00 | 0x00 | Bit[1]: 闪光灯操作 :关闭闪光灯 1:打开闪光灯 |
0x301C | PAD SELECT 00 | 0x00 | Bit[1]:闪光灯IO选择 |
0x3035 | SC PLL CONTRL1 | 0x11 | Bit[7:4]:系统时钟分频,用于降低所有的时钟频率 Bit[3:0]:MIPI分频 |
0x3036 | SC PLL CONTRL2 | 0x69 | Bit[7:0]:PLL倍频器(4~252) 4~127范围内支持任意整数分频 在128~252范围内仅支持偶数分频 |
0x3808 | TIMING DVPHO | 0x0A | Bit[3:0]:DVP 输出水平像素点数高4位 |
0x3809 | TIMING DVPHO | 0x20 | Bit[7:0]:DVP 输出水平像素点数低8位 |
0x380A | TIMING DVPVO | 0x07 | Bit[2:0]:DVP输出垂直像素点数高3位 |
0x380B | TIMING DVPVO | 0x98 | Bit[7:0]:输出垂直像素点数低8位 |
0x4300 | FORMAT CONTROL | 0xF8 | Bit[7:4]:数据输出格式 :RAW 1:Y8 2:YUV444/RGB888 3:YUV422 4:YUV420 5:YUV420(仅在MIPI输出接口有效) :RGB565 Bit[3:0]:输出顺序 0:{b[4:0],g[5:3]},{g[2:0],r[4:0]} 1:{r[4:0],g[5:3]},{g[2:0],b[4:0]} 2:{g[4:0],r[5:3]},{r[2:0],b[4:0]} 3:{b[4:0],r[5:3]},{r[2:0],g[4:0]} 4:{g[4:0],b[5:3]},{b[2:0],r[4:0]} 5:{r[4:0],b[5:3]},{b[2:0],g[4:0]} 6~14:不允许 15:{g[2:0],b[4:0]},{r[4:0],g[5:3]} 7:RGB555格式1 8:RGB555格式2 9:RGB444格式1 10:RGB444格式2 11~14:不允许 :Bypass formatter module |
OV5640的寄存器较多,对于其它寄存器的描述可以参考OV5640的数据手册。需要注意的是,OV5640的数据手册并没有提供全部的寄存器描述,而大多数必要的寄存器配置在ov5640的软件应用手册中可以找到,可以结合这两个手册学习如何对OV5640进行配置。
输出图像参数设置
接下来,我们介绍一下OV5640的ISP输入窗口设置、预缩放窗口设置和输出大小窗口设置,这几个设置与我们的正常使用密切相关,有必要了解一下,它们的设置关系如下图所示:
图30.1.3 图像窗口设置
ISP输入窗口设置(ISP Input Size)允许用户设置整个传感器显示区域(physical pixel size,2632*1951,其中2592*1944像素是有效的),开窗范围从0*0~2632*1951都可以任意设置。也就是上图中的X_ADDR_ST(寄存器地址0x3800、0x3801)、Y_ADDR_ST(寄存器地址0x3802、0x3803)、X_ADDR_END(寄存器地址0x3804、0x3805)和Y_ADDR_END(寄存器地址0x3806、0x3807)寄存器。该窗口设置范围中的像素数据将进入ISP进行图像处理。
预缩放窗口设置(pre-scaling size)允许用户在ISP输入窗口的基础上进行裁剪,用于设置将进行缩放的窗口大小,该设置仅在ISP输入窗口内进行X/Y方向的偏移。可以通过X_OFFSET(寄存器地址0x3810、0x3811)和Y_OFFSET(寄存器地址0x3812、0x3813)进行配置。
输出大小窗口设置(data)是在预缩放窗口的基础上,经过内部DSP进行缩放处理,并将处理后的数据输出给外部的图像窗口,图像窗口控制着最终的图像输出尺寸。可以通过X_OUTPUT_SIZE(寄存器地址0x3808、0x3809)和Y_OUTPUT_SIZE(寄存器地址0x380A、0x380B)进行配置。注意:当输出大小窗口与预缩放窗口比例不一致时,图像将进行缩放处理(图像变形),仅当两者比例一致时,输出比例才是1:1(正常图像)。
图中,右侧data output size区域,才是OV5640输出给外部的图像尺寸,也就是显示在显示器或者液晶屏上面的图像大小。输出大小窗口与预缩放窗口比例不一致时,会进行缩放处理,在显示器上面看到的图像将会变形。
输出像素格式
OV5640支持多种不同的数据像素格式,包括YUV(亮度参量和色度参量分开表示的像素格式)、RGB(其中RGB格式包含RGB565、RGB555等)以及RAW(原始图像数据),通过寄存器地址0x4300配置成不同的数据像素格式。
由于数据像素格式常用RGB565,我们这里也将ov5640配置为RGB565格式。由上表(表)可知,将寄存器0x4300寄存器的Bit[7:4]设置成0x6即可。OV5640支持调节RGB565输出格式中各颜色变量的顺序,对于我们常见的应用来说,一般是使用RGB或BGR序列。我们在“基于OV7725的PL以太网视频传输实验”的章节中介绍过,OV7725摄像头按照RGB的顺序输出,本章我们将OV5640输出的RGB565的颜色顺序和OV7725保持一致,将寄存器0x4300寄存器的Bit[3:0]设置成0x1。因此,“基于OV7725的PL以太网视频传输实验”章节中的图像采集模块可以直接用来采集OV5640输出的图像。
彩条测试模式
图像传感器配置成彩条测试模式后,会输出彩色的条纹,方便测试图像传感器是否正常工作,通过配置寄存器0x503d的Bit[7]位打开和关闭彩条模式。当需要打开彩条模式时,寄存器0x503d配置成0x80,关闭时配置成0x00,下图为打开彩条模式后图像输出的条纹。
图30.1.4 彩条模式下的图像条纹
LED闪光灯
当外界环境光较暗时,传感器采集图像会受到较大影响,此时可以通过打开LED补光灯来弥补光照不足所带来的影响,就像手机在夜晚拍照时也会打开闪光灯来提高图像质量。通过配置寄存器0x3016=0x02,0x301c=0x02来使能LED补光灯功能;配置寄存器0x3019=0x02打开闪光灯,0x3019=0x00关闭闪光灯。
图像输出时序
接下来,我们介绍一下OV5640的图像数据输出时序,首先我们简单介绍一些定义。
QSXGA,这里指:分辨率为2592*1944的输出格式,类似的还有:QXGA(2048*1536)、UXGA(1600*1200)、SXGA(1280*1024)、WXGA(1440*900)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、VGA(640*480)、QVGA(320*240)和QQVGA(160*120)等。
PCLK:像素时钟,一个PCLK时钟输出一个像素或者半个像素(像素数据的高8位或者低8位)。
VSYNC:帧同步信号。
HREF/HSYNC:行同步信号。
D[9:0]:像素数据,在RGB565格式中,只有高8位是有效的。
tPclk:一个时钟周期。
tp:一个像素点的周期,在RGB565和YUV422输出格式下,tp=2*tPclk;Raw输出格式下,tp=tPclk。
下图为OV5640输出图像数据的行时序图。
图30.1.5行时序图
从上图可以看出,传感器在HREF为高电平的时候输出图像数据,当HREF变高后,每一个 PCLK时钟,输出一个8位或者10位像素数据。比如我们采用QSXGA时序,RGB565格式输出,tp=2*tPclk,每2个字节组成一个像素的颜色,这样每行总共输出2592*2个PCLK,也就是2592*2个字节。
再来看看帧时序(QSXGA模式,分辨率2592*1944),如下图所示:
图30.1.6帧时序
由上图可知,VSYNC的上升沿作为一帧的开始,高电平同步脉冲的时间为5688tp,紧接着等待48276tp时间后,HREF开始拉高,此时输出有效数据;HREF由2592tp个高电平和252tp个低电平构成;最后一行图像数据输出完成之后等待14544tp时间,一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame = 5596992tp。
实验任务
本节实验任务是使用DFZU2EG/4EV MPSoC开发板及双目OV5640摄像头其中一个摄像头实现图像采集,并通过开发板上的以太网接口发送给上位机实时显示。
硬件设计
本次实验的硬件电路使用了DFZU2EG/4EV MPSoC开发板上的扩展接口,IO管脚位置的配置如下表。
在本实验中Zynq输出给OV5640的“电源休眠模式选择”信号是cam_pwdn;OV5640的cam_pwdn我们需要将其一直赋值为低电平,表示不休眠即正常工作模式。
OV5640摄像头的管脚分配如下表所示:
表30.3.1 OV5640摄像头管脚分配
信号名 | 方向 | 管脚 | 端口说明 |
cam_pclk | input | C13 | cmos 数据像素时钟 |
cam_vsync | input | G14 | cmos 场同步信号 |
cam_href | input | G13 | cmos 行同步信号 |
cam_rst_n | output | F13 | cmos 复位信号,低电平有效 |
cam_pwdn | output | B15 | cmos 电源休眠模式选择信号 |
cam_data[0] | input | E15 | cmos 数据 |
cam_data[1] | input | D15 | cmos 数据 |
cam_data[2] | input | E14 | cmos 数据 |
cam_data[3] | input | D14 | cmos 数据 |
cam_data[4] | input | E13 | cmos 数据 |
cam_data[5] | input | B13 | cmos 数据 |
cam_data[6] | input | C14 | cmos 数据 |
cam_data[7] | input | A13 | cmos 数据 |
cam_scl | output | H13 | cmos SCCB_SCL线 |
cam_sda | inout | F15 | cmos SCCB_SDA线 |
相关的管脚约束如下所示:
#时钟周期约束
create_clock -period 8.000 -name eth_rxc [get_ports eth_rxc]
create_clock -period 10.000 -name sys_clk_p [get_ports sys_clk_p]
#时钟
set_property -dict {PACKAGE_PIN AE5 IOSTANDARD DIFF_HSTL_I_12} [get_ports sys_clk_p]
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_n]
set_property PACKAGE_PIN AF5 [get_ports sys_clk_n]
#IO管脚约束
set_property -dict {PACKAGE_PIN AH11 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN E5 IOSTANDARD LVCMOS18} [get_ports eth_rxc]
set_property -dict {PACKAGE_PIN D5 IOSTANDARD LVCMOS18} [get_ports eth_rx_ctl]
set_property -dict {PACKAGE_PIN G6 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[0]}]
set_property -dict {PACKAGE_PIN F6 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[1]}]
set_property -dict {PACKAGE_PIN G8 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[2]}]
set_property -dict {PACKAGE_PIN F7 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[3]}]
set_property -dict {PACKAGE_PIN D6 IOSTANDARD LVCMOS18} [get_ports eth_txc]
set_property -dict {PACKAGE_PIN F8 IOSTANDARD LVCMOS18} [get_ports eth_tx_ctl]
set_property -dict {PACKAGE_PIN D7 IOSTANDARD LVCMOS18} [get_ports {eth_txd[0]}]
set_property -dict {PACKAGE_PIN E8 IOSTANDARD LVCMOS18} [get_ports {eth_txd[1]}]
set_property -dict {PACKAGE_PIN E9 IOSTANDARD LVCMOS18} [get_ports {eth_txd[2]}]
set_property -dict {PACKAGE_PIN D9 IOSTANDARD LVCMOS18} [get_ports {eth_txd[3]}]
#CAMERA
#摄像头接口的时钟
create_clock -period 40.000 -name cmos_pclk [get_ports cam_pclk]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_IBUF]
set_property -dict {PACKAGE_PIN C13 IOSTANDARD LVCMOS33} [get_ports cam_pclk]
set_property -dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports cam_rst_n]
set_property -dict {PACKAGE_PIN B15 IOSTANDARD LVCMOS33} [get_ports cam_pwdn]
set_property -dict {PACKAGE_PIN E15 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[0]}]
set_property -dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[1]}]
set_property -dict {PACKAGE_PIN E14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[2]}]
set_property -dict {PACKAGE_PIN D14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[3]}]
set_property -dict {PACKAGE_PIN E13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[4]}]
set_property -dict {PACKAGE_PIN B13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[5]}]
set_property -dict {PACKAGE_PIN C14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[6]}]
set_property -dict {PACKAGE_PIN A13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data[7]}]
set_property -dict {PACKAGE_PIN G14 IOSTANDARD LVCMOS33} [get_ports cam_vsync]
set_property -dict {PACKAGE_PIN G13 IOSTANDARD LVCMOS33} [get_ports cam_href]
set_property -dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33} [get_ports cam_scl]
set_property -dict {PACKAGE_PIN F15 IOSTANDARD LVCMOS33} [get_ports cam_sda]
程序设计
图30.4.1是根据本章实验任务画出的系统框图。时钟模块用于为I2C驱动模块、以太网顶层模块和开始传输控制模块提供驱动时钟;I2C驱动模块和I2C配置模块用于初始化OV5640图像传感器;摄像头采集模块负责采集摄像头图像数据,并且把图像数据连接至图像数据封装模块,图像数据封装模块将输入的图像数据进行位拼接,并添加图像的帧头和行场分辨率;以太网顶层模块实现以太网数据的收发;开始传输控制模块控制以太网顶层模块开始/停止发送数据。
OV5640的以太网视频传输系统框图如下图所示:
图30.4.1系统框图
顶层模块的原理图如下图所示:
图30.4.2 顶层模块原理图
FPGA顶层模块(ov5640_udp_pc)例化了以下7个模块:时钟模块(clk_wiz_0)、I2C配置模块(i2c_ov5640_rgb565_cfg)、I2C驱动模块(i2c_dri)、摄像头数据采集模块(cmos_capture_data)、开始传输控制模块(start_transfer_ctrl)、图像数据封装模块(img_data_pkt)和以太网顶层模块模块(eth_top)。
时钟模块(clk_wiz_0):时钟IP核模块通过调用MMCM IP核来实现,总共输出2个时钟,频率分别为100Mhz、200Mhz时钟。100Mhz时钟I2C驱动模块的驱动时钟;200Mhz时钟作为IDELAYCTRL源语的参考时钟。
I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV5640接口总线,用户可根据该模块提供的用户接口可以很方便的对OV5640的寄存器进行配置,该模块和“I2C读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“I2C读写实验”章节。
I2C配置模块(i2c_ov5640_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV5640的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV5640传感器的初始化。
摄像头图像采集模块(cmos_capture_data):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成DDR读写控制模块的写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。
开始传输控制模块(start_transfer_ctrl):该模块解析以太网顶层模块接收到的数据,如果收到1个字节的ASCII码“1”,则表示以太网开始传输图像数据;如果收到1个字节的ASCII码“0”,则表示以太网停止传输图像数据。
图像数据封装模块(img_data_pkt):图像数据封装模块负责将输入16位的图像数据,拼接成32位数据,以及添加图像数据的帧头和行场分辨率。该模块控制着以太网发送模块发送的字节数,单次发送一行图像数据的字节数,模块内部例化了一个异步FIFO模块,用于缓存待发送的图像数据。
以太网顶层模块(eth_top):该模块例化了一个时钟ip核,输出一个偏移90度的125Mhz时钟作为以太网顶层模块发送模块的驱动时钟,以太网顶层模块实现以太网通信的收发功能,有关该模块的详细介绍请大家参考“以太网通信实验”章节。
顶层模块部分代码如下:
1 module ov5640_udp_pc(
2 input sys_clk_p , //系统时钟
3 input sys_clk_n , //系统时钟
4 input sys_rst_n , //系统复位信号,低电平有效
5 //以太网接口
6 input eth_rxc , //RGMII接收数据时钟
7 input eth_rx_ctl , //RGMII输入数据有效信号
8 input [3:0] eth_rxd , //RGMII输入数据
9 output eth_txc , //RGMII发送数据时钟
10 output eth_tx_ctl , //RGMII输出数据有效信号
11 output [3:0] eth_txd , //RGMII输出数据
12
13 //摄像头接口
14 input cam_pclk , //cmos 数据像素时钟
15 input cam_vsync , //cmos 场同步信号
16 input cam_href , //cmos 行同步信号
17 input [7:0] cam_data , //cmos 数据
18 output cam_rst_n , //cmos 复位信号,低电平有效
19 output cam_pwdn , //电源休眠模式选择 0:正常模式 1:电源休眠模式
20 output cam_scl , //cmos SCCB_SCL线
21 inout cam_sda //cmos SCCB_SDA线
22 );
23
24 //parameter define
25 //开发板MAC地址 00-11-22-33-44-55
26 parameter BOARD_MAC = 48'h00_11_22_33_44_55;
27 //开发板IP地址 192.168.1.10
28 parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
29 //目的MAC地址 ff_ff_ff_ff_ff_ff
30 parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
31 //目的IP地址 192.168.1.102
32 parameter DES_IP = {8'd192,8'd168,8'd1,8'd102};
33
34 parameter H_CMOS_DISP = 11'd640; //CMOS分辨率--行
35 parameter V_CMOS_DISP = 11'd480; //CMOS分辨率--列
36 parameter TOTAL_H_PIXEL = H_CMOS_DISP + 12'd1216; //水平总像素大小
37 parameter TOTAL_V_PIXEL = V_CMOS_DISP + 12'd504; //垂直总像素大小
在代码的第25至32行定义了四个参量:开发板MAC地址BOARD_MAC,开发板IP地址 BOARD_IP,目的MAC地址DES_MAC(这里指PC MAC地址),目的IP地址 DES_IP(PC IP地址)。开发板的MAC地址和IP地址是我们随意定义的,只要不和目的MAC 地址和目的IP地址一样就可以,否则会产生地址冲突。目的MAC地址这里写的是公共MAC 地址(48'hff_ff_ff_ff_ff_ff),也可以修改成电脑网口的MAC地址,DES_IP是对应电脑以太网的IP地址,这里定义的四个参数是向下传递的,需要修改MAC地址或者IP地址时直接在这里修改即可,而不用在以太网顶层模块里面修改。
在代码的第34行至37行定义了CMOS水平/垂直方向像素个数和水平/垂直总像素个数,本次实验设置的摄像头分辨率为640*480。
78 assign rst_n = sys_rst_n & locked;
79 //电源休眠模式选择 0:正常模式 1:电源休眠模式
80 assign cam_pwdn = 1'b0;
81 assign cam_rst_n = 1'b1;
82
83 //例化时钟IP核
84 clk_wiz_0 u_clk_wiz_0
85 (
86 // Clock out ports
87 .clk_out1 (clk_100m ), // output clk_out1
88 .clk_out2 (clk_200m ), // output clk_out2
89 // Status and control signals
90 .reset (~sys_rst_n), // input reset
91 .locked (locked ), // output locked
92 // Clock in ports
93 .clk_in1_p (sys_clk_p ), // input clk_in1_p
94 .clk_in1_n (sys_clk_n ) // input clk_in1_n
95 );
96
97 //I2C配置模块
98 i2c_ov5640_rgb565_cfg u_i2c_cfg(
99 .clk (i2c_dri_clk ),
100 .rst_n (rst_n ),
101 .i2c_done (i2c_done ),
102 .i2c_data_r (i2c_data_r ),
103 .cmos_h_pixel (H_CMOS_DISP ),
104 .cmos_v_pixel (V_CMOS_DISP ),
105 .total_h_pixel (TOTAL_H_PIXEL),
106 .total_v_pixel (TOTAL_V_PIXEL),
107 .i2c_exec (i2c_exec ),
108 .i2c_data (i2c_data ),
109 .i2c_rh_wl (i2c_rh_wl ),
110 .init_done (cam_init_done)
111 );
112
113 //I2C驱动模块
114 i2c_dri
115 #(
116 .SLAVE_ADDR (SLAVE_ADDR ), //参数传递
117 .CLK_FREQ (CLK_FREQ ),
118 .I2C_FREQ (I2C_FREQ )
119 )
120 u_i2c_dri(
121 .clk (clk_100m ),
122 .rst_n (rst_n ),
123 //i2c interface
124 .i2c_exec (i2c_exec ),
125 .bit_ctrl (BIT_CTRL ),
126 .i2c_rh_wl (i2c_rh_wl ),
127 .i2c_addr (i2c_data[23:8]),
128 .i2c_data_w (i2c_data[7:0] ),
129 .i2c_data_r (i2c_data_r ),
130 .i2c_done (i2c_done ),
131 .i2c_ack ( ),
132 .scl (cam_scl ),
133 .sda (cam_sda ),
134 //user interface
135 .dri_clk (i2c_dri_clk ) //I2C操作时钟
136 );
137
138 //摄像头数据采集模块
139 cmos_capture_data u_cmos_capture_data(
140
141 .rst_n (rst_n & cam_init_done),
142 .cam_pclk (cam_pclk ),
143 .cam_vsync (cam_vsync ),
144 .cam_href (cam_href ),
145 .cam_data (cam_data ),
146 .cmos_frame_vsync (cmos_frame_vsync ),
147 .cmos_frame_href ( ),
148 .cmos_frame_valid (img_data_en ),
149 .cmos_frame_data (img_data )
150 );
程序中第97行至150行例化了I2C配置模块(i2c_ov5640_rgb565_cfg)、I2C驱动模块(i2c_dri)、摄像头数据采集模块(cmos_capture_data),三个模块实现了对OV5640摄像头的初始化及采集摄像头输出的图像,其输出的img_data_en(图像有效使能信号)和有效图像(img_data)连接至图像封装模块。
152 //开始传输控制模块
153 start_transfer_ctrl u_start_transfer_ctrl(
154 .clk (eth_rx_clk ),
155 .rst_n (rst_n ),
156 .udp_rec_pkt_done (udp_rec_pkt_done),
157 .udp_rec_en (udp_rec_en ),
158 .udp_rec_data (udp_rec_data ),
159 .udp_rec_byte_num (udp_rec_byte_num),
160 .transfer_flag (transfer_flag )
161 //图像开始传输标志,1:开始传输 0:停止传输
162 );
163
164 //图像封装模块
165 img_data_pkt u_img_data_pkt(
166 .rst_n (rst_n ),
167 .cam_pclk (cam_pclk ),
168 .img_vsync (cmos_frame_vsync),
169 .img_data_en (img_data_en ),
170 .img_data (img_data ),
171 .transfer_flag (transfer_flag ),
172 .eth_tx_clk (eth_tx_clk ),
173 .udp_tx_req (udp_tx_req ),
174 .udp_tx_done (udp_tx_done ),
175 .udp_tx_start_en (udp_tx_start_en ),
176 .udp_tx_data (udp_tx_data ),
177 .udp_tx_byte_num (udp_tx_byte_num )
178 );
179
180 //以太网顶层模块
181 eth_top #(
182 .BOARD_MAC (BOARD_MAC), //参数例化
183 .BOARD_IP (BOARD_IP ),
184 .DES_MAC (DES_MAC ),
185 .DES_IP (DES_IP )
186 )
187 u_eth_top(
188 .sys_rst_n (rst_n ), //系统复位信号,低电平有效
189 .clk_200m (clk_200m ),
190 //以太网RGMII接口
191 .eth_rxc (eth_rxc ), //RGMII接收数据时钟
192 .eth_rx_ctl (eth_rx_ctl), //RGMII输入数据有效信号
193 .eth_rxd (eth_rxd ), //RGMII输入数据
194 .eth_txc (eth_txc ), //RGMII发送数据时钟
195 .eth_tx_ctl (eth_tx_ctl), //RGMII输出数据有效信号
196 .eth_txd (eth_txd ), //RGMII输出数据
197
198 .gmii_rx_clk (eth_rx_clk ),
199 .gmii_tx_clk (eth_tx_clk ),
200 .udp_tx_start_en (udp_tx_start_en ),
201 .tx_data (udp_tx_data ),
202 .tx_byte_num (udp_tx_byte_num ),
203 .udp_tx_done (udp_tx_done ),
204 .tx_req (udp_tx_req ),
205 .rec_pkt_done (udp_rec_pkt_done),
206 .rec_en (udp_rec_en ),
207 .rec_data (udp_rec_data ),
208 .rec_byte_num (udp_rec_byte_num)
209 );
210
211 endmodule
在代码的第152行至162行例化了开始传输控制模块,由该模块端口可知,输入端口信号为以太网接收到的数据,而输出信号为图像开始传输标志(transfer_flag)。transfer_flag信号用于控制以太网发送图像数据的开始和停止,连接至图像数据封装模块。
在代码的第165行至178行例化了图像数据封装模块,该模块输入的端口信号为摄像头图像数据,而输出的udp_tx_start_en(以太网开始发送信号)和udp_tx_byte_num(发送的字节数)连接至以太网顶层模块的以太网发送控制端口,从而控制以太网的UDP发送模块开始发送图像数据。
OV5640的寄存器配置模块部分代码如下:
1 module i2c_ov5640_rgb565_cfg
2 (
3 input clk , //时钟信号
4 input rst_n , //复位信号,低电平有效
5
6 input [7:0] i2c_data_r, //I2C读出的数据
7 input i2c_done , //I2C寄存器配置完成信号
8 input [12:0] cmos_h_pixel ,
9 input [12:0] cmos_v_pixel ,
10 input [12:0] total_h_pixel, //水平总像素大小
11 input [12:0] total_v_pixel, //垂直总像素大小
12 output reg i2c_exec , //I2C触发执行信号
13 output reg [23:0] i2c_data , //I2C要配置的地址与数据(高16位地址,低8位数据)
14 output reg i2c_rh_wl, //I2C读写控制信号
15 output reg init_done //初始化完成信号
16 );
17
18 //parameter define
19 localparam REG_NUM = 8'd250 ; //总共需要配置的寄存器个数
20
21 //reg define
22 reg [12:0] start_init_cnt; //等待延时计数器
23 reg [7:0] init_reg_cnt ; //寄存器配置个数计数器
24
25 //*****************************************************
26 //** main code
27 //*****************************************************
28
29 //clk时钟配置成250khz,周期为4us 5000*4us = 20ms
30 //OV5640上电到开始配置IIC至少等待20ms
31 always @(posedge clk or negedge rst_n) begin
32 if(!rst_n)
33 start_init_cnt <= 13'b0;
34 else if(start_init_cnt < 13'd5000) begin
35 start_init_cnt <= start_init_cnt + 1'b1;
36 end
37 end
38
39 //寄存器配置个数计数
40 always @(posedge clk or negedge rst_n) begin
41 if(!rst_n)
42 init_reg_cnt <= 8'd0;
43 else if(i2c_exec)
44 init_reg_cnt <= init_reg_cnt + 8'b1;
45 end
46
47 //i2c触发执行信号
48 always @(posedge clk or negedge rst_n) begin
49 if(!rst_n)
50 i2c_exec <= 1'b0;
51 else if(start_init_cnt == 13'd4999)
52 i2c_exec <= 1'b1;
53 else if(i2c_done && (init_reg_cnt < REG_NUM))
54 i2c_exec <= 1'b1;
55 else
56 i2c_exec <= 1'b0;
57 end
58
59 //配置I2C读写控制信号
60 always @(posedge clk or negedge rst_n) begin
61 if(!rst_n)
62 i2c_rh_wl <= 1'b1;
63 else if(init_reg_cnt == 8'd2)
64 i2c_rh_wl <= 1'b0;
65 end
66
67 //初始化完成信号
68 always @(posedge clk or negedge rst_n) begin
69 if(!rst_n)
70 init_done <= 1'b0;
71 else if((init_reg_cnt == REG_NUM) && i2c_done)
72 init_done <= 1'b1;
73 end
74
75 //配置寄存器地址与数据
76 always @(posedge clk or negedge rst_n) begin
77 if(!rst_n)
78 i2c_data <= 24'b0;
79 else begin
80 case(init_reg_cnt)
81 //先对寄存器进行软件复位,使寄存器恢复初始值
82 //寄存器软件复位后,需要延时1ms才能配置其它寄存器
83 8'd0 : i2c_data <= {16'h300a,8'h0}; //
84 8'd1 : i2c_data <= {16'h300b,8'h0}; //
配置代码较长,省略部分源代码……
295 8'd204: i2c_data <= {16'h5025,8'h00};
296 //系统时钟分频 Bit[7:4]:系统时钟分频 input clock =24Mhz, PCLK = 48Mhz
297 8'd205: i2c_data <= {16'h3035,8'h11};
298 8'd206: i2c_data <= {16'h3036,8'h3c}; //PLL倍频
299 8'd207: i2c_data <= {16'h3c07,8'h08};
300 //时序控制 16'h3800~16'h3821
301 8'd208: i2c_data <= {16'h3820,8'h46};
302 8'd209: i2c_data <= {16'h3821,8'h01};
303 8'd210: i2c_data <= {16'h3814,8'h31};
304 8'd211: i2c_data <= {16'h3815,8'h31};
305 8'd212: i2c_data <= {16'h3800,8'h00};
306 8'd213: i2c_data <= {16'h3801,8'h00};
307 8'd214: i2c_data <= {16'h3802,8'h00};
308 8'd215: i2c_data <= {16'h3803,8'h04};
309 8'd216: i2c_data <= {16'h3804,8'h0a};
310 8'd217: i2c_data <= {16'h3805,8'h3f};
311 8'd218: i2c_data <= {16'h3806,8'h07};
312 8'd219: i2c_data <= {16'h3807,8'h9b};
313 //设置输出像素个数
314 //DVP 输出水平像素点数高4位
315 8'd220: i2c_data <= {16'h3808,{4'd0,cmos_h_pixel[11:8]}};
316 //DVP 输出水平像素点数低8位
317 8'd221: i2c_data <= {16'h3809,cmos_h_pixel[7:0]};
318 //DVP 输出垂直像素点数高3位
319 8'd222: i2c_data <= {16'h380a,{5'd0,cmos_v_pixel[10:8]}};
320 //DVP 输出垂直像素点数低8位
321 8'd223: i2c_data <= {16'h380b,cmos_v_pixel[7:0]};
322 //水平总像素大小高5位
323 8'd224: i2c_data <= {16'h380c,{3'd0,total_h_pixel[12:8]}};
324 //水平总像素大小低8位
325 8'd225: i2c_data <= {16'h380d,total_h_pixel[7:0]};
326 //垂直总像素大小高5位
327 8'd226: i2c_data <= {16'h380e,{3'd0,total_v_pixel[12:8]}};
328 //垂直总像素大小低8位
329 8'd227: i2c_data <= {16'h380f,total_v_pixel[7:0]};
配置代码较长,省略部分源代码……
350 8'd246: i2c_data <= {16'h3016,8'h02};
351 8'd247: i2c_data <= {16'h301c,8'h02};
352 8'd248: i2c_data <= {16'h3019,8'h02}; //打开闪光灯
353 8'd249: i2c_data <= {16'h3019,8'h00}; //关闭闪光灯
354 //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
355 default : i2c_data <= {16'h300a,8'h00}; //器件ID高8位
356 endcase
357 end
358 end
359
360 endmodule
I2C配置模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束。需要注意的是,由OV5640的数据手册可知,图像传感器上电后到开始配置寄存器需要延时20ms,所以程序中定义了一个延时计数器(start_init_cnt),用于延时20ms。当计数器计数到预设值之后,开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。在代码的第19行定义了总共需要配置的寄存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。
在程序的第313行至第329行,是对摄像头需要输出的行场分辨率和行场总像素进行设置的寄存器配置。
CMOS图像数据采集模块的代码如下所示:
1 module cmos_capture_data(
2 input rst_n , //复位信号
3 //摄像头接口
4 input cam_pclk , //cmos 数据像素时钟
5 input cam_vsync , //cmos 场同步信号
6 input cam_href , //cmos 行同步信号
7 input [7:0] cam_data ,
8 //用户接口
9 output cmos_frame_vsync , //帧有效信号
10 output cmos_frame_href , //行有效信号
11 output cmos_frame_valid , //数据有效使能信号
12 output [15:0] cmos_frame_data //有效数据
13 );
14
15 //寄存器全部配置完成后,先等待10帧数据
16 //待寄存器配置生效后再开始采集图像
17 parameter WAIT_FRAME = 4'd10 ; //寄存器数据稳定等待的帧个数
18
19 //reg define
20 reg cam_vsync_d0 ;
21 reg cam_vsync_d1 ;
22 reg cam_href_d0 ;
23 reg cam_href_d1 ;
24 reg [3:0] cmos_ps_cnt ; //等待帧数稳定计数器
25 reg [7:0] cam_data_d0 ;
26 reg [15:0] cmos_data_t ; //用于8位转16位的临时寄存器
27 reg byte_flag ; //16位RGB数据转换完成的标志信号
28 reg byte_flag_d0 ;
29 reg frame_val_flag ; //帧有效的标志
30
31 wire pos_vsync ; //采输入场同步信号的上升沿
32
33 //*****************************************************
34 //** main code
35 //*****************************************************
36
37 //采输入场同步信号的上升沿
38 assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
39
40 //输出帧有效信号
41 assign cmos_frame_vsync = frame_val_flag ? cam_vsync_d1 : 1'b0;
42
43 //输出行有效信号
44 assign cmos_frame_href = frame_val_flag ? cam_href_d1 : 1'b0;
45
46 //输出数据使能有效信号
47 assign cmos_frame_valid = frame_val_flag ? byte_flag_d0 : 1'b0;
48
49 //输出数据
50 assign cmos_frame_data = frame_val_flag ? cmos_data_t : 1'b0;
51
52 always @(posedge cam_pclk or negedge rst_n) begin
53 if(!rst_n) begin
54 cam_vsync_d0 <= 1'b0;
55 cam_vsync_d1 <= 1'b0;
56 cam_href_d0 <= 1'b0;
57 cam_href_d1 <= 1'b0;
58 end
59 else begin
60 cam_vsync_d0 <= cam_vsync;
61 cam_vsync_d1 <= cam_vsync_d0;
62 cam_href_d0 <= cam_href;
63 cam_href_d1 <= cam_href_d0;
64 end
65 end
66
67 //对帧数进行计数
68 always @(posedge cam_pclk or negedge rst_n) begin
69 if(!rst_n)
70 cmos_ps_cnt <= 4'd0;
71 else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
72 cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
73 end
74
75 //帧有效标志
76 always @(posedge cam_pclk or negedge rst_n) begin
77 if(!rst_n)
78 frame_val_flag <= 1'b0;
79 else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
80 frame_val_flag <= 1'b1;
81 else;
82 end
83
84 //8位数据转16位RGB565数据
85 always @(posedge cam_pclk or negedge rst_n) begin
86 if(!rst_n) begin
87 cmos_data_t <= 16'd0;
88 cam_data_d0 <= 8'd0;
89 byte_flag <= 1'b0;
90 end
91 else if(cam_href) begin
92 byte_flag <= ~byte_flag;
93 cam_data_d0 <= cam_data;
94 if(byte_flag)
95 cmos_data_t <= {cam_data_d0,cam_data};
96 else;
97 end
98 else begin
99 byte_flag <= 1'b0;
100 cam_data_d0 <= 8'b0;
101 end
102 end
103
104 //产生输出数据有效信号(cmos_frame_valid)
105 always @(posedge cam_pclk or negedge rst_n) begin
106 if(!rst_n)
107 byte_flag_d0 <= 1'b0;
108 else
109 byte_flag_d0 <= byte_flag;
110 end
111
112 endmodule
CMOS图像采集模块第17行定义了参数WAIT_FRAME(寄存器数据稳定等待的帧个数),这个参数是设置摄像头初始化完成后,等待摄像头输出稳定数据的帧数,此处等待10帧,即通过采集场同步信号的上升沿来统计帧数,计数器计数超过10次之后产生数据有效的标志,开始采集图像。在程序的第84行开始的always块实现了8位数据转16位数据的功能。需要注意的是摄像头的图像数据是在像素时钟(cam_pclk)下输出的,因此摄像头的图像数据必须使用像素钟来采集,否则会造成数据采集错误。
开始传输控制模块的代码如下:
1 module start_transfer_ctrl(
2 input clk , //时钟信号
3 input rst_n , //复位信号,低电平有效
4 input udp_rec_pkt_done , //GMII接收时钟
5 input udp_rec_en , //UDP单包数据接收完成信号
6 input [31:0] udp_rec_data , //UDP接收的数据使能信号
7 input [15:0] udp_rec_byte_num , //UDP接收的数据
8 //UDP接收到的字节数
9 output reg transfer_flag //图像开始传输标志,0:开始传输 1:停止传输
10 );
11
12 //parameter define
13 parameter START = "1"; //开始命令
14 parameter STOP = "0"; //停止命令
15
16 //*****************************************************
17 //** main code
18 //*****************************************************
19
20 //解析接收到的数据
21 always @(posedge clk or negedge rst_n) begin
22 if(!rst_n)
23 transfer_flag <= 1'b0;
24 else if(udp_rec_pkt_done && udp_rec_byte_num == 1'b1) begin
25 if(udp_rec_data[31:24] == START) //开始传输
26 transfer_flag <= 1'b1;
27 else if(udp_rec_data[31:24] == STOP) //停止传输
28 transfer_flag <= 1'b0;
29 end
30 end
31
32 endmodule
开始传输控制模块的代码比较简单,当接收到UDP的数据包,并且这个数据包的有效字节个数为1时,开始对接收到的数据进行判断。如果这个数据等于ASCII码的“1”时,此时将transfer_flag赋值为1,表示以太网开始发送图像数据;如果这个数据等于ASCII码的“0”时,此时将transfer_flag赋值为0,表示以太网停止发送图像数据。
图像封装模块的代码如下:
1 module img_data_pkt(
2 input rst_n , //复位信号,低电平有效
3 //图像相关信号
4 input cam_pclk , //像素时钟
5 input img_vsync , //帧同步信号
6 input img_data_en , //数据有效使能信号
7 input [15:0] img_data , //有效数据
8
9 input transfer_flag , //图像开始传输标志,0:开始传输 1:停止传输
10 //以太网相关信号
11 input eth_tx_clk , //以太网发送时钟
12 input udp_tx_req , //udp发送数据请求信号
13 input udp_tx_done , //udp发送数据完成信号
14 output reg udp_tx_start_en, //udp开始发送信号
15 output [31:0] udp_tx_data , //udp发送的数据
16 output reg [15:0] udp_tx_byte_num //udp单包发送的有效字节数
17 );
18
19 //parameter define
20 parameter CMOS_H_PIXEL = 16'd640; //图像水平方向分辨率
21 parameter CMOS_V_PIXEL = 16'd480; //图像垂直方向分辨率
22 //图像帧头,用于标志一帧数据的开始
23 parameter IMG_FRAME_HEAD = {32'hf0_5a_a5_0f};
程序中第20行至23行定义了三个参数,分别是CMOS_H_PIXEL(图像水平方向分辨率)、CMOS_V_PIXEL(图像垂直方向分辨率)和IMG_FRAME_HEAD(图像帧头)。我们在用网口传输图像数据时,一次发送一行图像数据。在发送一帧图像的第一行数据时,在一行数据的开头添加图像的帧头和图像的行场分辨率,共8个字节,图像的帧头是32'hf0_5a_a5_0f,共占用4个字节;而图像的行场分辨率占用4个字节,本次实验传输的图像分辨率为640*480。
这里我们来详细说明下在一帧的第一行添加帧头的原因。在使用UDP传输数据时,有时会出现数据丢包的现象。这里说的丢包现象并不是开发板没有把数据发送出去,而是电脑接收端系统繁忙或者其它的原因没有接收到数据。为了降低丢包对视频显示带来的影响,我们为每帧图像添加一个帧头,上位机解析到帧头之后,接下来将接收到的数据重新放到图像显示区域的起始位置,保证了在视频传输过程中,即使出现丢包的现象,视频也能恢复到正常显示的画面。图像帧头的值要尽量选择图像数据不容易出现的值,否则很容易把图像数据当成帧头,程序中把图像帧头设置为{32'hf0_5a_a5_0f},图像中出现的像素数据和帧头相同的概率极低。除此之外,上位机软件按照帧头为{32'hf0_5a_a5_0f}来解析数据,所以帧头的值不可以随意修改。
25 reg img_vsync_d0 ; //帧有效信号打拍
26 reg img_vsync_d1 ; //帧有效信号打拍
27 reg neg_vsync_d0 ; //帧有效信号下降沿打拍
28
29 reg wr_sw ; //用于位拼接的标志
30 reg [15:0] img_data_d0 ; //有效图像数据打拍
31 reg wr_fifo_en ; //写fifo使能
32 reg [31:0] wr_fifo_data ; //写fifo数据
33
34 reg img_vsync_txc_d0; //以太网发送时钟域下,帧有效信号打拍
35 reg img_vsync_txc_d1; //以太网发送时钟域下,帧有效信号打拍
36 reg tx_busy_flag ; //发送忙信号标志
37
38 //wire define
39 wire pos_vsync ; //帧有效信号上升沿
40 wire neg_vsync ; //帧有效信号下降沿
41 wire neg_vsynt_txc ; //以太网发送时钟域下,帧有效信号下降沿
42 wire [9:0] fifo_rdusedw ; //当前FIFO缓存的个数
43
44 //*****************************************************
45 //** main code
46 //*****************************************************
47
48 //信号采沿
49 assign neg_vsync = img_vsync_d1 & (~img_vsync_d0);
50 assign pos_vsync = ~img_vsync_d1 & img_vsync_d0;
51 assign neg_vsynt_txc = ~img_vsync_txc_d1 & img_vsync_txc_d0;
52
53 //对img_vsync信号延时两个时钟周期,用于采沿
54 always @(posedge cam_pclk or negedge rst_n) begin
55 if(!rst_n) begin
56 img_vsync_d0 <= 1'b0;
57 img_vsync_d1 <= 1'b0;
58 end
59 else begin
60 img_vsync_d0 <= img_vsync;
61 img_vsync_d1 <= img_vsync_d0;
62 end
63 end
64
65 //寄存neg_vsync信号
66 always @(posedge cam_pclk or negedge rst_n) begin
67 if(!rst_n)
68 neg_vsync_d0 <= 1'b0;
69 else
70 neg_vsync_d0 <= neg_vsync;
71 end
程序中第54行至63行在cam_pclk时钟下对img_vsync信号延时两拍,用于采该信号的上升沿和下降沿。程序中第66行至71行对下降沿延时一拍,我们后面的处理中会用到这些信号。
73 //对wr_sw和img_data_d0信号赋值,用于位拼接
74 always @(posedge cam_pclk or negedge rst_n) begin
75 if(!rst_n) begin
76 wr_sw <= 1'b0;
77 img_data_d0 <= 1'b0;
78 end
79 else if(neg_vsync)
80 wr_sw <= 1'b0;
81 else if(img_data_en) begin
82 wr_sw <= ~wr_sw;
83 img_data_d0 <= img_data;
84 end
85 end
程序中第74行至85行代码对wr_sw和img_data_d0信号进行赋值,用于位拼接。这是由于输入的图像数据为16位,而以太网顶层模块的用户数据端口是32位,因此这里需要对输入的数据进行寄存,用于将16位拼接成32位。
87 //将帧头和图像数据写入FIFO
88 always @(posedge cam_pclk or negedge rst_n) begin
89 if(!rst_n) begin
90 wr_fifo_en <= 1'b0;
91 wr_fifo_data <= 1'b0;
92 end
93 else begin
94 if(neg_vsync) begin
95 wr_fifo_en <= 1'b1;
96 wr_fifo_data <= IMG_FRAME_HEAD; //帧头
97 end
98 else if(neg_vsync_d0) begin
99 wr_fifo_en <= 1'b1;
100 wr_fifo_data <= {CMOS_H_PIXEL,CMOS_V_PIXEL}; //水平和垂直方向分辨率
101 end
102 else if(img_data_en && wr_sw) begin
103 wr_fifo_en <= 1'b1;
104 wr_fifo_data <= {img_data_d0,img_data}; //图像数据位拼接,16位转32位
105 end
106 else begin
107 wr_fifo_en <= 1'b0;
108 wr_fifo_data <= 1'b0;
109 end
110 end
111 end
本次模块中例化了异步FIFO,我们需要将一帧的帧头、图像行场分辨率和拼接后的数据都写入FIFO中。需要注意的是,只有一帧图像的第一行图像才需要添加帧头和图像行场分辨率,而其余行的图像是不需要添加的,因此我们需要根据img_vsync(帧同步信号)来判断什么时候输入的数据是第一行的图像。
img_vsync作为帧同步信号,该信号的下降沿之后输入的数据,就是一帧的第一行数据。因此我们在img_vsync的下降沿将帧头IMG_FRAME_HEAD写入FIFO,在下一个时钟周期将行场分辨率{CMOS_H_PIXEL,CMOS_V_PIXEL}写入FIFO中。当img_data_en信号和wr_sw同时为高时,将拼接后的32位数据写入FIFO中即可。
113 //以太网发送时钟域下,对img_vsync信号延时两个时钟周期,用于采沿
114 always @(posedge eth_tx_clk or negedge rst_n) begin
115 if(!rst_n) begin
116 img_vsync_txc_d0 <= 1'b0;
117 img_vsync_txc_d1 <= 1'b0;
118 end
119 else begin
120 img_vsync_txc_d0 <= img_vsync;
121 img_vsync_txc_d1 <= img_vsync_txc_d0;
122 end
123 end
由于cam_pclk和eth_tx_clk为异步时钟,因此不能直接在eth_tx_clk时钟域下直接采集neg_vsync信号(该信号在cam_pclk时钟域下产生),因此这里在eth_tx_clk时钟域下重新对img_vsync信号进行打拍和采沿。
125 //控制以太网发送的字节数
126 always @(posedge eth_tx_clk or negedge rst_n) begin
127 if(!rst_n)
128 udp_tx_byte_num <= 1'b0;
129 else if(neg_vsynt_txc)
130 udp_tx_byte_num <= {CMOS_H_PIXEL,1'b0} + 16'd8;
131 else if(udp_tx_done)
132 udp_tx_byte_num <= {CMOS_H_PIXEL,1'b0};
133 end
我们只在一帧的第一行添加了帧头和行场分辨率,因此只有在发送第一行图像数据时,发送的UDP字节数为1288(640*2+8),而其余行单包发送的字节数为1280。
135 //控制以太网发送开始信号
136 always @(posedge eth_tx_clk or negedge rst_n) begin
137 if(!rst_n) begin
138 udp_tx_start_en <= 1'b0;
139 tx_busy_flag <= 1'b0;
140 end
141 //上位机未发送“开始”命令时,以太网不发送图像数据
142 else if(transfer_flag == 1'b0) begin
143 udp_tx_start_en <= 1'b0;
144 tx_busy_flag <= 1'b0;
145 end
146 else begin
147 udp_tx_start_en <= 1'b0;
148 //当FIFO中的个数满足需要发送的字节数时
149 if(tx_busy_flag == 1'b0 && fifo_rdusedw >= udp_tx_byte_num[15:2]) begin
150 udp_tx_start_en <= 1'b1; //开始控制发送一包数据
151 tx_busy_flag <= 1'b1;
152 end
153 else if(udp_tx_done || neg_vsynt_txc)
154 tx_busy_flag <= 1'b0;
155 end
156 end
当上位机发送“开始”命令后,transfer_flag信号为高电平,此时以太网开始传输图像数据。当以太网发送空闲时,且FIFO中的字节数达到待发送的字节数时,此时拉高udp_tx_start_en信号,开始控制以太网顶层模块读取FIFO,并将FIFO中的数据发送给上位机。
158 //异步FIFO
159 async_fifo_1024x32b async_fifo_1024x32b_inst (
160 .rst (pos_vsync | (~transfer_flag)),
161 .din (wr_fifo_data ),
162 .rd_clk (eth_tx_clk ),
163 .rd_en (udp_tx_req ),
164 .wr_clk (cam_pclk ),
165 .wr_en (wr_fifo_en ),
166 .dout (udp_tx_data ),
167 .full (),
168 .empty (),
169 .rd_data_count (fifo_rdusedw),
170 .wr_rst_busy (),
171 .rd_rst_busy ()
172 );
173
174 endmodule
程序中第158行至172行代码例化了异步FIFO,存储深度为1024,数据位宽为32位。值得一提的是,我们帧同步的上升沿和transfer_flag信号作为FIFO的复位信号,避免上位机在传输图像中途发送停止命令,FIFO没有被清空的情况。
下图为图像数据封装模块采集过程中ILA抓取的波形图,在img_vsync的下降沿,依次将图像帧头和行场分辨率写入fifo(如下图的wr_fifo_en和wr_fifo_data),帧头为32’hf0_5a_a5_0f,行分辨率为16’h0280(640),场分辨率为32’h01e0(32’h480)。
图30.4.3 ILA波形图
下载验证
将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接;将网线一端连接开发板的网口,另一端连接电脑的网口;将OV5640摄像头连接开发板上的J19扩展接口,注意镜头方向朝外,如下图所示。
图30.5.1 开发板硬件连接示意图
接下来连接电源线后拨动开关按键给开发板上电,然后下载程序。程序下载完成后,此时开发板上还看不到现象,我们接下来打开正点原子UDP传输视频显示的上位机,位于资料盘(A盘)/6_软件资料/1_软件/video_transfer文件夹下,如下图所示:
图30.5.2 打开上位机软件
双击video_transfer.exe即可打开软件,如果软件无法打开,先双击安装上图中的vc_redist.x64.exe,安装完成后再打开video_transfer.exe。
打开后的界面如下图所示。
图30.5.3 上位机设置界面
按照上图的界面进行设置,这些设置和程序中定义的参数和代码是对应的。设置完成后,点击“打开”按钮,此时上位机会通过网口向开发板发送ASCII码“1”,开发板收到开始命令后,会开始传输图像数据,如下图所示:
图30.5.4 上位机实时显示画面
由上图可知,开发板传输的图像分辨率是640*480,帧率约为26fps,如果需要停止显示画面的话,只需要点击上方的“关闭”按钮即可。需要注意的是,上位机显示的画面受电脑性能的影响,如果电脑性能较差的话,上位机可能会出现卡顿和崩溃的现象。