01signal.com

使用 Smart Zynq从 OV7670 camera sensor 进行实时取景和视频捕捉

该网页属于一组探索 Smart Zynq 电路板功能的小项目

这个项目也在推荐给中国读者的 HelloFPGA上发表

介绍

本教程介绍如何将 OV7670 摄像头模块连接到 Smart Zynq 并在板子本身的 HDMI 输出上查看实时视频信号。我还将展示如何借助简单的 Linux 命令轻松地将 raw video 数据流保存到文件中。

本教程中的信息也适用于其他类型的数据采集(data acquisition): 下面所示的技术可与 image 数据的其他来源以及其他数字数据来源一起使用。

摄像头模块连接到 Zynq 芯片的 PL (FPGA) 部分。因此,在将 image 数据发送到 ARM 处理器之前,可以轻松添加实现 image processing 的逻辑。由于本教程基于 Xillinux,因此系统的处理器部分由完整的 Linux distribution组成。

此次演示选择 OV7670 模块是因为该硬件价格便宜、流行且易于购买。此外,该组件生成的数字信号很容易理解。

然而, OV7670 有一个不幸的缺陷: 默认情况下,视频流的颜色不正确。这是该相机传感器的一个已知问题。通过更换该相机的少量寄存器(registers)可以纠正这个缺陷。因此,本教程分为两部分:

请注意,本教程的大部分内容解释了实现的工作原理。无需为了使用相机而理解这些说明。

OV7670 模块

本教程基于下图所示的摄像头模块:

The OV7670 camera sensor module used in this project

在这个模块上,大部分引脚、排针都直接连接到 OV7670 组件。仅 3.3V 和 GND 连接到电压 regulators(voltage regulators)。因此, FPGA 和模块之间的所有连接都是 FPGA 和 OV7670 组件之间的直接连接。

市场上还有其他具有相同功能的模块。使用这些其他模块可能也是可以的。例如,有一个不同的模块,在电路板上写有“2017/3/15”。这个模块也正常工作。另一方面,有一个模块不能正常工作。不能正常工作的模块在电路板上写有“QYF-OV7670 V3.0”。

另一件需要注意的事情是, OV7670 组件有不同的修订版本。可以验证模块上是否使用了正确的修订版本。如何做到这一点将在本教程的第二部分中解释。

有关 OV7670的信息主要有两个来源。这两个文档可以在网上找到:

准备 Vivado 项目

从演示包的(demo bundle)的 zip 文件(启动 partition kit(boot partition kit))创建一个新的 Vivado 项目。在文本编辑器中打开 verilog/src/xillydemo.v 。删除标记为“PART 2”的代码部分。插入以下代码片段来代替该部分:

/*
    * PART 2
    * ======
    *
    * This code demonstrates a frame grabber (data acquisition) from
    * an OV7670 camera module.
    *
    */

   reg [1:0]  clkdiv;

   always @(posedge bus_clk)
     clkdiv <= clkdiv + 1;

   assign J6[10] = clkdiv[1]; // MCLK / XCLK

   assign J6[0] = 0; // PWDN, the camera is always on
   assign J6[1] = !user_w_write_32_open; // RESET#, active low

   wire [7:0] D_in;
   wire       pclk_in, hsync_in, vsync_in;

   assign D_in = J6[9:2];
   assign pclk_in = J6[11];
   assign hsync_in = J6[12];
   assign vsync_in = J6[13];

   (* IOB = "TRUE" *) reg [7:0] D_guard;
   (* IOB = "TRUE" *) reg       pclk_guard, hsync_guard, vsync_guard;

   reg [7:0]  D;
   reg 	      pclk, hsync, vsync;

   always @(posedge bus_clk)
     begin
	// Metastability guards on asynchronous inputs
	D_guard <= D_in;
	pclk_guard <= pclk_in;
	hsync_guard <= hsync_in;
	vsync_guard <= vsync_in;

	D <= D_guard;
	pclk <= pclk_guard;
	hsync <= hsync_guard;
	vsync <= vsync_guard;
     end

   wire       sample_valid;
   reg 	      previous_pclk;

   always @(posedge bus_clk)
     previous_pclk <= pclk;

   assign sample_valid = pclk && !previous_pclk;

   // wait_for_frame's purpose is to start getting data from the camera
   // at the beginning of a frame.
   reg 	      wait_for_frame;

   always @(posedge bus_clk)
     if (!user_r_read_32_open)
       wait_for_frame <= 1;
     else if (sample_valid && vsync)
       wait_for_frame <= 0;

   // fifo_has_been_full changes to '1' when the FIFO becomes full, so
   // that the data acquisition stops and an EOF is sent to the host.
   // This ensures that the data that arrives to the host is contiguous.

   reg 	      fifo_has_been_nonfull, fifo_has_been_full;
   wire       fifo_full;

   always @(posedge bus_clk)
     begin
	if (!fifo_full)
	  fifo_has_been_nonfull <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_nonfull <= 0;

	if (fifo_full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_full <= 0;
     end

   assign user_r_read_32_eof = fifo_has_been_full && user_r_read_32_empty;

   // This part writes pixels from the camera to the FIFO

   reg 	      fifo_wr_en;
   reg [1:0]  byte_position;
   reg [31:0] dataword;

   always @(posedge bus_clk)
     if (wait_for_frame)
       begin
	  byte_position <= 0;
	  fifo_wr_en <= 0;
       end
     else if (sample_valid && hsync)
       begin
	  case (byte_position)
	    0: dataword[7:0] <= D;
	    1: dataword[15:8] <= D;
	    2: dataword[23:16] <= D;
	    3: dataword[31:24] <= D;
	  endcase

	  if (byte_position == 3)
	    fifo_wr_en <= !fifo_has_been_full;
	  else
	    fifo_wr_en <= 0;

	  byte_position <= byte_position + 1;
       end
     else
       fifo_wr_en <= 0;

   fifo_32x512 fifo_32
     (
      .clk(bus_clk),
      .srst(!user_r_read_32_open),

      .din(dataword),
      .wr_en(fifo_wr_en),
      .full(fifo_full),

      .rd_en(user_r_read_32_rden),
      .dout(user_r_read_32_data),
      .empty(user_r_read_32_empty)
      );

或者,您可以从此处下载此更改后的 xillydemo.v 。

进行此更改后照常创建比特流(bitstream)文件。此 Verilog 代码的工作原理在本页下面详细解释。

连接摄像头模块

短杜邦(Dupont)跳线可用于连接摄像头模块和 Smart Zynq 板。电线长度应为 10 cm 或更短。最佳长度是 5 cm。如果电线较长,串扰(crosstalk)可能会损害数字信号的质量。 horizontal sync 上噪音过大会导致视频图像跳动,出现绿色和紫色条纹。

如果电线长度为 10 cm,则可能需要更换寄存器以减少 OV7670的 I/O driver 电流。本教程的第二部分展示了如何进行此更改。

这是 OV7670 模块连接到 Smart Zynq SP 板的图片:

OV7670 module connected to the Smart Zynq board

下面是从相反方向拍摄的照片。左上角的较小图像强调排针的最后一个引脚未连接任何东西。

OV7670 module connected to the Smart Zynq board

上图显示了如何连接电线: 首先,查找 Smart Zynq 板背面写有“Bank 33 VCCIO Vadj”的位置。靠近此标记的引脚行是我们将使用的排针。这是靠近 HDMI 连接器的排针。

相机传感器和 Smart Zynq 板之间并联有 16 根电线。只有 3.3V 和 GND 连接到排针上的不同位置。这两根线不必很短。

请注意,排针的最后一个引脚是 5V,因此不要将电线连接到此引脚。

这是相机模块和 Smart Zynq、排针之间的接线规格。这些信息也可以从上图中推断出来:

Pin header 1 3 5 7 9 11 13 15 35
Module pin PWDN D0 D2 D4 D6 MCLK HS SDA GND
Module pin RST D1 D3 D5 D7 PCLK VS SCL 3.3V
Pin header 2 4 6 8 10 12 14 16 37

再次,请特别注意 3.3V 和 GND的连接。这两根电线的错误连接可能会损坏摄像头模块。

截取帧(Frame grabbing)

使用基于更新的 xillydemo.v 的比特流文件启动 Xillinux (如上所示)。

此命令(位于 shell 提示符)从相机的输出创建一个短视频剪辑:

# cat /dev/xillybus_read_32 > clip.raw

此命令运行几秒钟,然后停止。这是因为传输速率(data rate)的视频流速度高于 SD 卡的写入数据速度。这会导致溢出(overflow)停止数据流。下面解释了这种行为背后的机制。

可以使用以下命令播放该视频剪辑:

# mplayer -demuxer rawvideo -rawvideo w=640:h=480:format=uyvy:size=614400:fps=31.25 clip.raw

如果在位于 Xillinux图形桌面内的终端窗口(terminal window)中使用此命令,则视频将在 Xillinux图形界面上播放。

还可以使用单独页面上描述的技术在不同计算机的屏幕上播放视频。例如,如果另一台计算机的 IP 地址是 192.168.1.11,请更改命令,使其开头为:

# DISPLAY=192.168.1.11:0 mplayer -demuxer rawvideo ...

如前所述,视频剪辑的颜色不正确,下一页将解释如何纠正此问题。

此命令将一台 video 帧读取到名为 frame.raw的文件中:

# dd if=/dev/xillybus_read_32 of=frame.raw bs=614400 iflag=fullblock count=1

raw 帧的格式是 UYVY 4:2:2。换句话说,每个像素由 16 比特组成。第一个字节是第一个像素的 U 分量(或 Cb)。下一个字节是同一像素的 Y 分量。第三和第四字节是第二像素(Cr 和 Y)的 V 和 Y 分量。

该文件可以转换为 PNG 文件:

# convert -size 640x480 pal:frame.raw frame.png

有一个简单的查看图像的工具:

# display frame.png &

Live view

为了获取相机的 live view ,请创建一个名为 liveview.sh 的文件,其中包含以下内容:

#!/bin/bash

while [ 1 ] ; do
  dd if=/dev/xillybus_read_32 bs=614400 iflag=fullblock count=1 2>/dev/null
done | mplayer -demuxer rawvideo -rawvideo w=640:h=480:format=uyvy:size=614400 -

运行此脚本(script)

# bash liveview.sh

为什么需要这个脚本(script)?直接从设备文件(device file)读取数据是不可能的,因为 mplayer 太慢了。换句话说, Zynq的 ARM 处理器不够强大,无法以正确的帧率(31.25 fps)播放视频。如果您尝试这样做,视频只会短暂播放。当 FPGA内部有溢出时,数据流停止。

该脚本基于每次从 /dev/xillybus_read_32 读取 raw 帧的无限循环。这与之前用于将一帧读入 frame.raw的命令相同。但这一次,没有为 dd定义输出文件。因此, dd 将数据写入标准输出(standard output)。

这个死循环的结果借助一 pipe 重定向到 mplayer的标准输入(standard input)(注意循环末尾的“|”)。 mplayer 播放从标准输入传来的视频数据。

脚本解决了溢出的问题,因为 dd 总是读取完整的视频帧。当 mplayer 没有准备好接受更多数据时, pipe 的数据流会暂时停止。因此, dd 从相机传感器上跳过了 video 帧。因此,屏幕上显示的帧速率低于相机的帧速率。更准确地说,显示的帧速率是 mplayer 能够显示的最大帧速率。

由于 mplayer自带的 buffering,屏幕上出现的视频图像略有延迟。为了获得低延迟(latency),需要编写一个简单的程序,在不添加缓冲区的情况下将图像显示在屏幕上。

mplayer 是一款功能强大的媒体播放器。例如,如果不正确的颜色令人烦恼,则可以将视频剪辑作为黑白视频播放。将以下部分添加到命令中,以便将 saturation 减少到零:

# mplayer -saturation -100 -demuxer rawvideo ...

本页实用部分结束

本页的其余部分解释了执行数据采集的逻辑的实现。如果您只对实用主题感兴趣,请继续阅读本教程的下一部分

FPGA 与主机(host)之间的通信

本示例中的逻辑基于 Xillybus IP core。该 IP core 负责与主机的通信。

回想一下上面 Verilog 代码中被替换的部分以此结尾:

fifo_32x512 fifo_32
     (
      .clk(bus_clk),
      .srst(!user_r_read_32_open),

      .din(dataword),
      .wr_en(fifo_wr_en),
      .full(fifo_full),

      .rd_en(user_r_read_32_rden),
      .dout(user_r_read_32_data),
      .empty(user_r_read_32_empty)
      );

这是标准 FIFO的例化(instantiation)。该 FIFO 有 3个端口,用于从 FIFO读取数据: rd_en、 dout 和 empty。这些端口连接到 Xillybus IP core。这使得 IP core 可以从 FIFO 读取数据并将该数据发送到主机。结果是写入 FIFO 的所有内容都到达名为 /dev/xillybus_read_32的设备文件。换句话说,主机上的普通计算机程序可以将 /dev/xillybus_read_32 作为常规文件打开。当程序读取该文件时,它接收到 FPGA内部的应用逻辑已写入 FIFO 的数据。

FIFO 有 3 个用于写入数据的端口: wr_en、 din 和 full。这些端口连接到从相机传感器收集像素数据的逻辑。本页的下一部分解释了逻辑的工作原理。现在我只是指出逻辑在 @dataword 和 @fifo_wr_en的帮助下将像素数据写入 FIFO 。从此时起, Xillybus IP core的作用就是将这些数据传送到主机上运行的计算机程序。这就是这个命令(上面已经提到的)将此数据写入文件的原因:

# cat /dev/xillybus_read_32 > clip.raw

有关 FIFO 工作原理的一般说明,请参阅此页面

数据流可以用下图总结:

Simplified diagram of data acquisition with Xillybus

该网站上有一个关于 Xillybus的部分,该部分有一个讨论数据采集的页面。通读该页面可能会有所帮助。

请注意, user_r_read_32_open 连接到 FIFO的 srst 端口。当 /dev/xillybus_read_32 在主机上打开时,这个信号变为高电平。信号与非(NOT)连接,因此当设备文件关闭时, FIFO 保持在复位(reset)状态。这可确保每次关闭文件时, FIFO 中的所有数据都会被删除。

与相机传感器的接口

现在我们来看看上面 Verilog 代码的开头:

reg [1:0]  clkdiv;

   always @(posedge bus_clk)
     clkdiv <= clkdiv + 1;

   assign J6[10] = clkdiv[1]; // MCLK / XCLK

@bus_clk的频率是 100 MHz。这个时钟在 @clkdiv的帮助下被除以四。因此,相机模块接收一个 25 MHz 参考时钟。根据相机的数据手册,这是允许的频率。然而,当参考时钟(reference clock)的频率是 24 MHz时,相机设计为产生 30 fps 视频流。因此实际的视频帧速率略高: 31.25 fps。

我应该指出,这通常是创建时钟的错误方法。正确的方法是使用锁相环(PLL)或类似的资源。在这种具体情况下该方法没有问题,因为 @clkdiv 仅用于创建输出信号: FPGA自带的逻辑不使用该信号。

Verilog 代码的下一部分是这样的:

assign J6[0] = 0; // PWDN, the camera is always on
   assign J6[1] = !user_w_write_32_open; // RESET#, active low

J6[0] 连接到摄像头模块的 PWDN 引脚。相机永远不会断电。

J6[1] 连接到相机的 RESET#。当这个引脚为低电平时,相机被重置。当主机上的程序打开 /dev/xillybus_write_32 时, @user_w_write_32_open 为高电平。所以通常情况下,相机不会重置,因为 @user_w_write_32_open 为低电平,因此 J6[1] 为高电平。这种安排允许使用以下命令重置相机:

# echo 1 > /dev/xillybus_write_32

该命令会短暂打开设备文件,从而达到预期的结果。

到目前为止,我已经展示了如何创建从 FPGA 到相机传感器的信号。现在介绍从相机传感器到 FPGA的信号。

OV7670 从 FPGA生成与参考时钟频率相同的像素时钟。也就是说, PCLK的频率就是 25 MHz。该信号连接到 Verilog 代码中的 @pclk_in 。

相机传感器还生成包含视频数据的三个信号。 Verilog 代码中这些信号的名称是 @D_in 、 @hsync_in 和 @vsync_in。当 @pclk_in 从高变为低 (下降沿(falling edge)) 时,相机传感器会同时更改这些信号的值。更准确地说, @D_in 、 @hsync_in 和 @vsync_in 的变化与 @pclk_in的下降沿保持一致。从 FPGA的角度来看,这被称为源同步输入(source synchronous input)。

现在我们看一下 Verilog 代码中的相关部分:

wire [7:0] D_in;
   wire       pclk_in, hsync_in, vsync_in;

   assign D_in = J6[9:2];
   assign pclk_in = J6[11];
   assign hsync_in = J6[12];
   assign vsync_in = J6[13];

   (* IOB = "TRUE" *) reg [7:0] D_guard;
   (* IOB = "TRUE" *) reg       pclk_guard, hsync_guard, vsync_guard;

   reg [7:0]  D;
   reg 	      pclk, hsync, vsync;

   always @(posedge bus_clk)
     begin
	// Metastability guards on asynchronous inputs
	D_guard <= D_in;
	pclk_guard <= pclk_in;
	hsync_guard <= hsync_in;
	vsync_guard <= vsync_in;

	D <= D_guard;
	pclk <= pclk_guard;
	hsync <= hsync_guard;
	vsync <= vsync_guard;
     end

请注意,来自相机传感器的所有信号都是在 @bus_clk的帮助下进行采样的。甚至相机的 PCLK 也以与其他信号相同的方式进行采样。换句话说, PCLK 不被视为时钟,而是被视为数据信号(data signal)。下面我将简要解释该技术。

另请注意, @bus_clk 和相机传感器信号之间的时序关系未知。因此,接收这些信号的触发器(flip-flops)的输出并不可靠: 无法确保这些触发器的时序要求(timing requirements),因此它们可能会在短时间内变得不稳定。这是与跨时钟域(clock domain crossing)相关的已知问题。

该问题的解决方案在单独的页面上讨论: Metastability guards。这意味着有两个触发器(flip-flops)相互串联。第一个触发器(例如@pclk_guard)连接到外部信号。第二个触发器连接到第一个触发器。因此,即使第一台触发器出现短暂不稳定,第二台触发器的时序要求也能得到保证。因此第二台触发器的输出是可靠的。

综上所述: @D、 @pclk、 @hsync 和 @vsync 是可靠的寄存器(与 @bus_clk同步)。

回想一下, @bus_clk的频率是 100 MHz。另一方面, PCLK的频率是 25 MHz。因此,每四个时钟周期(clock cycles)中, @D、 @hsync 和 @vsync 的值应仅消耗一次。但这四个时钟周期中的哪一个呢?

答案在 Verilog 代码的这些行中:

wire       sample_valid;
   reg 	      previous_pclk;

   always @(posedge bus_clk)
     previous_pclk <= pclk;

   assign sample_valid = pclk && !previous_pclk;

这段代码的意思很简单: 如果 @pclk 现在较高,而之前的时钟周期(clock cycle)较低,则使用 @D、 @hsync 和 @vsync中的值。回想一下,当相机的 PCLK 从高变为低时,相机传感器的信号会改变其值。所以当 PCLK 从低电平到高电平变化时,其他信号都是稳定的。

但 @pclk、 @D、 @hsync 和 @vsync 是寄存器。这些寄存器与 @bus_clk同步,它们代表特定时间相机传感器信号的快照。逻辑不是在 PCLK 本身上检测上升沿(rising edge),而是对 @pclk执行类似的操作: 当 @pclk 的值由低变高时,就是使用另一个寄存器(registers)的值的正确时机。

该技术称为 01-signal 采样。该技术背后的想法在有关 01-signal 采样的单独页面上进行了详细解释。该页面还解释了此方法如何保证 FPGA的时序要求。通过这样做,逻辑可确保 @D、 @hsync 和 @vsync 中的值正确。

启动和停止数据流

现在我们来看看两个寄存器,它们旨在阻止向主机传输数据:

我现在将详细讨论这两个寄存器。一、 @wait_for_frame:

reg 	      wait_for_frame;

   always @(posedge bus_clk)
     if (!user_r_read_32_open)
       wait_for_frame <= 1;
     else if (sample_valid && vsync)
       wait_for_frame <= 0;

当设备文件未打开时,@wait_for_frame的值较高。该寄存器的值响应相机传感器的 vsync 信号而变为低值。该信号在帧之间的时间段内为高电平。换句话说,当 @vsync 为高电平时,相机不传输任何像素数据。

综上所述,当忽略相机的像素数据时, @wait_for_frame 为高: 当设备文件关闭时,或者最近打开设备文件但相机仍在帧中间时。

现在我将继续解释 @fifo_has_been_full: 确保到达主机的数据与相机传感器产生的数据相同非常重要。但是,如果计算机程序没有足够快地从设备文件读取数据,则 FIFO 中可能会出现溢出: DMA 缓冲区最终会变满,因此将没有地方可以复制 FIFO 的内容。因此, Xillybus IP core 将无法从 FIFO读取数据。当这种情况发生时, FIFO 就会变满,从而无法向其写入新数据。

还有逻辑也无法阻止这种情况。然而,逻辑可以保证到达主机的数据是连续的: 如果 FIFO 已满,逻辑将停止向 FIFO写入数据。另外,当 FIFO 已满后 FIFO 变空时,逻辑请求向主机发送 EOF 。结果,计算机程序接收在 FIFO 变满之前写入 FIFO 的所有数据。在该数据之后,计算机收到 EOF。这与到达常规文件末尾时发生的情况相同。

这种机制确保计算机程序可以相信到达的数据是正确且连续的。如果连续性丢失, EOF 会强制计算机程序关闭设备文件。如果程序再次打开设备文件,数据将从新的帧开始,这要归功于 @wait_for_frame。

这是 Verilog 代码中的相关部分:

reg 	      fifo_has_been_nonfull, fifo_has_been_full;
   wire       fifo_full;

   always @(posedge bus_clk)
     begin
	if (!fifo_full)
	  fifo_has_been_nonfull <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_nonfull <= 0;

	if (fifo_full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_full <= 0;
     end

   assign user_r_read_32_eof = fifo_has_been_full && user_r_read_32_empty;

当 FIFO 已满时,@fifo_has_been_full 为高电平。当设备文件未打开时,该寄存器变为低电平。当 @fifo_full 和 @fifo_has_been_nonfull 都为高电平时, @fifo_has_been_full 变为高电平。

@fifo_full 连接到 FIFO的 "full" 端口。但为什么需要 @fifo_has_been_nonfull ?原因是,只要 FIFO 保持在复位状态, FIFO 就经常将其“full”保持在高电平。该功能的目的是告诉应用逻辑 FIFO 尚未准备好接收数据。 @fifo_has_been_nonfull 的目的是防止 @fifo_has_been_full 在这种情况下错误地变高。

当 @fifo_has_been_full 和 @user_r_read_32_empty 都为高电平时,@user_r_read_32_eof 变为高电平。换句话说,当 FIFO 过去已满、现在为空时,将 EOF 发送到主机。请注意,在这种情况下,无论如何都不会向 FIFO 写入新数据。

有一个单独的页面讨论了用于确保数据连续性的类似解决方案。当 FIFO 的两侧属于不同的时钟域(clock domains)时,需要该页面上提供的解决方案。在本页提供的代码中, FIFO 仅与一台时钟同步。因此,本页中 @fifo_has_been_full 的实现更加简单。

向 FIFO写入数据

Verilog 代码的下一部分将像素数据写入 FIFO:

reg 	      fifo_wr_en;
   reg [1:0]  byte_position;
   reg [31:0] dataword;

   always @(posedge bus_clk)
     if (wait_for_frame)
       begin
	  byte_position <= 0;
	  fifo_wr_en <= 0;
       end
     else if (sample_valid && hsync)
       begin
	  case (byte_position)
	    0: dataword[7:0] <= D;
	    1: dataword[15:8] <= D;
	    2: dataword[23:16] <= D;
	    3: dataword[31:24] <= D;
	  endcase

	  if (byte_position == 3)
	    fifo_wr_en <= !fifo_has_been_full;
	  else
	    fifo_wr_en <= 0;

	  byte_position <= byte_position + 1;
       end
     else
       fifo_wr_en <= 0;

来自相机传感器的像素数据作为 8 位宽的数据元素到达。逻辑的这部分将这些数据元素重新组织成32位,以便可以将数据写入 FIFO。 8-bit Xillybus 数据流(/dev/xillybus_write_8) 不用于此目的有两个原因:

当 @wait_for_frame 为高电平时,由于以下两种可能性之一,不会向 FIFO 写入任何内容: 设备文件尚未开放,或者设备文件已开放,但新的帧尚未开始。

当相机传感器的 HSYNC 为高电平时,意味着数据信号包含有效像素。表达式“sample_valid && hsync”的值结合了两个条件: 当 @sample_valid 为高电平时, @hsync 和 @D 包含有效值。因此,如果 @hsync 为高电平,则 @D 的值被复制到 @dataword的一部分中。另外,如果 @D 被复制到 @dataword 的最后部分(即 @byte_position 等于 3),则 @fifo_wr_en 变高。结果, @dataword 被写入 FIFO。更准确地说, @fifo_wr_en 的表达式是这样的:

fifo_wr_en <= !fifo_has_been_full;

因此,如果 @fifo_has_been_full 为高电平,则不会向 FIFO写入任何内容,如前所述。

Verilog 代码与真实引脚的关系

上面的 Verilog 代码使用了名为 J6的 inout 端口,但是与这个端口的连接如何到达排针呢?答案可以在 xillydemo.xdc中找到。该文件是创建比特流的 Vivado 项目的一部分(位于“vivado-essentials”目录中)。

xillydemo.xdc 包含 FPGA 作为电子元件正常工作所需的各种信息。其中,该文件包含以下行:

[ ... ]

## J6 on board (BANK33 VADJ)
set_property PACKAGE_PIN U22  [get_ports {J6[0]}];   #J6/1  = IO_B33_LN2
set_property PACKAGE_PIN T22  [get_ports {J6[1]}];   #J6/2  = IO_B33_LP2
set_property PACKAGE_PIN W22  [get_ports {J6[2]}];   #J6/3  = IO_B33_LN3
set_property PACKAGE_PIN V22  [get_ports {J6[3]}];   #J6/4  = IO_B33_LP3
set_property PACKAGE_PIN Y21  [get_ports {J6[4]}];   #J6/5  = IO_B33_LN9
set_property PACKAGE_PIN Y20  [get_ports {J6[5]}];   #J6/6  = IO_B33_LP9
set_property PACKAGE_PIN AB22 [get_ports {J6[6]}];   #J6/7  = IO_B33_LN7
set_property PACKAGE_PIN AA22 [get_ports {J6[7]}];   #J6/8  = IO_B33_LP7

[ ... ]

第一行表示信号 J6[0] 应连接到 U22。这是 FPGA物理包装上的位置。根据 Smart Zynq的原理图(schematics),这个 FPGA 引脚连接到排针的第一个引脚。另一个端口的位置也以同样的方式定义。

结论

本页介绍了如何从 OV7670 获取像素数据并使用 Xillybus IP core将此数据发送到主机。

本教程的下一部分将介绍如何使用 Xillybus IP core 在 SCCB (即 I2C)的帮助下更改相机传感器的寄存器。这对于更改相机的参数很有用。特别是,这对于获得具有正确颜色的图像是必要的。

此页面由英文自动翻译。 如果有不清楚的地方,请参考原始页面
Copyright © 2021-2024. All rights reserved. (b4b9813f)