diff --git a/red_vision/cameras/dvp_rp2_pio.py b/red_vision/cameras/dvp_rp2_pio.py index 81d2840..530b451 100644 --- a/red_vision/cameras/dvp_rp2_pio.py +++ b/red_vision/cameras/dvp_rp2_pio.py @@ -16,7 +16,9 @@ #------------------------------------------------------------------------------- import rp2 +import array from machine import Pin, PWM +from uctypes import addressof class DVP_RP2_PIO(): """ @@ -34,8 +36,9 @@ def __init__( xclk_freq, sm_id, num_data_pins, - bytes_per_frame, - byte_swap + bytes_per_pixel, + byte_swap, + continuous = False ): """ Initializes the DVP interface with the specified parameters. @@ -49,17 +52,19 @@ def __init__( xclk_freq (int): Frequency in Hz for the external clock sm_id (int): PIO state machine ID num_data_pins (int): Number of data pins used in DVP interface - bytes_per_frame (int): Number of bytes per frame to capture + bytes_per_pixel (int): Number of bytes per pixel byte_swap (bool): Whether to swap bytes in the captured data + continuous (bool): Whether to continuously capture frames """ + # Store pin assignments self._pin_d0 = pin_d0 self._pin_vsync = pin_vsync self._pin_hsync = pin_hsync self._pin_pclk = pin_pclk self._pin_xclk = pin_xclk - self._sm_id = sm_id # 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) @@ -72,6 +77,21 @@ def __init__( self._xclk.freq(xclk_freq) self._xclk.duty_u16(32768) # 50% duty cycle + # 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 _setup_pio(self): # Copy the PIO program program = self._pio_read_dvp @@ -81,102 +101,466 @@ def __init__( program[0][3] |= self._pin_pclk & 0x1F # Mask in the number of data pins - program[0][2] &= 0xFFFFFFE0 - program[0][2] |= num_data_pins + program[0][2] |= self._num_data_pins # Create PIO state machine to capture DVP data self._sm = rp2.StateMachine( self._sm_id, program, - in_base = pin_d0 + in_base = self._pin_d0, + push_thresh = self._bytes_per_pixel * 8 ) - # Create DMA controller to transfer data from PIO to buffer - self._dma = rp2.DMA() - req_num = ((self._sm_id // 4) << 3) + (self._sm_id % 4) + 4 - bytes_per_transfer = 4 - dma_ctrl = self._dma.pack_ctrl( - # 0 = 1 byte, 1 = 2 bytes, 2 = 4 bytes - size = {1:0, 2:1, 4:2}[bytes_per_transfer], - inc_read = False, - treq_sel = req_num, - bswap = byte_swap - ) - self._dma.config( - read = self._sm, - count = bytes_per_frame // bytes_per_transfer, - ctrl = dma_ctrl + # Here is the PIO program, which is configurable to mask in the GPIO pins + # and the number of data pins. It must be configured before the state + # machine is created + @rp2.asm_pio( + in_shiftdir = rp2.PIO.SHIFT_LEFT, + autopush = True, + fifo_join = rp2.PIO.JOIN_RX ) + def _pio_read_dvp(): + """ + PIO program to read DVP data from the GPIO pins. + """ + wait(1, gpio, 0) # Mask in HSYNC pin + wait(1, gpio, 0) # Mask in PCLK pin + in_(pins, 32) # Mask in number of pins + wait(0, gpio, 0) # Mask in PCLK pin - def _active(self, active=None): + def _is_in_sram(self, data_addr): """ - Sets or gets the active state of the DVP interface. + Checks whether a given memory address is in SRAM. Args: - active (bool, optional): - - True: Activate the DVP interface - - False: Deactivate the DVP interface - - None: Get the current active state - + data_addr (int): Memory address to check Returns: - bool: Current active state if no argument is provided + bool: True if address is in SRAM, False otherwise """ - # If no argument is provided, return the current active state - if active == None: - return self._sm.active() + # SRAM address range. + SRAM_BASE = 0x20000000 + total_sram_size = 520*1024 # 520 KB - # Disable the DMA, the VSYNC handler will re-enable it when needed - self._dma.active(False) + # Return whether address is in SRAM. + return data_addr >= SRAM_BASE and data_addr < SRAM_BASE + total_sram_size - # Set the active state of the state machine - self._sm.active(active) + def _setup_dmas(self): + """ + Sets up the DMA controllers for the DVP interface. + """ + # This driver uses multiple DMA channels to transfer pixel data from the + # camera to the image buffer. A detailed explanation and motive for this + # DMA structure is better explained in the RP2 HSTX DVI driver, but an + # abbreviated summary is provided here, along with some diagrams and + # differences specific to the DVP interface. + # + # One DMA channel (the "dispatcher") reads from a sequence of "control + # blocks", and writes them to the control registers of a second DMA + # channel (the "executer"). This pair of DMA channels continually + # trigger and reconfigure each other, and are capable of transferring + # data between any memory locations (including reconfiguring other + # peripherals by writing their control registers) with zero CPU overhead + # after initial configuration. + # + # If the image buffer is in SRAM, the executer just transfers an entire + # frame of data from the PIO RX FIFO to the image buffer with a single + # control block. The second control block then makes the executer write + # a third "nested" control block to the dispatcher, reconfiguring it to + # start the next frame, resulting in continuous video capture. + # + # +--------------------------+ + # |+------------+ +------+| +-------+ + # || Row Buffer |<---|Pixels||<-----| Image | + # |+------------+ ^ +------+| DVP +-------+ + # || Ctrl Blocks| | PIO FIFO| Camera + # |+------------+ | | + # | SRAM | | | + # | | +------------+| + # | | | Executer || + # | | +------------+| + # | | ^ | + # | | | | + # | | +------------+| + # | +->| Dispatcher || + # | +------------+| + # +--------------------------+ + # RP2350 + # + # If the image buffer is instead in PSRAM, transferring directly from + # the PIO FIFO to PSRAM is not reliable, because some cameras (eg. the + # OV5640) output each row of pixels in very short bursts that can exceed + # the QSPI bus transfer speed. So instead, the executer DMA transfers + # each row of pixel data to a small buffer in SRAM, then it triggers a + # third DMA channel (the "streamer") with another "nested" control block + # to transfer the row from SRAM to PSRAM as fast as the QSPI bus can + # handle. Both transfers in and out of the row buffer can happen at the + # same time, but the streamer starts earlier and stays ahead as long as + # the camera's average data speed is not too fast. + # + # The normal XIP memory mapped interface adds a lot of latency (see + # section 4.4.3 of the RP2350 datasheet), so it could be beneficial to + # use the QMI direct mode for the SRAM-to-PSRAM transfer (see section + # 12.14.5 of the RP2350 datasheet). But the "bursty" cameras usually + # have enough delay between each row that the normal memory mapped + # interface is sufficient. The QMI direct mode also *dramatically* + # complicates things, so this driver just uses the normal memory mapped + # interface for simplicity. + # + # +--------------------------------------+ + # +-----+ |+------+ +------------+ +------+| +-------+ + # |Image|<-----||Pixels|<---| Row Buffer |<---|Pixels||<-----| Image | + # +-----+ QSPI |+------+ ^ +------------+ ^ +------+| DVP +-------+ + # PSRAM | XIP | | Ctrl Blocks| | PIO FIFO| Camera + # | | +------------+ | | + # | | SRAM | | | + # | +------------+ | +------------+| + # | | Streamer |<--------| Executer || + # | +------------+ | +------------+| + # | | ^ | + # | | | | + # | | +------------+| + # | +->| Dispatcher || + # | +------------+| + # +--------------------------------------+ + # RP2350 + # + # When the image buffer is in PSRAM, the control block sequence is not + # automatically restarted at the end of each frame. The QSPI bus can end + # up being a real bottleneck if other things need to use it (eg. + # processing other image buffers, display output, flash memory access, + # etc.), so constantly spamming the QSPI bus with camera data can cause + # other things to slow down, or even cause pixels from the camera to be + # dropped if other transfers on the QSPI bus have higher priority. And + # in most real applications, most frames from the camera are ignored + # anyways, so it's better to just capture frames when needed. + + # Create the dispatcher and executer DMA channels. + self._dma_dispatcher = rp2.DMA() + 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)) + + # If the buffer is in PSRAM, create the streamer DMA channel and row + # buffer in SRAM. + if self._buffer_is_in_psram: + # Create the streamer DMA channel. + self._dma_streamer = rp2.DMA() + + # Create the row buffer. + self._bytes_per_row = self._width * self._bytes_per_pixel + self._row_buffer = array.array("I", [0] * (self._bytes_per_row // 4)) + + # 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)): + raise MemoryError("not enough space in SRAM for row buffer") + + # Create DMA control register values. + self._create_dma_ctrl_registers() + + # Create DMA control blocks. + self._create_control_blocks() + + # Assemble the control blocks in order. + self._assemble_control_blocks() + + def _create_dma_ctrl_registers(self): + """ + Creates the DMA control register values. + """ + # DMA DREQ (data request) signal selections for the RP2350 to pace + # transfers by the peripheral data request signals. For some reason, + # these are not defined in the `rp2.DMA` class. + # + # According to section 12.6.4.1 of the RP2350 datasheet, the PIO RX DREQ + # indices are determined by the PIO number (n) and state machine number + # (m) as follows: + # + # DREQ_PIOn_RXm = (n * 8) + 4 + m + pio_num = self._sm_id // 4 + sm_num = self._sm_id % 4 + DREQ_PIO_RX = (pio_num * 8) + 4 + sm_num # Pace transfers with PIO RX FIFO data request + DREQ_FORCE = 63 # Transfer as fast as possible + + # Dispatcher control register. The "ring" parameters are used to have + # the write address wrap around after 4 transfers (ring_sel = True means + # wrap the write address, and ring_size = 4 specifies a 4-bit address + # wrap, meaning 2**4 bits = 16 bits = 4 words), so the dispatcher + # continuously re-writes the 4 control registers of the executer. + self._dma_ctrl_cb_dispatcher = self._dma_dispatcher.pack_ctrl( + # size = 2, + # inc_read = True, + # inc_write = True, + ring_size = 4, + ring_sel = True, + # chain_to = self._dma_dispatcher.channel, + # treq_sel = DREQ_FORCE, + bswap = False, + ) + + # Executer control register for getting pixel data from the PIO FIFO. It + # transfers one pixel at a time (1, 2, or 4 bytes) and swaps bytes if + # 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], + inc_read = False, + inc_write = True, + # ring_size = 0, + # ring_sel = False, + chain_to = self._dma_dispatcher.channel, + treq_sel = DREQ_PIO_RX, + bswap = self._byte_swap, + ) - # If active, set up the VSYNC interrupt handler - if active: - Pin(self._pin_vsync).irq( - trigger = Pin.IRQ_FALLING, - handler = lambda pin: self._vsync_handler() + # Executer control register for sending nested control block to another + # DMA channel without chaining back to the dispatcher. + self._dma_ctrl_cb_executer_nested_single = self._dma_executer.pack_ctrl( + # size = 2, + # inc_read = True, + # inc_write = True, + # ring_size = 0, + # ring_sel = False, + # chain_to = self._dma_executer.channel, + # treq_sel = DREQ_FORCE, + bswap = False, + ) + + # If the display buffer is in PSRAM, we need additional DMA control + # registers. + if self._buffer_is_in_psram: + # DMA control register for the executer to send a nested control + # block with chaining back to the dispatcher, so another control + # block can be sent immediately. + self._dma_ctrl_cb_executer_nested_repeat = self._dma_executer.pack_ctrl( + # size = 2, + # inc_read = True, + # inc_write = True, + # ring_size = 0, + # ring_sel = False, + chain_to = self._dma_dispatcher.channel, + # treq_sel = DREQ_FORCE, + bswap = False, ) - # If not active, disable the VSYNC interrupt handler - else: - Pin(self._pin_vsync).irq( - handler = None + + # DMA control register for the streamer to transfer a row of pixel + # data from the row buffer in SRAM to PSRAM. + self._dma_ctrl_streamer = self._dma_streamer.pack_ctrl( + # size = 2, + # inc_read = True, + # inc_write = False, + # ring_size = 0, + # ring_sel = False, + # chain_to = self._dma_streamer.channel, + # treq_sel = DREQ_FORCE, + bswap = False, ) - def _vsync_handler(self): + def _create_control_blocks(self): """ - Handles the VSYNC interrupt to capture a frame of data. + Creates the DMA control blocks. """ - # Disable DMA before reconfiguring it - self._dma.active(False) + # Determine how many control blocks are needed. The control block + # sequence is created in `_assemble_control_blocks()`, but we need to + # create the control block array early so the restart frame block can + # reference it. + num_cb = 0 + if not self._buffer_is_in_psram: + num_cb += 1 # PIO read control block + if self._continuous: + num_cb += 1 # Restart frame control block + else: + num_cb += self._height # PIO read control blocks + num_cb += self._height # Streamer control blocks - # Reset state machine to ensure ISR is cleared - self._sm.restart() + # There are 4 words per control block. + num_cb *= 4 - # Ensure PIO RX FIFO is empty (it's not emptied by `sm.restart()`) - while self._sm.rx_fifo() > 0: - self._sm.get() + # Create control block array. + self._control_blocks = array.array('I', [0] * num_cb) - # Reset the DMA write address - self._dma.write = self._buffer + # Below are the individual control block definitions, to be written to + # the alias 0 registers of each DMA channel (see section 12.6.3.1 of the + # RP2350 datasheet). The registers are: + # + # 1) READ_ADDR + # 2) WRITE_ADDR + # 3) TRANS_COUNT + # 4) CTRL_TRIG + # + # When CTRL_TRIG is written, that DMA channel immediately starts. - # Start the DMA - self._dma.active(True) + # Conveniently gets the RX FIFO address instead of TX + pio_rx_fifo_addr = addressof(self._sm) - # Here is the PIO program, which is configurable to mask in the GPIO pins - # and the number of data pins. It must be configured before the state - # machine is created - @rp2.asm_pio( - in_shiftdir = rp2.PIO.SHIFT_LEFT, - push_thresh = 32, - autopush = True, - fifo_join = rp2.PIO.JOIN_RX - ) - def _pio_read_dvp(): + # Control blocks are different depending on whether the buffer is in + # SRAM or PSRAM. + if not self._buffer_is_in_psram: + # Control block for executer to read entire frame from PIO RX FIFO + # to image buffer. + self._cb_pio_repeat = array.array('I', [ + pio_rx_fifo_addr, # READ_ADDR + addressof(self._buffer), # WRITE_ADDR + self._width * self._height, # TRANS_COUNT + self._dma_ctrl_pio_repeat, # CTRL_TRIG + ]) + + # Control blocks for restarting the dispatcher. `_cb_restart_frame` + # must be the last control block in the sequence, which causes the + # executer to write the nested `_cb_restart_frame_nested` control + # block back to the dispatcher DMA registers, restarting it from the + # beginning of the control block sequence. + if self._continuous: + self._cb_restart_frame_nested = array.array('I', [ + addressof(self._control_blocks), # READ_ADDR + addressof(self._dma_executer.registers), # WRITE_ADDR + 4, # TRANS_COUNT + self._dma_ctrl_cb_dispatcher, # CTRL_TRIG + ]) + self._cb_restart_frame = array.array('I', [ + addressof(self._cb_restart_frame_nested), # READ_ADDR + addressof(self._dma_dispatcher.registers), # WRITE_ADDR + len(self._cb_restart_frame_nested), # TRANS_COUNT + self._dma_ctrl_cb_executer_nested_single, # CTRL_TRIG + ]) + else: + # Control block for executer to read 1 row from PIO RX FIFO to image + # buffer. + 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._dma_ctrl_pio_repeat, # CTRL_TRIG + ]) + + # Control blocks for the streamer DMA. `_cb_psram_repeat` and + # `_cb_psram_single` cause the executer to write the nested + # `_cb_psram_nested` control block to the streamer DMA registers, + # triggering it to transfer the row buffer to PSRAM. Only the + # `READ_ADDR_TRIG` register is used, since it's the only field that + # needs to change. We do not want to change the write address (let + # it keep incrementing to fill the whole image buffer), nor do we + # need to change the transfer count or control register. + self._cb_psram_nested = array.array('I', [ + addressof(self._row_buffer), # READ_ADDR_TRIG + ]) + self._cb_psram_repeat = array.array('I', [ + addressof(self._cb_psram_nested), # READ_ADDR + addressof(self._dma_streamer.registers[15:16]), # WRITE_ADDR + len(self._cb_psram_nested), # TRANS_COUNT + self._dma_ctrl_cb_executer_nested_repeat, # CTRL_TRIG + ]) + self._cb_psram_single = array.array('I', [ + addressof(self._cb_psram_nested), # READ_ADDR + addressof(self._dma_streamer.registers[15:16]), # WRITE_ADDR + len(self._cb_psram_nested), # TRANS_COUNT + self._dma_ctrl_cb_executer_nested_single, # CTRL_TRIG + ]) + + def _assemble_control_blocks(self): """ - PIO program to read DVP data from the GPIO pins. + Assembles the complete control block sequence to send the image buffer + over DVI, which includes sending all timing signals and pixel data to + the HSTX, starting the XIP stream and PSRAM DMA if needed, and + restarting the control block sequence for the next frame. """ - wait(1, gpio, 0) # Mask in HSYNC pin - wait(1, gpio, 0) # Mask in PCLK pin - in_(pins, 1) # Mask in number of pins - wait(0, gpio, 0) # Mask in PCLK pin + # Reset the control block index. + self._cb_index = 0 + + # Control blocks are different depending on whether the buffer is in + # SRAM or PSRAM. + if not self._buffer_is_in_psram: + # Add control block for executer to read entire frame from PIO RX + # FIFO to image buffer. + self._add_control_block(self._cb_pio_repeat) + + # If continuous mode is requested, add control block to restart the + # control block sequence reconfiguring the dispatcher DMA. + if self._continuous: + self._add_control_block(self._cb_restart_frame) + else: + # Loop through each row of the image. + for row in range(self._height): + # Add control block for executer to read 1 row from PIO RX FIFO + # to image buffer. + self._add_control_block(self._cb_pio_repeat) + + # Add control block for streamer to send row from SRAM to PSRAM. + self._add_control_block(self._cb_psram_repeat) + + # Overwrite the last control block to be a single transfer without + # chaining, so the control block sequence properly ends. + self._cb_index -= 4 + self._add_control_block(self._cb_psram_single) + + def _add_control_block(self, block): + """ + Helper function to add a control block to the control block array. + """ + # Add the control block to the array. Each control block is all 4 DMA + # alias 0 registers in order. + self._control_blocks[self._cb_index + 0] = block[0] # READ_ADDR + self._control_blocks[self._cb_index + 1] = block[1] # WRITE_ADDR + self._control_blocks[self._cb_index + 2] = block[2] # TRANS_COUNT + self._control_blocks[self._cb_index + 3] = block[3] # CTRL_TRIG + + # Increment the control block index for the next control block. + self._cb_index += 4 + + def _capture(self): + """ + Captures a frame of data from the DVP interface. + """ + # If the buffer is in SRAM and the state machine is already active, + # new frames are being captured continuously, so just return. + if not self._buffer_is_in_psram and self._sm.active() == True: + return + + # If the image buffer is in PSRAM, the streamer DMA channel needs to + # be reconfigured for each frame to reset the write address. + if self._buffer_is_in_psram: + self._dma_streamer.config( + read = addressof(self._row_buffer), # READ_ADDR + write = addressof(self._buffer), # WRITE_ADDR + count = self._bytes_per_row // 4, # TRANS_COUNT + ctrl = self._dma_ctrl_streamer, # CTRL + trigger = False, + ) + + # Configure the dispatcher DMA channel to start the control block + # sequence, but don't trigger it yet. + self._dma_dispatcher.config( + read = addressof(self._control_blocks), # READ_ADDR + write = addressof(self._dma_executer.registers), # WRITE_ADDR + count = 4, # TRANS_COUNT + ctrl = self._dma_ctrl_cb_dispatcher, # CTRL + trigger = False, + ) + + # Wait for VSYNC to go low, indicating the end of the current frame. + while Pin(self._pin_vsync, Pin.IN).value() == True: + pass + + # Activate the state machine and reset it. + self._sm.active(True) + self._sm.restart() + while self._sm.rx_fifo() > 0: + self._sm.get() + + # Start the dispatcher DMA channel. + self._dma_dispatcher.active(True) + + # If the buffer is in SRAM and we're in continuous mode, the control + # block sequence will restart automatically, so nothing else to do. But + # if the buffer is in PSRAM, we need to wait for the frame to finish. + if self._buffer_is_in_psram or self._continuous == False: + # Wait for VSYNC to go high then low again, indicating the end of + # the next frame. + while Pin(self._pin_vsync, Pin.IN).value() == False: + pass + while Pin(self._pin_vsync, Pin.IN).value() == True: + pass + + # Deactivate the state machine. + self._sm.active(False) diff --git a/red_vision/cameras/hm01b0.py b/red_vision/cameras/hm01b0.py index b847a6b..83d1bb1 100644 --- a/red_vision/cameras/hm01b0.py +++ b/red_vision/cameras/hm01b0.py @@ -349,4 +349,5 @@ def read(self, image=None): - success (bool): True if the image was read, otherwise False - image (ndarray): The captured image, or None if reading failed """ + self._capture() return (True, cv2.cvtColor(self._buffer, cv2.COLOR_BayerRG2BGR, image)) diff --git a/red_vision/cameras/hm01b0_pio.py b/red_vision/cameras/hm01b0_pio.py index e07dc31..8135dc2 100644 --- a/red_vision/cameras/hm01b0_pio.py +++ b/red_vision/cameras/hm01b0_pio.py @@ -30,6 +30,7 @@ def __init__( xclk_freq = 25_000_000, num_data_pins = 1, i2c_address = 0x24, + continuous = False, ): """ Initializes the HM01B0 PIO camera driver. @@ -50,6 +51,9 @@ def __init__( Default is 0x24 """ # Create the frame buffer + self._width = 324 + self._height = 244 + self._bytes_per_pixel = 1 self._buffer = np.zeros((244, 324), dtype=np.uint8) # Call both parent constructors @@ -63,8 +67,9 @@ def __init__( xclk_freq, sm_id, num_data_pins, - bytes_per_frame = self._buffer.size, - byte_swap = True + bytes_per_pixel = 2, + byte_swap = True, + continuous = continuous, ) HM01B0.__init__( self, @@ -77,10 +82,10 @@ def open(self): """ Opens the camera and prepares it for capturing images. """ - self._active(True) + pass def release(self): """ Releases the camera and frees any resources. """ - self._active(False) + pass diff --git a/red_vision/cameras/ov5640.py b/red_vision/cameras/ov5640.py index 2539942..ecad3b8 100644 --- a/red_vision/cameras/ov5640.py +++ b/red_vision/cameras/ov5640.py @@ -903,8 +903,8 @@ def __init__( self._write_list(self._sensor_default_regs) self._colorspace = self._OV5640_COLOR_RGB - self._flip_x = False - self._flip_y = False + self._flip_x = True + self._flip_y = True self._w = None self._h = None self._size = self._OV5640_SIZE_QVGA @@ -1180,6 +1180,7 @@ def read(self, image = None): - success (bool): True if the image was read, otherwise False - image (ndarray): The captured image, or None if reading failed """ + self._capture() if self._colorspace == self._OV5640_COLOR_RGB: return (True, cv2.cvtColor(self._buffer, cv2.COLOR_BGR5652BGR, image)) elif self._colorspace == self._OV5640_COLOR_GRAYSCALE: diff --git a/red_vision/cameras/ov5640_pio.py b/red_vision/cameras/ov5640_pio.py index dd35f05..c03f266 100644 --- a/red_vision/cameras/ov5640_pio.py +++ b/red_vision/cameras/ov5640_pio.py @@ -27,8 +27,10 @@ def __init__( pin_hsync, pin_pclk, pin_xclk = None, - xclk_freq = 5_000_000, - i2c_address = 0x3c + xclk_freq = 20_000_000, + i2c_address = 0x3c, + buffer = None, + continuous = False, ): """ Initializes the OV5640 PIO camera driver. @@ -47,7 +49,14 @@ def __init__( Default is 0x3c """ # Create the frame buffer - self._buffer = np.zeros((240, 320, 2), dtype=np.uint8) + if buffer is not None: + self._buffer = buffer + self._height, self._width, self._bytes_per_pixel = buffer.shape + else: + self._width = 320 + self._height = 240 + self._bytes_per_pixel = 2 + self._buffer = np.zeros((240, 320, 2), dtype=np.uint8) # Call both parent constructors DVP_RP2_PIO.__init__( @@ -60,8 +69,9 @@ def __init__( xclk_freq, sm_id, num_data_pins = 8, - bytes_per_frame = self._buffer.size, - byte_swap = False + bytes_per_pixel = 2, + byte_swap = False, + continuous = continuous, ) OV5640.__init__( self, @@ -73,10 +83,10 @@ def open(self): """ Opens the camera and prepares it for capturing images. """ - self._active(True) + pass def release(self): """ Releases the camera and frees any resources. """ - self._active(False) + pass