1
1
import rp2
2
2
from machine import Pin , PWM
3
3
4
+ # This class is derived from:
5
+ # https://github.com/adafruit/Adafruit_ImageCapture/blob/main/src/arch/rp2040.cpp
6
+ # Released under the MIT license.
7
+ # Copyright (c) 2021 Adafruit Industries
4
8
class DVP_RP2_PIO ():
9
+ """
10
+ This class implements a DVP (Digital Video Port) interface using the RP2 PIO
11
+ (Programmable Input/Output) interface. This is only available on Raspberry
12
+ Pi RP2 processors.
13
+ """
5
14
def __init__ (
6
15
self ,
7
16
pin_d0 ,
@@ -15,12 +24,27 @@ def __init__(
15
24
bytes_per_frame ,
16
25
byte_swap
17
26
):
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
27
+ """
28
+ Initializes the DVP interface with the specified parameters.
29
+
30
+ Args:
31
+ pin_d0 (int): Data 0 pin number for DVP interface
32
+ pin_vsync (int): Vertical sync pin number
33
+ pin_hsync (int): Horizontal sync pin number
34
+ pin_pclk (int): Pixel clock pin number
35
+ pin_xclk (int): External clock pin number
36
+ xclk_freq (int): Frequency in Hz for the external clock
37
+ sm_id (int): PIO state machine ID
38
+ num_data_pins (int): Number of data pins used in DVP interface
39
+ bytes_per_frame (int): Number of bytes per frame to capture
40
+ byte_swap (bool): Whether to swap bytes in the captured data
41
+ """
42
+ self ._pin_d0 = pin_d0
43
+ self ._pin_vsync = pin_vsync
44
+ self ._pin_hsync = pin_hsync
45
+ self ._pin_pclk = pin_pclk
46
+ self ._pin_xclk = pin_xclk
47
+ self ._sm_id = sm_id
24
48
25
49
# Initialize DVP pins as inputs
26
50
for i in range (num_data_pins ):
@@ -30,86 +54,98 @@ def __init__(
30
54
Pin (pin_pclk , Pin .IN )
31
55
32
56
# 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
57
+ if self ._pin_xclk is not None :
58
+ self ._xclk = PWM (Pin (pin_xclk ))
59
+ self ._xclk .freq (xclk_freq )
60
+ self ._xclk .duty_u16 (32768 ) # 50% duty cycle
37
61
38
62
# Copy the PIO program
39
63
program = self ._pio_read_dvp
40
64
41
65
# 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
66
+ program [0 ][0 ] |= self ._pin_hsync & 0x1F
67
+ program [0 ][1 ] |= self ._pin_pclk & 0x1F
68
+ program [0 ][3 ] |= self ._pin_pclk & 0x1F
45
69
46
70
# Mask in the number of data pins
47
71
program [0 ][2 ] &= 0xFFFFFFE0
48
72
program [0 ][2 ] |= num_data_pins
49
73
50
74
# Create PIO state machine to capture DVP data
51
- self .sm = rp2 .StateMachine (
52
- self .sm_id ,
75
+ self ._sm = rp2 .StateMachine (
76
+ self ._sm_id ,
53
77
program ,
54
78
in_base = pin_d0
55
79
)
56
80
57
81
# 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
82
+ self ._dma = rp2 .DMA ()
83
+ req_num = ((self ._sm_id // 4 ) << 3 ) + (self ._sm_id % 4 ) + 4
60
84
bytes_per_transfer = 4
61
- dma_ctrl = self .dma .pack_ctrl (
85
+ dma_ctrl = self ._dma .pack_ctrl (
62
86
# 0 = 1 byte, 1 = 2 bytes, 2 = 4 bytes
63
87
size = {1 :0 , 2 :1 , 4 :2 }[bytes_per_transfer ],
64
88
inc_read = False ,
65
89
treq_sel = req_num ,
66
90
bswap = byte_swap
67
91
)
68
- self .dma .config (
69
- read = self .sm ,
92
+ self ._dma .config (
93
+ read = self ._sm ,
70
94
count = bytes_per_frame // bytes_per_transfer ,
71
95
ctrl = dma_ctrl
72
96
)
73
97
74
- def active (self , active = None ):
98
+ def _active (self , active = None ):
99
+ """
100
+ Sets or gets the active state of the DVP interface.
101
+
102
+ Args:
103
+ active (bool, optional):
104
+ - True: Activate the DVP interface
105
+ - False: Deactivate the DVP interface
106
+ - None: Get the current active state
107
+ """
75
108
# If no argument is provided, return the current active state
76
109
if active == None :
77
- return self .sm .active ()
110
+ return self ._sm .active ()
78
111
79
112
# Disable the DMA, the VSYNC handler will re-enable it when needed
80
- self .dma .active (False )
113
+ self ._dma .active (False )
81
114
82
115
# Set the active state of the state machine
83
- self .sm .active (active )
116
+ self ._sm .active (active )
84
117
85
118
# If active, set up the VSYNC interrupt handler
86
119
if active :
87
- Pin (self .pin_vsync ).irq (
120
+ Pin (self ._pin_vsync ).irq (
88
121
trigger = Pin .IRQ_FALLING ,
89
122
handler = lambda pin : self ._vsync_handler ()
90
123
)
91
124
# If not active, disable the VSYNC interrupt handler
92
125
else :
93
- Pin (self .pin_vsync ).irq (
126
+ Pin (self ._pin_vsync ).irq (
94
127
handler = None
95
128
)
96
129
97
130
def _vsync_handler (self ):
131
+ """
132
+ Handles the VSYNC interrupt to capture a frame of data.
133
+ """
98
134
# Disable DMA before reconfiguring it
99
- self .dma .active (False )
135
+ self ._dma .active (False )
100
136
101
137
# Reset state machine to ensure ISR is cleared
102
- self .sm .restart ()
138
+ self ._sm .restart ()
103
139
104
140
# Ensure PIO RX FIFO is empty (it's not emptied by `sm.restart()`)
105
- while self .sm .rx_fifo () > 0 :
106
- self .sm .get ()
141
+ while self ._sm .rx_fifo () > 0 :
142
+ self ._sm .get ()
107
143
108
144
# Reset the DMA write address
109
- self .dma .write = self .buffer
145
+ self ._dma .write = self ._buffer
110
146
111
147
# Start the DMA
112
- self .dma .active (True )
148
+ self ._dma .active (True )
113
149
114
150
# Here is the PIO program, which is configurable to mask in the GPIO pins
115
151
# and the number of data pins. It must be configured before the state
@@ -121,6 +157,9 @@ def _vsync_handler(self):
121
157
fifo_join = rp2 .PIO .JOIN_RX
122
158
)
123
159
def _pio_read_dvp ():
160
+ """
161
+ PIO program to read DVP data from the GPIO pins.
162
+ """
124
163
wait (1 , gpio , 0 ) # Mask in HSYNC pin
125
164
wait (1 , gpio , 0 ) # Mask in PCLK pin
126
165
in_ (pins , 1 ) # Mask in number of pins
0 commit comments