Skip to content

Commit af5e630

Browse files
authored
Merge pull request #2 from adafruit/autofocus-etc
WIP enabling autofocus
2 parents 2262a39 + 9fa832c commit af5e630

File tree

3 files changed

+241
-68
lines changed

3 files changed

+241
-68
lines changed

adafruit_pycamera.py

Lines changed: 226 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import struct
55
import board
66
from digitalio import DigitalInOut, Direction, Pull
7-
from adafruit_debouncer import Debouncer
7+
from adafruit_debouncer import Debouncer, Button
88
import bitmaptools
99
import busio
1010
import adafruit_lis3dh
@@ -20,16 +20,52 @@
2020
import pwmio
2121
import microcontroller
2222
import 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

2829
from 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

3252
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+
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

Comments
 (0)