Skip to content

Commit 8013c70

Browse files
authored
Merge pull request #32 from jerryneedell/jerryn_af
Add AutoFocus
2 parents 980d1f3 + c02956c commit 8013c70

7 files changed

+479
-2
lines changed

LICENSES/GPL-2.0-only.txt

Lines changed: 117 additions & 0 deletions
Large diffs are not rendered by default.

adafruit_ov5640.py renamed to adafruit_ov5640/__init__.py

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"""
2626

2727
# pylint: disable=too-many-lines
28-
28+
# pylint: disable=too-many-public-methods
2929
# imports
3030
import time
3131
import imagecapture
@@ -414,6 +414,28 @@
414414
_REG_DLY = const(0xFFFF)
415415
_REGLIST_TAIL = const(0x0000)
416416

417+
_OV5640_STAT_FIRMWAREBAD = const(0x7F)
418+
_OV5640_STAT_STARTUP = const(0x7E)
419+
_OV5640_STAT_IDLE = const(0x70)
420+
_OV5640_STAT_FOCUSING = const(0x00)
421+
_OV5640_STAT_FOCUSED = const(0x10)
422+
423+
_OV5640_CMD_TRIGGER_AUTOFOCUS = const(0x03)
424+
_OV5640_CMD_AUTO_AUTOFOCUS = const(0x04)
425+
_OV5640_CMD_RELEASE_FOCUS = const(0x08)
426+
_OV5640_CMD_AF_SET_VCM_STEP = const(0x1A)
427+
_OV5640_CMD_AF_GET_VCM_STEP = const(0x1B)
428+
429+
_OV5640_CMD_MAIN = const(0x3022)
430+
_OV5640_CMD_ACK = const(0x3023)
431+
_OV5640_CMD_PARA0 = const(0x3024)
432+
_OV5640_CMD_PARA1 = const(0x3025)
433+
_OV5640_CMD_PARA2 = const(0x3026)
434+
_OV5640_CMD_PARA3 = const(0x3027)
435+
_OV5640_CMD_PARA4 = const(0x3028)
436+
_OV5640_CMD_FW_STATUS = const(0x3029)
437+
438+
417439
_sensor_default_regs = [
418440
_SYSTEM_CTROL0, 0x82, # software reset
419441
_REG_DLY, 10, # delay 10ms
@@ -936,6 +958,27 @@ def __set__(self, obj: "OV5640", value: int) -> None:
936958

937959

938960
class _SCCB16CameraBase: # pylint: disable=too-few-public-methods
961+
_finalize_firmware_load = (
962+
0x3022,
963+
0x00,
964+
0x3023,
965+
0x00,
966+
0x3024,
967+
0x00,
968+
0x3025,
969+
0x00,
970+
0x3026,
971+
0x00,
972+
0x3027,
973+
0x00,
974+
0x3028,
975+
0x00,
976+
0x3029,
977+
0x7F,
978+
0x3000,
979+
0x00,
980+
)
981+
939982
def __init__(self, i2c_bus: I2C, i2c_address: int) -> None:
940983
self._i2c_device = I2CDevice(i2c_bus, i2c_address)
941984
self._bank = None
@@ -1004,6 +1047,7 @@ def __init__(
10041047
mclk_frequency: int = 20_000_000,
10051048
i2c_address: int = 0x3C,
10061049
size: int = OV5640_SIZE_QQVGA,
1050+
init_autofocus: bool = True,
10071051
): # pylint: disable=too-many-arguments
10081052
"""
10091053
Args:
@@ -1028,6 +1072,7 @@ def __init__(
10281072
with sufficiently low jitter.
10291073
i2c_address (int): The I2C address of the camera.
10301074
size (int): The captured image size
1075+
init_autofocus (bool): initialize autofocus
10311076
"""
10321077

10331078
# Initialize the master clock
@@ -1078,8 +1123,100 @@ def __init__(
10781123
self._white_balance = 0
10791124
self.size = size
10801125

1126+
if init_autofocus:
1127+
self.autofocus_init()
1128+
10811129
chip_id = _RegBits16(_CHIP_ID_HIGH, 0, 0xFFFF)
10821130

1131+
def autofocus_init_from_file(self, filename):
1132+
"""Initialize the autofocus engine from a .bin file"""
1133+
with open(filename, mode="rb") as file:
1134+
firmware = file.read()
1135+
self.autofocus_init_from_bitstream(firmware)
1136+
1137+
def autofocus_init_from_bitstream(self, firmware: bytes):
1138+
"""Initialize the autofocus engine from a bytestring"""
1139+
self._write_register(0x3000, 0x20) # reset autofocus coprocessor
1140+
time.sleep(0.01)
1141+
1142+
arr = bytearray(256)
1143+
with self._i2c_device as i2c:
1144+
for offset in range(0, len(firmware), 254):
1145+
num_firmware_bytes = min(254, len(firmware) - offset)
1146+
reg = offset + 0x8000
1147+
arr[0] = reg >> 8
1148+
arr[1] = reg & 0xFF
1149+
arr[2 : 2 + num_firmware_bytes] = firmware[
1150+
offset : offset + num_firmware_bytes
1151+
]
1152+
i2c.write(arr, end=2 + num_firmware_bytes)
1153+
1154+
self._write_list(self._finalize_firmware_load)
1155+
for _ in range(100):
1156+
if self.autofocus_status == _OV5640_STAT_IDLE:
1157+
break
1158+
time.sleep(0.01)
1159+
else:
1160+
raise RuntimeError("Timed out after trying to load autofocus firmware")
1161+
1162+
def autofocus_init(self):
1163+
"""Initialize the autofocus engine from ov5640_autofocus.bin"""
1164+
if "/" in __file__:
1165+
binfile = (
1166+
__file__.rsplit("/", 1)[0].rsplit(".", 1)[0] + "/ov5640_autofocus.bin"
1167+
)
1168+
else:
1169+
binfile = "ov5640_autofocus.bin"
1170+
print(binfile)
1171+
return self.autofocus_init_from_file(binfile)
1172+
1173+
@property
1174+
def autofocus_status(self):
1175+
"""Read the camera autofocus status register"""
1176+
return self._read_register(_OV5640_CMD_FW_STATUS)
1177+
1178+
def _send_autofocus_command(self, command, msg): # pylint: disable=unused-argument
1179+
self._write_register(_OV5640_CMD_ACK, 0x01) # clear command ack
1180+
self._write_register(_OV5640_CMD_MAIN, command) # send command
1181+
for _ in range(1000):
1182+
if self._read_register(_OV5640_CMD_ACK) == 0x0: # command is finished
1183+
return True
1184+
time.sleep(0.01)
1185+
return False
1186+
1187+
def autofocus(self) -> list[int]:
1188+
"""Perform an autofocus operation.
1189+
1190+
If all elements of the list are 0, the autofocus operation failed. Otherwise,
1191+
if at least one element is nonzero, the operation succeeded.
1192+
1193+
In principle the elements correspond to 5 autofocus regions, if configured."""
1194+
if not self._send_autofocus_command(_OV5640_CMD_RELEASE_FOCUS, "release focus"):
1195+
return [False] * 5
1196+
if not self._send_autofocus_command(_OV5640_CMD_TRIGGER_AUTOFOCUS, "autofocus"):
1197+
return [False] * 5
1198+
zone_focus = [self._read_register(_OV5640_CMD_PARA0 + i) for i in range(5)]
1199+
print(f"zones focused: {zone_focus}")
1200+
return zone_focus
1201+
1202+
@property
1203+
def autofocus_vcm_step(self):
1204+
"""Get the voice coil motor step location"""
1205+
if not self._send_autofocus_command(
1206+
_OV5640_CMD_AF_GET_VCM_STEP, "get vcm step"
1207+
):
1208+
return None
1209+
return self._read_register(_OV5640_CMD_PARA4)
1210+
1211+
@autofocus_vcm_step.setter
1212+
def autofocus_vcm_step(self, step):
1213+
"""Get the voice coil motor step location, from 0 to 255"""
1214+
if not 0 <= step <= 255:
1215+
raise RuntimeError("VCM step must be 0 to 255")
1216+
self._write_register(_OV5640_CMD_PARA3, 0x00)
1217+
self._write_register(_OV5640_CMD_PARA4, step)
1218+
self._send_autofocus_command(_OV5640_CMD_AF_SET_VCM_STEP, "set vcm step")
1219+
10831220
def capture(self, buf: Union[bytearray, memoryview]) -> None:
10841221
"""Capture an image into the buffer.
10851222

adafruit_ov5640/ov5640_autofocus.bin

3.98 KB
Binary file not shown.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SPDX-FileCopyrightText: 2023 Unknown
2+
3+
SPDX-License-Identifier: GPL-2.0-only

examples/ov5640_jpeg_capture_af.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2023 Limor Fried for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
"""
5+
This demo is designed for the Raspberry Pi Pico and Camera PiCowbell
6+
When the shutter is pressed the camera is autofocussed before capturing
7+
an image andsaving it to the microSD card.
8+
"""
9+
10+
import os
11+
import time
12+
import busio
13+
import board
14+
import digitalio
15+
import keypad
16+
import sdcardio
17+
import storage
18+
import adafruit_ov5640
19+
20+
print("Initializing SD card")
21+
sd_spi = busio.SPI(clock=board.GP18, MOSI=board.GP19, MISO=board.GP16)
22+
sd_cs = board.GP17
23+
sdcard = sdcardio.SDCard(sd_spi, sd_cs)
24+
vfs = storage.VfsFat(sdcard)
25+
storage.mount(vfs, "/sd")
26+
27+
print("construct bus")
28+
i2c = busio.I2C(board.GP5, board.GP4)
29+
print("construct camera")
30+
reset = digitalio.DigitalInOut(board.GP14)
31+
cam = adafruit_ov5640.OV5640(
32+
i2c,
33+
data_pins=(
34+
board.GP6,
35+
board.GP7,
36+
board.GP8,
37+
board.GP9,
38+
board.GP10,
39+
board.GP11,
40+
board.GP12,
41+
board.GP13,
42+
),
43+
clock=board.GP3,
44+
vsync=board.GP0,
45+
href=board.GP2,
46+
mclk=None,
47+
shutdown=None,
48+
reset=reset,
49+
size=adafruit_ov5640.OV5640_SIZE_VGA,
50+
)
51+
print("print chip id")
52+
print(cam.chip_id)
53+
54+
keys = keypad.Keys((board.GP22,), value_when_pressed=False, pull=True)
55+
56+
57+
def exists(filename):
58+
try:
59+
os.stat(filename)
60+
return True
61+
except OSError as _:
62+
return False
63+
64+
65+
_image_counter = 0
66+
67+
68+
def open_next_image():
69+
global _image_counter # pylint: disable=global-statement
70+
while True:
71+
filename = f"/sd/img{_image_counter:04d}.jpg"
72+
_image_counter += 1
73+
if exists(filename):
74+
continue
75+
print("# writing to", filename)
76+
return open(filename, "wb")
77+
78+
79+
cam.colorspace = adafruit_ov5640.OV5640_COLOR_JPEG
80+
cam.quality = 3
81+
b = bytearray(cam.capture_buffer_size)
82+
83+
cam.autofocus()
84+
print("AF Status: ", cam.autofocus_status, cam.autofocus_vcm_step)
85+
86+
jpeg = cam.capture(b)
87+
88+
while True:
89+
shutter = keys.events.get()
90+
# event will be None if nothing has happened.
91+
if shutter:
92+
if shutter.pressed:
93+
cam.autofocus()
94+
print("AF Status: ", cam.autofocus_status, cam.autofocus_vcm_step)
95+
time.sleep(0.01)
96+
jpeg = cam.capture(b)
97+
print(f"Captured {len(jpeg)} bytes of jpeg data")
98+
print(f" (had allocated {cam.capture_buffer_size} bytes")
99+
print(f"Resolution {cam.width}x{cam.height}")
100+
try:
101+
with open_next_image() as f:
102+
f.write(jpeg)
103+
print("# Wrote image")
104+
except OSError as e:
105+
print(e)

0 commit comments

Comments
 (0)