Skip to content

Commit 81f7cb1

Browse files
committed
Clean up camera drivers a bit
Now switching between HM01B0 and OV5640 is as simple as changing only boot.py! RP2350 has issues capturing images correctly from OV5640 (see #22). Example 3 is funky even with 5MHz XCLK, since other things use the DMA at the same time
1 parent ade4731 commit 81f7cb1

File tree

7 files changed

+108
-63
lines changed

7 files changed

+108
-63
lines changed

cv2_drivers/cameras/cv2_camera.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from ulab import numpy as np
2-
31
class CV2_Camera():
4-
def __init__(self, buffer_size):
5-
self.buffer = np.zeros(buffer_size, dtype=np.uint8)
2+
def __init__(self):
3+
pass
4+
5+
# TODO: Implement common methods for all cameras

cv2_drivers/cameras/dvp_camera.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ class DVP_Camera(CV2_Camera):
66
def __init__(
77
self,
88
i2c,
9-
i2c_address,
10-
buffer_size
9+
i2c_address
1110
):
12-
super().__init__(buffer_size)
11+
super().__init__()
1312

1413
self.i2c = i2c
1514
self.i2c_address = i2c_address

cv2_drivers/cameras/dvp_rp2_pio.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ def __init__(
99
pin_hsync,
1010
pin_pclk,
1111
pin_xclk,
12+
xclk_freq,
1213
sm_id,
13-
num_data_pins
14+
num_data_pins,
15+
bytes_per_frame,
16+
byte_swap
1417
):
1518
self.pin_d0 = pin_d0
1619
self.pin_vsync = pin_vsync
@@ -19,22 +22,22 @@ def __init__(
1922
self.pin_xclk = pin_xclk
2023
self.sm_id = sm_id
2124

25+
# Initialize DVP pins as inputs
2226
for i in range(num_data_pins):
2327
Pin(pin_d0+i, Pin.IN)
2428
Pin(pin_vsync, Pin.IN)
2529
Pin(pin_hsync, Pin.IN)
2630
Pin(pin_pclk, Pin.IN)
2731

32+
# Set up XCLK pin if provided
2833
if self.pin_xclk is not None:
2934
self.xclk = PWM(Pin(pin_xclk))
30-
self.xclk.freq(25_000_000)
31-
# self.xclk.freq(15_000_000) # Test for OV5640
32-
self.xclk.duty_u16(32768)
35+
self.xclk.freq(xclk_freq)
36+
self.xclk.duty_u16(32768) # 50% duty cycle
3337

34-
self.start_pio_dma(num_data_pins)
35-
36-
def start_pio_dma(self, num_data_pins):
38+
# Copy the PIO program
3739
program = self._pio_read_dvp
40+
3841
# Mask in the GPIO pins
3942
program[0][0] |= self.pin_hsync & 0x1F
4043
program[0][1] |= self.pin_pclk & 0x1F
@@ -44,40 +47,45 @@ def start_pio_dma(self, num_data_pins):
4447
program[0][2] &= 0xFFFFFFE0
4548
program[0][2] |= num_data_pins
4649

50+
# Create PIO state machine to capture DVP data
4751
self.sm = rp2.StateMachine(
4852
self.sm_id,
4953
program,
50-
in_base = self.pin_d0
54+
in_base = pin_d0
5155
)
52-
self.sm.active(1)
5356

57+
# Create DMA controller to transfer data from PIO to buffer
5458
self.dma = rp2.DMA()
5559
req_num = ((self.sm_id // 4) << 3) + (self.sm_id % 4) + 4
60+
bytes_per_transfer = 4
5661
dma_ctrl = self.dma.pack_ctrl(
57-
size = 2, # 0 = 8-bit, 1 = 16-bit, 2 = 32-bit
62+
# 0 = 1 byte, 1 = 2 bytes, 2 = 4 bytes
63+
size = {1:0, 2:1, 4:2}[bytes_per_transfer],
5864
inc_read = False,
5965
treq_sel = req_num,
60-
bswap = True
61-
# bswap = False # Test for OV5640
66+
bswap = byte_swap
6267
)
6368
self.dma.config(
6469
read = self.sm,
65-
count = 244 * 324 // 4,
66-
# count = 240 * 320 * 2 // 4, # Test for OV5640
70+
count = bytes_per_frame // bytes_per_transfer,
6771
ctrl = dma_ctrl
6872
)
6973

7074
def active(self, active = None):
75+
# If no argument is provided, return the current active state
7176
if active == None:
7277
return self.sm.active()
7378

79+
# Set the active state of the state machine
7480
self.sm.active(active)
7581

82+
# If active, set up the VSYNC interrupt handler
7683
if active:
7784
Pin(self.pin_vsync).irq(
7885
trigger = Pin.IRQ_FALLING,
7986
handler = lambda pin: self._vsync_handler()
8087
)
88+
# If not active, disable the VSYNC interrupt handler
8189
else:
8290
Pin(self.pin_vsync).irq(
8391
handler = None
@@ -100,6 +108,9 @@ def _vsync_handler(self):
100108
# Start the DMA
101109
self.dma.active(True)
102110

111+
# Here is the PIO program, which is configurable to mask in the GPIO pins
112+
# and the number of data pins. It must be configured before the state
113+
# machine is created
103114
@rp2.asm_pio(
104115
in_shiftdir = rp2.PIO.SHIFT_LEFT,
105116
push_thresh = 32,

cv2_drivers/cameras/hm01b0.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,7 @@ def __init__(
184184
i2c_address = 0x24,
185185
num_data_pins = 1
186186
):
187-
super().__init__(i2c, i2c_address, (244, 324))
188-
189-
# for i in range(len(self.INIT_COMMANDS)):
190-
# if self.INIT_COMMANDS[i][0] == 0x3059:
191-
# if num_data_pins == 1:
192-
# self.INIT_COMMANDS[i][1] = 0x22
193-
# elif num_data_pins == 4:
194-
# self.INIT_COMMANDS[i][1] = 0x42
195-
# else:
196-
# self.INIT_COMMANDS[i][1] = 0x02
187+
super().__init__(i2c, i2c_address)
197188

198189
self.soft_reset()
199190
self.send_init(num_data_pins)

cv2_drivers/cameras/hm01b0_pio.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .hm01b0 import HM01B0
22
from .dvp_rp2_pio import DVP_RP2_PIO
3+
from ulab import numpy as np
34

45
class HM01B0_PIO(HM01B0, DVP_RP2_PIO):
56
def __init__(
@@ -10,13 +11,34 @@ def __init__(
1011
pin_hsync,
1112
pin_pclk,
1213
pin_xclk = None,
14+
xclk_freq = 25_000_000,
1315
sm_id = 0,
1416
num_data_pins = 1,
1517
i2c_address = 0x24,
1618
):
19+
# Create the frame buffer
20+
self.buffer = np.zeros((244, 324), dtype=np.uint8)
21+
1722
# Call both parent constructors
18-
HM01B0.__init__(self, i2c, i2c_address, num_data_pins)
19-
DVP_RP2_PIO.__init__(self, pin_d0, pin_vsync, pin_hsync, pin_pclk, pin_xclk, sm_id, num_data_pins)
23+
DVP_RP2_PIO.__init__(
24+
self,
25+
pin_d0,
26+
pin_vsync,
27+
pin_hsync,
28+
pin_pclk,
29+
pin_xclk,
30+
xclk_freq,
31+
sm_id,
32+
num_data_pins,
33+
bytes_per_frame = self.buffer.size,
34+
byte_swap = True
35+
)
36+
HM01B0.__init__(
37+
self,
38+
i2c,
39+
i2c_address,
40+
num_data_pins
41+
)
2042

2143
def open(self):
2244
self.active(True)

cv2_drivers/cameras/ov5640.py

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -355,15 +355,15 @@ class OV5640(DVP_Camera):
355355

356356
_ratio_table = [
357357
# mw, mh, sx, sy, ex, ey, ox, oy, tx, ty
358-
[2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968], # 4x3
359-
[2560, 1704, 0, 110, 2623, 1843, 32, 16, 2844, 1752], # 3x2
360-
[2560, 1600, 0, 160, 2623, 1791, 32, 16, 2844, 1648], # 16x10
361-
[2560, 1536, 0, 192, 2623, 1759, 32, 16, 2844, 1584], # 5x3
362-
[2560, 1440, 0, 240, 2623, 1711, 32, 16, 2844, 1488], # 16x9
363-
[2560, 1080, 0, 420, 2623, 1531, 32, 16, 2844, 1128], # 21x9
364-
[2400, 1920, 80, 0, 2543, 1951, 32, 16, 2684, 1968], # 5x4
365-
[1920, 1920, 320, 0, 2543, 1951, 32, 16, 2684, 1968], # 1x1
366-
[1088, 1920, 736, 0, 1887, 1951, 32, 16, 1884, 1968], # 9x16
358+
[2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968], # 4x3
359+
[2560, 1704, 0, 110, 2623, 1843, 32, 16, 2844, 1752], # 3x2
360+
[2560, 1600, 0, 160, 2623, 1791, 32, 16, 2844, 1648], # 16x10
361+
[2560, 1536, 0, 192, 2623, 1759, 32, 16, 2844, 1584], # 5x3
362+
[2560, 1440, 0, 240, 2623, 1711, 32, 16, 2844, 1488], # 16x9
363+
[2560, 1080, 0, 420, 2623, 1531, 32, 16, 2844, 1128], # 21x9
364+
[2400, 1920, 80, 0, 2543, 1951, 32, 16, 2684, 1968], # 5x4
365+
[1920, 1920, 320, 0, 2543, 1951, 32, 16, 2684, 1968], # 1x1
366+
[1088, 1920, 736, 0, 1887, 1951, 32, 16, 1884, 1968], # 9x16
367367
]
368368

369369
_pll_pre_div2x_factors = [1, 1, 2, 3, 4, 1.5, 6, 2.5, 8]
@@ -875,13 +875,10 @@ class OV5640(DVP_Camera):
875875
def __init__(
876876
self,
877877
i2c,
878-
i2c_address = 0x3C,
879-
num_data_pins = 1
878+
i2c_address = 0x3C
880879
):
881-
super().__init__(i2c, i2c_address, (240, 320, 2))
880+
super().__init__(i2c, i2c_address)
882881

883-
# self.soft_reset()
884-
# sleep_us(1_000_000)
885882
self.write_list(self._sensor_default_regs)
886883

887884
self._colorspace = self.OV5640_COLOR_RGB
@@ -950,7 +947,6 @@ def write_list(self, data):
950947
for i in range(len(data) // 2):
951948
reg = data[i * 2]
952949
value = data[i * 2 + 1]
953-
print(i, reg, value)
954950
if reg == self._REG_DLY:
955951
sleep_us(value)
956952
else:
@@ -999,16 +995,15 @@ def _set_size_and_colorspace(self) -> None:
999995

1000996
self._set_image_options()
1001997

1002-
# if self._colorspace == self.OV5640_COLOR_JPEG:
1003-
# sys_mul = 200
1004-
# if size < self.OV5640_SIZE_QVGA:
1005-
# sys_mul = 160
1006-
# if size < self.OV5640_SIZE_XGA:
1007-
# sys_mul = 180
1008-
# self._set_pll(False, sys_mul, 4, 2, False, 2, True, 4)
1009-
# else:
1010-
# self._set_pll(False, 32, 1, 1, False, 1, True, 4)
1011-
self._set_pll(False, 32, 1, 1, False, 1, True, 4)
998+
if self._colorspace == self.OV5640_COLOR_JPEG:
999+
sys_mul = 200
1000+
if size < self.OV5640_SIZE_QVGA:
1001+
sys_mul = 160
1002+
if size < self.OV5640_SIZE_XGA:
1003+
sys_mul = 180
1004+
self._set_pll(False, sys_mul, 4, 2, False, 2, True, 4)
1005+
else:
1006+
self._set_pll(False, 32, 1, 1, False, 1, True, 4)
10121007

10131008
self._set_colorspace()
10141009

@@ -1121,4 +1116,11 @@ def read(self, image = None):
11211116
Returns:
11221117
tuple: (success, frame)
11231118
"""
1124-
return (True, cv2.cvtColor(self.buffer, cv2.COLOR_BayerRG2BGR, image))
1119+
if self._colorspace == self.OV5640_COLOR_RGB:
1120+
return (True, cv2.cvtColor(self.buffer, cv2.COLOR_BGR5652BGR, image))
1121+
elif self._colorspace == self.OV5640_COLOR_GRAYSCALE:
1122+
return (True, cv2.cvtColor(self.buffer, cv2.COLOR_GRAY2BGR, image))
1123+
else:
1124+
NotImplementedError(
1125+
f"OV5640:Reading images in colorspace {self._colorspace} is not yet implemented."
1126+
)

cv2_drivers/cameras/ov5640_pio.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .ov5640 import OV5640
22
from .dvp_rp2_pio import DVP_RP2_PIO
3+
from ulab import numpy as np
34

45
class OV5640_PIO(OV5640, DVP_RP2_PIO):
56
def __init__(
@@ -10,13 +11,32 @@ def __init__(
1011
pin_hsync,
1112
pin_pclk,
1213
pin_xclk = None,
14+
xclk_freq = 5_000_000,
1315
sm_id = 0,
14-
num_data_pins = 8,
15-
i2c_address = 0x3c,
16+
i2c_address = 0x3c
1617
):
18+
# Create the frame buffer
19+
self.buffer = np.zeros((240, 320, 2), dtype=np.uint8)
20+
1721
# Call both parent constructors
18-
DVP_RP2_PIO.__init__(self, pin_d0, pin_vsync, pin_hsync, pin_pclk, pin_xclk, sm_id, num_data_pins)
19-
OV5640.__init__(self, i2c, i2c_address, num_data_pins)
22+
DVP_RP2_PIO.__init__(
23+
self,
24+
pin_d0,
25+
pin_vsync,
26+
pin_hsync,
27+
pin_pclk,
28+
pin_xclk,
29+
xclk_freq,
30+
sm_id,
31+
num_data_pins = 8,
32+
bytes_per_frame = self.buffer.size,
33+
byte_swap = False
34+
)
35+
OV5640.__init__(
36+
self,
37+
i2c,
38+
i2c_address
39+
)
2040

2141
def open(self):
2242
self.active(True)

0 commit comments

Comments
 (0)