OV7670 Camera ยท Astro Tech Blog

OV7670 Camera โ€“ Basic image capture

The OV7670 is a low-cost VGA (640ร—480) CMOS camera sensor from OmniVision that outputs 8-bit parallel data with pixel, line, and frame synchronisation signals. It supports many output formats including YUV, RGB565, and grayscale via SCCB (IยฒC-like) register configuration. Capturing images with an Arduino Uno is challenging due to limited RAM (2 KB) and clock speed โ€” practical use requires a FIFO buffer module (AL422B) or restricting output to low-resolution grayscale (QQVGA 160ร—120). This tutorial covers the FIFO-based OV7670 module.

Database schema showing relationships

For this interfacing you need the following components:

  • Arduino board (Uno, Nano, Mega โ€” preferably Mega for more RAM)
  • OV7670 camera module with FIFO (AL422B) buffer
  • Breadboard and jumper wires
  • USB cable to connect Arduino to your computer
  • (Optional) SD card module for storing captured images

Schematic

Connect the OV7670 FIFO module to the Arduino as follows:

OV7670 FIFO Module    Arduino Mega (recommended)
------------------    --------------------------
VCC           -->     5V
GND           -->     GND
SIOC (SCL)    -->     Digital Pin 21 (SCL on Mega)
SIOD (SDA)    -->     Digital Pin 20 (SDA on Mega)
VSYNC         -->     Digital Pin 2 (interrupt)
FIFO_RCLK     -->     Digital Pin 6
FIFO_RRST     -->     Digital Pin 7
FIFO_OE       -->     Digital Pin 5
FIFO_WRST     -->     Digital Pin 8
FIFO_WR       -->     Digital Pin 4
D0โ€“D7         -->     Digital Pins 22โ€“29 (PORT A on Mega)
XCLK          -->     Digital Pin 3 (PWM โ€” generates clock)

The FIFO buffer stores a full frame so the Arduino can read it out at its own pace. The OV7670 writes continuously to the FIFO, and the Arduino reads the FIFO when a frame is ready.

Arduino Uno limitations: Due to the 8-bit parallel data bus requiring 8 GPIOs plus control pins, and only 2 KB RAM (insufficient for a VGA frame buffer), the Uno is not practical for OV7670 capture without external storage. A Mega or Due is strongly recommended.

Pin Map

OV7670 PinNameMega Connection
VCCPower5V
GNDGroundGND
SIOCSCCB clock21 (SCL)
SIODSCCB data20 (SDA)
VSYNCFrame sync2 (interrupt)
XCLKMaster clock3 (PWM ~8 MHz)
D0โ€“D7Pixel data (8-bit)22โ€“29
FIFO_RCLKFIFO read clock6
FIFO_RRSTFIFO read reset7
FIFO_OEFIFO output enable5
FIFO_WRSTFIFO write reset8
FIFO_WRFIFO write enable4
PWDNPower downGND (normal mode)
RESETReset5V (inactive)

Install necessary Library

Install the Arduino_OV767X library by Arduino via the Library Manager (Tools > Manage Libraries). This library supports OV7670 with FIFO on Mega and Due.

arduino-cli lib install "Arduino_OV767X"

For direct register-level control, install the OV7670 library by Ricardo Andrade.

Code with complete explanation

This sketch captures a QQVGA (160ร—120) grayscale image from the OV7670 and outputs it as a PGM (portable graymap) over the Serial Monitor, which can be saved and viewed in any image viewer.

#include <Arduino_OV767X.h>

const int cameraWidth  = 160;
const int cameraHeight = 120;

byte imageBuffer[cameraWidth * cameraHeight];

void setup()
{
  Serial.begin(115200); // High baud rate for image transfer

  // Initialise the camera
  if (!Camera.begin(QQVGA, GRAYSCALE, cameraWidth, cameraHeight))
  {
    Serial.println("Camera init failed!");
    while (1) {}
  }

  // Custom register settings (optional)
  // Camera.writeRegister(0x11, 0x00); // Clock divider
  // Camera.writeRegister(0x3D, 0x00); // Reserved

  Serial.println("Camera ready. Type 'c' to capture.");
  Serial.println();
}

void loop()
{
  if (Serial.available() && Serial.read() == 'c')
  {
    captureAndSendPGM();
  }
}

void captureAndSendPGM()
{
  // Read a frame from the camera
  Camera.readFrame(imageBuffer);

  // Send PGM header
  Serial.println("P5");
  Serial.print(cameraWidth);
  Serial.print(" ");
  Serial.println(cameraHeight);
  Serial.println(255);

  // Send pixel data (raw bytes)
  Serial.write(imageBuffer, cameraWidth * cameraHeight);
}

Code breakdown

  • #include <Arduino_OV767X.h> โ€” includes the Arduino OV767X camera library.
  • Camera.begin(QQVGA, GRAYSCALE, width, height) โ€” initialises the camera in QQVGA (160ร—120) grayscale mode. Other resolutions include VGA (640ร—480) and QVGA (320ร—240). Other pixel formats include RGB565 and YUV.
  • Camera.readFrame(buffer) โ€” reads one complete frame into the provided byte array. The array must be at least width ร— height bytes for grayscale or width ร— height ร— 2 bytes for RGB565.
  • Camera.writeRegister(reg, value) โ€” sets a register on the OV7670 via SCCB. Useful for adjusting gain, exposure, white balance, and output format.
  • The PGM format: P5 header, width and height in ASCII, max pixel value (255), then raw binary pixel data.

Capturing RGB565 frames

For colour images:

const int cameraWidth  = 320;
const int cameraHeight = 240;
byte imageBuffer[cameraWidth * cameraHeight * 2]; // 2 bytes per pixel

void setup()
{
  Camera.begin(QVGA, RGB565, cameraWidth, cameraHeight);
}

void captureRGB()
{
  Camera.readFrame(imageBuffer);

  // Send as PPM (portable pixmap)
  Serial.println("P6");
  Serial.print(cameraWidth);
  Serial.print(" ");
  Serial.println(cameraHeight);
  Serial.println(255);

  // Convert RGB565 to RGB888 and send
  for (int i = 0; i < cameraWidth * cameraHeight; i++)
  {
    uint16_t pixel = (imageBuffer[i * 2 + 1] << 8) | imageBuffer[i * 2];
    uint8_t r = (pixel >> 11) & 0x1F; r = (r << 3) | (r >> 2);
    uint8_t g = (pixel >> 5)  & 0x3F; g = (g << 2) | (g >> 4);
    uint8_t b = pixel & 0x1F;         b = (b << 3) | (b >> 2);
    Serial.write(r);
    Serial.write(g);
    Serial.write(b);
  }
}

Using an SD card for storage

To save images to an SD card instead of sending them over serial:

#include <SD.h>

const int chipSelect = 53; // Mega SS pin

void setup()
{
  SD.begin(chipSelect);
}

void captureToSD()
{
  Camera.readFrame(imageBuffer);

  File imageFile = SD.open("image.pgm", FILE_WRITE);

  if (imageFile)
  {
    imageFile.println("P5");
    imageFile.println("160 120");
    imageFile.println(255);
    imageFile.write(imageBuffer, 160 * 120);
    imageFile.close();
    Serial.println("Image saved to SD card");
  }
}

Steps to perform this interfacing

  1. Connect the OV7670 FIFO module to the Arduino Mega as shown in the schematic. The data pins (D0โ€“D7) must be connected to pins 22โ€“29 for the Arduino_OV767X library defaults.
  2. Install the Arduino_OV767X library via the Library Manager.
  3. Copy the code into the Arduino IDE.
  4. Select Arduino Mega or Due as the board (Tools > Board).
  5. Upload the sketch.
  6. Open the Serial Monitor (Tools > Serial Monitor, set baud rate to 115200).
  7. Type c and press Enter โ€” the Serial Monitor will display raw PGM binary data.
  8. Save the Serial Monitor output to a file with a .pgm extension and open it in an image viewer (GIMP, IrfanView, etc.) to see the captured image.

Viewing the PGM image

  • On Linux: Save the serial output to a file: cat /dev/ttyACM0 > capture.pgm (after the c command is sent).
  • On Windows: Use a serial terminal that supports binary logging (e.g., Tera Term, RealTerm).
  • Convert to JPEG/PNG: Use ImageMagick: convert capture.pgm capture.jpg.

Caution

  • Arduino Uno is not suitable: The OV7670 requires 8โ€“12 GPIOs for the parallel data bus, which leaves few pins for other functions on a Uno. More importantly, a full VGA frame (307 KB) far exceeds the Unoโ€™s 2 KB RAM. Even QQVGA (19 KB) exceeds it. The FIFO buffer helps with timing, but the RAM constraint remains โ€” use a Mega (8 KB) for QQVGA grayscale or a Due (96 KB) for larger images.
  • FIFO module required: Without the AL422B FIFO buffer, the OV7670 must be read in real time (pixel-by-pixel as they arrive). This is nearly impossible on an Arduino because the pixel clock runs at 8โ€“24 MHz. Always use the FIFO version of the module for any Arduino-based capture.
  • XCLK generation: The OV7670 requires an external master clock (XCLK) of 8โ€“24 MHz. The Arduino_OV767X library generates this via PWM on pin 3 (~8 MHz). The frequency must be stable โ€” toggle rate variations cause corrupted images.
  • Register configuration: The default OV7670 registers produce VGA YUV output. The library configures the sensor for the requested resolution and format via SCCB during Camera.begin(). If the image appears garbled, the register settings may need adjustment for your specific module variant (OV7670 vs OV7675, or different clock configurations).
  • Serial bandwidth: Sending a full image over serial is slow. At 115200 baud (~11.5 KB/s), a QQVGA grayscale frame (19 KB) takes about 1.7 seconds. A VGA grayscale frame (307 KB) would take 27 seconds โ€” faster interfaces (SPI to SD card) or hardware flow control are recommended for larger images.
  • Light sensitivity: The OV7670 requires good lighting. Without adequate illumination, images will be dark and noisy even at maximum gain. The default automatic gain control (AGC) and automatic white balance (AWB) can be adjusted via registers to improve low-light performance.
  • Lens focus: The OV7670 lens is fixed-focus, set for distances of about 30 cm to infinity. For close-up shots (macro), add an external M12x0.5 lens mount with adjustable focus.