Skip to content

Commit 3d65c02

Browse files
committed
Clean up examples and drivers
Safe imports of RP2 drivers Add docstring comments Make driver members private Update comments in examples
1 parent 3439047 commit 3d65c02

22 files changed

+1010
-665
lines changed

cv2_drivers/cameras/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
from . import hm01b0_pio
2-
from . import ov5640_pio
1+
# Import sys module to check platform
2+
import sys
3+
4+
# Import RP2 drivers
5+
if 'rp2' in sys.platform:
6+
from . import hm01b0_pio
7+
from . import ov5640_pio

cv2_drivers/cameras/cv2_camera.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
class CV2_Camera():
2+
"""
3+
Base class for OpenCV camera drivers.
4+
"""
25
def __init__(self):
6+
"""
7+
Initializes the camera.
8+
"""
39
pass
410

5-
# TODO: Implement common methods for all cameras
11+
def open(self):
12+
"""
13+
Opens the camera and prepares it for capturing images.
14+
"""
15+
raise NotImplementedError("open() must be implemented by driver")
16+
17+
def release(self):
18+
"""
19+
Releases the camera and frees any resources.
20+
"""
21+
raise NotImplementedError("release() must be implemented by driver")
22+
23+
def read(self, image=None):
24+
"""
25+
Reads an image from the camera.
26+
27+
Args:
28+
image (ndarray, optional): Image to read into
29+
30+
Returns:
31+
tuple: (success, image)
32+
- success (bool): True if the image was read, otherwise False
33+
- image (ndarray): The captured image, or None if reading failed
34+
"""
35+
raise NotImplementedError("read() must be implemented by driver")

cv2_drivers/cameras/dvp_camera.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
11
from .cv2_camera import CV2_Camera
2-
from machine import Pin
3-
from time import sleep_us
42

53
class DVP_Camera(CV2_Camera):
4+
"""
5+
Base class for OpenCV DVP (Digital Video Port) camera drivers.
6+
"""
67
def __init__(
78
self,
89
i2c,
910
i2c_address
1011
):
12+
"""
13+
Initializes the DVP camera with I2C communication.
14+
15+
Args:
16+
i2c (I2C): I2C object for communication
17+
i2c_address (int): I2C address of the camera
18+
"""
1119
super().__init__()
1220

13-
self.i2c = i2c
14-
self.i2c_address = i2c_address
21+
self._i2c = i2c
22+
self._i2c_address = i2c_address
23+
24+
def _read_register(self, reg, nbytes=1):
25+
"""
26+
Reads a register from the camera over I2C.
27+
28+
Args:
29+
reg (int): Register address to read
30+
nbytes (int): Number of bytes to read from the register
31+
32+
Returns:
33+
bytes: Data read from the register
34+
"""
35+
self._i2c.writeto(self._i2c_address, bytes([reg >> 8, reg & 0xFF]))
36+
return self._i2c.readfrom(self._i2c_address, nbytes)
37+
38+
def _write_register(self, reg, data):
39+
"""
40+
Writes data to a register on the camera over I2C.
1541
16-
def readRegister(self, reg, nbytes=1):
17-
self.i2c.writeto(self.i2c_address, bytes([reg >> 8, reg & 0xFF]))
18-
return self.i2c.readfrom(self.i2c_address, nbytes)
19-
20-
def writeRegister(self, reg, data):
42+
Args:
43+
reg (int): Register address to write
44+
data (bytes, int, list, tuple): Data to write to the register
45+
"""
2146
if isinstance(data, int):
2247
data = bytes([data])
2348
elif isinstance(data, (list, tuple)):
2449
data = bytes(data)
25-
self.i2c.writeto(self.i2c_address, bytes([reg >> 8, reg & 0xFF]) + data)
50+
self._i2c.writeto(self._i2c_address, bytes([reg >> 8, reg & 0xFF]) + data)

cv2_drivers/cameras/dvp_rp2_pio.py

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import rp2
22
from machine import Pin, PWM
33

4+
# This class is derived from:
5+
# https://github.com/adafruit/Adafruit_ImageCapture/blob/main/src/arch/rp2040.cpp
6+
# Released under the MIT license.
7+
# Copyright (c) 2021 Adafruit Industries
48
class DVP_RP2_PIO():
9+
"""
10+
This class implements a DVP (Digital Video Port) interface using the RP2 PIO
11+
(Programmable Input/Output) interface. This is only available on Raspberry
12+
Pi RP2 processors.
13+
"""
514
def __init__(
615
self,
716
pin_d0,
@@ -15,12 +24,27 @@ def __init__(
1524
bytes_per_frame,
1625
byte_swap
1726
):
18-
self.pin_d0 = pin_d0
19-
self.pin_vsync = pin_vsync
20-
self.pin_hsync = pin_hsync
21-
self.pin_pclk = pin_pclk
22-
self.pin_xclk = pin_xclk
23-
self.sm_id = sm_id
27+
"""
28+
Initializes the DVP interface with the specified parameters.
29+
30+
Args:
31+
pin_d0 (int): Data 0 pin number for DVP interface
32+
pin_vsync (int): Vertical sync pin number
33+
pin_hsync (int): Horizontal sync pin number
34+
pin_pclk (int): Pixel clock pin number
35+
pin_xclk (int): External clock pin number
36+
xclk_freq (int): Frequency in Hz for the external clock
37+
sm_id (int): PIO state machine ID
38+
num_data_pins (int): Number of data pins used in DVP interface
39+
bytes_per_frame (int): Number of bytes per frame to capture
40+
byte_swap (bool): Whether to swap bytes in the captured data
41+
"""
42+
self._pin_d0 = pin_d0
43+
self._pin_vsync = pin_vsync
44+
self._pin_hsync = pin_hsync
45+
self._pin_pclk = pin_pclk
46+
self._pin_xclk = pin_xclk
47+
self._sm_id = sm_id
2448

2549
# Initialize DVP pins as inputs
2650
for i in range(num_data_pins):
@@ -30,86 +54,98 @@ def __init__(
3054
Pin(pin_pclk, Pin.IN)
3155

3256
# Set up XCLK pin if provided
33-
if self.pin_xclk is not None:
34-
self.xclk = PWM(Pin(pin_xclk))
35-
self.xclk.freq(xclk_freq)
36-
self.xclk.duty_u16(32768) # 50% duty cycle
57+
if self._pin_xclk is not None:
58+
self._xclk = PWM(Pin(pin_xclk))
59+
self._xclk.freq(xclk_freq)
60+
self._xclk.duty_u16(32768) # 50% duty cycle
3761

3862
# Copy the PIO program
3963
program = self._pio_read_dvp
4064

4165
# Mask in the GPIO pins
42-
program[0][0] |= self.pin_hsync & 0x1F
43-
program[0][1] |= self.pin_pclk & 0x1F
44-
program[0][3] |= self.pin_pclk & 0x1F
66+
program[0][0] |= self._pin_hsync & 0x1F
67+
program[0][1] |= self._pin_pclk & 0x1F
68+
program[0][3] |= self._pin_pclk & 0x1F
4569

4670
# Mask in the number of data pins
4771
program[0][2] &= 0xFFFFFFE0
4872
program[0][2] |= num_data_pins
4973

5074
# Create PIO state machine to capture DVP data
51-
self.sm = rp2.StateMachine(
52-
self.sm_id,
75+
self._sm = rp2.StateMachine(
76+
self._sm_id,
5377
program,
5478
in_base = pin_d0
5579
)
5680

5781
# Create DMA controller to transfer data from PIO to buffer
58-
self.dma = rp2.DMA()
59-
req_num = ((self.sm_id // 4) << 3) + (self.sm_id % 4) + 4
82+
self._dma = rp2.DMA()
83+
req_num = ((self._sm_id // 4) << 3) + (self._sm_id % 4) + 4
6084
bytes_per_transfer = 4
61-
dma_ctrl = self.dma.pack_ctrl(
85+
dma_ctrl = self._dma.pack_ctrl(
6286
# 0 = 1 byte, 1 = 2 bytes, 2 = 4 bytes
6387
size = {1:0, 2:1, 4:2}[bytes_per_transfer],
6488
inc_read = False,
6589
treq_sel = req_num,
6690
bswap = byte_swap
6791
)
68-
self.dma.config(
69-
read = self.sm,
92+
self._dma.config(
93+
read = self._sm,
7094
count = bytes_per_frame // bytes_per_transfer,
7195
ctrl = dma_ctrl
7296
)
7397

74-
def active(self, active = None):
98+
def _active(self, active=None):
99+
"""
100+
Sets or gets the active state of the DVP interface.
101+
102+
Args:
103+
active (bool, optional):
104+
- True: Activate the DVP interface
105+
- False: Deactivate the DVP interface
106+
- None: Get the current active state
107+
"""
75108
# If no argument is provided, return the current active state
76109
if active == None:
77-
return self.sm.active()
110+
return self._sm.active()
78111

79112
# Disable the DMA, the VSYNC handler will re-enable it when needed
80-
self.dma.active(False)
113+
self._dma.active(False)
81114

82115
# Set the active state of the state machine
83-
self.sm.active(active)
116+
self._sm.active(active)
84117

85118
# If active, set up the VSYNC interrupt handler
86119
if active:
87-
Pin(self.pin_vsync).irq(
120+
Pin(self._pin_vsync).irq(
88121
trigger = Pin.IRQ_FALLING,
89122
handler = lambda pin: self._vsync_handler()
90123
)
91124
# If not active, disable the VSYNC interrupt handler
92125
else:
93-
Pin(self.pin_vsync).irq(
126+
Pin(self._pin_vsync).irq(
94127
handler = None
95128
)
96129

97130
def _vsync_handler(self):
131+
"""
132+
Handles the VSYNC interrupt to capture a frame of data.
133+
"""
98134
# Disable DMA before reconfiguring it
99-
self.dma.active(False)
135+
self._dma.active(False)
100136

101137
# Reset state machine to ensure ISR is cleared
102-
self.sm.restart()
138+
self._sm.restart()
103139

104140
# Ensure PIO RX FIFO is empty (it's not emptied by `sm.restart()`)
105-
while self.sm.rx_fifo() > 0:
106-
self.sm.get()
141+
while self._sm.rx_fifo() > 0:
142+
self._sm.get()
107143

108144
# Reset the DMA write address
109-
self.dma.write = self.buffer
145+
self._dma.write = self._buffer
110146

111147
# Start the DMA
112-
self.dma.active(True)
148+
self._dma.active(True)
113149

114150
# Here is the PIO program, which is configurable to mask in the GPIO pins
115151
# and the number of data pins. It must be configured before the state
@@ -121,6 +157,9 @@ def _vsync_handler(self):
121157
fifo_join = rp2.PIO.JOIN_RX
122158
)
123159
def _pio_read_dvp():
160+
"""
161+
PIO program to read DVP data from the GPIO pins.
162+
"""
124163
wait(1, gpio, 0) # Mask in HSYNC pin
125164
wait(1, gpio, 0) # Mask in PCLK pin
126165
in_(pins, 1) # Mask in number of pins

0 commit comments

Comments
 (0)