Simple test

Ensure your device works with this simple test.

examples/ov5640_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_ov5640
30
31print("construct bus")
32bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
33print("construct camera")
34cam = adafruit_ov5640.OV5640(
35    bus,
36    data_pins=board.CAMERA_DATA,
37    clock=board.CAMERA_PCLK,
38    vsync=board.CAMERA_VSYNC,
39    href=board.CAMERA_HREF,
40    mclk=board.CAMERA_XCLK,
41    size=adafruit_ov5640.OV5640_SIZE_QQVGA,
42)
43print("print chip id")
44print(cam.chip_id)
45
46
47cam.colorspace = adafruit_ov5640.OV5640_COLOR_YUV
48cam.flip_y = True
49cam.flip_x = True
50cam.test_pattern = False
51
52buf = bytearray(cam.capture_buffer_size)
53chars = b" .':-+=*%$#"
54remap = [chars[i * (len(chars) - 1) // 255] for i in range(256)]
55
56width = cam.width
57row = bytearray(width)
58
59print("capturing")
60cam.capture(buf)
61print("capture complete")
62
63sys.stdout.write("\033[2J")
64while True:
65    cam.capture(buf)
66    for j in range(0, cam.height, 2):
67        sys.stdout.write(f"\033[{j//2}H")
68        for i in range(cam.width):
69            row[i] = remap[buf[2 * (width * j + i)]]
70        sys.stdout.write(row)
71        sys.stdout.write("\033[K")
72    sys.stdout.write("\033[J")
73    time.sleep(0.05)

Directio

Use an LCD as a viewfinder, bypassing displayio

examples/ov5640_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"""
 18
 19import struct
 20
 21import board
 22import busio
 23import digitalio
 24import displayio
 25from adafruit_ticks import ticks_ms, ticks_less
 26import adafruit_ov5640
 27
 28# Set to True to enable the various effects & exposure modes to be tested
 29test_effects = False
 30
 31# Release any resources currently in use for the displays
 32displayio.release_displays()
 33
 34state = digitalio.DigitalInOut(board.IO4)
 35state.switch_to_output(True)
 36
 37spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 38display_bus = displayio.FourWire(
 39    spi,
 40    command=board.LCD_D_C,
 41    chip_select=board.LCD_CS,
 42    reset=board.LCD_RST,
 43    baudrate=80_000_000,
 44)
 45_INIT_SEQUENCE = (
 46    b"\x01\x80\x80"  # Software reset then delay 0x80 (128ms)
 47    b"\xEF\x03\x03\x80\x02"
 48    b"\xCF\x03\x00\xC1\x30"
 49    b"\xED\x04\x64\x03\x12\x81"
 50    b"\xE8\x03\x85\x00\x78"
 51    b"\xCB\x05\x39\x2C\x00\x34\x02"
 52    b"\xF7\x01\x20"
 53    b"\xEA\x02\x00\x00"
 54    b"\xc0\x01\x23"  # Power control VRH[5:0]
 55    b"\xc1\x01\x10"  # Power control SAP[2:0];BT[3:0]
 56    b"\xc5\x02\x3e\x28"  # VCM control
 57    b"\xc7\x01\x86"  # VCM control2
 58    b"\x36\x01\x40"  # Memory Access Control
 59    b"\x37\x01\x00"  # Vertical scroll zero
 60    b"\x3a\x01\x55"  # COLMOD: Pixel Format Set
 61    b"\xb1\x02\x00\x18"  # Frame Rate Control (In Normal Mode/Full Colors)
 62    b"\xb6\x03\x08\x82\x27"  # Display Function Control
 63    b"\xF2\x01\x00"  # 3Gamma Function Disable
 64    b"\x26\x01\x01"  # Gamma curve selected
 65    b"\xe0\x0f\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00"  # Set Gamma
 66    b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F"  # Set Gamma
 67    b"\x11\x80\x78"  # Exit Sleep then delay 0x78 (120ms)
 68    b"\x29\x80\x78"  # Display on then delay 0x78 (120ms)
 69)
 70
 71display = displayio.Display(display_bus, _INIT_SEQUENCE, width=320, height=240)
 72
 73bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 74cam = adafruit_ov5640.OV5640(
 75    bus,
 76    data_pins=board.CAMERA_DATA,
 77    clock=board.CAMERA_PCLK,
 78    vsync=board.CAMERA_VSYNC,
 79    href=board.CAMERA_HREF,
 80    mclk=board.CAMERA_XCLK,
 81    size=adafruit_ov5640.OV5640_SIZE_QVGA,
 82)
 83
 84cam.flip_x = False
 85cam.flip_y = False
 86chip_id = cam.chip_id
 87print(f"Detected 0x{chip_id:x}")
 88cam.test_pattern = False
 89cam.effect = adafruit_ov5640.OV5640_SPECIAL_EFFECT_NONE
 90cam.saturation = 3
 91bitmap = displayio.Bitmap(cam.width, cam.height, 65536)
 92print(len(memoryview(bitmap)))
 93display.auto_refresh = False
 94
 95
 96def special_modes(cam_obj):
 97    def effect_modes(cam_obj):
 98        for i in [
 99            "NONE",
100            "NEGATIVE",
101            "GRAYSCALE",
102            "RED_TINT",
103            "GREEN_TINT",
104            "BLUE_TINT",
105            "SEPIA",
106        ]:
107            print(f"Effect {i}")
108            cam_obj.effect = getattr(adafruit_ov5640, f"OV5640_SPECIAL_EFFECT_{i}")
109            yield
110        cam_obj.effect = adafruit_ov5640.OV5640_SPECIAL_EFFECT_NONE
111
112    def saturation_modes(cam_obj):
113        for i in range(-4, 5):
114            print(f"Saturation {i}")
115            cam_obj.saturation = i
116            yield
117        cam_obj.saturation = 0
118
119    def brightness_modes(cam_obj):
120        for i in range(-4, 5):
121            print(f"Brightness {i}")
122            cam_obj.brightness = i
123            yield
124        cam_obj.brightness = 0
125
126    def contrast_modes(cam_obj):
127        for i in range(-3, 4):
128            print(f"Contrast {i}")
129            cam_obj.contrast = i
130            yield
131        cam_obj.contrast = 0
132
133    def white_balance_modes(cam_obj):  # pylint: disable=unused-variable
134        for i in ["AUTO", "SUNNY", "FLUORESCENT", "CLOUDY", "INCANDESCENT"]:
135            print(f"White Balance {i}")
136            cam_obj.white_balance = getattr(
137                adafruit_ov5640, f"OV5640_WHITE_BALANCE_{i}"
138            )
139            yield
140        cam_obj.white_balance = adafruit_ov5640.OV5640_WHITE_BALANCE_AUTO
141
142    def exposure_value_modes(cam_obj):  # pylint: disable=unused-variable
143        for i in range(-3, 4):
144            print(f"EV {i}")
145            cam_obj.exposure_value = i
146            yield
147        cam_obj.exposure_value = 0
148
149    def nite_modes(cam_obj):  # pylint: disable=unused-variable
150        print("Night Mode On")
151        cam_obj.night_mode = True
152        print(cam_obj.night_mode)
153        yield
154        print("Night Mode Off")
155        cam_obj.night_mode = False
156        print(cam_obj.night_mode)
157        yield
158
159    def test_modes(cam_obj):
160        print("Test pattern On")
161        cam_obj.test_pattern = True
162        yield
163        print("Test pattern Off")
164        cam_obj.test_pattern = False
165        yield
166
167    while True:
168        yield from test_modes(cam_obj)
169        yield from contrast_modes(cam_obj)
170        yield from effect_modes(cam_obj)
171        yield from saturation_modes(cam_obj)
172        yield from brightness_modes(cam_obj)
173        # These don't work right (yet)
174        # yield from exposure_value_modes(cam_obj)  # Issue #8
175        # yield from nite_modes(cam_obj) # Issue #6
176
177
178def main():
179    deadline = 0
180    effects = iter((None,))
181
182    display.auto_refresh = False
183    display_bus.send(42, struct.pack(">hh", 0, bitmap.width - 1))
184    display_bus.send(43, struct.pack(">hh", 0, bitmap.height - 1))
185
186    if test_effects:
187        time_per_effect = 1500
188        deadline = ticks_ms() + time_per_effect
189        effects = special_modes(cam)
190
191    while True:
192        if test_effects:
193            now = ticks_ms()
194            if ticks_less(deadline, now):
195                deadline += time_per_effect
196                next(effects)
197        state.value = True
198        cam.capture(bitmap)
199        state.value = False
200        display_bus.send(44, bitmap)
201
202
203main()

JPEG (internal storage)

Record JPEG images onto internal storage. Requires use of ov5640_jpeg_kaluga1_3_boot.py (below) as boot.py.

examples/ov5640_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 ov5640_jpeg_kaluga1_3_boot.py at CIRCUITPY/boot.py.
14Then, hold the Mode button (button K2 on the audio board) while resetting the
15board to make the internal flash readable by CircuitPython.
16"""
17
18import time
19
20import board
21import busio
22import displayio
23import microcontroller
24
25import adafruit_ili9341
26import adafruit_ov5640
27
28# Release any resources currently in use for the displays
29displayio.release_displays()
30spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
31display_bus = displayio.FourWire(
32    spi,
33    command=board.LCD_D_C,
34    chip_select=board.LCD_CS,
35    reset=board.LCD_RST,
36    baudrate=80_000_000,
37)
38display = adafruit_ili9341.ILI9341(display_bus, width=320, height=240, rotation=90)
39
40try:
41    with open("/boot_out.txt", "ab") as f:
42        pass
43except OSError as e:
44    print(e)
45    print(
46        "A 'read-only filesystem' error occurs if you did not correctly install"
47        "\nov5640_jpeg_kaluga1_3_boot.py as CIRCUITPY/boot.py and reset the"
48        '\nboard while holding the "mode" button'
49        "\n\nThis message is also shown after the board takes a picture and auto-restarts"
50    )
51    raise SystemExit from e
52
53bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
54cam = adafruit_ov5640.OV5640(
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    size=adafruit_ov5640.OV5640_SIZE_QSXGA,
62)
63
64cam.colorspace = adafruit_ov5640.OV5640_COLOR_JPEG
65cam.quality = 5
66b = bytearray(cam.capture_buffer_size)
67print(f"Capturing jpeg image of up to {len(b)} bytes")
68jpeg = cam.capture(b)
69
70print(f"Captured {len(jpeg)} bytes of jpeg data")
71try:
72    print(end="Writing to internal storage (this is SLOW)")
73    with open("/cam.jpg", "wb") as f:
74        for i in range(0, len(jpeg), 4096):
75            print(end=".")
76            f.write(jpeg[i : i + 4096])
77    print()
78    print("Wrote to CIRCUITPY/cam.jpg")
79    print("Resetting so computer sees new content of CIRCUITPY")
80    time.sleep(0.5)
81    microcontroller.reset()  # pylint: disable=no-member
82
83except OSError as e:
84    print(e)
85    print(
86        "A 'read-only filesystem' error occurs if you did not correctly install"
87        "\nov5640_jpeg_kaluga1_3_boot.py as CIRCUITPY/boot.py and reset the board"
88    )

Use with above example as boot.py

examples/ov5640_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 ov5640_jpeg_kaluga1_3.py
 6
 7It makes the CIRCUITPY filesystem writable to CircuitPython
 8(and read-only to the PC) if 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
21print("measured voltage", a_voltage)
22if abs(a_voltage - V_MODE) < 0.05:  # If mode IS pressed...
23    print("storage writable by CircuitPython")
24    storage.remount("/", readonly=False)

JPEG (SD card)

Record JPEG images to an SD card.

examples/ov5640_sdcard_kaluga_1_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 also requires an SD card breakout wired as follows:
 14 * IO18: SD Clock Input
 15 * IO17: SD Serial Output (MISO)
 16 * IO14: SD Serial Input (MOSI)
 17 * IO12: SD Chip Select
 18
 19Insert a CircuitPython-compatible SD card before powering on the Kaluga.
 20Press the "Record" button on the audio daughterboard to take a photo.
 21"""
 22
 23import os
 24import time
 25
 26import analogio
 27import board
 28import busio
 29import displayio
 30import neopixel
 31import sdcardio
 32import storage
 33
 34import adafruit_ili9341
 35import adafruit_ov5640
 36
 37# Release any resources currently in use for the displays
 38displayio.release_displays()
 39spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 40display_bus = displayio.FourWire(
 41    spi,
 42    command=board.LCD_D_C,
 43    chip_select=board.LCD_CS,
 44    reset=board.LCD_RST,
 45    baudrate=80_000_000,
 46)
 47display = adafruit_ili9341.ILI9341(display_bus, width=320, height=240, rotation=90)
 48
 49V_MODE = 1.98
 50V_RECORD = 2.41
 51
 52a = analogio.AnalogIn(board.IO6)
 53
 54pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.3, auto_write=False)
 55
 56bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 57cam = adafruit_ov5640.OV5640(
 58    bus,
 59    data_pins=board.CAMERA_DATA,
 60    clock=board.CAMERA_PCLK,
 61    vsync=board.CAMERA_VSYNC,
 62    href=board.CAMERA_HREF,
 63    mclk=board.CAMERA_XCLK,
 64    size=adafruit_ov5640.OV5640_SIZE_QSXGA,
 65)
 66
 67sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
 68sd_cs = board.IO12
 69sdcard = sdcardio.SDCard(sd_spi, sd_cs)
 70vfs = storage.VfsFat(sdcard)
 71storage.mount(vfs, "/sd")
 72
 73
 74def exists(filename):
 75    try:
 76        os.stat(filename)
 77        return True
 78    except OSError as _:
 79        return False
 80
 81
 82_image_counter = 0
 83
 84
 85def open_next_image():
 86    global _image_counter  # pylint: disable=global-statement
 87    while True:
 88        filename = f"/sd/img{_image_counter:04d}.jpg"
 89        _image_counter += 1
 90        if exists(filename):
 91            continue
 92        print("# writing to", filename)
 93        return open(filename, "wb")
 94
 95
 96cam.colorspace = adafruit_ov5640.OV5640_COLOR_JPEG
 97cam.quality = 7
 98b = bytearray(cam.capture_buffer_size)
 99
100print("Press 'record' button to take a JPEG image")
101while True:
102    pixel[0] = 0x0000FF
103    pixel.write()
104    a_voltage = a.value * a.reference_voltage / 65535  # pylint: disable=no-member
105    record_pressed = abs(a_voltage - V_RECORD) < 0.05
106    if record_pressed:
107        pixel[0] = 0xFF0000
108        pixel.write()
109        time.sleep(0.01)
110        jpeg = cam.capture(b)
111        print(
112            f"Captured {len(jpeg)} bytes of jpeg data"
113            f" (had allocated {cam.capture_buffer_size} bytes"
114        )
115        print(f"Resolution {cam.width}x{cam.height}")
116        try:
117            pixel[0] = 0x00FF00
118            pixel.write()
119            with open_next_image() as f:
120                f.write(jpeg)
121            print("# Wrote image")
122            pixel[0] = 0x000000
123            pixel.write()
124        except OSError as e:
125            print(e)
126        while record_pressed:
127            a_voltage = (
128                a.value * a.reference_voltage / 65535
129            )  # pylint: disable=no-member
130            record_pressed = abs(a_voltage - V_RECORD) < 0.05

GIF (SD card)

Record stop-motion GIF images to an SD card.

examples/ov5640_stopmotion_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"""
  7Take a 10-frame stop motion GIF image.
  8
  9This example requires:
 10 * `Espressif Kaluga v1.3 <https://www.adafruit.com/product/4729>`_ with compatible LCD display
 11 * `MicroSD card breakout board + <https://www.adafruit.com/product/254>`_ connected as follows:
 12    * CLK to board.IO18
 13    * DI to board.IO14
 14    * DO to board.IO17
 15    * CS to IO12
 16    * GND to GND
 17    * 5V to 5V
 18 * A compatible SD card inserted in the SD card slot
 19 * A compatible OV5640 camera module connected to the camera header
 20
 21To use:
 22
 23Insert an SD card and power on.
 24
 25Set up the first frame using the viewfinder. Click the REC button to take a frame.
 26
 27Set up the next frame using the viewfinder. The previous and current frames are
 28blended together on the display, which is called an "onionskin".  Click the REC
 29button to take the next frame.
 30
 31After 10 frames are recorded, the GIF is complete and you can begin recording another.
 32
 33
 34About the Kaluga development kit:
 35
 36The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
 37tested on v1.3.
 38
 39The audio board must be mounted between the Kaluga and the LCD, it provides the
 40I2C pull-ups(!)
 41
 42The v1.3 development kit's LCD can have one of two chips, the ili9341 or
 43st7789.  Furthermore, there are at least 2 ILI9341 variants, which differ
 44by rotation.  This example is written for one if the ILI9341 variants,
 45the one which usually uses rotation=90 to get a landscape display.
 46"""
 47
 48import os
 49import struct
 50
 51import analogio
 52import bitmaptools
 53import board
 54import busio
 55import displayio
 56import gifio
 57import sdcardio
 58import storage
 59
 60import adafruit_ov5640
 61
 62V_RECORD = int(2.41 * 65536 / 3.3)
 63V_FUZZ = 2000
 64
 65a = analogio.AnalogIn(board.IO6)
 66
 67
 68def record_pressed():
 69    value = a.value
 70    return abs(value - V_RECORD) < V_FUZZ
 71
 72
 73displayio.release_displays()
 74spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 75display_bus = displayio.FourWire(
 76    spi,
 77    command=board.LCD_D_C,
 78    chip_select=board.LCD_CS,
 79    reset=board.LCD_RST,
 80    baudrate=80_000_000,
 81)
 82_INIT_SEQUENCE = (
 83    b"\x01\x80\x80"  # Software reset then delay 0x80 (128ms)
 84    b"\xEF\x03\x03\x80\x02"
 85    b"\xCF\x03\x00\xC1\x30"
 86    b"\xED\x04\x64\x03\x12\x81"
 87    b"\xE8\x03\x85\x00\x78"
 88    b"\xCB\x05\x39\x2C\x00\x34\x02"
 89    b"\xF7\x01\x20"
 90    b"\xEA\x02\x00\x00"
 91    b"\xc0\x01\x23"  # Power control VRH[5:0]
 92    b"\xc1\x01\x10"  # Power control SAP[2:0];BT[3:0]
 93    b"\xc5\x02\x3e\x28"  # VCM control
 94    b"\xc7\x01\x86"  # VCM control2
 95    b"\x36\x01\x40"  # Memory Access Control
 96    b"\x37\x01\x00"  # Vertical scroll zero
 97    b"\x3a\x01\x55"  # COLMOD: Pixel Format Set
 98    b"\xb1\x02\x00\x18"  # Frame Rate Control (In Normal Mode/Full Colors)
 99    b"\xb6\x03\x08\x82\x27"  # Display Function Control
100    b"\xF2\x01\x00"  # 3Gamma Function Disable
101    b"\x26\x01\x01"  # Gamma curve selected
102    b"\xe0\x0f\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00"  # Set Gamma
103    b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F"  # Set Gamma
104    b"\x11\x80\x78"  # Exit Sleep then delay 0x78 (120ms)
105    b"\x29\x80\x78"  # Display on then delay 0x78 (120ms)
106)
107
108display = displayio.Display(display_bus, _INIT_SEQUENCE, width=320, height=240)
109
110sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
111sd_cs = board.IO12
112sdcard = sdcardio.SDCard(sd_spi, sd_cs, baudrate=24_000_000)
113vfs = storage.VfsFat(sdcard)
114storage.mount(vfs, "/sd")
115
116bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
117cam = adafruit_ov5640.OV5640(
118    bus,
119    data_pins=board.CAMERA_DATA,
120    clock=board.CAMERA_PCLK,
121    vsync=board.CAMERA_VSYNC,
122    href=board.CAMERA_HREF,
123    mclk=board.CAMERA_XCLK,
124    size=adafruit_ov5640.OV5640_SIZE_240X240,
125)
126
127
128def exists(filename):
129    try:
130        os.stat(filename)
131        return True
132    except OSError as _:
133        return False
134
135
136_image_counter = 0
137
138
139def next_filename(extension="jpg"):
140    global _image_counter  # pylint: disable=global-statement
141    while True:
142        filename = f"/sd/img{_image_counter:04d}.{extension}"
143        if exists(filename):
144            print(f"File exists: {filename}", end="\r")
145            _image_counter += 1
146            continue
147        print()
148        return filename
149
150
151# Pre-cache the next image number
152next_filename("gif")
153
154# Blank the whole display, we'll draw what we want with directio
155empty_group = displayio.Group()
156display.root_group = empty_group
157display.auto_refresh = False
158display.refresh()
159
160
161def open_next_image(extension="jpg"):
162    while True:
163        filename = next_filename(extension)
164        print("# writing to", filename)
165        return open(filename, "wb")
166
167
168cam.flip_x = False
169cam.flip_y = False
170chip_id = cam.chip_id
171print(f"Detected 0x{chip_id:x}")
172cam.test_pattern = False
173cam.effect = adafruit_ov5640.OV5640_SPECIAL_EFFECT_NONE
174cam.saturation = 3
175
176# Alternately recording to these two bitmaps
177rec1 = displayio.Bitmap(cam.width, cam.height, 65536)
178rec2 = displayio.Bitmap(cam.width, cam.height, 65536)
179# Prior frame kept here
180old_frame = displayio.Bitmap(cam.width, cam.height, 65536)
181# Displayed (onion skinned) frame here
182onionskin = displayio.Bitmap(cam.width, cam.height, 65536)
183
184ow = (display.width - onionskin.width) // 2
185oh = (display.height - onionskin.height) // 2
186display_bus.send(42, struct.pack(">hh", ow, onionskin.width + ow - 1))
187display_bus.send(43, struct.pack(">hh", oh, onionskin.height + ow - 1))
188
189
190class ContinuousCapture:
191    def __init__(self, camera, buffer1, buffer2):
192        camera = getattr(camera, "_imagecapture", camera)
193        self._camera = camera
194        print("buffer1", buffer1)
195        print("buffer2", buffer2)
196        camera.continuous_capture_start(buffer1, buffer2)
197
198    def __exit__(self, exc_type, exc_val, exc_tb):
199        self._camera.continuous_capture_stop()
200
201    def __enter__(self):
202        return self
203
204    def get_frame(self):
205        return self._camera.continuous_capture_get_frame()
206
207    __next__ = get_frame
208
209
210def wait_record_pressed_update_display(first_frame, cap):
211    while record_pressed():
212        pass
213    while True:
214        frame = cap.get_frame()
215        if record_pressed():
216            return frame
217
218        if first_frame:
219            # First frame -- display as-is
220            display_bus.send(44, frame)
221        else:
222            bitmaptools.alphablend(
223                onionskin, old_frame, frame, displayio.Colorspace.RGB565_SWAPPED
224            )
225            display_bus.send(44, onionskin)
226
227
228def take_stop_motion_gif(n_frames=10, replay_frame_time=0.3):
229    print(f"0/{n_frames}")
230    with ContinuousCapture(cam, rec1, rec2) as cap:
231        frame = wait_record_pressed_update_display(True, cap)
232        with open_next_image("gif") as f, gifio.GifWriter(
233            f, cam.width, cam.height, displayio.Colorspace.RGB565_SWAPPED, dither=True
234        ) as g:
235            g.add_frame(frame, replay_frame_time)
236            for i in range(1, n_frames):
237                print(f"{i}/{n_frames}")
238
239                # CircuitPython Versions <= 8.2.0
240                if hasattr(old_frame, "blit"):
241                    old_frame.blit(
242                        0, 0, frame, x1=0, y1=0, x2=frame.width, y2=frame.height
243                    )
244
245                # CircuitPython Versions >= 9.0.0
246                else:
247                    bitmaptools.blit(
248                        old_frame,
249                        frame,
250                        0,
251                        0,
252                        x1=0,
253                        y1=0,
254                        x2=frame.width,
255                        y2=frame.height,
256                    )
257
258                frame = wait_record_pressed_update_display(False, cap)
259                g.add_frame(frame, replay_frame_time)
260            print("done")
261
262
263est_frame_size = cam.width * cam.height * 128 // 126 + 1
264est_hdr_size = 1000
265
266dither = True
267while True:
268    take_stop_motion_gif()