44import struct
55import board
66from digitalio import DigitalInOut , Direction , Pull
7- from adafruit_debouncer import Debouncer
7+ from adafruit_debouncer import Debouncer , Button
88import bitmaptools
99import busio
1010import adafruit_lis3dh
2020import pwmio
2121import microcontroller
2222import adafruit_aw9523
23- import espidf
23+ from adafruit_bus_device .i2c_device import I2CDevice
24+ from rainbowio import colorwheel
2425
2526__version__ = "0.0.0-auto.0"
2627__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PyCamera.git"
2728
2829from micropython import const
2930
31+ _REG_DLY = const (0xFFFF )
32+
33+ _OV5640_STAT_FIRMWAREBAD = const (0x7F )
34+ _OV5640_STAT_STARTUP = const (0x7E )
35+ _OV5640_STAT_IDLE = const (0x70 )
36+ _OV5640_STAT_FOCUSING = const (0x00 )
37+ _OV5640_STAT_FOCUSED = const (0x10 )
38+
39+ _OV5640_CMD_TRIGGER_AUTOFOCUS = const (0x03 )
40+ _OV5640_CMD_AUTO_AUTOFOCUS = const (0x04 )
41+ _OV5640_CMD_RELEASE_FOCUS = const (0x08 )
3042
43+ _OV5640_CMD_MAIN = const (0x3022 )
44+ _OV5640_CMD_ACK = const (0x3023 )
45+ _OV5640_CMD_PARA0 = const (0x3024 )
46+ _OV5640_CMD_PARA1 = const (0x3025 )
47+ _OV5640_CMD_PARA2 = const (0x3026 )
48+ _OV5640_CMD_PARA3 = const (0x3027 )
49+ _OV5640_CMD_PARA4 = const (0x3028 )
50+ _OV5640_CMD_FW_STATUS = const (0x3029 )
3151
3252class PyCamera :
53+ _finalize_firmware_load = (
54+ 0x3022 , 0x00 ,
55+ 0x3023 , 0x00 ,
56+ 0x3024 , 0x00 ,
57+ 0x3025 , 0x00 ,
58+ 0x3026 , 0x00 ,
59+ 0x3027 , 0x00 ,
60+ 0x3028 , 0x00 ,
61+ 0x3029 , 0x7f ,
62+ 0x3000 , 0x00 ,
63+ )
64+ led_levels = [0. , .1 , .2 , .5 , 1. ]
65+
66+ colors = [0xffffff , 0xff0000 , 0xffff00 , 0x00ff00 , 0x00ffff , 0x0000ff , 0xff00ff ,
67+ [colorwheel (i * (256 // 8 )) for i in range (8 )]]
68+
3369 resolutions = (
3470 #"160x120",
3571 #"176x144",
@@ -125,31 +161,70 @@ def i2c_scan(self):
125161
126162
127163 def __init__ (self ) -> None :
128- if espidf .get_reserved_psram () < 1024 * 512 :
129- raise RuntimeError ("Please reserve at least 512kB of PSRAM!" )
130-
131164 self .t = time .monotonic ()
132165 self ._i2c = board .I2C ()
133166 self ._spi = board .SPI ()
134167 self .deinit_display ()
135168
136169 self .splash = displayio .Group ()
137- self ._sd_label = label .Label (terminalio .FONT , text = "SD ??" , color = 0x0 , x = 180 , y = 10 , scale = 2 )
170+ self ._sd_label = label .Label (terminalio .FONT , text = "SD ??" , color = 0x0 , x = 150 , y = 10 , scale = 2 )
138171 self ._effect_label = label .Label (terminalio .FONT , text = "EFFECT" , color = 0xFFFFFF , x = 4 , y = 10 , scale = 2 )
139172 self ._mode_label = label .Label (terminalio .FONT , text = "MODE" , color = 0xFFFFFF , x = 150 , y = 10 , scale = 2 )
140173
174+ # turn on the display first, its reset line may be shared with the IO expander(?)
175+ if not self .display :
176+ self .init_display ()
177+
178+ self .shutter_button = DigitalInOut (board .BUTTON )
179+ self .shutter_button .switch_to_input (Pull .UP )
180+ self .shutter = Button (self .shutter_button )
181+
182+ print ("reset camera" )
183+ self ._cam_reset = DigitalInOut (board .CAMERA_RESET )
184+ self ._cam_pwdn = DigitalInOut (board .CAMERA_PWDN )
185+
186+ self ._cam_reset .switch_to_output (False )
187+ self ._cam_pwdn .switch_to_output (True )
188+ time .sleep (0.01 )
189+ self ._cam_pwdn .switch_to_output (False )
190+ time .sleep (0.01 )
191+ self ._cam_reset .switch_to_output (True )
192+ time .sleep (0.01 )
193+
194+ print ("pre cam @" , time .monotonic ()- self .t )
195+ self .i2c_scan ()
196+
141197 # AW9523 GPIO expander
142198 self ._aw = adafruit_aw9523 .AW9523 (self ._i2c , address = 0x58 )
143199 print ("Found AW9523" )
200+
201+ def make_expander_input (pin_no ):
202+ pin = self ._aw .get_pin (pin_no )
203+ pin .switch_to_input ()
204+ return pin
205+
206+ def make_expander_output (pin_no , value ):
207+ pin = self ._aw .get_pin (pin_no )
208+ pin .switch_to_output (value )
209+ return pin
210+
211+ def make_debounced_expander_pin (pin_no ):
212+ pin = self ._aw .get_pin (pin_no )
213+ pin .switch_to_input ()
214+ return Debouncer (make_expander_input (pin_no ))
215+
216+
217+ self .up = make_debounced_expander_pin (_AW_UP )
218+ self .left = make_debounced_expander_pin (_AW_LEFT )
219+ self .right = make_debounced_expander_pin (_AW_RIGHT )
220+ self .down = make_debounced_expander_pin (_AW_DOWN )
221+ self .select = make_debounced_expander_pin (_AW_SELECT )
222+ self .ok = make_debounced_expander_pin (_AW_OK )
223+ self .card_detect = make_debounced_expander_pin (_AW_CARDDET )
144224
145- self .carddet_pin = self ._aw .get_pin (_AW_CARDDET )
146- self .card_detect = Debouncer (self .carddet_pin )
147-
148- self ._card_power = self ._aw .get_pin (_AW_SDPWR )
149- self ._card_power .switch_to_output (True )
225+ self ._card_power = make_expander_output (_AW_SDPWR , True )
150226
151- self .mute = self ._aw .get_pin (_AW_MUTE )
152- self .mute .switch_to_output (False )
227+ self .mute = make_expander_input (_AW_MUTE )
153228
154229 self .sdcard = None
155230 try :
@@ -162,24 +237,14 @@ def __init__(self) -> None:
162237 self .accel = adafruit_lis3dh .LIS3DH_I2C (self ._i2c , address = 0x19 )
163238 self .accel .range = adafruit_lis3dh .RANGE_2_G
164239
165- # built in neopixels
240+ # main board neopixel
166241 neopix = neopixel .NeoPixel (board .NEOPIXEL , 1 , brightness = 0.1 )
167242 neopix .fill (0 )
168-
169- # camera!
170- self ._cam_reset = DigitalInOut (board .CAMERA_RESET )
171- self ._cam_pwdn = DigitalInOut (board .CAMERA_PWDN )
243+ neopix .deinit ()
172244
173- self ._cam_reset .switch_to_output (False )
174- self ._cam_pwdn .switch_to_output (True )
175- time .sleep (0.01 )
176- self ._cam_pwdn .switch_to_output (False )
177- time .sleep (0.01 )
178- self ._cam_reset .switch_to_output (True )
179- time .sleep (0.01 )
180-
181- print ("pre cam @" , time .monotonic ()- self .t )
182- self .i2c_scan ()
245+ # front bezel neopixels
246+ self .pixels = neopixel .NeoPixel (board .A1 , 8 , brightness = 0.1 , pixel_order = neopixel .RGBW )
247+ self .pixels .fill (0 )
183248
184249 print ("Initializing camera" )
185250 self .camera = espcamera .Camera (
@@ -194,36 +259,16 @@ def __init__(self) -> None:
194259 external_clock_frequency = 20_000_000 ,
195260 framebuffer_count = 2 )
196261
197- print ("Found camera %s (%d x %d)" % (self .camera .sensor_name , self .camera .width , self .camera .height ))
262+ print ("Found camera %s (%d x %d) at I2C address %02x " % (self .camera .sensor_name , self .camera .width , self .camera .height , self . camera . address ))
198263 print ("camera done @" , time .monotonic ()- self .t )
199264 print (dir (self .camera ))
200265
266+ self ._camera_device = I2CDevice (self ._i2c , self .camera .address )
201267 #display.auto_refresh = False
202268
203269 self .camera .hmirror = True
204270 self .camera .vflip = True
205271
206- # action!
207- if not self .display :
208- self .init_display ()
209-
210- self .shutter_button = DigitalInOut (board .BUTTON )
211- self .shutter_button .switch_to_input (Pull .UP )
212- self .shutter = Debouncer (self .shutter_button )
213-
214- self .up_pin = self ._aw .get_pin (_AW_UP )
215- self .up_pin .switch_to_input ()
216- self .up = Debouncer (self .up_pin )
217- self .down_pin = self ._aw .get_pin (_AW_DOWN )
218- self .down_pin .switch_to_input ()
219- self .down = Debouncer (self .down_pin )
220- self .left_pin = self ._aw .get_pin (_AW_LEFT )
221- self .left_pin .switch_to_input ()
222- self .left = Debouncer (self .left_pin )
223- self .right_pin = self ._aw .get_pin (_AW_RIGHT )
224- self .right_pin .switch_to_input ()
225- self .right = Debouncer (self .right_pin )
226-
227272 self ._bigbuf = None
228273
229274 self ._topbar = displayio .Group ()
@@ -240,30 +285,126 @@ def __init__(self) -> None:
240285 self .display .root_group = self .splash
241286 self .display .refresh ()
242287
288+ self .led_color = 0
289+ self .led_level = 0
290+
243291 #self.camera.colorbar = True
244292 self .effect = microcontroller .nvm [_NVM_EFFECT ]
245293 self .camera .saturation = 3
246294 self .resolution = microcontroller .nvm [_NVM_RESOLUTION ]
247295 self .mode = microcontroller .nvm [_NVM_MODE ]
248296 print ("init done @" , time .monotonic ()- self .t )
249297
298+ def autofocus_init_from_file (self , filename ):
299+ with open (filename , mode = 'rb' ) as file :
300+ firmware = file .read ()
301+ self .autofocus_init_from_bitstream (firmware )
302+
303+ def write_camera_register (self , reg : int , value : int ) -> None :
304+ b = bytearray (3 )
305+ b [0 ] = reg >> 8
306+ b [1 ] = reg & 0xFF
307+ b [2 ] = value
308+ with self ._camera_device as i2c :
309+ i2c .write (b )
310+
311+ def write_camera_list (self , reg_list : Sequence [int ]) -> None :
312+ for i in range (0 , len (reg_list ), 2 ):
313+ register = reg_list [i ]
314+ value = reg_list [i + 1 ]
315+ if register == _REG_DLY :
316+ time .sleep (value / 1000 )
317+ else :
318+ self .write_camera_register (register , value )
319+
320+ def read_camera_register (self , reg : int ) -> int :
321+ b = bytearray (2 )
322+ b [0 ] = reg >> 8
323+ b [1 ] = reg & 0xFF
324+ with self ._camera_device as i2c :
325+ i2c .write (b )
326+ i2c .readinto (b , end = 1 )
327+ return b [0 ]
328+
329+ def autofocus_init_from_bitstream (self , firmware ):
330+ if self .camera .sensor_name != "OV5640" :
331+ raise RuntimeError (f"Autofocus not supported on { self .camera .sensor_name } " )
332+
333+ self .write_camera_register (0x3000 , 0x20 ) # reset autofocus coprocessor
334+
335+ for addr , val in enumerate (firmware ):
336+ self .write_camera_register (0x8000 + addr , val )
337+
338+ self .write_camera_list (self ._finalize_firmware_load )
339+ for _ in range (100 ):
340+ self ._print_focus_status ("init from bitstream" )
341+ if self .autofocus_status == _OV5640_STAT_IDLE :
342+ break
343+ time .sleep (0.01 )
344+ else :
345+ raise RuntimeError ("Timed out after trying to load autofocus firmware" )
346+
347+ def autofocus_init (self ):
348+ if '/' in __file__ :
349+ binfile = __file__ .rsplit ("/" , 1 )[0 ].rsplit ("." , 1 )[0 ] + "/ov5640_autofocus.bin"
350+ else :
351+ binfile = "ov5640_autofocus.bin"
352+ print (binfile )
353+ return self .autofocus_init_from_file (binfile )
354+
355+ @property
356+ def autofocus_status (self ):
357+ return self .read_camera_register (_OV5640_CMD_FW_STATUS )
358+
359+ def _print_focus_status (self , msg ):
360+ if False :
361+ print (f"{ msg :36} status={ self .autofocus_status :02x} busy?={ self .read_camera_register (_OV5640_CMD_ACK ):02x} " )
362+
363+ def _send_autofocus_command (self , command , msg ):
364+ self .write_camera_register (_OV5640_CMD_ACK , 0x01 ) # clear command ack
365+ self .write_camera_register (_OV5640_CMD_MAIN , command ) # send command
366+ for _ in range (100 ):
367+ self ._print_focus_status (msg )
368+ if self .read_camera_register (_OV5640_CMD_ACK ) == 0x0 : # command is finished
369+ return True
370+ time .sleep (0.01 )
371+ else :
372+ return False
373+
374+ def autofocus (self ) -> list [int ]:
375+ if not self ._send_autofocus_command (_OV5640_CMD_RELEASE_FOCUS , "release focus" ):
376+ return [False ] * 5
377+ if not self ._send_autofocus_command (_OV5640_CMD_TRIGGER_AUTOFOCUS , "autofocus" ):
378+ return [False ] * 5
379+ zone_focus = [self .read_camera_register (_OV5640_CMD_PARA0 + i ) for i in range (5 )]
380+ print (f"zones focused: { zone_focus } " )
381+ return zone_focus
382+
250383 def select_setting (self , setting_name ):
251384 self ._effect_label .color = 0xFFFFFF
252385 self ._effect_label .background_color = 0x0
253386 self ._res_label .color = 0xFFFFFF
254387 self ._res_label .background_color = 0x0
388+ self ._res_label .text = self .resolutions [self ._resolution ]
255389 self ._mode_label .color = 0xFFFFFF
256390 self ._mode_label .background_color = 0x0
257391 if setting_name == "effect" :
258392 self ._effect_label .color = 0x0
259393 self ._effect_label .background_color = 0xFFFFFF
260- if setting_name == "resolution" :
394+ elif setting_name == "resolution" :
261395 self ._res_label .color = 0x0
262396 self ._res_label .background_color = 0xFFFFFF
263- if setting_name == "mode" :
397+ elif setting_name == "mode" :
264398 self ._mode_label .color = 0x0
265399 self ._mode_label .background_color = 0xFFFFFF
266-
400+ elif setting_name == "led_level" :
401+ self ._res_label .text = "LED LV"
402+ self ._res_label .color = 0x0
403+ self ._res_label .background_color = 0xFFFFFF
404+ elif setting_name == "led_color" :
405+ self ._res_label .text = "LED CLR"
406+ self ._res_label .color = 0x0
407+ self ._res_label .background_color = 0xFFFFFF
267408 self .display .refresh ()
268409
269410 @property
@@ -405,11 +546,13 @@ def unmount_sd_card(self):
405546 def keys_debounce (self ):
406547 # shutter button is true GPIO so we debounce as normal
407548 self .shutter .update ()
408- self .card_detect .update (self .carddet_pin .value )
409- self .up .update (self .up_pin .value )
410- self .down .update (self .down_pin .value )
411- self .left .update (self .left_pin .value )
412- self .right .update (self .right_pin .value )
549+ self .card_detect .update ()
550+ self .up .update ()
551+ self .down .update ()
552+ self .left .update ()
553+ self .right .update ()
554+ self .select .update ()
555+ self .ok .update ()
413556
414557 def tone (self , frequency , duration = 0.1 ):
415558 with pwmio .PWMOut (
@@ -486,3 +629,28 @@ def blit(self, bitmap):
486629 32 + bitmap .height - 1 ))
487630 self ._display_bus .send (44 , bitmap )
488631
632+ @property
633+ def led_level (self ):
634+ return self ._led_level
635+
636+ @led_level .setter
637+ def led_level (self , new_level ):
638+ level = (new_level + len (self .led_levels )) % len (self .led_levels )
639+ self ._led_level = level
640+ self .pixels .brightness = self .led_levels [level ]
641+ self .led_color = self .led_color
642+
643+ @property
644+ def led_color (self ):
645+ return self ._led_color
646+
647+ @led_color .setter
648+ def led_color (self , new_color ):
649+ color = (new_color + len (self .colors )) % len (self .colors )
650+ self ._led_color = color
651+ colors = self .colors [color ]
652+ print ("colors" , colors )
653+ if isinstance (colors , int ):
654+ self .pixels .fill (colors )
655+ else :
656+ self .pixels [:] = colors
0 commit comments