01signal.com

使用 Smart Zynq通过 I2C 写入 OV7670 camera sensor的 registers

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

介绍

本教程介绍如何使用 Smart Zynq 板访问 OV7670 camera sensor的 registers 。这是上一页的后续内容,上一页描述了如何从相机传感器接收视频数据。

OV7670 有一个 Serial Camera Control Bus 接口(SCCB),用于配置相机传感器的参数。 SCCB protocol 在 Omnivision的名为“OmniVision Serial Camera Control Bus (SCCB) Functional Specification”的文档中定义。这款 protocol 与众所周知的 I2C protocol兼容。

访问相机传感器 registers 的主要动机是让相机生成具有正确颜色的图像。然而,通过 registers控制相机还有其他优点: 控制数字信号的电流,控制并可能停止相机的亮度和颜色自动调整,要求 test pattern 等等。

Zynq的 processor 有两个内置单元,每个单元实现一个 I2C bus master 。可以使用这些单元之一来与相机传感器通信。不幸的是,尝试使用这些内置 I2C 单元后发现它们不能与 OV7670很好地配合使用。原因可能是电线上存在大量噪音。缺乏专用的 pull-up resistors 是另一个可能的原因( FPGA的内部 pull-ups 用于此目的)。

由于无法使用内置 I2C 单元,因此在项目中添加了 Verilog module 。这款 logic 旨在更好地应对噪声信号。

Vivado 项目的更改

下面的说明基于已根据上一页创建的 Vivado 项目。

从此链接下载 I2C bus master 的 Verilog 实现。将此文件复制到 verilog/src/ 目录中。然后将该文件添加到 Vivado 项目中: 单击 File > Add Sources… 并选择“Add or create design sources”。然后单击“ Next”。单击“Add Files”按钮并选择 verilog/src/ 目录中名为“i2c_if.v”的文件。然后单击“Finish”按钮。

processor 在两个 Xillybus streams的帮助下控制这台 module 。在文本编辑器中打开 verilog/src/xillydemo.v 。删除标记为“PART 3”的代码部分。插入以下代码片段来代替该部分:

/*
    * PART 3
    * ======
    *
    * The instantiation of i2c_if demonstrates how to use two Xillybus
    * streams to implement an I2C interface with the camera sensor module.
    *
    */

   i2c_if i2c_if_ins
     (
      .bus_clk(bus_clk),
      .quiesce(quiesce),

      .i2c_clk(J6[15]),
      .i2c_data(J6[14]),

      .user_w_write_8_open(user_w_write_8_open),
      .user_w_write_8_wren(user_w_write_8_wren),
      .user_w_write_8_data(user_w_write_8_data),
      .user_w_write_8_full(user_w_write_8_full),

      .user_r_read_8_open(user_r_read_8_open),
      .user_r_read_8_rden(user_r_read_8_rden),
      .user_r_read_8_data(user_r_read_8_data),
      .user_r_read_8_empty(user_r_read_8_empty),
      .user_r_read_8_eof(user_r_read_8_eof)
      );

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

进行此更改后照常创建 bitstream 文件。

请注意, i2c_if module 连接到 J6[15] 和 J6[14]。这些 ports 通过 pin header 连接到相机模块的 SCL 和 SDA。

更改相机传感器的 registers

可以从此链接下载向相机发送 I2C 命令的计算机程序。将此程序复制到 Xillinux的 file system 中(例如使用 scp 或直接将文件复制到 TF card中)。

将目录更改为文件所在的位置。在 shell prompt 处键入以下命令以执行 compilation:

# gcc -Wall -O3 -o i2c i2c.c

该命令应该静默完成。

这就是程序的运行方式。还显示了程序正常生成的输出:

# ./i2c
Camera sensor's product ID is 0x7673
Reg 0x3d = 0x88 (to be altered)
Reg 0xb0 = 0x00 (to be altered)
Reg 0x6f = 0x9a (to be altered)
Wrote 0x3d = 0x81
Wrote 0xb0 = 0x84
Wrote 0x6f = 0x9f

首先,程序读取包含相机传感器 product ID的 registers 。 OV7670 相机传感器有不同版本。本教程基于使用编号 0x7673 (如 product ID)标识自身的相机传感器。它不太可能遇到 OV7670 相机传感器与不同的 product ID。如果 product ID 不同,则相机模块上可能安装了其他型号的相机传感器。

该程序进行最少的必要更改,以使相机传感器图像的颜色正确。为此更换了三台 registers 。

在进行任何更改之前,程序会读取 registers的现有值。这些是程序输出中的接下来的三行。然后程序将正确的值写入这些 registers。

registers这三个的含义

不幸的是,相机传感器 registers 的含义仅被部分记录。很多 OV7670的 registers 都被定义为“reserved”。因此,没有解释为什么 registers 中的一些改变是必要的。关于 OV7670和 registers的信息来源有很多。寻找有关 registers 提示的最佳位置是相机传感器的 Linux driver: ov7670.c。特别是, ov7670_default_regs[] ( driver中定义的 variable )包含许多有价值的提示。

这些是关于为什么 i2c.c 程序改变三个 registers的解释。不幸的是,尽管这些改变显然是必要的,但其中一些改变的原因尚不清楚。

写入其他 registers

这是 @writelist的定义,可以在 i2c.c 程序的开头附近找到:

static const struct {
  int addr;
  int value;
} writelist [] = {
  { 0x3d, 0x81 }, // COM13, swap UV, turn off reserved bit 3
  { 0xb0, 0x84 },
  { 0x6f, 0x9f }, // AWBCTR0, crucial for white balance

  { -1, -1 }, // Terminate
};

@writelist array 的 elements 由两个数字组成: 第一个数字是 register的地址。第二个数字是应写入该 register的值。

例如,第一个 element 是 { 0x3d, 0x81 }。这意味着值 0x81 被写入 COM13。这个 register的 address 就是 0x3d。

数组中的最后一个元素必须是 { -1, -1 }。

减少相机传感器的 drive current

当摄像头传感器与 Smart Zynq 板之间的连线过长时,可能会导致视频图像不稳定: 视频帧跳跃,图像上出现绿色和紫色条纹。这种情况经常发生是因为电线之间出现 crosstalk 。

通过减少相机传感器施加到电线上的电流可以解决这个问题。为此,请将值 0x00 写入 COM2。这个 register的 address 就是 0x09。

换句话说,将 @writelist的定义改为:

static const struct {
  int addr;
  int value;
} writelist [] = {
  { 0x09, 0x00 }, // Drive current to 1x level
  { -1, -1 }, // Terminate
};

然后执行该程序的 compilation 并像以前一样运行该程序。

其他可能性

相机传感器的文档(特别是OV7670/OV7171 CMOS VGA (640x480) CameraChip Implementation Guide )提供了有关其他几个 registers的信息。如上所述, Linux kernel的driver 也可以提供重要提示。

回想一下本教程的第一部分,可以使用以下命令重置相机传感器:

# echo 1 > /dev/xillybus_write_32

所有 registers 都会因该命令而返回到其默认值。

打印出所有 registers的值

这是 i2c.c 程序中 main() 函数的一部分:

if (0) { // Change this in order to print out registers instead
    for (i=0; i<=0xc9; i++) {
      i2c_read(i, &value);

      printf("Reg 0x%02x = 0x%02x\n", i, value);
    }

    return 0;
  }

这部分的目的是展示所有 registers的数值。由于“if (0)”条件,通常永远不会到达此部分。将其更改为“if (1)”以获得所有 registers值的打印输出。

打印出所有 registers 的时间应该不到一秒。如果程序执行暂时暂停或程序卡住,则原因是 I2C bus上的通信错误。发生这种情况时,程序的输出可能不正确或不完整。重新运行该程序,直至运行快速、流畅。

所有 registers 的打印输出均可在此链接下载。当相机传感器生成具有正确颜色的图像时,此打印输出反映了 registers 。默认值的打印输出(相机传感器重置后立即)可以在此处下载。请注意,由于自动亮度控制、白平衡等,相机会不断改变一些 registers 。

I2C 写操作是如何执行的

本节需要熟悉 I2C protocol的基础知识。

i2c.c 程序通过两个 Xillybus streams与 FPGA 内部的 i2c_if.v module 进行通信: /dev/xillybus_write_8 和 /dev/xillybus_read_8。

I2C write operation 的发生过程如下:

这些步骤由 i2c_write() 函数实现:

static void i2c_write(int addr, unsigned char data) {
  unsigned char sendbuf[3] = { i2c_addr << 1, addr, data };

  allwrite(sendbuf, sizeof(sendbuf));
}

该函数准备一个由 3 个字节组成的 buffer :

FPGA 通过 I2C bus将这三个字节发送到相机传感器。 i2c_write() 函数打开 /dev/xillybus_write_8,从 buffer写入数据,然后关闭文件。

根据 I2C protocol,接收方必须确认 bus上发送的每个字节: 对于每个字节(由 8 位组成),有一个第九位用于此目的。这第九位在传输期间有一个特殊的时隙。接收字节的一侧必须在该时隙期间将 SDA 线拉至 '0' ,以确认已接收到该字节。

如果相机传感器不以这种方式响应, FPGA 内部的 i2c_if module 将拒绝通过 device file接受更多字节。这不会导致错误,但对 close() 的函数调用只有在延迟 1000 ms后才会返回。原因是 Xillybus的 driver 在关闭文件之前等待所有剩余数据到达 FPGA 。但如果 I2C slave 还没有确认一个字节, FPGA 将拒绝接受下一个字节。在这种情况下, driver 等待 1000 ms,然后关闭文件,并将此消息添加到 kernel log:

Timed out while flushing. Output data may be lost.

kernel log 的消息可以使用“dmesg”命令查看。

总之,如果对 i2c_write() 的函数调用需要一秒钟才能完成,原因可能是相机传感器没有正确响应 I2C bus 操作。摄像头传感器可能未正确连接到 FPGA,或者根本没有连接。

I2C 的读操作是如何执行的

读操作更复杂,因为它由两个单独的操作组成:

i2c_read() 功能如下图:

static void i2c_read(int addr, unsigned char *data) {
  int fdr;

  unsigned char cmdbuf[2] = { i2c_addr << 1, addr };
  unsigned char dummybuf[2] = { (i2c_addr << 1) | 1, 0 };

  allwrite(cmdbuf, sizeof(cmdbuf));

  // We open xillybus_read_8 only now. Had it been open during the first
  // operation, there would have been a restart condition rather than a
  // stop condition after the first command.

  fdr = open("/dev/xillybus_read_8", O_RDONLY);

  if (fdr < 0) {
    perror("Failed to open /dev/xillybus_read_8 read-only");
    exit(1);
  }

  allwrite(dummybuf, sizeof(dummybuf));

  allread(fdr, data, sizeof(*data));

  close(fdr);
}

该函数首先向 slave发送两个字节 (@cmdbuf):

i2c_read() 然后打开 /dev/xillybus_read_8。请注意,与 allwrite()相比, allread()没有这样做。

接下来, i2c_read() 借助 allwrite()向 bus 写入两个字节(@dummybuf):

FPGA 中的 i2c_if module 检查它接收到的第一个字节的 bit 0 。 FPGA 据此推断 bus应该进行写操作还是读操作。如果需要读取操作,则忽略所有其他字节的内容。这些字节仅用于通知 FPGA 应接收多少字节。

i2c_if module 从 bus 读取请求的字节数,并通过 /dev/xillybus_read_8将其发送到 host 。在通过写入 @dummybuf启动 bus 上的读操作之前,必须打开该 device file 。此后, allread() 读取 register的值。 allread() 不会打开和关闭文件,因为文件必须提前打开。

Bus restart

本节与 OV7670 相机传感器无关。但如果 i2c_if 与不同的 slave一起使用,此信息可能有用。

请注意, i2c_read() 对 allwrite() 执行了两次函数调用。每次, /dev/xillybus_write_8 都会打开和关闭。这样一来,数据发送之前有一个 I2C start condition ,之后有一个 I2C stop condition 。

也就是说, register的 address 发送到 slave之后,还有一个 stop condition 。然后在 master 开始读操作之前还有一个 start condition 。

相机传感器预计会发生这一系列事件。但是,如果尝试执行如下读取操作,则充当 I2C slaves 的其他电子组件将无法正常工作: 这些组件忘记了 register的 address 以响应 stop condition。因此,有必要在 bus上的第一个和第二个操作之间生成一个 restart condition 。

i2c_if module 支持这种可能性: 如果 /dev/xillybus_write_8 关闭然后重新打开,而 /dev/xillybus_read_8 持续打开,那么 bus 上就会出现一个 restart condition 。换句话说,如果 slave 需要 restart condition,则需要移动对 allwrite() 的函数调用。那么 i2c_read() 将会是这样的:

fdr = open("/dev/xillybus_read_8", O_RDONLY);

  if (fdr < 0) {
    [ ... ]
  }

  allwrite(cmdbuf, sizeof(cmdbuf));
  allwrite(dummybuf, sizeof(dummybuf));

  allread(fdr, data, sizeof(*data));

  close(fdr);

再次强调,此代码不适合 OV7670。

结论

可以使用 Xillybus IP core 访问相机传感器的 registers。与 I2C bus连接需要一个附加模块: i2c_if。该模块对于与其他 I2C slaves通信也很有用。

不幸的是,缺乏有关 OV7670 相机模块寄存器的可用信息。因此,可能需要在互联网上搜索解决方案或向相机传感器的 Linux driver寻求帮助。

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