Skip to content

Commit 65cb21a

Browse files
committed
Merge branch 'features_for_launch' into feature/build_release
2 parents 4fc3bbe + 18c4039 commit 65cb21a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1595
-759
lines changed

cv2_drivers/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
#-------------------------------------------------------------------------------
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Copyright (c) 2025 SparkFun Electronics
5+
#-------------------------------------------------------------------------------
6+
# cv2_drivers/touch_screens/__init__.py
7+
#
8+
# Imports all available drivers for MicroPython OpenCV.
9+
#-------------------------------------------------------------------------------
10+
111
from . import displays
212
from . import cameras
3-
from . import touch_screens
13+
from . import touch_screens

cv2_drivers/cameras/__init__.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,17 @@
1-
from . import hm01b0_pio
2-
from . import ov5640_pio
1+
#-------------------------------------------------------------------------------
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Copyright (c) 2025 SparkFun Electronics
5+
#-------------------------------------------------------------------------------
6+
# cv2_drivers/cameras/__init__.py
7+
#
8+
# Imports all available camera drivers for MicroPython OpenCV.
9+
#-------------------------------------------------------------------------------
10+
11+
# Import sys module to check platform
12+
import sys
13+
14+
# Import RP2 drivers
15+
if 'rp2' in sys.platform:
16+
from . import hm01b0_pio
17+
from . import ov5640_pio

cv2_drivers/cameras/cv2_camera.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,45 @@
1+
#-------------------------------------------------------------------------------
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Copyright (c) 2025 SparkFun Electronics
5+
#-------------------------------------------------------------------------------
6+
# cv2_camera.py
7+
#
8+
# Base class for OpenCV camera drivers.
9+
#-------------------------------------------------------------------------------
10+
111
class CV2_Camera():
12+
"""
13+
Base class for OpenCV camera drivers.
14+
"""
215
def __init__(self):
16+
"""
17+
Initializes the camera.
18+
"""
319
pass
420

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

cv2_drivers/cameras/dvp_camera.py

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,60 @@
1+
#-------------------------------------------------------------------------------
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Copyright (c) 2025 SparkFun Electronics
5+
#-------------------------------------------------------------------------------
6+
# dvp_camera.py
7+
#
8+
# Base class for OpenCV DVP (Digital Video Port) camera drivers.
9+
#-------------------------------------------------------------------------------
10+
111
from .cv2_camera import CV2_Camera
2-
from machine import Pin
3-
from time import sleep_us
412

513
class DVP_Camera(CV2_Camera):
14+
"""
15+
Base class for OpenCV DVP (Digital Video Port) camera drivers.
16+
"""
617
def __init__(
718
self,
819
i2c,
920
i2c_address
1021
):
22+
"""
23+
Initializes the DVP camera with I2C communication.
24+
25+
Args:
26+
i2c (I2C): I2C object for communication
27+
i2c_address (int): I2C address of the camera
28+
"""
1129
super().__init__()
1230

13-
self.i2c = i2c
14-
self.i2c_address = i2c_address
31+
self._i2c = i2c
32+
self._i2c_address = i2c_address
33+
34+
def _read_register(self, reg, nbytes=1):
35+
"""
36+
Reads a register from the camera over I2C.
37+
38+
Args:
39+
reg (int): Register address to read
40+
nbytes (int): Number of bytes to read from the register
41+
42+
Returns:
43+
bytes: Data read from the register
44+
"""
45+
self._i2c.writeto(self._i2c_address, bytes([reg >> 8, reg & 0xFF]))
46+
return self._i2c.readfrom(self._i2c_address, nbytes)
47+
48+
def _write_register(self, reg, data):
49+
"""
50+
Writes data to a register on the camera over I2C.
1551
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):
52+
Args:
53+
reg (int): Register address to write
54+
data (bytes, int, list, tuple): Data to write to the register
55+
"""
2156
if isinstance(data, int):
2257
data = bytes([data])
2358
elif isinstance(data, (list, tuple)):
2459
data = bytes(data)
25-
self.i2c.writeto(self.i2c_address, bytes([reg >> 8, reg & 0xFF]) + data)
60+
self._i2c.writeto(self._i2c_address, bytes([reg >> 8, reg & 0xFF]) + data)

cv2_drivers/cameras/dvp_rp2_pio.py

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
1+
#-------------------------------------------------------------------------------
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Copyright (c) 2025 SparkFun Electronics
5+
#-------------------------------------------------------------------------------
6+
# dvp_rp2_pio.py
7+
#
8+
# This class implements a DVP (Digital Video Port) interface using the RP2 PIO
9+
# (Programmable Input/Output) interface. This is only available on Raspberry Pi
10+
# RP2 processors.
11+
#
12+
# This class is derived from:
13+
# https://github.com/adafruit/Adafruit_ImageCapture/blob/main/src/arch/rp2040.cpp
14+
# Released under the MIT license.
15+
# Copyright (c) 2021 Adafruit Industries
16+
#-------------------------------------------------------------------------------
17+
118
import rp2
219
from machine import Pin, PWM
320

421
class DVP_RP2_PIO():
22+
"""
23+
This class implements a DVP (Digital Video Port) interface using the RP2 PIO
24+
(Programmable Input/Output) interface. This is only available on Raspberry
25+
Pi RP2 processors.
26+
"""
527
def __init__(
628
self,
729
pin_d0,
@@ -15,12 +37,27 @@ def __init__(
1537
bytes_per_frame,
1638
byte_swap
1739
):
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
40+
"""
41+
Initializes the DVP interface with the specified parameters.
42+
43+
Args:
44+
pin_d0 (int): Data 0 pin number for DVP interface
45+
pin_vsync (int): Vertical sync pin number
46+
pin_hsync (int): Horizontal sync pin number
47+
pin_pclk (int): Pixel clock pin number
48+
pin_xclk (int): External clock pin number
49+
xclk_freq (int): Frequency in Hz for the external clock
50+
sm_id (int): PIO state machine ID
51+
num_data_pins (int): Number of data pins used in DVP interface
52+
bytes_per_frame (int): Number of bytes per frame to capture
53+
byte_swap (bool): Whether to swap bytes in the captured data
54+
"""
55+
self._pin_d0 = pin_d0
56+
self._pin_vsync = pin_vsync
57+
self._pin_hsync = pin_hsync
58+
self._pin_pclk = pin_pclk
59+
self._pin_xclk = pin_xclk
60+
self._sm_id = sm_id
2461

2562
# Initialize DVP pins as inputs
2663
for i in range(num_data_pins):
@@ -30,86 +67,101 @@ def __init__(
3067
Pin(pin_pclk, Pin.IN)
3168

3269
# 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
70+
if self._pin_xclk is not None:
71+
self._xclk = PWM(Pin(pin_xclk))
72+
self._xclk.freq(xclk_freq)
73+
self._xclk.duty_u16(32768) # 50% duty cycle
3774

3875
# Copy the PIO program
3976
program = self._pio_read_dvp
4077

4178
# 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
79+
program[0][0] |= self._pin_hsync & 0x1F
80+
program[0][1] |= self._pin_pclk & 0x1F
81+
program[0][3] |= self._pin_pclk & 0x1F
4582

4683
# Mask in the number of data pins
4784
program[0][2] &= 0xFFFFFFE0
4885
program[0][2] |= num_data_pins
4986

5087
# Create PIO state machine to capture DVP data
51-
self.sm = rp2.StateMachine(
52-
self.sm_id,
88+
self._sm = rp2.StateMachine(
89+
self._sm_id,
5390
program,
5491
in_base = pin_d0
5592
)
5693

5794
# 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
95+
self._dma = rp2.DMA()
96+
req_num = ((self._sm_id // 4) << 3) + (self._sm_id % 4) + 4
6097
bytes_per_transfer = 4
61-
dma_ctrl = self.dma.pack_ctrl(
98+
dma_ctrl = self._dma.pack_ctrl(
6299
# 0 = 1 byte, 1 = 2 bytes, 2 = 4 bytes
63100
size = {1:0, 2:1, 4:2}[bytes_per_transfer],
64101
inc_read = False,
65102
treq_sel = req_num,
66103
bswap = byte_swap
67104
)
68-
self.dma.config(
69-
read = self.sm,
105+
self._dma.config(
106+
read = self._sm,
70107
count = bytes_per_frame // bytes_per_transfer,
71108
ctrl = dma_ctrl
72109
)
73110

74-
def active(self, active = None):
111+
def _active(self, active=None):
112+
"""
113+
Sets or gets the active state of the DVP interface.
114+
115+
Args:
116+
active (bool, optional):
117+
- True: Activate the DVP interface
118+
- False: Deactivate the DVP interface
119+
- None: Get the current active state
120+
121+
Returns:
122+
bool: Current active state if no argument is provided
123+
"""
75124
# If no argument is provided, return the current active state
76125
if active == None:
77-
return self.sm.active()
126+
return self._sm.active()
78127

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

82131
# Set the active state of the state machine
83-
self.sm.active(active)
132+
self._sm.active(active)
84133

85134
# If active, set up the VSYNC interrupt handler
86135
if active:
87-
Pin(self.pin_vsync).irq(
136+
Pin(self._pin_vsync).irq(
88137
trigger = Pin.IRQ_FALLING,
89138
handler = lambda pin: self._vsync_handler()
90139
)
91140
# If not active, disable the VSYNC interrupt handler
92141
else:
93-
Pin(self.pin_vsync).irq(
142+
Pin(self._pin_vsync).irq(
94143
handler = None
95144
)
96145

97146
def _vsync_handler(self):
147+
"""
148+
Handles the VSYNC interrupt to capture a frame of data.
149+
"""
98150
# Disable DMA before reconfiguring it
99-
self.dma.active(False)
151+
self._dma.active(False)
100152

101153
# Reset state machine to ensure ISR is cleared
102-
self.sm.restart()
154+
self._sm.restart()
103155

104156
# Ensure PIO RX FIFO is empty (it's not emptied by `sm.restart()`)
105-
while self.sm.rx_fifo() > 0:
106-
self.sm.get()
157+
while self._sm.rx_fifo() > 0:
158+
self._sm.get()
107159

108160
# Reset the DMA write address
109-
self.dma.write = self.buffer
161+
self._dma.write = self._buffer
110162

111163
# Start the DMA
112-
self.dma.active(True)
164+
self._dma.active(True)
113165

114166
# Here is the PIO program, which is configurable to mask in the GPIO pins
115167
# and the number of data pins. It must be configured before the state
@@ -121,6 +173,9 @@ def _vsync_handler(self):
121173
fifo_join = rp2.PIO.JOIN_RX
122174
)
123175
def _pio_read_dvp():
176+
"""
177+
PIO program to read DVP data from the GPIO pins.
178+
"""
124179
wait(1, gpio, 0) # Mask in HSYNC pin
125180
wait(1, gpio, 0) # Mask in PCLK pin
126181
in_(pins, 1) # Mask in number of pins

0 commit comments

Comments
 (0)