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
+
1
18
import rp2
2
19
from machine import Pin , PWM
3
20
4
21
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
+ """
5
27
def __init__ (
6
28
self ,
7
29
pin_d0 ,
@@ -15,12 +37,27 @@ def __init__(
15
37
bytes_per_frame ,
16
38
byte_swap
17
39
):
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
24
61
25
62
# Initialize DVP pins as inputs
26
63
for i in range (num_data_pins ):
@@ -30,86 +67,101 @@ def __init__(
30
67
Pin (pin_pclk , Pin .IN )
31
68
32
69
# 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
37
74
38
75
# Copy the PIO program
39
76
program = self ._pio_read_dvp
40
77
41
78
# 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
45
82
46
83
# Mask in the number of data pins
47
84
program [0 ][2 ] &= 0xFFFFFFE0
48
85
program [0 ][2 ] |= num_data_pins
49
86
50
87
# 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 ,
53
90
program ,
54
91
in_base = pin_d0
55
92
)
56
93
57
94
# 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
60
97
bytes_per_transfer = 4
61
- dma_ctrl = self .dma .pack_ctrl (
98
+ dma_ctrl = self ._dma .pack_ctrl (
62
99
# 0 = 1 byte, 1 = 2 bytes, 2 = 4 bytes
63
100
size = {1 :0 , 2 :1 , 4 :2 }[bytes_per_transfer ],
64
101
inc_read = False ,
65
102
treq_sel = req_num ,
66
103
bswap = byte_swap
67
104
)
68
- self .dma .config (
69
- read = self .sm ,
105
+ self ._dma .config (
106
+ read = self ._sm ,
70
107
count = bytes_per_frame // bytes_per_transfer ,
71
108
ctrl = dma_ctrl
72
109
)
73
110
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
+ """
75
124
# If no argument is provided, return the current active state
76
125
if active == None :
77
- return self .sm .active ()
126
+ return self ._sm .active ()
78
127
79
128
# Disable the DMA, the VSYNC handler will re-enable it when needed
80
- self .dma .active (False )
129
+ self ._dma .active (False )
81
130
82
131
# Set the active state of the state machine
83
- self .sm .active (active )
132
+ self ._sm .active (active )
84
133
85
134
# If active, set up the VSYNC interrupt handler
86
135
if active :
87
- Pin (self .pin_vsync ).irq (
136
+ Pin (self ._pin_vsync ).irq (
88
137
trigger = Pin .IRQ_FALLING ,
89
138
handler = lambda pin : self ._vsync_handler ()
90
139
)
91
140
# If not active, disable the VSYNC interrupt handler
92
141
else :
93
- Pin (self .pin_vsync ).irq (
142
+ Pin (self ._pin_vsync ).irq (
94
143
handler = None
95
144
)
96
145
97
146
def _vsync_handler (self ):
147
+ """
148
+ Handles the VSYNC interrupt to capture a frame of data.
149
+ """
98
150
# Disable DMA before reconfiguring it
99
- self .dma .active (False )
151
+ self ._dma .active (False )
100
152
101
153
# Reset state machine to ensure ISR is cleared
102
- self .sm .restart ()
154
+ self ._sm .restart ()
103
155
104
156
# 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 ()
107
159
108
160
# Reset the DMA write address
109
- self .dma .write = self .buffer
161
+ self ._dma .write = self ._buffer
110
162
111
163
# Start the DMA
112
- self .dma .active (True )
164
+ self ._dma .active (True )
113
165
114
166
# Here is the PIO program, which is configurable to mask in the GPIO pins
115
167
# and the number of data pins. It must be configured before the state
@@ -121,6 +173,9 @@ def _vsync_handler(self):
121
173
fifo_join = rp2 .PIO .JOIN_RX
122
174
)
123
175
def _pio_read_dvp ():
176
+ """
177
+ PIO program to read DVP data from the GPIO pins.
178
+ """
124
179
wait (1 , gpio , 0 ) # Mask in HSYNC pin
125
180
wait (1 , gpio , 0 ) # Mask in PCLK pin
126
181
in_ (pins , 1 ) # Mask in number of pins
0 commit comments