Simple test

Ensure your device works with this simple test.

ov2640_simpletest.py
 1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
 2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
 3#
 4# SPDX-License-Identifier: Unlicense
 5
 6"""Capture an image from the camera and display it as ASCII art.
 7
 8This demo is designed to run on the Kaluga, but you can adapt it
 9to other boards by changing the constructors for `bus` and `cam`
10appropriately.
11
12The camera is placed in YUV mode, so the top 8 bits of each color
13value can be treated as "greyscale".
14
15It's important that you use a terminal program that can interpret
16"ANSI" escape sequences.  The demo uses them to "paint" each frame
17on top of the prevous one, rather than scrolling.
18
19Remember to take the lens cap off, or un-comment the line setting
20the test pattern!
21"""
22
23import sys
24import time
25
26import board
27import busio
28
29import adafruit_ov2640
30
31bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
32cam = adafruit_ov2640.OV2640(
33    bus,
34    data_pins=board.CAMERA_DATA,
35    clock=board.CAMERA_PCLK,
36    vsync=board.CAMERA_VSYNC,
37    href=board.CAMERA_HREF,
38    mclk=board.CAMERA_XCLK,
39    mclk_frequency=20_000_000,
40    size=adafruit_ov2640.OV2640_SIZE_QQVGA,
41)
42cam.colorspace = adafruit_ov2640.OV2640_COLOR_YUV
43cam.flip_y = True
44# cam.test_pattern = True
45
46buf = bytearray(2 * cam.width * cam.height)
47chars = b" .:-=+*#%@"
48remap = [chars[i * (len(chars) - 1) // 255] for i in range(256)]
49
50width = cam.width
51row = bytearray(2 * width)
52
53sys.stdout.write("\033[2J")
54while True:
55    cam.capture(buf)
56    for j in range(cam.height // 2):
57        sys.stdout.write(f"\033[{j}H")
58        for i in range(cam.width // 2):
59            row[i * 2] = row[i * 2 + 1] = remap[buf[4 * (width * j + i)]]
60        sys.stdout.write(row)
61        sys.stdout.write("\033[K")
62    sys.stdout.write("\033[J")
63    time.sleep(0.05)

LCD tests

Kaluga 1.3 with ili9341

Display an image from the camera on the Kaluga 1.3 board, if it is fitted with an ili9341 display.

ov2640_displayio_kaluga1_3_ili9341.py
 1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
 2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
 3#
 4# SPDX-License-Identifier: Unlicense
 5
 6"""
 7The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
 8tested on v1.3.  It probably won't work on v1.2 without modification.
 9
10The v1.3 development kit's LCD can have one of two chips, the ili9341 or
11st7789.  Furthermore, there are at least 2 ILI9341 variants, one of which needs
12rotation=90!  This demo is for the ili9341.  If the display is garbled, try adding
13rotation=90, or try modifying it to use ST7799.
14
15The audio board must be mounted between the Kaluga and the LCD, it provides the
16I2C pull-ups(!)
17"""
18
19import board
20import busio
21import displayio
22import fourwire
23from adafruit_ili9341 import ILI9341
24
25import adafruit_ov2640
26
27# Pylint is unable to see that the "size" property of OV2640_GrandCentral exists
28
29# Release any resources currently in use for the displays
30displayio.release_displays()
31
32spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
33display_bus = fourwire.FourWire(
34    spi, command=board.LCD_D_C, chip_select=board.LCD_CS, reset=board.LCD_RST
35)
36display = ILI9341(display_bus, width=320, height=240, rotation=90)
37
38bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
39cam = adafruit_ov2640.OV2640(
40    bus,
41    data_pins=board.CAMERA_DATA,
42    clock=board.CAMERA_PCLK,
43    vsync=board.CAMERA_VSYNC,
44    href=board.CAMERA_HREF,
45    mclk=board.CAMERA_XCLK,
46    mclk_frequency=20_000_000,
47    size=adafruit_ov2640.OV2640_SIZE_QVGA,
48)
49
50cam.flip_x = False
51cam.flip_y = True
52pid = cam.product_id
53ver = cam.product_version
54print(f"Detected pid={pid:x} ver={ver:x}")
55# cam.test_pattern = True
56
57g = displayio.Group(scale=1)
58bitmap = displayio.Bitmap(320, 240, 65536)
59tg = displayio.TileGrid(
60    bitmap,
61    pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.BGR565_SWAPPED),
62)
63g.append(tg)
64display.root_group = g
65
66display.auto_refresh = False
67while True:
68    cam.capture(bitmap)
69    bitmap.dirty()
70    display.refresh(minimum_frames_per_second=0)
71
72cam.deinit()

Kaluga 1.3 with st7789

Display an image from the camera on the Kaluga 1.3 board, if it is fitted with an st7789 display.

ov2640_displayio_kaluga1_3_st7789.py
 1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
 2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
 3#
 4# SPDX-License-Identifier: Unlicense
 5
 6"""
 7The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
 8tested on v1.3.
 9
10The v1.3 development kit's LCD can have one of two chips, the ili9341 or
11st7789.  This demo is for the ili9341.  There is no marking to distinguish the
12two chips.  If the visible portion of the display's flexible cable has a bunch
13of straight lines, it may be an ili9341.  If it has a bunch of wiggly traces,
14it may be an st7789.  If in doubt, try both demos.
15
16The audio board must be mounted between the Kaluga and the LCD, it provides the
17I2C pull-ups(!)
18"""
19
20import board
21import busio
22import displayio
23import fourwire
24from adafruit_st7789 import ST7789
25
26import adafruit_ov2640
27
28# Pylint is unable to see that the "size" property of OV2640_GrandCentral exists
29
30# Release any resources currently in use for the displays
31displayio.release_displays()
32
33spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
34display_bus = fourwire.FourWire(
35    spi, command=board.LCD_D_C, chip_select=board.LCD_CS, reset=board.LCD_RST
36)
37display = ST7789(display_bus, width=320, height=240, rotation=90, reverse_bytes_in_word=True)
38
39bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
40cam = adafruit_ov2640.OV2640(
41    bus,
42    data_pins=board.CAMERA_DATA,
43    clock=board.CAMERA_PCLK,
44    vsync=board.CAMERA_VSYNC,
45    href=board.CAMERA_HREF,
46    mclk=board.CAMERA_XCLK,
47    mclk_frequency=20_000_000,
48    size=adafruit_ov2640.OV2640_SIZE_QVGA,
49)
50
51# cam.flip_x = False
52# cam.flip_y = True
53pid = cam.product_id
54ver = cam.product_version
55print(f"Detected pid={pid:x} ver={ver:x}")
56# cam.test_pattern = True
57
58g = displayio.Group(scale=1)
59bitmap = displayio.Bitmap(320, 240, 65536)
60tg = displayio.TileGrid(
61    bitmap,
62    pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.BGR565_SWAPPED),
63)
64g.append(tg)
65display.root_group = g
66
67display.auto_refresh = False
68while True:
69    cam.capture(bitmap)
70    bitmap.dirty()
71    display.refresh(minimum_frames_per_second=0)
72    print(".")
73
74cam.deinit()

Raspberry Pi Pico with st7789

Display an image from the camera connected to a Raspberry Pi Pico with an st7789 2” display

ov2640_displayio_pico_st7789_2in.py
 1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
 2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
 3#
 4# SPDX-License-Identifier: Unlicense
 5
 6"""
 7Capture an image from the camera and display it on a supported LCD.
 8"""
 9
10import time
11
12import board
13import busio
14import digitalio
15from adafruit_st7789 import ST7789
16from displayio import (
17    Bitmap,
18    ColorConverter,
19    Colorspace,
20    FourWire,
21    Group,
22    TileGrid,
23    release_displays,
24)
25
26import adafruit_ov2640
27
28release_displays()
29# Set up the display (You must customize this block for your display!)
30spi = busio.SPI(clock=board.GP2, MOSI=board.GP3)
31display_bus = FourWire(spi, command=board.GP0, chip_select=board.GP1, reset=None)
32display = ST7789(display_bus, width=320, height=240, rotation=270)
33display.auto_refresh = False
34
35# Ensure the camera is shut down, so that it releases the SDA/SCL lines,
36# then create the configuration I2C bus
37
38with digitalio.DigitalInOut(board.GP10) as reset:
39    reset.switch_to_output(False)
40    time.sleep(0.001)
41    bus = busio.I2C(board.GP9, board.GP8)
42
43# Set up the camera (you must customize this for your board!)
44cam = adafruit_ov2640.OV2640(
45    bus,
46    data_pins=[
47        board.GP12,
48        board.GP13,
49        board.GP14,
50        board.GP15,
51        board.GP16,
52        board.GP17,
53        board.GP18,
54        board.GP19,
55    ],  # [16]     [org] etc
56    clock=board.GP11,  # [15]     [blk]
57    vsync=board.GP7,  # [10]     [brn]
58    href=board.GP21,  # [27/o14] [red]
59    mclk=board.GP20,  # [16/o15]
60    shutdown=None,
61    reset=board.GP10,
62)  # [14]
63
64width = display.width
65height = display.height
66
67cam.size = adafruit_ov2640.OV2640_SIZE_QQVGA
68# cam.test_pattern = True
69bitmap = Bitmap(cam.width, cam.height, 65536)
70
71print(width, height, cam.width, cam.height)
72if bitmap is None:
73    raise SystemExit("Could not allocate a bitmap")
74
75g = Group(scale=1, x=(width - cam.width) // 2, y=(height - cam.height) // 2)
76tg = TileGrid(bitmap, pixel_shader=ColorConverter(input_colorspace=Colorspace.BGR565_SWAPPED))
77g.append(tg)
78display.root_group = g
79
80display.auto_refresh = False
81while True:
82    cam.capture(bitmap)
83    bitmap.dirty()
84    display.refresh(minimum_frames_per_second=0)

Kaluga 1.3 with ili9341, direct display

Preview images on LCD, bypassing displayio for slightly higher framerate

../examples/ov2640_directio_kaluga1_3_ili9341.py
  1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
  2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
  3#
  4# SPDX-License-Identifier: Unlicense
  5
  6"""
  7The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
  8tested on v1.3.
  9
 10The audio board must be mounted between the Kaluga and the LCD, it provides the
 11I2C pull-ups(!)
 12
 13The v1.3 development kit's LCD can have one of two chips, the ili9341 or
 14st7789.  Furthermore, there are at least 2 ILI9341 variants, which differ
 15by rotation.  This example is written for one if the ILI9341 variants,
 16the one which usually uses rotation=90 to get a landscape display.
 17
 18This example also requires an SD card breakout wired as follows:
 19 * IO18: SD Clock Input
 20 * IO17: SD Serial Output (MISO)
 21 * IO14: SD Serial Input (MOSI)
 22 * IO12: SD Chip Select
 23
 24Insert a CircuitPython-compatible SD card before powering on the Kaluga.
 25Press the "Record" button on the audio daughterboard to take a photo.
 26"""
 27
 28import os
 29import struct
 30
 31import analogio
 32import board
 33import busdisplay
 34import busio
 35import displayio
 36import fourwire
 37import sdcardio
 38import storage
 39
 40import adafruit_ov2640
 41
 42V_MODE = 1.98
 43V_RECORD = 2.41
 44
 45a = analogio.AnalogIn(board.IO6)
 46
 47# Release any resources currently in use for the displays
 48displayio.release_displays()
 49
 50spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 51display_bus = fourwire.FourWire(
 52    spi,
 53    command=board.LCD_D_C,
 54    chip_select=board.LCD_CS,
 55    reset=board.LCD_RST,
 56    baudrate=80_000_000,
 57)
 58_INIT_SEQUENCE = (
 59    b"\x01\x80\x80"  # Software reset then delay 0x80 (128ms)
 60    b"\xef\x03\x03\x80\x02"
 61    b"\xcf\x03\x00\xc1\x30"
 62    b"\xed\x04\x64\x03\x12\x81"
 63    b"\xe8\x03\x85\x00\x78"
 64    b"\xcb\x05\x39\x2c\x00\x34\x02"
 65    b"\xf7\x01\x20"
 66    b"\xea\x02\x00\x00"
 67    b"\xc0\x01\x23"  # Power control VRH[5:0]
 68    b"\xc1\x01\x10"  # Power control SAP[2:0];BT[3:0]
 69    b"\xc5\x02\x3e\x28"  # VCM control
 70    b"\xc7\x01\x86"  # VCM control2
 71    b"\x36\x01\x40"  # Memory Access Control
 72    b"\x37\x01\x00"  # Vertical scroll zero
 73    b"\x3a\x01\x55"  # COLMOD: Pixel Format Set
 74    b"\xb1\x02\x00\x18"  # Frame Rate Control (In Normal Mode/Full Colors)
 75    b"\xb6\x03\x08\x82\x27"  # Display Function Control
 76    b"\xf2\x01\x00"  # 3Gamma Function Disable
 77    b"\x26\x01\x01"  # Gamma curve selected
 78    b"\xe0\x0f\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"  # Set Gamma
 79    b"\xe1\x0f\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f"  # Set Gamma
 80    b"\x11\x80\x78"  # Exit Sleep then delay 0x78 (120ms)
 81    b"\x29\x80\x78"  # Display on then delay 0x78 (120ms)
 82)
 83
 84display = busdisplay.BusDisplay(display_bus, _INIT_SEQUENCE, width=320, height=240)
 85
 86bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 87cam = adafruit_ov2640.OV2640(
 88    bus,
 89    data_pins=board.CAMERA_DATA,
 90    clock=board.CAMERA_PCLK,
 91    vsync=board.CAMERA_VSYNC,
 92    href=board.CAMERA_HREF,
 93    mclk=board.CAMERA_XCLK,
 94    mclk_frequency=20_000_000,
 95    size=adafruit_ov2640.OV2640_SIZE_QVGA,
 96)
 97
 98cam.flip_x = False
 99cam.flip_y = True
100pid = cam.product_id
101ver = cam.product_version
102print(f"Detected pid={pid:x} ver={ver:x}")
103# cam.test_pattern = True
104
105bitmap = displayio.Bitmap(320, 240, 65536)
106
107display.auto_refresh = False
108
109sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
110sd_cs = board.IO12
111sdcard = sdcardio.SDCard(sd_spi, sd_cs)
112vfs = storage.VfsFat(sdcard)
113storage.mount(vfs, "/sd")
114
115
116def exists(filename):
117    try:
118        os.stat(filename)
119        return True
120    except OSError:
121        return False
122
123
124_image_counter = 0
125
126
127def open_next_image():
128    global _image_counter  # noqa: PLW0603
129    while True:
130        filename = f"/sd/img{_image_counter:04d}.jpg"
131        _image_counter += 1
132        if exists(filename):
133            continue
134        print("#", filename)
135        return open(filename, "wb")
136
137
138def capture_image():
139    old_size = cam.size
140    old_colorspace = cam.colorspace
141    exposure = cam.exposure
142    try:
143        cam.size = adafruit_ov2640.OV2640_SIZE_UXGA
144        cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
145        cam.exposure = exposure
146        b = bytearray(cam.capture_buffer_size)
147        jpeg = cam.capture(b)
148
149        print(f"Captured {len(jpeg)} bytes of jpeg data")
150        with open_next_image() as f:
151            f.write(jpeg)
152    finally:
153        cam.size = old_size
154        cam.colorspace = old_colorspace
155        cam.exposure = exposure
156
157
158def main():
159    display.auto_refresh = False
160    display_bus.send(42, struct.pack(">hh", 0, 319))
161    display_bus.send(43, struct.pack(">hh", 0, 239))
162    while True:
163        a_voltage = a.value * a.reference_voltage / 65535
164        record_pressed = abs(a_voltage - V_RECORD) < 0.05
165        if record_pressed:
166            capture_image()
167        cam.capture(bitmap)
168        display_bus.send(44, bitmap)
169
170
171main()

Image-saving tests

Kaluga 1.3 with ili9341, internal flash, JPEG

Preview images on LCD t hen save JPEG images to internal flash on Kaluga 1.3. Requires the second snippet of code to be saved as boot.py.

ov2640_jpeg_kaluga1_3.py
 1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
 2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
 3#
 4# SPDX-License-Identifier: Unlicense
 5
 6"""
 7The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
 8tested on v1.3.
 9
10The audio board must be mounted between the Kaluga and the LCD, it provides the
11I2C pull-ups(!)
12
13You also need to place ov2640_jpeg_kaluga1_3_boot.py at CIRCUITPY/boot.py
14and reset the board to make the internal flash readable by CircuitPython.
15You can make CIRCUITPY readable from your PC by booting CircuitPython in
16safe mode or holding the "MODE" button on the audio daughterboard while
17powering on or resetting the board.
18"""
19
20import board
21import busio
22
23import adafruit_ov2640
24
25bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
26cam = adafruit_ov2640.OV2640(
27    bus,
28    data_pins=board.CAMERA_DATA,
29    clock=board.CAMERA_PCLK,
30    vsync=board.CAMERA_VSYNC,
31    href=board.CAMERA_HREF,
32    mclk=board.CAMERA_XCLK,
33    mclk_frequency=20_000_000,
34    size=adafruit_ov2640.OV2640_SIZE_QVGA,
35)
36
37pid = cam.product_id
38ver = cam.product_version
39print(f"Detected pid={pid:x} ver={ver:x}")
40# cam.test_pattern = True
41
42cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
43b = bytearray(cam.capture_buffer_size)
44jpeg = cam.capture(b)
45
46print(f"Captured {len(jpeg)} bytes of jpeg data")
47try:
48    with open("/jpeg.jpg", "wb") as f:
49        f.write(jpeg)
50except OSError as e:
51    print(e)
52    print(
53        "A 'read-only filesystem' error occurs if you did not correctly install"
54        "\nov2640_jpeg_kaluga1_3_boot.py as CIRCUITPY/boot.py and reset the board"
55    )
56print("Wrote to CIRCUITPY/jpeg.jpg")

boot.py for the above program

ov2640_jpeg_kaluga1_3_boot.py
 1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
 2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
 3#
 4# SPDX-License-Identifier: Unlicense
 5"""Use this file as CIRCUITPY/boot.py in conjunction with ov2640_jpeg_kaluga1_3.py
 6
 7It makes the CIRCUITPY filesystem writable to CircuitPython
 8(and read-only to the PC) unless the "MODE" button on the audio
 9daughterboard is held while the board is powered on or reset.
10"""
11
12import analogio
13import board
14import storage
15
16V_MODE = 1.98
17V_RECORD = 2.41
18
19a = analogio.AnalogIn(board.IO6)
20a_voltage = a.value * a.reference_voltage / 65535
21if abs(a_voltage - V_MODE) > 0.05:  # If mode is NOT pressed...
22    print("storage writable by CircuitPython")
23    storage.remount("/", readonly=False)

Kaluga 1.3 with ili9341, external SD card, JPEG

Preview images on LCD then save JPEG images to SD on Kaluga 1.3 fitted with an ili9341 display.

ov2640_jpeg_sd_kaluga1_3.py
  1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
  2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
  3#
  4# SPDX-License-Identifier: Unlicense
  5
  6"""
  7Display an image on the LCD, then record an image when the REC button is pressed/held.
  8
  9The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
 10tested on v1.3.
 11
 12The audio board must be mounted between the Kaluga and the LCD, it provides the
 13I2C pull-ups(!)
 14
 15The v1.3 development kit's LCD can have one of two chips, the ili9341 or
 16st7789.  Furthermore, there are at least 2 ILI9341 variants, one of which needs
 17rotation=90!  This demo is for the ili9341.  If the display is garbled, try adding
 18rotation=90, or try modifying it to use ST7799.
 19
 20This example also requires an SD card breakout wired as follows:
 21 * IO18: SD Clock Input
 22 * IO17: SD Serial Output (MISO)
 23 * IO14: SD Serial Input (MOSI)
 24 * IO12: SD Chip Select
 25
 26Insert a CircuitPython-compatible SD card before powering on the Kaluga.
 27Press the "Record" button on the audio daughterboard to take a photo.
 28"""
 29
 30import os
 31
 32import analogio
 33import board
 34import busio
 35import displayio
 36import fourwire
 37import sdcardio
 38import storage
 39from adafruit_ili9341 import ILI9341
 40
 41import adafruit_ov2640
 42
 43V_MODE = 1.98
 44V_RECORD = 2.41
 45
 46a = analogio.AnalogIn(board.IO6)
 47
 48# Release any resources currently in use for the displays
 49displayio.release_displays()
 50
 51spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 52display_bus = fourwire.FourWire(
 53    spi, command=board.LCD_D_C, chip_select=board.LCD_CS, reset=board.LCD_RST
 54)
 55display = ILI9341(display_bus, width=320, height=240, rotation=90)
 56
 57bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 58cam = adafruit_ov2640.OV2640(
 59    bus,
 60    data_pins=board.CAMERA_DATA,
 61    clock=board.CAMERA_PCLK,
 62    vsync=board.CAMERA_VSYNC,
 63    href=board.CAMERA_HREF,
 64    mclk=board.CAMERA_XCLK,
 65    mclk_frequency=20_000_000,
 66    size=adafruit_ov2640.OV2640_SIZE_QVGA,
 67)
 68
 69cam.flip_x = False
 70cam.flip_y = True
 71pid = cam.product_id
 72ver = cam.product_version
 73print(f"Detected pid={pid:x} ver={ver:x}")
 74# cam.test_pattern = True
 75
 76g = displayio.Group(scale=1)
 77bitmap = displayio.Bitmap(320, 240, 65536)
 78tg = displayio.TileGrid(
 79    bitmap,
 80    pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.BGR565_SWAPPED),
 81)
 82g.append(tg)
 83display.root_group = g
 84
 85display.auto_refresh = False
 86
 87sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
 88sd_cs = board.IO12
 89sdcard = sdcardio.SDCard(sd_spi, sd_cs)
 90vfs = storage.VfsFat(sdcard)
 91storage.mount(vfs, "/sd")
 92
 93
 94def exists(filename):
 95    try:
 96        os.stat(filename)
 97        return True
 98    except OSError:
 99        return False
100
101
102_image_counter = 0
103
104
105def open_next_image():
106    global _image_counter  # noqa: PLW0603
107    while True:
108        filename = f"/sd/img{_image_counter:04d}.jpg"
109        _image_counter += 1
110        if exists(filename):
111            continue
112        print("#", filename)
113        return open(filename, "wb")
114
115
116def capture_image():
117    old_size = cam.size
118    old_colorspace = cam.colorspace
119
120    try:
121        cam.size = adafruit_ov2640.OV2640_SIZE_UXGA
122        cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
123        b = bytearray(cam.capture_buffer_size)
124        jpeg = cam.capture(b)
125
126        print(f"Captured {len(jpeg)} bytes of jpeg data")
127        with open_next_image() as f:
128            f.write(jpeg)
129    finally:
130        cam.size = old_size
131        cam.colorspace = old_colorspace
132
133
134display.auto_refresh = False
135while True:
136    a_voltage = a.value * a.reference_voltage / 65535
137    record_pressed = abs(a_voltage - V_RECORD) < 0.05
138    if record_pressed:
139        capture_image()
140    cam.capture(bitmap)
141    bitmap.dirty()
142    display.refresh(minimum_frames_per_second=0)

Kaluga 1.3 with ili9341, external SD card, BMP

Preview images on LCD then save BMP images to SD on Kaluga 1.3 fitted with an ili9341 display.

ov2640_bmp_sd_kaluga1_3.py
  1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
  2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
  3#
  4# SPDX-License-Identifier: Unlicense
  5
  6"""
  7The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
  8tested on v1.3.
  9
 10The audio board must be mounted between the Kaluga and the LCD, it provides the
 11I2C pull-ups(!)
 12
 13The v1.3 development kit's LCD can have one of two chips, the ili9341 or
 14st7789.  Furthermore, there are at least 2 ILI9341 variants, one of which needs
 15rotation=90!  This demo is for the ili9341.  If the display is garbled, try adding
 16rotation=90, or try modifying it to use ST7799.
 17
 18This example also requires an SD card breakout wired as follows:
 19 * IO18: SD Clock Input
 20 * IO17: SD Serial Output (MISO)
 21 * IO14: SD Serial Input (MOSI)
 22 * IO12: SD Chip Select
 23
 24Insert a CircuitPython-compatible SD card before powering on the Kaluga.
 25Press the "Record" button on the audio daughterboard to take a photo in BMP format.
 26"""
 27
 28import os
 29import struct
 30
 31import analogio
 32import board
 33import busdisplay
 34import busio
 35import displayio
 36import fourwire
 37import sdcardio
 38import storage
 39import ulab.numpy as np
 40
 41import adafruit_ov2640
 42
 43# Nominal voltages of several of the buttons on the audio daughterboard
 44V_MODE = 1.98
 45V_RECORD = 2.41
 46
 47a = analogio.AnalogIn(board.IO6)
 48
 49# Release any resources currently in use for the displays
 50displayio.release_displays()
 51
 52spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 53display_bus = fourwire.FourWire(
 54    spi,
 55    command=board.LCD_D_C,
 56    chip_select=board.LCD_CS,
 57    reset=board.LCD_RST,
 58    baudrate=80_000_000,
 59)
 60_INIT_SEQUENCE = (
 61    b"\x01\x80\x80"  # Software reset then delay 0x80 (128ms)
 62    b"\xef\x03\x03\x80\x02"
 63    b"\xcf\x03\x00\xc1\x30"
 64    b"\xed\x04\x64\x03\x12\x81"
 65    b"\xe8\x03\x85\x00\x78"
 66    b"\xcb\x05\x39\x2c\x00\x34\x02"
 67    b"\xf7\x01\x20"
 68    b"\xea\x02\x00\x00"
 69    b"\xc0\x01\x23"  # Power control VRH[5:0]
 70    b"\xc1\x01\x10"  # Power control SAP[2:0];BT[3:0]
 71    b"\xc5\x02\x3e\x28"  # VCM control
 72    b"\xc7\x01\x86"  # VCM control2
 73    b"\x36\x01\x90"  # Memory Access Control
 74    b"\x37\x01\x00"  # Vertical scroll zero
 75    b"\x3a\x01\x55"  # COLMOD: Pixel Format Set
 76    b"\xb1\x02\x00\x18"  # Frame Rate Control (In Normal Mode/Full Colors)
 77    b"\xb6\x03\x08\x82\x27"  # Display Function Control
 78    b"\xf2\x01\x00"  # 3Gamma Function Disable
 79    b"\x26\x01\x01"  # Gamma curve selected
 80    b"\xe0\x0f\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"  # Set Gamma
 81    b"\xe1\x0f\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f"  # Set Gamma
 82    b"\x11\x80\x78"  # Exit Sleep then delay 0x78 (120ms)
 83    b"\x29\x80\x78"  # Display on then delay 0x78 (120ms)
 84)
 85
 86display = busdisplay.BusDisplay(
 87    display_bus, _INIT_SEQUENCE, width=320, height=240, auto_refresh=False
 88)
 89
 90bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 91cam = adafruit_ov2640.OV2640(
 92    bus,
 93    data_pins=board.CAMERA_DATA,
 94    clock=board.CAMERA_PCLK,
 95    vsync=board.CAMERA_VSYNC,
 96    href=board.CAMERA_HREF,
 97    mclk=board.CAMERA_XCLK,
 98    mclk_frequency=20_000_000,
 99    size=adafruit_ov2640.OV2640_SIZE_QVGA,
100)
101
102cam.flip_x = False
103cam.flip_y = False
104cam.test_pattern = False
105
106g = displayio.Group(scale=1)
107bitmap = displayio.Bitmap(320, 240, 65536)
108tg = displayio.TileGrid(
109    bitmap,
110    pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565_SWAPPED),
111)
112g.append(tg)
113display.root_group = g
114
115
116sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
117sd_cs = board.IO12
118sdcard = sdcardio.SDCard(sd_spi, sd_cs)
119vfs = storage.VfsFat(sdcard)
120storage.mount(vfs, "/sd")
121
122
123def exists(filename):
124    try:
125        os.stat(filename)
126        return True
127    except OSError:
128        return False
129
130
131_image_counter = 0
132
133
134def open_next_image(extension="jpg"):
135    global _image_counter  # noqa: PLW0603
136    while True:
137        filename = f"/sd/img{_image_counter:04d}.{extension}"
138        _image_counter += 1
139        if exists(filename):
140            continue
141        print("#", filename)
142        return open(filename, "wb")
143
144
145### These routines are for writing BMP files in the RGB565 or BGR565 formats.
146_BI_BITFIELDS = 3
147
148_bitmask_rgb565 = (0xF800, 0x7E0, 0x1F)
149_bitmask_bgr565 = (0x1F, 0x7E0, 0xF800)
150
151
152def write_header(output_file, width, height, masks):
153    def put_word(value):
154        output_file.write(struct.pack("<H", value))
155
156    def put_dword(value):
157        output_file.write(struct.pack("<I", value))
158
159    def put_long(value):
160        output_file.write(struct.pack("<i", value))
161
162    def put_padding(length):
163        output_file.write(b"\0" * length)
164
165    filesize = 14 + 108 + height * width * 2
166
167    # BMP header
168    output_file.write(b"BM")
169    put_dword(filesize)
170    put_word(0)  # Creator 1
171    put_word(0)  # Creator 2
172    put_dword(14 + 108)  # Offset of bitmap data
173
174    # DIB header (BITMAPV4HEADER)
175    put_dword(108)  # sizeof(BITMAPV4HEADER)
176    put_long(width)
177    put_long(-height)
178    put_word(1)  # number of color planes (must be 1)
179    put_word(16)  # number of bits per pixel
180    put_dword(_BI_BITFIELDS)  # "compression"
181    put_dword(2 * width * height)  # size of raw bitmap data
182    put_long(11811)  # 72dpi -> pixels/meter
183    put_long(11811)  # 72dpi -> pixels/meter
184    put_dword(0)  # palette size
185    put_dword(0)  # important color count
186    put_dword(masks[0])  # red mask
187    put_dword(masks[1])  # green mask
188    put_dword(masks[2])  # blue mask
189    put_dword(0)  # alpha mask
190    put_dword(0)  # CS Type
191    put_padding(3 * 3 * 4)  # CIEXYZ infrmation
192    put_dword(144179)  # 2.2 gamma red
193    put_dword(144179)  # 2.2 gamma green
194    put_dword(144179)  # 2.2 gamma blue
195
196
197def capture_image_bmp(the_bitmap):
198    with open_next_image("bmp") as f:
199        swapped = np.frombuffer(the_bitmap, dtype=np.uint16)
200        swapped.byteswap(inplace=True)
201        write_header(f, the_bitmap.width, the_bitmap.height, _bitmask_rgb565)
202        f.write(swapped)
203
204
205display.auto_refresh = False
206old_record_pressed = True
207
208while True:
209    a_voltage = a.value * a.reference_voltage / 65535
210    cam.capture(bitmap)
211    bitmap.dirty()
212
213    record_pressed = abs(a_voltage - V_RECORD) < 0.05
214    display.refresh(minimum_frames_per_second=0)
215    if record_pressed and not old_record_pressed:
216        capture_image_bmp(bitmap)
217    old_record_pressed = record_pressed

Kaluga 1.3 with Adafruit IO

Upload JPEG images to Adafruit IO. Requires that WIFI and Adafruit IO be configured in settings.toml.

ov2640_aio_kaluga1_3.py
 1# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
 2# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
 3#
 4# SPDX-License-Identifier: Unlicense
 5
 6"""
 7The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
 8tested on v1.3.
 9
10The audio board must be mounted between the Kaluga and the LCD, it provides the
11I2C pull-ups(!)
12
13This example requires that your WIFI and Adafruit IO credentials be configured
14in CIRCUITPY/settings.toml, and that you have created a feed called "image" with
15history disabled.
16
17The maximum image size is 100kB after base64 encoding, or about 65kB before
18base64 encoding.  In practice, "SVGA" (800x600) images are typically around
1940kB even though the "capture_buffer_size" (theoretical maximum size) is
20(width*height/5) bytes or 96kB.
21"""
22
23import binascii
24import time
25from os import getenv
26
27import adafruit_connection_manager
28import adafruit_minimqtt.adafruit_minimqtt as MQTT
29import board
30import busio
31import wifi
32from adafruit_io.adafruit_io import IO_MQTT
33
34import adafruit_ov2640
35
36feed_name = "image"
37
38# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
39# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
40ssid = getenv("CIRCUITPY_WIFI_SSID")
41password = getenv("CIRCUITPY_WIFI_PASSWORD")
42aio_username = getenv("ADAFRUIT_AIO_USERNAME")
43aio_key = getenv("ADAFRUIT_AIO_KEY")
44
45print("Connecting to WIFI")
46wifi.radio.connect(ssid, password)
47pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
48ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
49
50print("Connecting to Adafruit IO")
51mqtt_client = MQTT.MQTT(
52    broker="io.adafruit.com",
53    username=aio_username,
54    password=aio_key,
55    socket_pool=pool,
56    ssl_context=ssl_context,
57)
58mqtt_client.connect()
59io = IO_MQTT(mqtt_client)
60
61bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
62cam = adafruit_ov2640.OV2640(
63    bus,
64    data_pins=board.CAMERA_DATA,
65    clock=board.CAMERA_PCLK,
66    vsync=board.CAMERA_VSYNC,
67    href=board.CAMERA_HREF,
68    mclk=board.CAMERA_XCLK,
69    mclk_frequency=20_000_000,
70    size=adafruit_ov2640.OV2640_SIZE_QVGA,
71)
72
73cam.flip_x = False
74cam.flip_y = False
75cam.test_pattern = False
76
77cam.size = adafruit_ov2640.OV2640_SIZE_SVGA
78cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
79jpeg_buffer = bytearray(cam.capture_buffer_size)
80while True:
81    jpeg = cam.capture(jpeg_buffer)
82    print(f"Captured {len(jpeg)} bytes of jpeg data")
83
84    # b2a_base64() appends a trailing newline, which IO does not like
85    encoded_data = binascii.b2a_base64(jpeg).strip()
86    print(f"Expanded to {len(encoded_data)} for IO upload")
87
88    io.publish("image", encoded_data)
89
90    print("Waiting 3s")
91    time.sleep(3)