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+
118import rp2
219from machine import Pin , PWM
320
421class 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