17
17
import bitmaptools
18
18
import board
19
19
import displayio
20
+ import fourwire
21
+ import busdisplay
20
22
import espcamera
21
23
import microcontroller
22
24
import neopixel
71
73
_NVM_MODE = const (3 )
72
74
73
75
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"""
76
80
77
81
_finalize_firmware_load = (
78
82
0x3022 ,
@@ -176,59 +180,41 @@ class PyCamera: # pylint: disable=too-many-instance-attributes,too-many-public-
176
180
b"\x29 \x80 \x05 " # _DISPON and Delay 5ms
177
181
)
178
182
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
-
192
183
def __init__ (self ) -> None : # pylint: disable=too-many-statements
193
- self . _timestamp = time . monotonic ()
184
+ displayio . release_displays ()
194
185
self ._i2c = board .I2C ()
195
186
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
198
203
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
- )
208
204
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 )
212
210
213
211
self .shutter_button = DigitalInOut (board .BUTTON )
214
212
self .shutter_button .switch_to_input (Pull .UP )
215
213
self .shutter = Button (self .shutter_button )
216
214
217
- print ("reset camera" )
218
215
self ._cam_reset = DigitalInOut (board .CAMERA_RESET )
219
216
self ._cam_pwdn = DigitalInOut (board .CAMERA_PWDN )
220
217
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
-
232
218
# AW9523 GPIO expander
233
219
self ._aw = adafruit_aw9523 .AW9523 (self ._i2c , address = 0x58 )
234
220
print ("Found AW9523" )
@@ -260,17 +246,39 @@ def make_debounced_expander_pin(pin_no):
260
246
261
247
self .mute = make_expander_output (_AW_MUTE , False )
262
248
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 )
269
270
271
+ self .splash .append (self ._topbar )
272
+ self .splash .append (self ._botbar )
273
+
274
+ def init_accelerometer (self ):
275
+ """Initialize the accelerometer"""
270
276
# lis3dh accelerometer
271
277
self .accel = adafruit_lis3dh .LIS3DH_I2C (self ._i2c , address = 0x19 )
272
278
self .accel .range = adafruit_lis3dh .RANGE_2_G
273
279
280
+ def init_neopixel (self ):
281
+ """Initialize the neopixels (onboard & ring)"""
274
282
# main board neopixel
275
283
neopix = neopixel .NeoPixel (board .NEOPIXEL , 1 , brightness = 0.1 )
276
284
neopix .fill (0 )
@@ -282,6 +290,17 @@ def make_debounced_expander_pin(pin_no):
282
290
)
283
291
self .pixels .fill (0 )
284
292
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
+
285
304
print ("Initializing camera" )
286
305
self .camera = espcamera .Camera (
287
306
data_pins = board .CAMERA_DATA ,
@@ -305,33 +324,12 @@ def make_debounced_expander_pin(pin_no):
305
324
self .camera .address ,
306
325
)
307
326
)
308
- print ("camera done @" , time .monotonic () - self ._timestamp )
309
- print (dir (self .camera ))
310
327
311
328
self ._camera_device = I2CDevice (self ._i2c , self .camera .address )
312
- # display.auto_refresh = False
313
329
314
330
self .camera .hmirror = False
315
331
self .camera .vflip = True
316
332
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
-
335
333
self .led_color = 0
336
334
self .led_level = 0
337
335
@@ -340,6 +338,10 @@ def make_debounced_expander_pin(pin_no):
340
338
self .camera .saturation = 3
341
339
self .resolution = microcontroller .nvm [_NVM_RESOLUTION ]
342
340
self .mode = microcontroller .nvm [_NVM_MODE ]
341
+
342
+ if init_autofocus :
343
+ self .autofocus_init ()
344
+
343
345
print ("init done @" , time .monotonic () - self ._timestamp )
344
346
345
347
def autofocus_init_from_file (self , filename ):
@@ -383,9 +385,19 @@ def autofocus_init_from_bitstream(self, firmware: bytes):
383
385
raise RuntimeError (f"Autofocus not supported on { self .camera .sensor_name } " )
384
386
385
387
self .write_camera_register (0x3000 , 0x20 ) # reset autofocus coprocessor
388
+ time .sleep (0.01 )
386
389
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 )
389
401
390
402
self .write_camera_list (self ._finalize_firmware_load )
391
403
for _ in range (100 ):
@@ -526,26 +538,26 @@ def resolution(self, res):
526
538
self ._res_label .text = self .resolutions [res ]
527
539
self .display .refresh ()
528
540
529
- def init_display (self , reset = True ):
541
+ def init_display (self ):
530
542
"""Initialize the TFT display"""
531
543
# construct displayio by hand
532
544
displayio .release_displays ()
533
- self ._display_bus = displayio .FourWire (
545
+ self ._display_bus = fourwire .FourWire (
534
546
self ._spi ,
535
547
command = board .TFT_DC ,
536
548
chip_select = board .TFT_CS ,
537
- reset = board . TFT_RESET if reset else None ,
549
+ reset = None ,
538
550
baudrate = 60_000_000 ,
539
551
)
540
- self .display = board .DISPLAY
541
552
# init specially since we are going to write directly below
542
- self .display = displayio . Display (
553
+ self .display = busdisplay . BusDisplay (
543
554
self ._display_bus ,
544
555
self ._INIT_SEQUENCE ,
545
556
width = 240 ,
546
557
height = 240 ,
547
558
colstart = 80 ,
548
559
auto_refresh = False ,
560
+ backlight_pin = board .TFT_BACKLIGHT ,
549
561
)
550
562
self .display .root_group = self .splash
551
563
self .display .refresh ()
@@ -562,7 +574,7 @@ def display_message(self, message, color=0xFF0000, scale=3):
562
574
text_area = label .Label (terminalio .FONT , text = message , color = color , scale = scale )
563
575
text_area .anchor_point = (0.5 , 0.5 )
564
576
if not self .display :
565
- self .init_display (None )
577
+ self .init_display ()
566
578
text_area .anchored_position = (self .display .width / 2 , self .display .height / 2 )
567
579
568
580
# Show it
@@ -572,10 +584,11 @@ def display_message(self, message, color=0xFF0000, scale=3):
572
584
573
585
def mount_sd_card (self ):
574
586
"""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
577
590
if not self .card_detect .value :
578
- raise RuntimeError ("SD card detection failed " )
591
+ raise RuntimeError ("No SD card inserted " )
579
592
if self .sdcard :
580
593
self .sdcard .deinit ()
581
594
# depower SD card
@@ -585,6 +598,7 @@ def mount_sd_card(self):
585
598
# deinit display and SPI bus because we need to drive all SD pins LOW
586
599
# to ensure nothing, not even an I/O pin, could possibly power the SD
587
600
# card
601
+ had_display = self .display is not None
588
602
self .deinit_display ()
589
603
self ._spi .deinit ()
590
604
sckpin = DigitalInOut (board .SCK )
@@ -604,23 +618,28 @@ def mount_sd_card(self):
604
618
self ._card_power .value = False
605
619
card_cs .deinit ()
606
620
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 ()
615
633
616
634
def unmount_sd_card (self ):
617
635
"""Unmount the SD card, if mounted"""
618
636
try :
619
637
storage .umount ("/sd" )
620
638
except OSError :
621
639
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
624
643
625
644
def keys_debounce (self ):
626
645
"""Debounce all keys.
@@ -733,7 +752,7 @@ def continuous_capture(self):
733
752
or the camera's capture mode is changed"""
734
753
return self .camera .take (1 )
735
754
736
- def blit (self , bitmap ):
755
+ def blit (self , bitmap , x_offset = 0 , y_offset = 32 ):
737
756
"""Display a bitmap direct to the LCD, bypassing displayio
738
757
739
758
This can be more efficient than displaying a bitmap as a displayio
@@ -744,8 +763,12 @@ def blit(self, bitmap):
744
763
for status information.
745
764
"""
746
765
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
+ )
749
772
self ._display_bus .send (44 , bitmap )
750
773
751
774
@property
@@ -775,3 +798,21 @@ def led_color(self, new_color):
775
798
self .pixels .fill (colors )
776
799
else :
777
800
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