Skip to content

Commit 1eaded3

Browse files
committed
Add new example
1 parent 73bce44 commit 1eaded3

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed

docs/examples.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,42 @@ Ensure your device works with this simple test.
66
.. literalinclude:: ../examples/ov5640_simpletest.py
77
:caption: examples/ov5640_simpletest.py
88
:linenos:
9+
10+
Directio
11+
--------
12+
Use an LCD as a viewfinder, bypassing displayio
13+
14+
.. literalinclude:: ../examples/ov5640_directio_kaluga1_3_ili9341.py
15+
:caption: examples/ov5640_directio_kaluga1_3_ili9341.py
16+
:linenos:
17+
18+
JPEG (internal storage)
19+
-----------------------
20+
Record JPEG images onto internal storage. Requires use of ov5640_jpeg_kaluga1_3_boot.py (below) as boot.py.
21+
22+
.. literalinclude:: ../examples/ov5640_jpeg_kaluga1_3.py
23+
:caption: examples/ov5640_jpeg_kaluga1_3.py
24+
:linenos:
25+
26+
Use with above example as boot.py
27+
28+
.. literalinclude:: ../examples/ov5640_jpeg_kaluga1_3_boot.py
29+
:caption: examples/ov5640_jpeg_kaluga1_3_boot.py
30+
:linenos:
31+
32+
JPEG (SD card)
33+
--------------
34+
Record JPEG images to an SD card.
35+
36+
.. literalinclude:: ../examples/ov5640_sdcard_kaluga_1_3.py
37+
:caption: examples/ov5640_sdcard_kaluga_1_3.py
38+
:linenos:
39+
40+
GIF (SD card)
41+
-------------
42+
43+
Record stop-motion GIF images to an SD card.
44+
45+
.. literalinclude:: ../examples/ov5640_stopmotion_kaluga1_3.py
46+
:caption: examples/ov5640_stopmotion_kaluga1_3.py
47+
:linenos:
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
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+
"""
7+
Take a 10-frame stop motion GIF image.
8+
9+
This 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+
21+
To use:
22+
23+
Insert an SD card and power on.
24+
25+
Set up the first frame using the viewfinder. Click the REC button to take a frame.
26+
27+
Set up the next frame using the viewfinder. The previous and current frames are
28+
blended together on the display, which is called an "onionskin". Click the REC
29+
button to take the next frame.
30+
31+
After 10 frames are recorded, the GIF is complete and you can begin recording another.
32+
33+
34+
About the Kaluga development kit:
35+
36+
The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
37+
tested on v1.3.
38+
39+
The audio board must be mounted between the Kaluga and the LCD, it provides the
40+
I2C pull-ups(!)
41+
42+
The v1.3 development kit's LCD can have one of two chips, the ili9341 or
43+
st7789. Furthermore, there are at least 2 ILI9341 variants, which differ
44+
by rotation. This example is written for one if the ILI9341 variants,
45+
the one which usually uses rotation=90 to get a landscape display.
46+
"""
47+
48+
import os
49+
import struct
50+
51+
import analogio
52+
import bitmaptools
53+
import board
54+
import busio
55+
import displayio
56+
import gifio
57+
import sdcardio
58+
import storage
59+
60+
import adafruit_ov5640
61+
62+
V_RECORD = int(2.41 * 65536 / 3.3)
63+
V_FUZZ = 2000
64+
65+
a = analogio.AnalogIn(board.IO6)
66+
67+
68+
def record_pressed():
69+
value = a.value
70+
return abs(value - V_RECORD) < V_FUZZ
71+
72+
73+
displayio.release_displays()
74+
spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
75+
display_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+
108+
display = displayio.Display(display_bus, _INIT_SEQUENCE, width=320, height=240)
109+
110+
sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
111+
sd_cs = board.IO12
112+
sdcard = sdcardio.SDCard(sd_spi, sd_cs, baudrate=24_000_000)
113+
vfs = storage.VfsFat(sdcard)
114+
storage.mount(vfs, "/sd")
115+
116+
bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
117+
cam = 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+
128+
def 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+
139+
def 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
152+
next_filename("gif")
153+
154+
# Blank the whole display, we'll draw what we want with directio
155+
empty_group = displayio.Group()
156+
display.show(empty_group)
157+
display.auto_refresh = False
158+
display.refresh()
159+
160+
161+
def 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+
168+
cam.flip_x = False
169+
cam.flip_y = False
170+
chip_id = cam.chip_id
171+
print(f"Detected 0x{chip_id:x}")
172+
cam.test_pattern = False
173+
cam.effect = adafruit_ov5640.OV5640_SPECIAL_EFFECT_NONE
174+
cam.saturation = 3
175+
176+
# Alternately recording to these two bitmaps
177+
rec1 = displayio.Bitmap(cam.width, cam.height, 65536)
178+
rec2 = displayio.Bitmap(cam.width, cam.height, 65536)
179+
# Prior frame kept here
180+
old_frame = displayio.Bitmap(cam.width, cam.height, 65536)
181+
# Displayed (onion skinned) frame here
182+
onionskin = displayio.Bitmap(cam.width, cam.height, 65536)
183+
184+
ow = (display.width - onionskin.width) // 2
185+
oh = (display.height - onionskin.height) // 2
186+
display_bus.send(42, struct.pack(">hh", ow, onionskin.width + ow - 1))
187+
display_bus.send(43, struct.pack(">hh", oh, onionskin.height + ow - 1))
188+
189+
190+
class 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+
210+
def 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+
228+
def 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+
old_frame.blit(0, 0, frame, x1=0, y1=0, x2=frame.width, y2=frame.height)
239+
frame = wait_record_pressed_update_display(False, cap)
240+
g.add_frame(frame, replay_frame_time)
241+
print("done")
242+
243+
244+
est_frame_size = cam.width * cam.height * 128 // 126 + 1
245+
est_hdr_size = 1000
246+
247+
dither = True
248+
while True:
249+
take_stop_motion_gif()

0 commit comments

Comments
 (0)