Unmodified JPEG taken with an OV2640 camera on Espressif Kaluga

The OV2640 camera module can also capture JPEG images up to 2 megapixels (1600x1200 pixels). This still requires a large buffer (of width×height÷5 bytes, or 384,000 bytes for a 2-megapixel image), so the demo below is coded for the Kaluga development board together with the MicroSD card breakout board+.

CircuitPython doesn't encode or decode the JPEG image itself, it just uses a JPEG-encoded file produced by the camera and stores it on the SD card.

Make the following connections for the SD card breakout:

  • IO18 to CLK
  • IO14 to DI
  • IO17 to DO
  • IO12 to CS
  • GND to GND
  • 5V to 5V

While the demo runs, it will show a live image on the LCD. When you hold the REC button, it will save the picture as a jpeg image to the inserted SD card. Note that because the REC button is only polled when the screen is not updating, you have to hold it, not just quickly press it.

The exposure in JPEG mode doesn't match what's shown on the LCD. We hope a future enhancement of the OV2640 library will improve this.


This code is for CircuitPython 7. Revised code will be required for CircuitPython 8.
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
# SPDX-License-Identifier: Unlicense

Display an image on the LCD, then record an image when the REC button is pressed/held.

The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
tested on v1.3.

The audio board must be mounted between the Kaluga and the LCD, it provides the
I2C pull-ups(!)

The v1.3 development kit's LCD can have one of two chips, the ili9341 or
st7789.  Furthermore, there are at least 2 ILI9341 variants, one of which needs
rotation=90!  This demo is for the ili9341.  If the display is garbled, try adding
rotation=90, or try modifying it to use ST7799.

This example also requires an SD card breakout wired as follows:
 * IO18: SD Clock Input
 * IO17: SD Serial Output (MISO)
 * IO14: SD Serial Input (MOSI)
 * IO12: SD Chip Select

Insert a CircuitPython-compatible SD card before powering on the Kaluga.
Press the "Record" button on the audio daughterboard to take a photo.

import os

import analogio
import board
import busio
import displayio
import sdcardio
import storage
from adafruit_ili9341 import ILI9341
import adafruit_ov2640

V_MODE = 1.98
V_RECORD = 2.41

a = analogio.AnalogIn(board.IO6)

# Release any resources currently in use for the displays

spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
display_bus = displayio.FourWire(
    spi, command=board.LCD_D_C, chip_select=board.LCD_CS, reset=board.LCD_RST
display = ILI9341(display_bus, width=320, height=240, rotation=90)

bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
cam = adafruit_ov2640.OV2640(

cam.flip_x = False
cam.flip_y = True
pid = cam.product_id
ver = cam.product_version
print(f"Detected pid={pid:x} ver={ver:x}")
# cam.test_pattern = True

g = displayio.Group(scale=1)
bitmap = displayio.Bitmap(320, 240, 65536)
tg = displayio.TileGrid(
display.root_group = g

display.auto_refresh = False

sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
sd_cs = board.IO12
sdcard = sdcardio.SDCard(sd_spi, sd_cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")

def exists(filename):
        return True
    except OSError:
        return False

_image_counter = 0

def open_next_image():
    global _image_counter  # pylint: disable=global-statement
    while True:
        filename = f"/sd/img{_image_counter:04d}.jpg"
        _image_counter += 1
        if exists(filename):
        print("#", filename)
        return open(filename, "wb")  # pylint: disable=consider-using-with

def capture_image():
    old_size = cam.size
    old_colorspace = cam.colorspace

        cam.size = adafruit_ov2640.OV2640_SIZE_UXGA
        cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
        b = bytearray(cam.capture_buffer_size)
        jpeg = cam.capture(b)

        print(f"Captured {len(jpeg)} bytes of jpeg data")
        with open_next_image() as f:
        cam.size = old_size
        cam.colorspace = old_colorspace

display.auto_refresh = False
while True:
    a_voltage = a.value * a.reference_voltage / 65535  # pylint: disable=no-member
    record_pressed = abs(a_voltage - V_RECORD) < 0.05
    if record_pressed:


Angled shot of a MicroSD card breakout board+.
Not just a simple breakout board, this microSD adapter goes the extra mile - designed for ease of use.Onboard 5v->3v regulator provides 150mA for power-hungry...
In Stock
USB Type A Plug Breakout Cable with Premium Female Jumpers
If you'd like to connect a USB-capable chip to your USB host, this cable will make the task very simple. There is no converter chip in this cable! Its basically a...
In Stock
USB Type A Extension Cable
This handy USB extension cable will make it easy for you to extend your USB cable when it won't reach. The connectors are gold plated for years of reliability. We use these handy...
In Stock

This guide was first published on Jun 29, 2021. It was last updated on Jun 13, 2024.

This page (Capturing JPEG data (OV2640)) was last updated on Jun 13, 2024.

Text editor powered by tinymce.