4
4
import struct
5
5
import board
6
6
from digitalio import DigitalInOut , Direction , Pull
7
- from adafruit_debouncer import Debouncer
7
+ from adafruit_debouncer import Debouncer , Button
8
8
import bitmaptools
9
9
import busio
10
10
import adafruit_lis3dh
20
20
import pwmio
21
21
import microcontroller
22
22
import adafruit_aw9523
23
- import espidf
23
+ from adafruit_bus_device .i2c_device import I2CDevice
24
+ from rainbowio import colorwheel
24
25
25
26
__version__ = "0.0.0-auto.0"
26
27
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PyCamera.git"
27
28
28
29
from micropython import const
29
30
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 )
30
42
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 )
31
51
32
52
class 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
+
33
69
resolutions = (
34
70
#"160x120",
35
71
#"176x144",
@@ -125,31 +161,70 @@ def i2c_scan(self):
125
161
126
162
127
163
def __init__ (self ) -> None :
128
- if espidf .get_reserved_psram () < 1024 * 512 :
129
- raise RuntimeError ("Please reserve at least 512kB of PSRAM!" )
130
-
131
164
self .t = time .monotonic ()
132
165
self ._i2c = board .I2C ()
133
166
self ._spi = board .SPI ()
134
167
self .deinit_display ()
135
168
136
169
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 )
138
171
self ._effect_label = label .Label (terminalio .FONT , text = "EFFECT" , color = 0xFFFFFF , x = 4 , y = 10 , scale = 2 )
139
172
self ._mode_label = label .Label (terminalio .FONT , text = "MODE" , color = 0xFFFFFF , x = 150 , y = 10 , scale = 2 )
140
173
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
+
141
197
# AW9523 GPIO expander
142
198
self ._aw = adafruit_aw9523 .AW9523 (self ._i2c , address = 0x58 )
143
199
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 )
144
224
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 )
150
226
151
- self .mute = self ._aw .get_pin (_AW_MUTE )
152
- self .mute .switch_to_output (False )
227
+ self .mute = make_expander_input (_AW_MUTE )
153
228
154
229
self .sdcard = None
155
230
try :
@@ -162,24 +237,14 @@ def __init__(self) -> None:
162
237
self .accel = adafruit_lis3dh .LIS3DH_I2C (self ._i2c , address = 0x19 )
163
238
self .accel .range = adafruit_lis3dh .RANGE_2_G
164
239
165
- # built in neopixels
240
+ # main board neopixel
166
241
neopix = neopixel .NeoPixel (board .NEOPIXEL , 1 , brightness = 0.1 )
167
242
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 ()
172
244
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 )
183
248
184
249
print ("Initializing camera" )
185
250
self .camera = espcamera .Camera (
@@ -194,36 +259,16 @@ def __init__(self) -> None:
194
259
external_clock_frequency = 20_000_000 ,
195
260
framebuffer_count = 2 )
196
261
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 ))
198
263
print ("camera done @" , time .monotonic ()- self .t )
199
264
print (dir (self .camera ))
200
265
266
+ self ._camera_device = I2CDevice (self ._i2c , self .camera .address )
201
267
#display.auto_refresh = False
202
268
203
269
self .camera .hmirror = True
204
270
self .camera .vflip = True
205
271
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
-
227
272
self ._bigbuf = None
228
273
229
274
self ._topbar = displayio .Group ()
@@ -240,30 +285,126 @@ def __init__(self) -> None:
240
285
self .display .root_group = self .splash
241
286
self .display .refresh ()
242
287
288
+ self .led_color = 0
289
+ self .led_level = 0
290
+
243
291
#self.camera.colorbar = True
244
292
self .effect = microcontroller .nvm [_NVM_EFFECT ]
245
293
self .camera .saturation = 3
246
294
self .resolution = microcontroller .nvm [_NVM_RESOLUTION ]
247
295
self .mode = microcontroller .nvm [_NVM_MODE ]
248
296
print ("init done @" , time .monotonic ()- self .t )
249
297
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
+
250
383
def select_setting (self , setting_name ):
251
384
self ._effect_label .color = 0xFFFFFF
252
385
self ._effect_label .background_color = 0x0
253
386
self ._res_label .color = 0xFFFFFF
254
387
self ._res_label .background_color = 0x0
388
+ self ._res_label .text = self .resolutions [self ._resolution ]
255
389
self ._mode_label .color = 0xFFFFFF
256
390
self ._mode_label .background_color = 0x0
257
391
if setting_name == "effect" :
258
392
self ._effect_label .color = 0x0
259
393
self ._effect_label .background_color = 0xFFFFFF
260
- if setting_name == "resolution" :
394
+ elif setting_name == "resolution" :
261
395
self ._res_label .color = 0x0
262
396
self ._res_label .background_color = 0xFFFFFF
263
- if setting_name == "mode" :
397
+ elif setting_name == "mode" :
264
398
self ._mode_label .color = 0x0
265
399
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
267
408
self .display .refresh ()
268
409
269
410
@property
@@ -405,11 +546,13 @@ def unmount_sd_card(self):
405
546
def keys_debounce (self ):
406
547
# shutter button is true GPIO so we debounce as normal
407
548
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 ()
413
556
414
557
def tone (self , frequency , duration = 0.1 ):
415
558
with pwmio .PWMOut (
@@ -486,3 +629,28 @@ def blit(self, bitmap):
486
629
32 + bitmap .height - 1 ))
487
630
self ._display_bus .send (44 , bitmap )
488
631
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