Skip to content

Commit 4614ad3

Browse files
committed
Add new demo
1 parent 55cb01c commit 4614ad3

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

  • circuitpython-esp32-camera/esp32-kaluga-onionskin-gif
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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 camera module (such as OV5640) 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 blended together on the display, which is called an "onionskin". Click the REC button to take the next frame.
28+
29+
After 10 frames are recorded, the GIF is complete and you can begin recording another.
30+
31+
32+
About the Kaluga development kit:
33+
34+
The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
35+
tested on v1.3.
36+
37+
The audio board must be mounted between the Kaluga and the LCD, it provides the
38+
I2C pull-ups(!)
39+
40+
The v1.3 development kit's LCD can have one of two chips, the ili9341 or
41+
st7789. Furthermore, there are at least 2 ILI9341 variants, which differ
42+
by rotation. This example is written for one if the ILI9341 variants,
43+
the one which usually uses rotation=90 to get a landscape display.
44+
"""
45+
46+
import os
47+
import struct
48+
import time
49+
50+
import esp32_camera
51+
import analogio
52+
import board
53+
import busio
54+
import bitmaptools
55+
import digitalio
56+
import displayio
57+
import sdcardio
58+
import storage
59+
import gifio
60+
61+
V_RECORD = int(2.41 * 65536 / 3.3)
62+
V_FUZZ = 2000
63+
64+
a = analogio.AnalogIn(board.IO6)
65+
66+
def record_pressed():
67+
value = a.value
68+
return abs(value - V_RECORD) < V_FUZZ
69+
70+
displayio.release_displays()
71+
spi = busio.SPI(MOSI=board.LCD_MOSI, clock=board.LCD_CLK)
72+
display_bus = displayio.FourWire(
73+
spi,
74+
command=board.LCD_D_C,
75+
chip_select=board.LCD_CS,
76+
reset=board.LCD_RST,
77+
baudrate=80_000_000,
78+
)
79+
_INIT_SEQUENCE = (
80+
b"\x01\x80\x80" # Software reset then delay 0x80 (128ms)
81+
b"\xEF\x03\x03\x80\x02"
82+
b"\xCF\x03\x00\xC1\x30"
83+
b"\xED\x04\x64\x03\x12\x81"
84+
b"\xE8\x03\x85\x00\x78"
85+
b"\xCB\x05\x39\x2C\x00\x34\x02"
86+
b"\xF7\x01\x20"
87+
b"\xEA\x02\x00\x00"
88+
b"\xc0\x01\x23" # Power control VRH[5:0]
89+
b"\xc1\x01\x10" # Power control SAP[2:0];BT[3:0]
90+
b"\xc5\x02\x3e\x28" # VCM control
91+
b"\xc7\x01\x86" # VCM control2
92+
b"\x36\x01\x40" # Memory Access Control
93+
b"\x37\x01\x00" # Vertical scroll zero
94+
b"\x3a\x01\x55" # COLMOD: Pixel Format Set
95+
b"\xb1\x02\x00\x18" # Frame Rate Control (In Normal Mode/Full Colors)
96+
b"\xb6\x03\x08\x82\x27" # Display Function Control
97+
b"\xF2\x01\x00" # 3Gamma Function Disable
98+
b"\x26\x01\x01" # Gamma curve selected
99+
b"\xe0\x0f\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00" # Set Gamma
100+
b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F" # Set Gamma
101+
b"\x11\x80\x78" # Exit Sleep then delay 0x78 (120ms)
102+
b"\x29\x80\x78" # Display on then delay 0x78 (120ms)
103+
)
104+
105+
display = displayio.Display(display_bus, _INIT_SEQUENCE, width=320, height=240)
106+
107+
sd_spi = busio.SPI(clock=board.IO18, MOSI=board.IO14, MISO=board.IO17)
108+
sd_cs = board.IO12
109+
sdcard = sdcardio.SDCard(sd_spi, sd_cs, baudrate=24_000_000)
110+
vfs = storage.VfsFat(sdcard)
111+
storage.mount(vfs, "/sd")
112+
113+
cam = esp32_camera.Camera(
114+
data_pins=board.CAMERA_DATA,
115+
external_clock_pin=board.CAMERA_XCLK,
116+
pixel_clock_pin=board.CAMERA_PCLK,
117+
vsync_pin=board.CAMERA_VSYNC,
118+
href_pin=board.CAMERA_HREF,
119+
pixel_format=esp32_camera.PixelFormat.RGB565,
120+
frame_size=esp32_camera.FrameSize.QVGA,
121+
i2c=board.I2C(),
122+
external_clock_frequency=20_000_000,
123+
framebuffer_count=2)
124+
125+
def exists(filename):
126+
try:
127+
os.stat(filename)
128+
return True
129+
except OSError as _:
130+
return False
131+
132+
133+
_image_counter = 0
134+
135+
def next_filename(extension="jpg"):
136+
global _image_counter # pylint: disable=global-statement
137+
while True:
138+
filename = f"/sd/img{_image_counter:04d}.{extension}"
139+
if exists(filename):
140+
print(f"File exists: {filename}", end='\r')
141+
_image_counter += 1
142+
continue
143+
print()
144+
return filename
145+
146+
# Pre-cache the next image number
147+
next_filename("gif")
148+
149+
# Blank the whole display
150+
g = displayio.Group()
151+
display.show(g)
152+
display.auto_refresh = False
153+
display.refresh()
154+
155+
def open_next_image(extension="jpg"):
156+
global _image_counter # pylint: disable=global-statement
157+
while True:
158+
filename = next_filename(extension)
159+
print("# writing to", filename)
160+
return open(filename, "wb")
161+
162+
cam.saturation = 3
163+
164+
old_frame = displayio.Bitmap(cam.width, cam.height, 65536)
165+
# Displayed (onion skinned) frame here
166+
onionskin = displayio.Bitmap(cam.width, cam.height, 65536)
167+
168+
ow = (display.width - onionskin.width) // 2
169+
oh = (display.height - onionskin.height) // 2
170+
display_bus.send(42, struct.pack(">hh", ow, onionskin.width + ow - 1))
171+
display_bus.send(43, struct.pack(">hh", oh, onionskin.height + ow - 1))
172+
173+
def wait_record_pressed_update_display(first_frame, cam):
174+
while record_pressed():
175+
pass
176+
while True:
177+
frame = cam.take(1)
178+
print(type(frame))
179+
if record_pressed():
180+
return frame
181+
182+
if first_frame:
183+
# First frame -- display as-is
184+
display_bus.send(44, frame)
185+
else:
186+
bitmaptools.alphablend(onionskin, old_frame, frame, displayio.Colorspace.RGB565_SWAPPED)
187+
display_bus.send(44, onionskin)
188+
189+
def take_stop_motion_gif(n_frames=10, replay_frame_time=.3):
190+
print(f"0/{n_frames}")
191+
frame = wait_record_pressed_update_display(True, cam)
192+
with open_next_image("gif") as f, gifio.GifWriter(f, cam.width, cam.height, displayio.Colorspace.RGB565_SWAPPED, dither=True) as g:
193+
g.add_frame(frame, replay_frame_time)
194+
for i in range(1, n_frames):
195+
print(f"{i}/{n_frames}")
196+
old_frame.blit(0, 0, frame, x1=0, y1=0, x2=cam.width, y2=cam.height)
197+
frame = wait_record_pressed_update_display(False, cam)
198+
g.add_frame(frame, replay_frame_time)
199+
print(f"done")
200+
201+
while True:
202+
take_stop_motion_gif()

0 commit comments

Comments
 (0)