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

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

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
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 adafruit_ili9341
 27import analogio
 28import board
 29import busio
 30import displayio
 31import fourwire
 32import neopixel
 33import sdcardio
 34import storage
 35
 36import adafruit_ov5640
 37
 38# Release any resources currently in use for the displays
 39displayio.release_displays()
 40spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 41display_bus = fourwire.FourWire(
 42    spi,
 43    command=board.LCD_D_C,
 44    chip_select=board.LCD_CS,
 45    reset=board.LCD_RST,
 46    baudrate=80_000_000,
 47)
 48display = adafruit_ili9341.ILI9341(display_bus, width=320, height=240, rotation=90)
 49
 50V_MODE = 1.98
 51V_RECORD = 2.41
 52
 53a = analogio.AnalogIn(board.IO6)
 54
 55pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.3, auto_write=False)
 56
 57bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
 58cam = adafruit_ov5640.OV5640(
 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    size=adafruit_ov5640.OV5640_SIZE_QSXGA,
 66)
 67
 68sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
 69sd_cs = board.IO12
 70sdcard = sdcardio.SDCard(sd_spi, sd_cs)
 71vfs = storage.VfsFat(sdcard)
 72storage.mount(vfs, "/sd")
 73
 74
 75def exists(filename):
 76    try:
 77        os.stat(filename)
 78        return True
 79    except OSError as _:
 80        return False
 81
 82
 83class ImageCounter:
 84    def __init__(self):
 85        self.count = 0
 86
 87    def get_next(self):
 88        while True:
 89            filename = f"/sd/img{self.count:04d}.jpg"
 90            self.count += 1
 91            if exists(filename):
 92                continue
 93            print("# writing to", filename)
 94            return open(filename, "wb")
 95
 96
 97_image_counter = ImageCounter()
 98
 99
100def open_next_image():
101    return _image_counter.get_next()
102
103
104cam.colorspace = adafruit_ov5640.OV5640_COLOR_JPEG
105cam.quality = 7
106b = bytearray(cam.capture_buffer_size)
107
108print("Press 'record' button to take a JPEG image")
109while True:
110    pixel[0] = 0x0000FF
111    pixel.write()
112    a_voltage = a.value * a.reference_voltage / 65535
113    record_pressed = abs(a_voltage - V_RECORD) < 0.05
114    if record_pressed:
115        pixel[0] = 0xFF0000
116        pixel.write()
117        time.sleep(0.01)
118        jpeg = cam.capture(b)
119        print(
120            f"Captured {len(jpeg)} bytes of jpeg data"
121            f" (had allocated {cam.capture_buffer_size} bytes"
122        )
123        print(f"Resolution {cam.width}x{cam.height}")
124        try:
125            pixel[0] = 0x00FF00
126            pixel.write()
127            with open_next_image() as f:
128                f.write(jpeg)
129            print("# Wrote image")
130            pixel[0] = 0x000000
131            pixel.write()
132        except OSError as e:
133            print(e)
134        while record_pressed:
135            a_voltage = a.value * a.reference_voltage / 65535
136            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 busdisplay
 55import busio
 56import displayio
 57import fourwire
 58import gifio
 59import sdcardio
 60import storage
 61
 62import adafruit_ov5640
 63
 64V_RECORD = int(2.41 * 65536 / 3.3)
 65V_FUZZ = 2000
 66
 67a = analogio.AnalogIn(board.IO6)
 68
 69
 70def record_pressed():
 71    value = a.value
 72    return abs(value - V_RECORD) < V_FUZZ
 73
 74
 75displayio.release_displays()
 76spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
 77display_bus = fourwire.FourWire(
 78    spi,
 79    command=board.LCD_D_C,
 80    chip_select=board.LCD_CS,
 81    reset=board.LCD_RST,
 82    baudrate=80_000_000,
 83)
 84_INIT_SEQUENCE = (
 85    b"\x01\x80\x80"  # Software reset then delay 0x80 (128ms)
 86    b"\xef\x03\x03\x80\x02"
 87    b"\xcf\x03\x00\xc1\x30"
 88    b"\xed\x04\x64\x03\x12\x81"
 89    b"\xe8\x03\x85\x00\x78"
 90    b"\xcb\x05\x39\x2c\x00\x34\x02"
 91    b"\xf7\x01\x20"
 92    b"\xea\x02\x00\x00"
 93    b"\xc0\x01\x23"  # Power control VRH[5:0]
 94    b"\xc1\x01\x10"  # Power control SAP[2:0];BT[3:0]
 95    b"\xc5\x02\x3e\x28"  # VCM control
 96    b"\xc7\x01\x86"  # VCM control2
 97    b"\x36\x01\x40"  # Memory Access Control
 98    b"\x37\x01\x00"  # Vertical scroll zero
 99    b"\x3a\x01\x55"  # COLMOD: Pixel Format Set
100    b"\xb1\x02\x00\x18"  # Frame Rate Control (In Normal Mode/Full Colors)
101    b"\xb6\x03\x08\x82\x27"  # Display Function Control
102    b"\xf2\x01\x00"  # 3Gamma Function Disable
103    b"\x26\x01\x01"  # Gamma curve selected
104    b"\xe0\x0f\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"  # Set Gamma
105    b"\xe1\x0f\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f"  # Set Gamma
106    b"\x11\x80\x78"  # Exit Sleep then delay 0x78 (120ms)
107    b"\x29\x80\x78"  # Display on then delay 0x78 (120ms)
108)
109
110display = busdisplay.BusDisplay(display_bus, _INIT_SEQUENCE, width=320, height=240)
111
112sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
113sd_cs = board.IO12
114sdcard = sdcardio.SDCard(sd_spi, sd_cs, baudrate=24_000_000)
115vfs = storage.VfsFat(sdcard)
116storage.mount(vfs, "/sd")
117
118bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
119cam = adafruit_ov5640.OV5640(
120    bus,
121    data_pins=board.CAMERA_DATA,
122    clock=board.CAMERA_PCLK,
123    vsync=board.CAMERA_VSYNC,
124    href=board.CAMERA_HREF,
125    mclk=board.CAMERA_XCLK,
126    size=adafruit_ov5640.OV5640_SIZE_240X240,
127)
128
129
130def exists(filename):
131    try:
132        os.stat(filename)
133        return True
134    except OSError as _:
135        return False
136
137
138class ImageCounter:
139    def __init__(self):
140        self.count = 0
141
142    def next_filename(self, extension="jpg"):
143        while True:
144            filename = f"/sd/img{self.count:04d}.{extension}"
145            if exists(filename):
146                print(f"File exists: {filename}", end="\r")
147                self.count += 1
148                continue
149            print()
150            return filename
151
152
153_image_counter = ImageCounter()
154
155
156def next_filename(extension="jpg"):
157    return _image_counter.next_filename(extension)
158
159
160# Pre-cache the next image number
161next_filename("gif")
162
163# Blank the whole display, we'll draw what we want with directio
164empty_group = displayio.Group()
165display.root_group = empty_group
166display.auto_refresh = False
167display.refresh()
168
169
170def open_next_image(extension="jpg"):
171    while True:
172        filename = next_filename(extension)
173        print("# writing to", filename)
174        return open(filename, "wb")
175
176
177cam.flip_x = False
178cam.flip_y = False
179chip_id = cam.chip_id
180print(f"Detected 0x{chip_id:x}")
181cam.test_pattern = False
182cam.effect = adafruit_ov5640.OV5640_SPECIAL_EFFECT_NONE
183cam.saturation = 3
184
185# Alternately recording to these two bitmaps
186rec1 = displayio.Bitmap(cam.width, cam.height, 65536)
187rec2 = displayio.Bitmap(cam.width, cam.height, 65536)
188# Prior frame kept here
189old_frame = displayio.Bitmap(cam.width, cam.height, 65536)
190# Displayed (onion skinned) frame here
191onionskin = displayio.Bitmap(cam.width, cam.height, 65536)
192
193ow = (display.width - onionskin.width) // 2
194oh = (display.height - onionskin.height) // 2
195display_bus.send(42, struct.pack(">hh", ow, onionskin.width + ow - 1))
196display_bus.send(43, struct.pack(">hh", oh, onionskin.height + ow - 1))
197
198
199class ContinuousCapture:
200    def __init__(self, camera, buffer1, buffer2):
201        camera = getattr(camera, "_imagecapture", camera)
202        self._camera = camera
203        print("buffer1", buffer1)
204        print("buffer2", buffer2)
205        camera.continuous_capture_start(buffer1, buffer2)
206
207    def __exit__(self, exc_type, exc_val, exc_tb):
208        self._camera.continuous_capture_stop()
209
210    def __enter__(self):
211        return self
212
213    def get_frame(self):
214        return self._camera.continuous_capture_get_frame()
215
216    __next__ = get_frame
217
218
219def wait_record_pressed_update_display(first_frame, cap):
220    while record_pressed():
221        pass
222    while True:
223        frame = cap.get_frame()
224        if record_pressed():
225            return frame
226
227        if first_frame:
228            # First frame -- display as-is
229            display_bus.send(44, frame)
230        else:
231            bitmaptools.alphablend(onionskin, old_frame, frame, displayio.Colorspace.RGB565_SWAPPED)
232            display_bus.send(44, onionskin)
233
234
235def take_stop_motion_gif(n_frames=10, replay_frame_time=0.3):
236    print(f"0/{n_frames}")
237    with ContinuousCapture(cam, rec1, rec2) as cap:
238        frame = wait_record_pressed_update_display(True, cap)
239        with open_next_image("gif") as f, gifio.GifWriter(
240            f, cam.width, cam.height, displayio.Colorspace.RGB565_SWAPPED, dither=True
241        ) as g:
242            g.add_frame(frame, replay_frame_time)
243            for i in range(1, n_frames):
244                print(f"{i}/{n_frames}")
245
246                # CircuitPython Versions <= 8.2.0
247                if hasattr(old_frame, "blit"):
248                    old_frame.blit(0, 0, frame, x1=0, y1=0, x2=frame.width, y2=frame.height)
249
250                # CircuitPython Versions >= 9.0.0
251                else:
252                    bitmaptools.blit(
253                        old_frame,
254                        frame,
255                        0,
256                        0,
257                        x1=0,
258                        y1=0,
259                        x2=frame.width,
260                        y2=frame.height,
261                    )
262
263                frame = wait_record_pressed_update_display(False, cap)
264                g.add_frame(frame, replay_frame_time)
265            print("done")
266
267
268est_frame_size = cam.width * cam.height * 128 // 126 + 1
269est_hdr_size = 1000
270
271dither = True
272while True:
273    take_stop_motion_gif()