01signal.com

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

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

介绍

本教程介绍如何将 OV7670 camera module 连接到 Smart Zynq 并在板子本身的 HDMI output上查看实时视频信号。我还将展示如何借助简单的 Linux 命令轻松地将 raw video stream 保存到文件中。

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

camera module 连接到 Zynq chip的 PL (FPGA) 部分。因此,在将 image data 发送到 ARM processor之前,可以轻松添加实现 image processing 的 logic 。由于本教程基于 Xillinux,因此系统的 processor 部分由完整的 Linux distribution组成。

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

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

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

OV7670 module

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

The OV7670 camera sensor module used in this project

在这个模块上,大部分 pins 、 pin header 都直接连接到 OV7670 组件。仅 3.3V 和 GND 连接到 voltage regulators。因此, FPGA 和模块之间的所有连接都是 FPGA 和 OV7670 组件之间的直接连接。

市场上还有其他具有相同功能的模块。使用这些其他模块可能也可以。但是, OV7670 组件有不同的修订版。可以验证模块上使用的版本是否正确。本教程的第二部分解释了如何执行此操作。

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

准备 Vivado 项目

从 demo bundle的 zip 文件( 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,则可能需要更换 register 以减少 OV7670的 I/O driver current。本教程的第二部分展示了如何进行此更改。

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

OV7670 module connected to the Smart Zynq board

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

OV7670 module connected to the Smart Zynq board

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

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

请注意, pin header的最后一个 pin 是 5V,因此不要将电线连接到此 pin。

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

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 的 bitstream 文件启动 Xillinux (如上所示)。

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

# cat /dev/xillybus_read_32 > clip.raw

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

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

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

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

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

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

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

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

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

raw frame 的格式是 UYVY 4:2:2。换句话说,每个像素由 16 bits组成。第一个字节是第一个像素的 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 processor 不够强大,无法以正确的帧率(31.25 fps)播放视频。如果您尝试这样做,视频只会短暂播放。当 FPGA内部有 overflow 时,数据流停止。

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

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

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

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

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

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

本页实用部分结束

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

FPGA 与 host之间的通信

本示例中的 logic 基于 Xillybus IP core。该 IP core 负责与 host的通信。

回想一下上面 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 个 ports ,用于从 FIFO读取数据: rd_en、 dout 和 empty。这些 ports 连接到 Xillybus IP core。这使得 IP core 可以从 FIFO 读取数据并将该数据发送到 host。结果是写入 FIFO 的所有内容都到达名为 /dev/xillybus_read_32的 device file 。换句话说, host 上的普通计算机程序可以将 /dev/xillybus_read_32 作为常规文件打开。当程序读取该文件时,它接收到 FPGA内部的 application logic 已写入 FIFO 的数据。

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

# cat /dev/xillybus_read_32 > clip.raw

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

数据流可以用下图总结:

Simplified diagram of data acquisition with Xillybus

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

请注意, user_r_read_32_open 连接到 FIFO的 srst port。当 /dev/xillybus_read_32 在 host上打开时,这个 signal 变为高电平。 signal 与 NOT连接,因此当 device file 关闭时, 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。这个 clock 在 @clkdiv的帮助下除以四。因此,相机模块接收 25 MHz reference clock。根据相机的数据表,这是允许的频率。然而,当 reference clock的频率为 24 MHz时,摄像机被设计为产生 30 fps 视频流。因此,实际视频帧速率略高: 31.25 fps。

我应该指出,这通常是创建 clock的错误方法。正确的方法是使用 PLL 或类似的资源。在这种具体情况下该方法没有问题,因为 @clkdiv 仅用于创建 output signal: FPGA自带的 logic 不使用该信号。

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 pin。相机永远不会断电。

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

# echo 1 > /dev/xillybus_write_32

该命令会短暂打开 device file ,从而达到预期的结果。

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

OV7670 从 FPGA生成与 reference clock 频率相同的 pixel clock 。也就是说, 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的 falling edge 保持一致。从 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 不被视为 clock,而是被视为 data signal。下面我将简要解释该技术。

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

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

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

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

答案在 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 是 registers。这些 registers 与 @bus_clk同步,它们代表特定时间相机传感器信号的快照。 logic 不是在 PCLK 本身上检测 rising edge ,而是对 @pclk执行类似的操作: 当 @pclk 的值由低变高时,就是使用另一个 registers的值的正确时机。

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

启动和停止数据流

现在我们来看看两个 registers ,它们旨在阻止向 host传输数据:

我现在将详细讨论这两个 registers 。一、 @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;

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

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

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

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

这种机制确保计算机程序可以相信到达的数据是正确且连续的。如果连续性丢失, EOF 会强制计算机程序关闭 device file。如果程序再次打开 device file ,数据将从新的帧开始,这要归功于 @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 为高电平。当 device file 未打开时,该 register 变为低电平。当 @fifo_full 和 @fifo_has_been_nonfull 都为高电平时, @fifo_has_been_full 变为高电平。

@fifo_full 连接到 FIFO的 "full" port。但为什么需要 @fifo_has_been_nonfull ?原因是,只要 FIFO 保持在 reset 状态, FIFO 就经常将其“full”保持在高电平。该功能的目的是告诉 application logic 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 发送到 host 。请注意,在这种情况下,无论如何都不会向 FIFO 写入新数据。

有一个单独的页面讨论了用于确保数据连续性的类似解决方案。当 FIFO 的两侧属于不同的 clock domains时,需要该页面上提供的解决方案。在本页提供的代码中, FIFO 仅与一台 clock同步。因此,本页中 @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 位宽的数据元素到达。 logic 的这部分将这些数据元素重新组织成32位,以便可以将数据写入 FIFO。 8-bit Xillybus stream (/dev/xillybus_write_8) 不用于此目的有两个原因:

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

当相机传感器的 HSYNC 为高电平时,意味着 data signals 包含有效像素。表达式“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 代码与真实 pins的关系

上面的 Verilog 代码使用了名为 J6的 inout port ,但是与这个 port 的连接如何到达 pin header呢?答案可以在 xillydemo.xdc中找到。该文件是创建 bitstream 的 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 pin 连接到 pin header的第一个 pin 。另一个 ports 的位置也以同样的方式定义。

结论

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

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

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