1717import bitmaptools
1818import board
1919import displayio
20+ import fourwire
21+ import busdisplay
2022import espcamera
2123import microcontroller
2224import neopixel
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