Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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