01signal.com

Schreiben auf die Register des OV7670-Kamerasensors über I2C mit Smart Zynq

Diese Webseite gehört zu einer Gruppe kleiner Projekte, die die Funktionen des Smart-Zynq-Boards untersuchen.

Dieses Projekt ist auch bei HelloFPGA veröffentlicht, das chinesischen Lesern empfohlen wird.

Einführung

Dieses Tutorial erklärt, wie man mit einem Smart-Zynq-Board auf die Register des OV7670-Kamerasensors zugreift. Es ist die Fortsetzung einer vorherigen Seite, auf der beschrieben wird, wie die Videodaten vom Kamerasensor empfangen werden.

Der OV7670 besitzt eine SCCB-Schnittstelle (Serial Camera Control Bus), mit der die Parameter des Kamerasensors konfiguriert werden. Das SCCB-Protokoll ist in OmniVisions Dokument mit dem Titel „OmniVision Serial Camera Control Bus (SCCB) Functional Specification“ definiert. Dieses Protokoll ist mit dem bekannten I2C-Protokoll kompatibel.

Der Hauptgrund für den Zugriff auf die Register des Kamerasensors ist, die Kamera dazu zu bringen, ein Bild mit korrekten Farben zu erzeugen. Es gibt jedoch weitere Vorteile, die Kamera über ihre Register zu steuern: Steuerung des elektrischen Stroms der digitalen Signale, Steuerung und möglicherweise Abschaltung der automatischen Helligkeits- und Farbanpassung der Kamera, Anfordern eines Testmusters und vieles mehr.

Der Prozessor des Zynq besitzt zwei eingebaute Einheiten, die jeweils einen I2C-Bus-Master implementieren. Man kann eine dieser Einheiten für die Kommunikation mit dem Kamerasensor verwenden. Ein Versuch mit diesen eingebauten I2C-Einheiten hat allerdings gezeigt, dass sie mit dem OV7670 nicht gut funktionieren. Der Grund ist vermutlich das starke Rauschen auf den Leitungen. Auch das Fehlen eigener Pull-up-Widerstände kommt infrage (die internen Pull-ups des FPGA wurden dafür benutzt).

Da die eingebauten I2C-Einheiten nicht verwendet werden können, wird stattdessen ein Verilog-Modul zum Projekt hinzugefügt. Diese Logik ist dafür ausgelegt, mit verrauschten Signalen besser zurechtzukommen.

Änderungen am Vivado-Projekt

Die folgenden Anweisungen basieren auf dem Vivado-Projekt, das bereits gemäß der vorherigen Seite erstellt wurde.

Laden Sie die Verilog-Implementierung des I2C-Bus-Masters unter diesem Link herunter. Kopieren Sie diese Datei in das Verzeichnis verilog/src/. Fügen Sie die Datei dann zum Vivado-Projekt hinzu: Klicken Sie auf File > Add Sources… und wählen Sie „Add or create design sources“. Klicken Sie dann auf Next. Klicken Sie auf die Schaltfläche „Add Files“ und wählen Sie die Datei „i2c_if.v“ im Verzeichnis verilog/src/. Klicken Sie anschließend auf „Finish“.

Der Prozessor steuert dieses Modul mit Hilfe von zwei Xillybus-Datenströmen. Öffnen Sie verilog/src/xillydemo.v in einem Texteditor. Löschen Sie den Teil des Codes, der mit „PART 3“ gekennzeichnet ist. Fügen Sie anstelle dieses Teils den folgenden Codeschnipsel ein:

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

Alternativ können Sie xillydemo.v nach dieser Änderung von hier herunterladen.

Erzeugen Sie nach dieser Änderung wie üblich eine Bitstromdatei (bitstream).

Beachten Sie, dass das i2c_if-Modul mit J6[15] und J6[14] verbunden ist. Diese Ports sind über die Stiftleiste mit den Anschlüssen SCL und SDA des Kameramoduls verbunden.

Ändern der Register des Kamerasensors

Das Computerprogramm, das I2C-Befehle an die Kamera sendet, kann unter diesem Link heruntergeladen werden. Kopieren Sie dieses Programm in das Dateisystem von Xillinux (zum Beispiel mit scp oder indem Sie die Datei direkt auf die TF-Karte kopieren).

Wechseln Sie in das Verzeichnis, in dem sich die Datei befindet. Geben Sie an der Shell-Eingabeaufforderung den folgenden Befehl ein, um die Kompilierung durchzuführen:

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

Dieser Befehl sollte ohne Meldungen durchlaufen.

So führen Sie das Programm aus. Dabei ist auch die Ausgabe gezeigt, die das Programm normalerweise erzeugt:

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

Zuerst liest das Programm die Register, die die Produkt-ID des Kamerasensors enthalten. Es gibt verschiedene Versionen des OV7670-Kamerasensors. Dieses Tutorial basiert auf dem Kamerasensor, der sich mit der Nummer 0x7673 als Produkt-ID ausweist. Es ist unwahrscheinlich, auf einen OV7670-Kamerasensor mit einer anderen Produkt-ID zu stoßen. Ist die Produkt-ID anders, wurde auf dem Kameramodul möglicherweise ein anderes Modell des Kamerasensors verbaut.

Das Programm nimmt die minimal notwendigen Änderungen vor, damit die Farben des Bildes des Kamerasensors korrekt sind. Dazu werden drei Register geändert.

Bevor das Programm Änderungen vornimmt, liest es die vorhandenen Werte der Register. Das sind die nächsten drei Zeilen in der Ausgabe des Programms. Danach schreibt das Programm die korrekten Werte in diese Register.

Die Bedeutung dieser drei Register

Leider ist die Bedeutung der Register des Kamerasensors nur teilweise dokumentiert. Viele Register des OV7670 sind als „reserved“ definiert. Deshalb gibt es keine Erklärung dafür, warum einige der Änderungen an den Registern notwendig sind. Es gibt viele Quellen über die Register des OV7670. Der beste Ort, um Hinweise zu den Registern zu finden, ist der Linux-Treiber des Kamerasensors: ov7670.c. Insbesondere ov7670_default_regs[] (eine Variable im Treiber) enthält viele wertvolle Hinweise.

Das sind die verfügbaren Erklärungen dafür, warum das Programm i2c.c diese drei Register ändert. Leider sind die Gründe für einige dieser Änderungen nicht bekannt, auch wenn klar ist, dass diese Änderungen notwendig sind.

Schreiben in andere Register

Das ist die Definition von @writelist, die nahe am Anfang des Programms i2c.c zu finden ist:

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

Die Elemente des Arrays @writelist bestehen aus zwei Zahlen: Die erste Zahl ist die Adresse des Registers. Die zweite Zahl ist der Wert, der in dieses Register geschrieben werden soll.

Das erste Element ist beispielsweise { 0x3d, 0x81 }. Das bedeutet, dass der Wert 0x81 in COM13 geschrieben wird. Die Adresse dieses Registers ist 0x3d.

Das letzte Element im Array muss { -1, -1 } sein.

Reduzieren des Treiberstroms des Kamerasensors

Wenn die Kabel zwischen dem Kamerasensor und dem Smart-Zynq-Board zu lang sind, kann das Videobild instabil werden: Das Bild springt, und im Bild erscheinen grüne und violette Streifen. Das passiert häufig wegen Übersprechens (Crosstalk) zwischen den Kabeln.

Möglicherweise lässt sich dieses Problem beheben, indem man den elektrischen Strom verringert, mit dem der Kamerasensor die Leitungen ansteuert. Schreiben Sie dazu den Wert 0x00 in COM2. Die Adresse dieses Registers ist 0x09.

Ändern Sie also die Definition von @writelist folgendermaßen:

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

Kompilieren Sie das Programm anschließend und führen Sie es wie zuvor aus.

Weitere Möglichkeiten

Die Dokumentation des Kamerasensors (insbesondere der OV7670/OV7171 CMOS VGA (640x480) CameraChip Implementation Guide) bietet Informationen zu einigen weiteren Registern. Wie bereits erwähnt, kann auch der Treiber des Linux-Kernels wichtige Hinweise liefern.

Zur Erinnerung aus dem ersten Teil dieses Tutorials: Man kann den Kamerasensor mit diesem Befehl zurücksetzen:

# echo 1 > /dev/xillybus_write_32

Durch diesen Befehl kehren alle Register zu ihren Standardwerten zurück.

Die Werte aller Register ausgeben

Das ist ein Teil der Funktion main() im Programm 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;
  }

Der Zweck dieses Teils ist, die Werte aller Register anzuzeigen. Wegen der Bedingung „if (0)“ wird dieser Teil normalerweise nie erreicht. Ändern Sie das in „if (1)“, um eine Ausgabe aller Registerwerte zu erhalten.

Das Ausdrucken aller Register sollte weniger als eine Sekunde dauern. Wenn das Programm kurz anhält oder hängen bleibt, liegt die Ursache in Kommunikationsfehlern auf dem I2C-Bus. In diesem Fall kann die Ausgabe des Programms falsch oder unvollständig sein. Führen Sie das Programm erneut aus, bis es schnell und reibungslos läuft.

Eine Ausgabe aller Register kann unter diesem Link heruntergeladen werden. Diese Ausgabe zeigt die Register, wenn der Kamerasensor ein Bild mit korrekten Farben erzeugt. Die Ausgabe der Standardwerte (unmittelbar nach dem Zurücksetzen des Kamerasensors) kann hier heruntergeladen werden. Beachten Sie, dass die Kamera infolge der automatischen Belichtungssteuerung, des Weißabgleichs usw. laufend einige Register ändert.

Wie ein I2C-Schreibvorgang abläuft

Dieser Abschnitt setzt Grundkenntnisse des I2C-Protokolls voraus.

Das Programm i2c.c kommuniziert über zwei Xillybus-Datenströme mit dem Modul i2c_if.v im FPGA: /dev/xillybus_write_8 und /dev/xillybus_read_8.

Ein I2C-Schreibvorgang läuft folgendermaßen ab:

Diese Schritte sind in der Funktion i2c_write() implementiert:

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

  allwrite(sendbuf, sizeof(sendbuf));
}

Diese Funktion bereitet einen Puffer vor, der aus drei Bytes besteht:

Das FPGA sendet diese drei Bytes über den I2C-Bus an den Kamerasensor. Die Funktion i2c_write() öffnet /dev/xillybus_write_8, schreibt die Daten aus dem Puffer und schließt die Datei.

Gemäß I2C-Protokoll muss der Empfänger jedes gesendete Byte bestätigen: Auf jedes Byte (das aus acht Bits besteht) folgt zu diesem Zweck ein neuntes Bit. Dieses neunte Bit hat während der Übertragung einen eigenen Zeitschlitz. Die Seite, die das Byte empfangen hat, muss die SDA-Leitung während dieses Zeitschlitzes auf „0“ ziehen, um zu bestätigen, dass das Byte empfangen wurde.

Wenn der Kamerasensor nicht auf diese Weise antwortet, verweigert das Modul i2c_if im FPGA die Annahme weiterer Bytes über die Gerätedatei. Das führt nicht direkt zu einem Fehler, aber der Aufruf von close() kehrt erst nach einer Verzögerung von 1000 ms zurück. Der Grund ist, dass der Xillybus-Treiber vor dem Schließen der Datei darauf wartet, dass alle restlichen Daten das FPGA erreichen. Hat der I2C-Slave aber ein Byte nicht bestätigt, verweigert das FPGA die Annahme des nächsten Bytes. In dieser Situation wartet der Treiber 1000 ms und schließt die Datei dann trotzdem. Dabei fügt er dem Kernel-Log diese Meldung hinzu:

Timed out while flushing. Output data may be lost.

Die Meldungen des Kernel-Logs können mit dem Befehl dmesg angezeigt werden.

Zusammenfassend gilt: Wenn ein Funktionsaufruf von i2c_write() eine Sekunde dauert, hat der Kamerasensor vermutlich nicht richtig auf die I2C-Busoperationen reagiert. Es ist möglich, dass der Kamerasensor nicht richtig mit dem FPGA verbunden ist oder gar nicht angeschlossen wurde.

Wie ein I2C-Lesevorgang abläuft

Der Lesevorgang ist komplizierter, weil er aus zwei getrennten Vorgängen besteht:

Die Funktion i2c_read() ist unten dargestellt:

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

Diese Funktion beginnt damit, zwei Bytes (@cmdbuf) an den Slave zu senden:

Danach öffnet i2c_read() die Gerätedatei /dev/xillybus_read_8. Beachten Sie, dass dies nicht von allread() erledigt wird, im Gegensatz zu allwrite().

Als Nächstes schreibt i2c_read() mit Hilfe von allwrite() zwei Bytes (@dummybuf) auf den Bus:

Das Modul i2c_if im FPGA untersucht Bit 0 des ersten Bytes, das es empfängt. Anhand dieses Bits erkennt das FPGA, ob auf dem Bus ein Schreib- oder ein Lesevorgang stattfinden soll. Wenn ein Lesevorgang erforderlich ist, wird der Inhalt aller anderen Bytes ignoriert. Diese Bytes dienen nur dazu, dem FPGA mitzuteilen, wie viele Bytes empfangen werden sollen.

Das Modul i2c_if liest die gewünschte Anzahl Bytes vom Bus und sendet sie über /dev/xillybus_read_8 an den Host. Diese Gerätedatei muss geöffnet sein, bevor der Lesevorgang auf dem Bus durch das Schreiben von @dummybuf eingeleitet wird. Danach liest allread() den Wert des Registers. allread() öffnet und schließt die Datei nicht, weil die Datei früh geöffnet sein muss.

Bus-Restart

Dieser Abschnitt ist für den OV7670-Kamerasensor nicht relevant. Diese Informationen können jedoch nützlich sein, wenn i2c_if mit einem anderen Slave verwendet wird.

Beachten Sie, dass i2c_read() zweimal einen Funktionsaufruf an allwrite() durchführt. Dabei wird /dev/xillybus_write_8 jeweils geöffnet und geschlossen. Dadurch gibt es vor dem Senden der Daten eine I2C-Startbedingung und danach eine I2C-Stoppbedingung.

Mit anderen Worten: Nachdem die Registeradresse an den Slave gesendet wurde, folgt eine Stoppbedingung. Danach gibt es eine Startbedingung, bevor der Master den Lesevorgang beginnt.

Der Kamerasensor erwartet diese Abfolge. Manche elektronischen Bausteine, die als I2C-Slaves arbeiten, funktionieren jedoch nicht richtig, wenn ein Lesevorgang auf diese Weise versucht wird: Diese Bausteine vergessen die Registeradresse infolge der Stoppbedingung. Deshalb muss zwischen der ersten und der zweiten Aktion auf dem Bus eine Restart-Bedingung erzeugt werden.

Das Modul i2c_if unterstützt diese Möglichkeit: Wenn /dev/xillybus_write_8 geschlossen und wieder geöffnet wird, während /dev/xillybus_read_8 durchgehend geöffnet bleibt, wird stattdessen eine Restart-Bedingung auf dem Bus erzeugt. Wenn der Slave also eine Restart-Bedingung verlangt, muss der Funktionsaufruf von allwrite() verschoben werden. i2c_read() sieht dann so aus:

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

Noch einmal: Dieser Code ist für OV7670 nicht geeignet.

Fazit

Mit dem IP-Kern (IP core) von Xillybus ist es möglich, auf die Register des Kamerasensors zuzugreifen. Dazu wird ein zusätzliches Modul für die Anbindung an den I2C-Bus benötigt: i2c_if. Dieses Modul eignet sich auch für die Kommunikation mit anderen I2C-Slaves.

Die verfügbaren Informationen über die Register des OV7670-Kameramoduls sind leider unvollständig. Möglicherweise muss man im Internet nach Lösungen suchen oder den Linux-Treiber des Kamerasensors zu Rate ziehen.

Diese Seite wurde maschinell aus dem Englischen übersetzt. Im Zweifelsfall siehe den Originaltext.
Copyright © 2021-2026. All rights reserved. (dcc38493)