Skip to content

Commit 8957115

Browse files
committed
Add bitmap example
1 parent 1bd0e11 commit 8957115

File tree

2 files changed

+234
-4
lines changed

2 files changed

+234
-4
lines changed

docs/examples.rst

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ Preview images on LCD, bypassing displayio for slightly higher framerate
5151
Image-saving tests
5252
------------------
5353

54-
Kaluga 1.3 with ili9341, internal flash
55-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54+
Kaluga 1.3 with ili9341, internal flash, JPEG
55+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5656

5757
Preview images on LCD t hen save JPEG images to internal flash on Kaluga 1.3. Requires the second snippet of
5858
code to be saved as ``boot.py``.
@@ -67,15 +67,25 @@ code to be saved as ``boot.py``.
6767
:caption: ov2640_jpeg_kaluga1_3_boot.py
6868
:linenos:
6969

70-
Kaluga 1.3 with ili9341, external SD card
71-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
70+
Kaluga 1.3 with ili9341, external SD card, JPEG
71+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7272

7373
Preview images on LCD then save JPEG images to SD on Kaluga 1.3 fitted with an ili9341 display.
7474

7575
.. literalinclude:: ../examples/ov2640_jpeg_sd_kaluga1_3.py
7676
:caption: ov2640_jpeg_sd_kaluga1_3.py
7777
:linenos:
7878

79+
Kaluga 1.3 with ili9341, external SD card, BMP
80+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
81+
82+
Preview images on LCD then save BMP images to SD on Kaluga 1.3 fitted with an ili9341 display.
83+
84+
.. literalinclude:: ../examples/ov2640_bmp_sd_kaluga1_3.py
85+
:caption: ov2640_bmp_sd_kaluga1_3.py
86+
:linenos:
87+
88+
7989
Kaluga 1.3 with Adafruit IO
8090
~~~~~~~~~~~~~~~~~~~~~~~~~~~
8191

examples/ov2640_bmp_sd_kaluga1_3.py

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

0 commit comments

Comments
 (0)