01signal.com

Escritura en los registros del sensor de cámara OV7670 mediante I2C con Smart Zynq

Esta página web pertenece a un grupo de pequeños proyectos que exploran las características de la placa Smart Zynq.

Este proyecto está también publicado en HelloFPGA, un sitio recomendado para los lectores chinos.

Introducción

Este tutorial explica cómo acceder a los registros del sensor de cámara OV7670 con una placa Smart Zynq. Es la continuación de una página anterior, en la que se describe cómo recibir los datos de vídeo del sensor de cámara.

El OV7670 dispone de una interfaz de bus de control de cámara serie (SCCB, del inglés Serial Camera Control Bus) para configurar los parámetros del sensor de cámara. El protocolo SCCB está definido en el documento de Omnivision titulado «OmniVision Serial Camera Control Bus (SCCB) Functional Specification». Este protocolo es compatible con el conocido protocolo I2C.

La principal motivación para acceder a los registros del sensor de cámara es lograr que la cámara produzca una imagen con colores correctos. Pero hay otras ventajas en controlar la cámara a través de sus registros: controlar la corriente eléctrica de las señales digitales, controlar y posiblemente detener el ajuste automático de brillo y color de la cámara, solicitar un patrón de prueba y mucho más.

El procesador del Zynq incluye dos unidades integradas que implementan cada una un maestro de bus I2C. Se podría usar una de ellas para comunicarse con el sensor de cámara. Desafortunadamente, al intentar usar estas unidades I2C integradas se comprobó que no funcionan bien con el OV7670. La razón es probablemente la gran cantidad de ruido en los cables. La falta de resistencias de pull-up dedicadas es otra posible razón (para este fin se usaron las pull-up internas de la FPGA).

Como no se pueden usar las unidades I2C integradas, se añade al proyecto un módulo Verilog. Esta lógica está diseñada para soportar mejor las señales con ruido.

Cambios en el proyecto Vivado

Las instrucciones que siguen se basan en el proyecto Vivado ya creado según la página anterior.

Descargue la implementación en Verilog del maestro de bus I2C desde este enlace. Copie este archivo en el directorio verilog/src/. Después añada el archivo al proyecto Vivado: haga clic en File > Add Sources… y elija «Add or create design sources». Luego haga clic en Next. Pulse el botón «Add Files» y elija el archivo llamado «i2c_if.v» del directorio verilog/src/. A continuación pulse el botón «Finish».

El procesador controla este módulo con la ayuda de dos flujos Xillybus. Abra verilog/src/xillydemo.v en un editor de texto. Elimine la parte del código etiquetada como «PART 3». En lugar de esa parte, inserte este fragmento de código:

   /*
    * 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)
      );

Alternativamente, puede descargar xillydemo.v con este cambio desde aquí.

Después de hacer este cambio, cree un archivo de flujo de bits (bitstream) como de costumbre.

Tenga en cuenta que el módulo i2c_if se conecta a J6[15] y J6[14]. Estos puertos se conectan a través del conector de pines al SCL y al SDA del módulo de cámara.

Cambio de los registros del sensor de cámara

El programa informático que envía los comandos I2C a la cámara se puede descargar desde este enlace. Copie este programa al sistema de archivos de Xillinux (por ejemplo, con scp o copiando directamente el archivo a la tarjeta TF).

Cambie al directorio donde se encuentra el archivo. Escriba este comando en el indicador de shell para compilar:

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

Este comando debería completarse sin mostrar ningún mensaje.

Esta es la forma de ejecutar el programa. También se muestra la salida que el programa genera normalmente:

# ./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

Primero, el programa lee los registros que contienen el identificador de producto del sensor de cámara. Hay distintas versiones del sensor de cámara OV7670. Este tutorial se basa en el sensor que se identifica con el número 0x7673 como identificador de producto. No es probable encontrarse con un sensor OV7670 que tenga un identificador distinto. Si el identificador es diferente, es posible que en el módulo de cámara esté montado otro modelo de sensor.

El programa hace los cambios mínimos necesarios para que los colores de la imagen del sensor de cámara sean correctos. Para ello se modifican tres registros.

Antes de hacer ningún cambio, el programa lee los valores actuales de los registros. Esas son las tres filas siguientes de la salida del programa. Después escribe los valores correctos en esos registros.

El significado de estos tres registros

Desgraciadamente, el significado de los registros del sensor de cámara está solo documentado parcialmente. Muchos de los registros del OV7670 están definidos como «reserved» (reservados). Por tanto, no hay explicación de por qué son necesarios algunos de los cambios en los registros. Hay muchas fuentes de información sobre los registros del OV7670. El mejor lugar para buscar pistas sobre los registros es el driver de Linux del sensor: ov7670.c. En particular, ov7670_default_regs[] (una variable definida en el driver) contiene muchas pistas valiosas.

Estas son las explicaciones disponibles de por qué el programa i2c.c cambia esos tres registros. Desafortunadamente, las razones de algunos de estos cambios no se conocen, aunque está claro que son necesarios.

Escritura en otros registros

Esta es la definición de @writelist, que se puede encontrar cerca del comienzo del programa 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
};

Los elementos de @writelist constan de dos números: el primero es la dirección del registro; el segundo es el valor que se debe escribir en ese registro.

Por ejemplo, el primer elemento es { 0x3d, 0x81 }. Esto significa que el valor 0x81 se escribe en COM13. La dirección de este registro es 0x3d.

El último elemento de la lista debe ser { -1, -1 }.

Reducción de la corriente de salida del sensor de cámara

Cuando los cables entre el sensor de cámara y la placa Smart Zynq son demasiado largos, es posible que la imagen de vídeo sea inestable: el fotograma de vídeo salta y aparecen franjas verdes y moradas en la imagen. Esto ocurre a menudo por la diafonía (crosstalk) entre los cables.

Puede que sea posible resolver este problema reduciendo la corriente eléctrica que el sensor de cámara aplica a los cables. Para ello, escriba el valor 0x00 en COM2. La dirección de este registro es 0x09.

En otras palabras, cambie la definición de @writelist a esta:

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

A continuación compile el programa y ejecútelo como antes.

Otras posibilidades

La documentación del sensor de cámara (en particular la OV7670/OV7171 CMOS VGA (640x480) CameraChip Implementation Guide) ofrece información sobre varios otros registros. Como ya se ha mencionado, el driver del kernel de Linux también puede dar pistas importantes.

Recuerde de la primera parte de este tutorial que es posible reiniciar el sensor de cámara con este comando:

# echo 1 > /dev/xillybus_write_32

Como resultado de este comando, todos los registros vuelven a sus valores por defecto.

Impresión de los valores de todos los registros

Esta es una parte de la función main() del programa i2c.c:

  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;
  }

La finalidad de esta parte es mostrar los valores de todos los registros. Normalmente no se llega a ejecutar porque la condición es «if (0)». Cámbiela a «if (1)» para obtener un listado de los valores de todos los registros.

Imprimir todos los registros debería tardar menos de un segundo. Si la ejecución del programa se detiene momentáneamente o se queda atascada, la razón son errores de comunicación en el bus I2C. Cuando esto ocurre, la salida del programa puede ser incorrecta o incompleta. Vuelva a ejecutar el programa hasta que corra con rapidez y sin problemas.

Puede descargar un listado de todos los registros en este enlace. Este listado refleja los registros cuando el sensor de cámara produce una imagen con colores correctos. El listado de los valores por defecto (inmediatamente después de reiniciar el sensor de cámara) se puede descargar aquí. Nótese que la cámara cambia continuamente algunos registros como consecuencia del control automático de brillo, equilibrio de blancos, etc.

Cómo se realiza una operación de escritura I2C

Esta sección requiere estar familiarizado con los fundamentos del protocolo I2C.

El programa i2c.c se comunica con el módulo i2c_if.v dentro de la FPGA mediante dos flujos Xillybus: /dev/xillybus_write_8 y /dev/xillybus_read_8.

Una operación de escritura I2C se desarrolla de la siguiente manera:

Estos pasos los implementa la función i2c_write():

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

  allwrite(sendbuf, sizeof(sendbuf));
}

Esta función prepara un búfer que consta de 3 bytes:

La FPGA envía estos tres bytes al sensor de cámara a través del bus I2C. La función i2c_write() abre /dev/xillybus_write_8, escribe los datos del búfer y cierra el archivo.

Según el protocolo I2C, el receptor debe confirmar (ACK) cada byte enviado por el bus: por cada byte, que consta de 8 bits, hay un noveno bit para este fin. Ese noveno bit tiene una ranura de tiempo especial durante la transmisión. El lado que recibe el byte debe llevar el cable SDA a '0' durante esa ranura para confirmar que el byte se ha recibido.

Si el sensor de cámara no responde de esta manera, el módulo i2c_if dentro de la FPGA se niega a aceptar más bytes a través del archivo de dispositivo. Esto no produce un error como tal, pero la llamada a close() no retorna hasta transcurridos 1000 ms. La razón es que el driver de Xillybus espera a que todos los datos pendientes lleguen a la FPGA antes de cerrar el archivo. Pero si el esclavo I2C no ha confirmado un byte, la FPGA se negará a aceptar el byte siguiente. En esta situación, el driver espera 1000 ms y luego cierra el archivo de todos modos, añadiendo este mensaje al registro del kernel:

Timed out while flushing. Output data may be lost.

Los mensajes del registro del kernel (kernel log) se pueden ver con el comando «dmesg».

En conclusión, si una llamada a i2c_write() tarda un segundo en completarse, la razón es probablemente que el sensor de cámara no respondió correctamente a las operaciones del bus I2C. Es posible que el sensor de cámara esté mal conectado a la FPGA, o que no esté conectado en absoluto.

Cómo se realiza una operación de lectura I2C

La operación de lectura es más complicada, porque consta de dos operaciones separadas:

La función i2c_read() se muestra a continuación:

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);
}

Esta función comienza enviando dos bytes (@cmdbuf) al esclavo:

A continuación, i2c_read() abre /dev/xillybus_read_8. Nótese que esta apertura no la realiza allread(), a diferencia de allwrite().

Después, i2c_read() escribe dos bytes (@dummybuf) en el bus mediante allwrite():

El módulo i2c_if de la FPGA examina el bit 0 del primer byte que recibe. A partir de ahí, la FPGA deduce si debe realizarse una operación de escritura o de lectura en el bus. Si se trata de una lectura, el contenido de todos los demás bytes se ignora. Esos bytes solo sirven para informar a la FPGA de cuántos bytes debe recibir.

El módulo i2c_if lee del bus el número de bytes solicitado y los envía al host a través de /dev/xillybus_read_8. Este archivo de dispositivo debe estar abierto antes de que se inicie la operación de lectura en el bus, la cual se inicia al escribir @dummybuf. Después, allread() lee el valor del registro. allread() no abre ni cierra el archivo, porque el archivo debe estar abierto desde el principio.

Reinicio del bus

Esta sección no es relevante para el sensor de cámara OV7670. Pero esta información puede ser útil si se usa i2c_if con otro esclavo.

Obsérvese que i2c_read() llama a allwrite() dos veces. Cada vez se abre y se cierra /dev/xillybus_write_8. Como resultado, se produce una condición de inicio (start) antes de enviar los datos y una condición de parada (stop) después.

En otras palabras, hay una condición de parada después de que la dirección del registro se haya enviado al esclavo. Después hay una condición de inicio antes de que el maestro comience la operación de lectura.

El sensor de cámara espera esta secuencia de eventos. Sin embargo, otros componentes electrónicos que actúan como esclavos I2C no funcionan correctamente si se intenta una lectura de esta manera: estos componentes olvidan la dirección del registro a causa de la condición de parada. Por tanto, es necesario generar una condición de reinicio (restart) entre la primera y la segunda acción en el bus.

El módulo i2c_if admite esta posibilidad: si se cierra /dev/xillybus_write_8 y se vuelve a abrir mientras /dev/xillybus_read_8 permanece abierto, se generará una condición de reinicio (restart) en el bus. En otras palabras, si el esclavo requiere una condición de reinicio, la llamada a allwrite() debe cambiarse de sitio. Entonces i2c_read() quedaría así:

  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);

Una vez más, este código no es adecuado para el OV7670.

Conclusión

Es posible usar el núcleo IP (IP core) de Xillybus para acceder a los registros del sensor de cámara. Se necesita un módulo adicional para la interfaz con el bus I2C: i2c_if. Este módulo también es útil para comunicarse con otros esclavos I2C.

La información disponible sobre los registros del módulo de cámara OV7670 es, desgraciadamente, escasa. Por tanto, puede ser necesario buscar soluciones en Internet o ayudarse del driver de Linux del sensor de cámara.

Esta página se ha traducido del inglés mediante traducción automática. En caso de duda, consulta el texto original.
Copyright © 2021-2026. All rights reserved. (dcc38493)