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 busio
27import board
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
22from adafruit_ili9341 import ILI9341
23import adafruit_ov2640
24
25# Pylint is unable to see that the "size" property of OV2640_GrandCentral exists
26# pylint: disable=attribute-defined-outside-init
27
28# Release any resources currently in use for the displays
29displayio.release_displays()
30
31spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
32display_bus = displayio.FourWire(
33    spi, command=board.LCD_D_C, chip_select=board.LCD_CS, reset=board.LCD_RST
34)
35display = ILI9341(display_bus, width=320, height=240, rotation=90)
36
37bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
38cam = adafruit_ov2640.OV2640(
39    bus,
40    data_pins=board.CAMERA_DATA,
41    clock=board.CAMERA_PCLK,
42    vsync=board.CAMERA_VSYNC,
43    href=board.CAMERA_HREF,
44    mclk=board.CAMERA_XCLK,
45    mclk_frequency=20_000_000,
46    size=adafruit_ov2640.OV2640_SIZE_QVGA,
47)
48
49cam.flip_x = False
50cam.flip_y = True
51pid = cam.product_id
52ver = cam.product_version
53print(f"Detected pid={pid:x} ver={ver:x}")
54# cam.test_pattern = True
55
56g = displayio.Group(scale=1)
57bitmap = displayio.Bitmap(320, 240, 65536)
58tg = displayio.TileGrid(
59    bitmap,
60    pixel_shader=displayio.ColorConverter(
61        input_colorspace=displayio.Colorspace.BGR565_SWAPPED
62    ),
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
73cam.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
23from adafruit_st7789 import ST7789
24import adafruit_ov2640
25
26# Pylint is unable to see that the "size" property of OV2640_GrandCentral exists
27# pylint: disable=attribute-defined-outside-init
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 = displayio.FourWire(
34    spi, command=board.LCD_D_C, chip_select=board.LCD_CS, reset=board.LCD_RST
35)
36display = ST7789(
37    display_bus, width=320, height=240, rotation=90, reverse_bytes_in_word=True
38)
39
40bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
41cam = adafruit_ov2640.OV2640(
42    bus,
43    data_pins=board.CAMERA_DATA,
44    clock=board.CAMERA_PCLK,
45    vsync=board.CAMERA_VSYNC,
46    href=board.CAMERA_HREF,
47    mclk=board.CAMERA_XCLK,
48    mclk_frequency=20_000_000,
49    size=adafruit_ov2640.OV2640_SIZE_QVGA,
50)
51
52# cam.flip_x = False
53# cam.flip_y = True
54pid = cam.product_id
55ver = cam.product_version
56print(f"Detected pid={pid:x} ver={ver:x}")
57# cam.test_pattern = True
58
59g = displayio.Group(scale=1)
60bitmap = displayio.Bitmap(320, 240, 65536)
61tg = displayio.TileGrid(
62    bitmap,
63    pixel_shader=displayio.ColorConverter(
64        input_colorspace=displayio.Colorspace.BGR565_SWAPPED
65    ),
66)
67g.append(tg)
68display.root_group = g
69
70display.auto_refresh = False
71while True:
72    cam.capture(bitmap)
73    bitmap.dirty()
74    display.refresh(minimum_frames_per_second=0)
75    print(".")
76
77cam.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
11from displayio import (
12    Bitmap,
13    Group,
14    TileGrid,
15    FourWire,
16    release_displays,
17    ColorConverter,
18    Colorspace,
19)
20
21from adafruit_st7789 import ST7789
22import board
23import busio
24import digitalio
25import adafruit_ov2640
26
27release_displays()
28# Set up the display (You must customize this block for your display!)
29spi = busio.SPI(clock=board.GP2, MOSI=board.GP3)
30display_bus = FourWire(spi, command=board.GP0, chip_select=board.GP1, reset=None)
31display = ST7789(display_bus, width=320, height=240, rotation=270)
32display.auto_refresh = False
33
34# Ensure the camera is shut down, so that it releases the SDA/SCL lines,
35# then create the configuration I2C bus
36
37with digitalio.DigitalInOut(board.GP10) as reset:
38    reset.switch_to_output(False)
39    time.sleep(0.001)
40    bus = busio.I2C(board.GP9, board.GP8)
41
42# Set up the camera (you must customize this for your board!)
43cam = adafruit_ov2640.OV2640(
44    bus,
45    data_pins=[
46        board.GP12,
47        board.GP13,
48        board.GP14,
49        board.GP15,
50        board.GP16,
51        board.GP17,
52        board.GP18,
53        board.GP19,
54    ],  # [16]     [org] etc
55    clock=board.GP11,  # [15]     [blk]
56    vsync=board.GP7,  # [10]     [brn]
57    href=board.GP21,  # [27/o14] [red]
58    mclk=board.GP20,  # [16/o15]
59    shutdown=None,
60    reset=board.GP10,
61)  # [14]
62
63width = display.width
64height = display.height
65
66cam.size = adafruit_ov2640.OV2640_SIZE_QQVGA
67# cam.test_pattern = True
68bitmap = Bitmap(cam.width, cam.height, 65536)
69
70print(width, height, cam.width, cam.height)
71if bitmap is None:
72    raise SystemExit("Could not allocate a bitmap")
73
74g = Group(scale=1, x=(width - cam.width) // 2, y=(height - cam.height) // 2)
75tg = TileGrid(
76    bitmap, pixel_shader=ColorConverter(input_colorspace=Colorspace.BGR565_SWAPPED)
77)
78g.append(tg)
79display.root_group = g
80
81display.auto_refresh = False
82while True:
83    cam.capture(bitmap)
84    bitmap.dirty()
85    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 busio
 34import displayio
 35import sdcardio
 36import storage
 37import adafruit_ov2640
 38
 39V_MODE = 1.98
 40V_RECORD = 2.41
 41
 42a = analogio.AnalogIn(board.IO6)
 43
 44# Release any resources currently in use for the displays
 45displayio.release_displays()
 46
 47spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 48display_bus = displayio.FourWire(
 49    spi,
 50    command=board.LCD_D_C,
 51    chip_select=board.LCD_CS,
 52    reset=board.LCD_RST,
 53    baudrate=80_000_000,
 54)
 55_INIT_SEQUENCE = (
 56    b"\x01\x80\x80"  # Software reset then delay 0x80 (128ms)
 57    b"\xEF\x03\x03\x80\x02"
 58    b"\xCF\x03\x00\xC1\x30"
 59    b"\xED\x04\x64\x03\x12\x81"
 60    b"\xE8\x03\x85\x00\x78"
 61    b"\xCB\x05\x39\x2C\x00\x34\x02"
 62    b"\xF7\x01\x20"
 63    b"\xEA\x02\x00\x00"
 64    b"\xc0\x01\x23"  # Power control VRH[5:0]
 65    b"\xc1\x01\x10"  # Power control SAP[2:0];BT[3:0]
 66    b"\xc5\x02\x3e\x28"  # VCM control
 67    b"\xc7\x01\x86"  # VCM control2
 68    b"\x36\x01\x40"  # Memory Access Control
 69    b"\x37\x01\x00"  # Vertical scroll zero
 70    b"\x3a\x01\x55"  # COLMOD: Pixel Format Set
 71    b"\xb1\x02\x00\x18"  # Frame Rate Control (In Normal Mode/Full Colors)
 72    b"\xb6\x03\x08\x82\x27"  # Display Function Control
 73    b"\xF2\x01\x00"  # 3Gamma Function Disable
 74    b"\x26\x01\x01"  # Gamma curve selected
 75    b"\xe0\x0f\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00"  # Set Gamma
 76    b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F"  # Set Gamma
 77    b"\x11\x80\x78"  # Exit Sleep then delay 0x78 (120ms)
 78    b"\x29\x80\x78"  # Display on then delay 0x78 (120ms)
 79)
 80
 81display = displayio.Display(display_bus, _INIT_SEQUENCE, width=320, height=240)
 82
 83bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 84cam = adafruit_ov2640.OV2640(
 85    bus,
 86    data_pins=board.CAMERA_DATA,
 87    clock=board.CAMERA_PCLK,
 88    vsync=board.CAMERA_VSYNC,
 89    href=board.CAMERA_HREF,
 90    mclk=board.CAMERA_XCLK,
 91    mclk_frequency=20_000_000,
 92    size=adafruit_ov2640.OV2640_SIZE_QVGA,
 93)
 94
 95cam.flip_x = False
 96cam.flip_y = True
 97pid = cam.product_id
 98ver = cam.product_version
 99print(f"Detected pid={pid:x} ver={ver:x}")
100# cam.test_pattern = True
101
102bitmap = displayio.Bitmap(320, 240, 65536)
103
104display.auto_refresh = False
105
106sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
107sd_cs = board.IO12
108sdcard = sdcardio.SDCard(sd_spi, sd_cs)
109vfs = storage.VfsFat(sdcard)
110storage.mount(vfs, "/sd")
111
112
113def exists(filename):
114    try:
115        os.stat(filename)
116        return True
117    except OSError:
118        return False
119
120
121_image_counter = 0
122
123
124def open_next_image():
125    global _image_counter  # pylint: disable=global-statement
126    while True:
127        filename = f"/sd/img{_image_counter:04d}.jpg"
128        _image_counter += 1
129        if exists(filename):
130            continue
131        print("#", filename)
132        return open(filename, "wb")  # pylint: disable=consider-using-with
133
134
135def capture_image():
136    old_size = cam.size
137    old_colorspace = cam.colorspace
138    exposure = cam.exposure
139    try:
140        cam.size = adafruit_ov2640.OV2640_SIZE_UXGA
141        cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
142        cam.exposure = exposure
143        b = bytearray(cam.capture_buffer_size)
144        jpeg = cam.capture(b)
145
146        print(f"Captured {len(jpeg)} bytes of jpeg data")
147        with open_next_image() as f:
148            f.write(jpeg)
149    finally:
150        cam.size = old_size
151        cam.colorspace = old_colorspace
152        cam.exposure = exposure
153
154
155def main():
156    display.auto_refresh = False
157    display_bus.send(42, struct.pack(">hh", 0, 319))
158    display_bus.send(43, struct.pack(">hh", 0, 239))
159    while True:
160        a_voltage = a.value * a.reference_voltage / 65535  # pylint: disable=no-member
161        record_pressed = abs(a_voltage - V_RECORD) < 0.05
162        if record_pressed:
163            capture_image()
164        cam.capture(bitmap)
165        display_bus.send(44, bitmap)
166
167
168main()

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
22import adafruit_ov2640
23
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  # pylint: disable=no-member
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 sdcardio
 37import storage
 38from adafruit_ili9341 import ILI9341
 39import adafruit_ov2640
 40
 41V_MODE = 1.98
 42V_RECORD = 2.41
 43
 44a = analogio.AnalogIn(board.IO6)
 45
 46# Release any resources currently in use for the displays
 47displayio.release_displays()
 48
 49spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 50display_bus = displayio.FourWire(
 51    spi, command=board.LCD_D_C, chip_select=board.LCD_CS, reset=board.LCD_RST
 52)
 53display = ILI9341(display_bus, width=320, height=240, rotation=90)
 54
 55bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 56cam = adafruit_ov2640.OV2640(
 57    bus,
 58    data_pins=board.CAMERA_DATA,
 59    clock=board.CAMERA_PCLK,
 60    vsync=board.CAMERA_VSYNC,
 61    href=board.CAMERA_HREF,
 62    mclk=board.CAMERA_XCLK,
 63    mclk_frequency=20_000_000,
 64    size=adafruit_ov2640.OV2640_SIZE_QVGA,
 65)
 66
 67cam.flip_x = False
 68cam.flip_y = True
 69pid = cam.product_id
 70ver = cam.product_version
 71print(f"Detected pid={pid:x} ver={ver:x}")
 72# cam.test_pattern = True
 73
 74g = displayio.Group(scale=1)
 75bitmap = displayio.Bitmap(320, 240, 65536)
 76tg = displayio.TileGrid(
 77    bitmap,
 78    pixel_shader=displayio.ColorConverter(
 79        input_colorspace=displayio.Colorspace.BGR565_SWAPPED
 80    ),
 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  # pylint: disable=global-statement
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")  # pylint: disable=consider-using-with
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  # pylint: disable=no-member
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
 30import ulab.numpy as np
 31
 32import analogio
 33import board
 34import busio
 35import displayio
 36import sdcardio
 37import storage
 38import adafruit_ov2640
 39
 40# Nominal voltages of several of the buttons on the audio daughterboard
 41V_MODE = 1.98
 42V_RECORD = 2.41
 43
 44a = analogio.AnalogIn(board.IO6)
 45
 46# Release any resources currently in use for the displays
 47displayio.release_displays()
 48
 49spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 50display_bus = displayio.FourWire(
 51    spi,
 52    command=board.LCD_D_C,
 53    chip_select=board.LCD_CS,
 54    reset=board.LCD_RST,
 55    baudrate=80_000_000,
 56)
 57_INIT_SEQUENCE = (
 58    b"\x01\x80\x80"  # Software reset then delay 0x80 (128ms)
 59    b"\xEF\x03\x03\x80\x02"
 60    b"\xCF\x03\x00\xC1\x30"
 61    b"\xED\x04\x64\x03\x12\x81"
 62    b"\xE8\x03\x85\x00\x78"
 63    b"\xCB\x05\x39\x2C\x00\x34\x02"
 64    b"\xF7\x01\x20"
 65    b"\xEA\x02\x00\x00"
 66    b"\xc0\x01\x23"  # Power control VRH[5:0]
 67    b"\xc1\x01\x10"  # Power control SAP[2:0];BT[3:0]
 68    b"\xc5\x02\x3e\x28"  # VCM control
 69    b"\xc7\x01\x86"  # VCM control2
 70    b"\x36\x01\x90"  # Memory Access Control
 71    b"\x37\x01\x00"  # Vertical scroll zero
 72    b"\x3a\x01\x55"  # COLMOD: Pixel Format Set
 73    b"\xb1\x02\x00\x18"  # Frame Rate Control (In Normal Mode/Full Colors)
 74    b"\xb6\x03\x08\x82\x27"  # Display Function Control
 75    b"\xF2\x01\x00"  # 3Gamma Function Disable
 76    b"\x26\x01\x01"  # Gamma curve selected
 77    b"\xe0\x0f\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00"  # Set Gamma
 78    b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F"  # Set Gamma
 79    b"\x11\x80\x78"  # Exit Sleep then delay 0x78 (120ms)
 80    b"\x29\x80\x78"  # Display on then delay 0x78 (120ms)
 81)
 82
 83display = displayio.Display(
 84    display_bus, _INIT_SEQUENCE, width=320, height=240, auto_refresh=False
 85)
 86
 87bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 88cam = adafruit_ov2640.OV2640(
 89    bus,
 90    data_pins=board.CAMERA_DATA,
 91    clock=board.CAMERA_PCLK,
 92    vsync=board.CAMERA_VSYNC,
 93    href=board.CAMERA_HREF,
 94    mclk=board.CAMERA_XCLK,
 95    mclk_frequency=20_000_000,
 96    size=adafruit_ov2640.OV2640_SIZE_QVGA,
 97)
 98
 99cam.flip_x = False
100cam.flip_y = False
101cam.test_pattern = False
102
103g = displayio.Group(scale=1)
104bitmap = displayio.Bitmap(320, 240, 65536)
105tg = displayio.TileGrid(
106    bitmap,
107    pixel_shader=displayio.ColorConverter(
108        input_colorspace=displayio.Colorspace.RGB565_SWAPPED
109    ),
110)
111g.append(tg)
112display.root_group = g
113
114
115sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
116sd_cs = board.IO12
117sdcard = sdcardio.SDCard(sd_spi, sd_cs)
118vfs = storage.VfsFat(sdcard)
119storage.mount(vfs, "/sd")
120
121
122def exists(filename):
123    try:
124        os.stat(filename)
125        return True
126    except OSError:
127        return False
128
129
130_image_counter = 0
131
132
133def open_next_image(extension="jpg"):
134    global _image_counter  # pylint: disable=global-statement
135    while True:
136        filename = f"/sd/img{_image_counter:04d}.{extension}"
137        _image_counter += 1
138        if exists(filename):
139            continue
140        print("#", filename)
141        return open(filename, "wb")  # pylint: disable=consider-using-with
142
143
144### These routines are for writing BMP files in the RGB565 or BGR565 formats.
145_BI_BITFIELDS = 3
146
147_bitmask_rgb565 = (0xF800, 0x7E0, 0x1F)
148_bitmask_bgr565 = (0x1F, 0x7E0, 0xF800)
149
150
151def write_header(output_file, width, height, masks):
152    def put_word(value):
153        output_file.write(struct.pack("<H", value))
154
155    def put_dword(value):
156        output_file.write(struct.pack("<I", value))
157
158    def put_long(value):
159        output_file.write(struct.pack("<i", value))
160
161    def put_padding(length):
162        output_file.write(b"\0" * length)
163
164    filesize = 14 + 108 + height * width * 2
165
166    # BMP header
167    output_file.write(b"BM")
168    put_dword(filesize)
169    put_word(0)  # Creator 1
170    put_word(0)  # Creator 2
171    put_dword(14 + 108)  # Offset of bitmap data
172
173    # DIB header (BITMAPV4HEADER)
174    put_dword(108)  # sizeof(BITMAPV4HEADER)
175    put_long(width)
176    put_long(-height)
177    put_word(1)  # number of color planes (must be 1)
178    put_word(16)  # number of bits per pixel
179    put_dword(_BI_BITFIELDS)  # "compression"
180    put_dword(2 * width * height)  # size of raw bitmap data
181    put_long(11811)  # 72dpi -> pixels/meter
182    put_long(11811)  # 72dpi -> pixels/meter
183    put_dword(0)  # palette size
184    put_dword(0)  # important color count
185    put_dword(masks[0])  # red mask
186    put_dword(masks[1])  # green mask
187    put_dword(masks[2])  # blue mask
188    put_dword(0)  # alpha mask
189    put_dword(0)  # CS Type
190    put_padding(3 * 3 * 4)  # CIEXYZ infrmation
191    put_dword(144179)  # 2.2 gamma red
192    put_dword(144179)  # 2.2 gamma green
193    put_dword(144179)  # 2.2 gamma blue
194
195
196def capture_image_bmp(the_bitmap):
197    with open_next_image("bmp") as f:
198        swapped = np.frombuffer(the_bitmap, dtype=np.uint16)
199        swapped.byteswap(inplace=True)
200        write_header(f, the_bitmap.width, the_bitmap.height, _bitmask_rgb565)
201        f.write(swapped)
202
203
204display.auto_refresh = False
205old_record_pressed = True
206
207while True:
208    a_voltage = a.value * a.reference_voltage / 65535  # pylint: disable=no-member
209    cam.capture(bitmap)
210    bitmap.dirty()
211
212    record_pressed = abs(a_voltage - V_RECORD) < 0.05
213    display.refresh(minimum_frames_per_second=0)
214    if record_pressed and not old_record_pressed:
215        capture_image_bmp(bitmap)
216    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 secrets.py.

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/secrets.py, 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 ssl
25import time
26from secrets import secrets  # pylint: disable=no-name-in-module
27
28import board
29import busio
30import wifi
31import socketpool
32import adafruit_minimqtt.adafruit_minimqtt as MQTT
33from adafruit_io.adafruit_io import IO_MQTT
34import adafruit_ov2640
35
36feed_name = "image"
37
38print("Connecting to WIFI")
39wifi.radio.connect(secrets["ssid"], secrets["password"])
40pool = socketpool.SocketPool(wifi.radio)
41
42print("Connecting to Adafruit IO")
43mqtt_client = MQTT.MQTT(
44    broker="io.adafruit.com",
45    username=secrets["aio_username"],
46    password=secrets["aio_key"],
47    socket_pool=pool,
48    ssl_context=ssl.create_default_context(),
49)
50mqtt_client.connect()
51io = IO_MQTT(mqtt_client)
52
53bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
54cam = adafruit_ov2640.OV2640(
55    bus,
56    data_pins=board.CAMERA_DATA,
57    clock=board.CAMERA_PCLK,
58    vsync=board.CAMERA_VSYNC,
59    href=board.CAMERA_HREF,
60    mclk=board.CAMERA_XCLK,
61    mclk_frequency=20_000_000,
62    size=adafruit_ov2640.OV2640_SIZE_QVGA,
63)
64
65cam.flip_x = False
66cam.flip_y = False
67cam.test_pattern = False
68
69cam.size = adafruit_ov2640.OV2640_SIZE_SVGA
70cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
71jpeg_buffer = bytearray(cam.capture_buffer_size)
72while True:
73    jpeg = cam.capture(jpeg_buffer)
74    print(f"Captured {len(jpeg)} bytes of jpeg data")
75
76    # b2a_base64() appends a trailing newline, which IO does not like
77    encoded_data = binascii.b2a_base64(jpeg).strip()
78    print(f"Expanded to {len(encoded_data)} for IO upload")
79
80    io.publish("image", encoded_data)
81
82    print("Waiting 3s")
83    time.sleep(3)