Skip to content

Commit 16530b5

Browse files
authored
Merge pull request #15 from adafruit/enhancements
Various enhancements
2 parents 460b169 + 05dc862 commit 16530b5

File tree

5 files changed

+295
-101
lines changed

5 files changed

+295
-101
lines changed

adafruit_pycamera/__init__.py

+134-93
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import bitmaptools
1818
import board
1919
import displayio
20+
import fourwire
21+
import busdisplay
2022
import espcamera
2123
import microcontroller
2224
import neopixel
@@ -71,8 +73,10 @@
7173
_NVM_MODE = const(3)
7274

7375

74-
class PyCamera: # pylint: disable=too-many-instance-attributes,too-many-public-methods
75-
"""Wrapper class for the PyCamera hardware"""
76+
class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-public-methods
77+
"""Base class for PyCamera hardware"""
78+
79+
"""Wrapper class for the PyCamera hardware with lots of smarts"""
7680

7781
_finalize_firmware_load = (
7882
0x3022,
@@ -176,59 +180,41 @@ class PyCamera: # pylint: disable=too-many-instance-attributes,too-many-public-
176180
b"\x29\x80\x05" # _DISPON and Delay 5ms
177181
)
178182

179-
def i2c_scan(self):
180-
"""Print an I2C bus scan"""
181-
while not self._i2c.try_lock():
182-
pass
183-
184-
try:
185-
print(
186-
"I2C addresses found:",
187-
[hex(device_address) for device_address in self._i2c.scan()],
188-
)
189-
finally: # unlock the i2c bus when ctrl-c'ing out of the loop
190-
self._i2c.unlock()
191-
192183
def __init__(self) -> None: # pylint: disable=too-many-statements
193-
self._timestamp = time.monotonic()
184+
displayio.release_displays()
194185
self._i2c = board.I2C()
195186
self._spi = board.SPI()
196-
self.deinit_display()
197-
187+
self._timestamp = time.monotonic()
188+
self._bigbuf = None
189+
self._botbar = None
190+
self._camera_device = None
191+
self._display_bus = None
192+
self._effect_label = None
193+
self._image_counter = 0
194+
self._mode_label = None
195+
self._res_label = None
196+
self._sd_label = None
197+
self._topbar = None
198+
self.accel = None
199+
self.camera = None
200+
self.display = None
201+
self.pixels = None
202+
self.sdcard = None
198203
self.splash = displayio.Group()
199-
self._sd_label = label.Label(
200-
terminalio.FONT, text="SD ??", color=0x0, x=150, y=10, scale=2
201-
)
202-
self._effect_label = label.Label(
203-
terminalio.FONT, text="EFFECT", color=0xFFFFFF, x=4, y=10, scale=2
204-
)
205-
self._mode_label = label.Label(
206-
terminalio.FONT, text="MODE", color=0xFFFFFF, x=150, y=10, scale=2
207-
)
208204

209-
# turn on the display first, its reset line may be shared with the IO expander(?)
210-
if not self.display:
211-
self.init_display()
205+
# Reset display and I/O expander
206+
self._tft_aw_reset = DigitalInOut(board.TFT_RESET)
207+
self._tft_aw_reset.switch_to_output(False)
208+
time.sleep(0.05)
209+
self._tft_aw_reset.switch_to_output(True)
212210

213211
self.shutter_button = DigitalInOut(board.BUTTON)
214212
self.shutter_button.switch_to_input(Pull.UP)
215213
self.shutter = Button(self.shutter_button)
216214

217-
print("reset camera")
218215
self._cam_reset = DigitalInOut(board.CAMERA_RESET)
219216
self._cam_pwdn = DigitalInOut(board.CAMERA_PWDN)
220217

221-
self._cam_reset.switch_to_output(False)
222-
self._cam_pwdn.switch_to_output(True)
223-
time.sleep(0.01)
224-
self._cam_pwdn.switch_to_output(False)
225-
time.sleep(0.01)
226-
self._cam_reset.switch_to_output(True)
227-
time.sleep(0.01)
228-
229-
print("pre cam @", time.monotonic() - self._timestamp)
230-
self.i2c_scan()
231-
232218
# AW9523 GPIO expander
233219
self._aw = adafruit_aw9523.AW9523(self._i2c, address=0x58)
234220
print("Found AW9523")
@@ -260,17 +246,39 @@ def make_debounced_expander_pin(pin_no):
260246

261247
self.mute = make_expander_output(_AW_MUTE, False)
262248

263-
self.sdcard = None
264-
try:
265-
self.mount_sd_card()
266-
except RuntimeError:
267-
pass # no card found, its ok!
268-
print("sdcard done @", time.monotonic() - self._timestamp)
249+
def make_camera_ui(self):
250+
"""Create displayio widgets for the standard camera UI"""
251+
self._sd_label = label.Label(
252+
terminalio.FONT, text="SD ??", color=0x0, x=150, y=10, scale=2
253+
)
254+
self._effect_label = label.Label(
255+
terminalio.FONT, text="EFFECT", color=0xFFFFFF, x=4, y=10, scale=2
256+
)
257+
self._mode_label = label.Label(
258+
terminalio.FONT, text="MODE", color=0xFFFFFF, x=150, y=10, scale=2
259+
)
260+
self._topbar = displayio.Group()
261+
self._res_label = label.Label(
262+
terminalio.FONT, text="", color=0xFFFFFF, x=0, y=10, scale=2
263+
)
264+
self._topbar.append(self._res_label)
265+
self._topbar.append(self._sd_label)
266+
267+
self._botbar = displayio.Group(x=0, y=210)
268+
self._botbar.append(self._effect_label)
269+
self._botbar.append(self._mode_label)
269270

271+
self.splash.append(self._topbar)
272+
self.splash.append(self._botbar)
273+
274+
def init_accelerometer(self):
275+
"""Initialize the accelerometer"""
270276
# lis3dh accelerometer
271277
self.accel = adafruit_lis3dh.LIS3DH_I2C(self._i2c, address=0x19)
272278
self.accel.range = adafruit_lis3dh.RANGE_2_G
273279

280+
def init_neopixel(self):
281+
"""Initialize the neopixels (onboard & ring)"""
274282
# main board neopixel
275283
neopix = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.1)
276284
neopix.fill(0)
@@ -282,6 +290,17 @@ def make_debounced_expander_pin(pin_no):
282290
)
283291
self.pixels.fill(0)
284292

293+
def init_camera(self, init_autofocus=True) -> None:
294+
"""Initialize the camera, by default including autofocus"""
295+
print("reset camera")
296+
self._cam_reset.switch_to_output(False)
297+
self._cam_pwdn.switch_to_output(True)
298+
time.sleep(0.01)
299+
self._cam_pwdn.switch_to_output(False)
300+
time.sleep(0.01)
301+
self._cam_reset.switch_to_output(True)
302+
time.sleep(0.01)
303+
285304
print("Initializing camera")
286305
self.camera = espcamera.Camera(
287306
data_pins=board.CAMERA_DATA,
@@ -305,33 +324,12 @@ def make_debounced_expander_pin(pin_no):
305324
self.camera.address,
306325
)
307326
)
308-
print("camera done @", time.monotonic() - self._timestamp)
309-
print(dir(self.camera))
310327

311328
self._camera_device = I2CDevice(self._i2c, self.camera.address)
312-
# display.auto_refresh = False
313329

314330
self.camera.hmirror = False
315331
self.camera.vflip = True
316332

317-
self._bigbuf = None
318-
319-
self._topbar = displayio.Group()
320-
self._res_label = label.Label(
321-
terminalio.FONT, text="", color=0xFFFFFF, x=0, y=10, scale=2
322-
)
323-
self._topbar.append(self._res_label)
324-
self._topbar.append(self._sd_label)
325-
326-
self._botbar = displayio.Group(x=0, y=210)
327-
self._botbar.append(self._effect_label)
328-
self._botbar.append(self._mode_label)
329-
330-
self.splash.append(self._topbar)
331-
self.splash.append(self._botbar)
332-
self.display.root_group = self.splash
333-
self.display.refresh()
334-
335333
self.led_color = 0
336334
self.led_level = 0
337335

@@ -340,6 +338,10 @@ def make_debounced_expander_pin(pin_no):
340338
self.camera.saturation = 3
341339
self.resolution = microcontroller.nvm[_NVM_RESOLUTION]
342340
self.mode = microcontroller.nvm[_NVM_MODE]
341+
342+
if init_autofocus:
343+
self.autofocus_init()
344+
343345
print("init done @", time.monotonic() - self._timestamp)
344346

345347
def autofocus_init_from_file(self, filename):
@@ -383,9 +385,19 @@ def autofocus_init_from_bitstream(self, firmware: bytes):
383385
raise RuntimeError(f"Autofocus not supported on {self.camera.sensor_name}")
384386

385387
self.write_camera_register(0x3000, 0x20) # reset autofocus coprocessor
388+
time.sleep(0.01)
386389

387-
for addr, val in enumerate(firmware):
388-
self.write_camera_register(0x8000 + addr, val)
390+
arr = bytearray(256)
391+
with self._camera_device as i2c:
392+
for offset in range(0, len(firmware), 254):
393+
num_firmware_bytes = min(254, len(firmware) - offset)
394+
reg = offset + 0x8000
395+
arr[0] = reg >> 8
396+
arr[1] = reg & 0xFF
397+
arr[2 : 2 + num_firmware_bytes] = firmware[
398+
offset : offset + num_firmware_bytes
399+
]
400+
i2c.write(arr, end=2 + num_firmware_bytes)
389401

390402
self.write_camera_list(self._finalize_firmware_load)
391403
for _ in range(100):
@@ -526,26 +538,26 @@ def resolution(self, res):
526538
self._res_label.text = self.resolutions[res]
527539
self.display.refresh()
528540

529-
def init_display(self, reset=True):
541+
def init_display(self):
530542
"""Initialize the TFT display"""
531543
# construct displayio by hand
532544
displayio.release_displays()
533-
self._display_bus = displayio.FourWire(
545+
self._display_bus = fourwire.FourWire(
534546
self._spi,
535547
command=board.TFT_DC,
536548
chip_select=board.TFT_CS,
537-
reset=board.TFT_RESET if reset else None,
549+
reset=None,
538550
baudrate=60_000_000,
539551
)
540-
self.display = board.DISPLAY
541552
# init specially since we are going to write directly below
542-
self.display = displayio.Display(
553+
self.display = busdisplay.BusDisplay(
543554
self._display_bus,
544555
self._INIT_SEQUENCE,
545556
width=240,
546557
height=240,
547558
colstart=80,
548559
auto_refresh=False,
560+
backlight_pin=board.TFT_BACKLIGHT,
549561
)
550562
self.display.root_group = self.splash
551563
self.display.refresh()
@@ -562,7 +574,7 @@ def display_message(self, message, color=0xFF0000, scale=3):
562574
text_area = label.Label(terminalio.FONT, text=message, color=color, scale=scale)
563575
text_area.anchor_point = (0.5, 0.5)
564576
if not self.display:
565-
self.init_display(None)
577+
self.init_display()
566578
text_area.anchored_position = (self.display.width / 2, self.display.height / 2)
567579

568580
# Show it
@@ -572,10 +584,11 @@ def display_message(self, message, color=0xFF0000, scale=3):
572584

573585
def mount_sd_card(self):
574586
"""Attempt to mount the SD card"""
575-
self._sd_label.text = "NO SD"
576-
self._sd_label.color = 0xFF0000
587+
if self._sd_label is not None:
588+
self._sd_label.text = "NO SD"
589+
self._sd_label.color = 0xFF0000
577590
if not self.card_detect.value:
578-
raise RuntimeError("SD card detection failed")
591+
raise RuntimeError("No SD card inserted")
579592
if self.sdcard:
580593
self.sdcard.deinit()
581594
# depower SD card
@@ -585,6 +598,7 @@ def mount_sd_card(self):
585598
# deinit display and SPI bus because we need to drive all SD pins LOW
586599
# to ensure nothing, not even an I/O pin, could possibly power the SD
587600
# card
601+
had_display = self.display is not None
588602
self.deinit_display()
589603
self._spi.deinit()
590604
sckpin = DigitalInOut(board.SCK)
@@ -604,23 +618,28 @@ def mount_sd_card(self):
604618
self._card_power.value = False
605619
card_cs.deinit()
606620
print("sdcard init @", time.monotonic() - self._timestamp)
607-
self.sdcard = sdcardio.SDCard(self._spi, board.CARD_CS, baudrate=20_000_000)
608-
vfs = storage.VfsFat(self.sdcard)
609-
print("mount vfs @", time.monotonic() - self._timestamp)
610-
storage.mount(vfs, "/sd")
611-
self.init_display(None)
612-
self._image_counter = 0
613-
self._sd_label.text = "SD OK"
614-
self._sd_label.color = 0x00FF00
621+
try:
622+
self.sdcard = sdcardio.SDCard(self._spi, board.CARD_CS, baudrate=20_000_000)
623+
vfs = storage.VfsFat(self.sdcard)
624+
print("mount vfs @", time.monotonic() - self._timestamp)
625+
storage.mount(vfs, "/sd")
626+
self._image_counter = 0
627+
if self._sd_label is not None:
628+
self._sd_label.text = "SD OK"
629+
self._sd_label.color = 0x00FF00
630+
finally:
631+
if had_display:
632+
self.init_display()
615633

616634
def unmount_sd_card(self):
617635
"""Unmount the SD card, if mounted"""
618636
try:
619637
storage.umount("/sd")
620638
except OSError:
621639
pass
622-
self._sd_label.text = "NO SD"
623-
self._sd_label.color = 0xFF0000
640+
if self._sd_label is not None:
641+
self._sd_label.text = "NO SD"
642+
self._sd_label.color = 0xFF0000
624643

625644
def keys_debounce(self):
626645
"""Debounce all keys.
@@ -733,7 +752,7 @@ def continuous_capture(self):
733752
or the camera's capture mode is changed"""
734753
return self.camera.take(1)
735754

736-
def blit(self, bitmap):
755+
def blit(self, bitmap, x_offset=0, y_offset=32):
737756
"""Display a bitmap direct to the LCD, bypassing displayio
738757
739758
This can be more efficient than displaying a bitmap as a displayio
@@ -744,8 +763,12 @@ def blit(self, bitmap):
744763
for status information.
745764
"""
746765

747-
self._display_bus.send(42, struct.pack(">hh", 80, 80 + bitmap.width - 1))
748-
self._display_bus.send(43, struct.pack(">hh", 32, 32 + bitmap.height - 1))
766+
self._display_bus.send(
767+
42, struct.pack(">hh", 80 + x_offset, 80 + x_offset + bitmap.width - 1)
768+
)
769+
self._display_bus.send(
770+
43, struct.pack(">hh", y_offset, y_offset + bitmap.height - 1)
771+
)
749772
self._display_bus.send(44, bitmap)
750773

751774
@property
@@ -775,3 +798,21 @@ def led_color(self, new_color):
775798
self.pixels.fill(colors)
776799
else:
777800
self.pixels[:] = colors
801+
802+
803+
class PyCamera(PyCameraBase):
804+
"""Wrapper class for the PyCamera hardware"""
805+
806+
def __init__(self, init_autofocus=True):
807+
super().__init__()
808+
809+
self.make_camera_ui()
810+
self.init_accelerometer()
811+
self.init_neopixel()
812+
self.init_display()
813+
self.init_camera(init_autofocus)
814+
try:
815+
self.mount_sd_card()
816+
except Exception as exc: # pylint: disable=broad-exception-caught
817+
# No SD card inserted, it's OK
818+
print(exc)

0 commit comments

Comments
 (0)