Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions red_vision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
#
# Copyright (c) 2025 SparkFun Electronics
#-------------------------------------------------------------------------------
# cv2_drivers/touch_screens/__init__.py
# red_vision/__init__.py
#
# Imports all available drivers for MicroPython OpenCV.
# Imports all available Red Vision drivers, modules, and utilities.
#-------------------------------------------------------------------------------

from . import displays
from . import cameras
from . import displays
from . import touch_screens
from .utils import colors
from .utils import memory
18 changes: 11 additions & 7 deletions red_vision/cameras/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
#
# Copyright (c) 2025 SparkFun Electronics
#-------------------------------------------------------------------------------
# cv2_drivers/cameras/__init__.py
# red_vision/cameras/__init__.py
#
# Imports all available camera drivers for MicroPython OpenCV.
# Imports all available Red Vision camera drivers.
#-------------------------------------------------------------------------------

# Import sys module to check platform
import sys
# Import the generic VideoCapture class.
from .video_capture import VideoCapture

# Import platform agnostic drivers.
from .hm01b0 import HM01B0
from .ov5640 import OV5640

# Import RP2 drivers
# Import platform specific drivers.
import sys
if 'rp2' in sys.platform:
from . import hm01b0_pio
from . import ov5640_pio
from .dvp_rp2_pio import DVP_RP2_PIO
45 changes: 0 additions & 45 deletions red_vision/cameras/cv2_camera.py

This file was deleted.

18 changes: 11 additions & 7 deletions red_vision/cameras/dvp_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@
#
# Copyright (c) 2025 SparkFun Electronics
#-------------------------------------------------------------------------------
# dvp_camera.py
# red_vision/cameras/dvp_camera.py
#
# Base class for OpenCV DVP (Digital Video Port) camera drivers.
# Red Vision abstract base class for DVP (Digital Video Port) camera drivers.
#-------------------------------------------------------------------------------

from .cv2_camera import CV2_Camera
from .video_capture_driver import VideoCaptureDriver

class DVP_Camera(CV2_Camera):
class DVP_Camera(VideoCaptureDriver):
"""
Base class for OpenCV DVP (Digital Video Port) camera drivers.
Red Vision abstract base class for DVP (Digital Video Port) camera drivers.
"""
def __init__(
self,
i2c,
i2c_address
i2c_address,
height = None,
width = None,
color_mode = None,
buffer = None,
):
"""
Initializes the DVP camera with I2C communication.
Expand All @@ -26,7 +30,7 @@ def __init__(
i2c (I2C): I2C object for communication
i2c_address (int): I2C address of the camera
"""
super().__init__()
super().__init__(height, width, color_mode, buffer)

self._i2c = i2c
self._i2c_address = i2c_address
Expand Down
94 changes: 51 additions & 43 deletions red_vision/cameras/dvp_rp2_pio.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
#
# Copyright (c) 2025 SparkFun Electronics
#-------------------------------------------------------------------------------
# dvp_rp2_pio.py
# red_vision/cameras/dvp_rp2_pio.py
#
# This class implements a DVP (Digital Video Port) interface using the RP2 PIO
# (Programmable Input/Output) interface. This is only available on Raspberry Pi
# RP2 processors.
# Red Vision DVP (Digital Video Port) camera interface using the RP2 PIO
# (Programmable Input/Output). Only available on Raspberry Pi RP2 processors.
#
# This class is derived from:
# https://github.com/adafruit/Adafruit_ImageCapture/blob/main/src/arch/rp2040.cpp
Expand All @@ -19,26 +18,21 @@
import array
from machine import Pin, PWM
from uctypes import addressof
from ..utils import memory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a better name for this module. memory - especially later in the code - appears to be a build in, native module which is confusing ... Maybe rv_memory, or something ...

Or if this is just internal, so it's accessed using the package name rv, probably not an issue at all


class DVP_RP2_PIO():
"""
This class implements a DVP (Digital Video Port) interface using the RP2 PIO
(Programmable Input/Output) interface. This is only available on Raspberry
Pi RP2 processors.
Red Vision DVP (Digital Video Port) camera interface using the RP2 PIO
(Programmable Input/Output). Only available on Raspberry Pi RP2 processors.
"""
def __init__(
self,
sm_id,
pin_d0,
pin_vsync,
pin_hsync,
pin_pclk,
pin_xclk,
xclk_freq,
sm_id,
num_data_pins,
bytes_per_pixel,
byte_swap,
continuous = False
pin_xclk = None,
):
"""
Initializes the DVP interface with the specified parameters.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the "Args:" comment to reflect the new changed arguments.

Expand All @@ -62,35 +56,65 @@ def __init__(
self._pin_hsync = pin_hsync
self._pin_pclk = pin_pclk
self._pin_xclk = pin_xclk
self._sm_id = sm_id

def begin(
self,
buffer,
xclk_freq,
num_data_pins,
byte_swap,
continuous = False,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment saying what these args are similar to what you have for __init__(). Of particular interest is the buffer arg whose type is not immediately obvious when just staring at this function.

Might want to add a short/simple block function comment describing what this function does as well

):
self._buffer = buffer
self._height, self._width, self._bytes_per_pixel = buffer.shape
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should buffer be type checked before the assignment statements are performed?


# Initialize DVP pins as inputs
self._num_data_pins = num_data_pins
for i in range(num_data_pins):
Pin(pin_d0+i, Pin.IN)
Pin(pin_vsync, Pin.IN)
Pin(pin_hsync, Pin.IN)
Pin(pin_pclk, Pin.IN)
Pin(self._pin_d0+i, Pin.IN)
Pin(self._pin_vsync, Pin.IN)
Pin(self._pin_hsync, Pin.IN)
Pin(self._pin_pclk, Pin.IN)

# Set up XCLK pin if provided
if self._pin_xclk is not None:
self._xclk = PWM(Pin(pin_xclk))
self._xclk = PWM(Pin(self._pin_xclk))
self._xclk.freq(xclk_freq)
self._xclk.duty_u16(32768) # 50% duty cycle

# If there's only 1 byte per pixel, we can safely transfer multiple
# pixels at a time without worrying about byte alignment. So we use the
# maximum of 4 pixels per transfer to improve DMA efficiency.
if self._bytes_per_pixel == 1:
self._bytes_per_transfer = 4
# The PIO left shifts the pixel data in the FIFO buffer, so we need
# to swap the bytes to get the correct order.
byte_swap = True
else:
self._bytes_per_transfer = self._bytes_per_pixel

# Store transfer parameters
self._bytes_per_pixel = bytes_per_pixel
self._byte_swap = byte_swap

# Whether to continuously capture frames
self._continuous = continuous

# Set up the PIO state machine
self._sm_id = sm_id
self._setup_pio()

# Set up the DMA controllers
self._setup_dmas()

def buffer(self):
"""
Returns the current frame buffer from the camera.

Returns:
ndarray: Frame buffer
"""
return self._buffer

def _setup_pio(self):
# Copy the PIO program
program = self._pio_read_dvp
Expand All @@ -108,7 +132,7 @@ def _setup_pio(self):
self._sm_id,
program,
in_base = self._pin_d0,
push_thresh = self._bytes_per_pixel * 8
push_thresh = self._bytes_per_transfer * 8
)

# Here is the PIO program, which is configurable to mask in the GPIO pins
Expand All @@ -128,22 +152,6 @@ def _pio_read_dvp():
in_(pins, 32) # Mask in number of pins
wait(0, gpio, 0) # Mask in PCLK pin

def _is_in_sram(self, data_addr):
"""
Checks whether a given memory address is in SRAM.

Args:
data_addr (int): Memory address to check
Returns:
bool: True if address is in SRAM, False otherwise
"""
# SRAM address range.
SRAM_BASE = 0x20000000
total_sram_size = 520*1024 # 520 KB

# Return whether address is in SRAM.
return data_addr >= SRAM_BASE and data_addr < SRAM_BASE + total_sram_size

def _setup_dmas(self):
"""
Sets up the DMA controllers for the DVP interface.
Expand Down Expand Up @@ -239,7 +247,7 @@ def _setup_dmas(self):
self._dma_executer = rp2.DMA()

# Check if the display buffer is in PSRAM.
self._buffer_is_in_psram = not self._is_in_sram(addressof(self._buffer))
self._buffer_is_in_psram = memory.is_in_external_ram(self._buffer)

# If the buffer is in PSRAM, create the streamer DMA channel and row
# buffer in SRAM.
Expand All @@ -253,7 +261,7 @@ def _setup_dmas(self):

# Verify row buffer is in SRAM. If not, we'll still have the same
# latency problem.
if not self._is_in_sram(addressof(self._row_buffer)):
if memory.is_in_external_ram(self._row_buffer):
raise MemoryError("not enough space in SRAM for row buffer")

# Create DMA control register values.
Expand Down Expand Up @@ -304,7 +312,7 @@ def _create_dma_ctrl_registers(self):
# needed. Once done, it chains back to the dispatcher to get the next
# control block.
self._dma_ctrl_pio_repeat = self._dma_executer.pack_ctrl(
size = {1:0, 2:1, 4:2}[self._bytes_per_pixel],
size = {1:0, 2:1, 4:2}[self._bytes_per_transfer],
inc_read = False,
inc_write = True,
# ring_size = 0,
Expand Down Expand Up @@ -430,7 +438,7 @@ def _create_control_blocks(self):
self._cb_pio_repeat = array.array('I', [
pio_rx_fifo_addr, # READ_ADDR
addressof(self._row_buffer), # WRITE_ADDR
self._bytes_per_row // self._bytes_per_pixel, # TRANS_COUNT
self._bytes_per_row // self._bytes_per_transfer, # TRANS_COUNT
self._dma_ctrl_pio_repeat, # CTRL_TRIG
])

Expand Down
Loading