Skip to content

Commit 99a7ece

Browse files
authored
Merge pull request #2760 from jepler/xerox-dvi
Code for the xerox 820 to digital video project
2 parents 96ed6bf + 818abfb commit 99a7ece

File tree

1 file changed

+139
-0
lines changed
  • CircuitPython_Feather_DVI_Xerox_820

1 file changed

+139
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# SPDX-FileCopyrightText: 2024 Jeff Epler for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
import array
6+
7+
import ulab
8+
import rp2pio
9+
import board
10+
import adafruit_pioasm
11+
import picodvi
12+
import displayio
13+
14+
# The connections from the Xerox 820
15+
vdata = board.D9 # Followed by hsync on D10 & vsync on D11
16+
# The nominal frequency of the Xerox 820 video circuitry. Can modify by steps
17+
# of approximately ±42000 to improve display stability
18+
pixel_frequency = 10_694_250
19+
# The PIO peripheral is run at a multiple of the pixel frequency. This must be less
20+
# than the CPU speed, normally 120MHz.
21+
clocks_per_pixel = 10
22+
# The "fine pixel offset", shifts the sample time by this many sub-pixels
23+
fine_pixel = 0
24+
# A pin that shows when the Pico samples the pixel value. With an oscilloscope, this can
25+
# be used to help fine tune the pixel_frequency & fine_pixel numbers. Ideally, the rising
26+
# edge of pixel_sync_out is exactly in the middle of time a pixel is high/low.
27+
pixel_sync_out = board.D5
28+
29+
# Details of the Xerox display timing. You may need to modify `blanking_lines` and
30+
# `blanking_pixels` to adjust the vertical and horizontal position of the screen content.
31+
# Normally you wouldn't change `active_lines` or `active_pixels`.
32+
active_lines = 240
33+
blanking_lines = 18
34+
active_pixels = 640
35+
blanking_pixels = 58
36+
total_lines = active_lines + blanking_lines
37+
38+
# Pins for the DVI connector
39+
dvi_pins = dict(
40+
clk_dp=board.CKP,
41+
clk_dn=board.CKN,
42+
red_dp=board.D0P,
43+
red_dn=board.D0N,
44+
green_dp=board.D1P,
45+
green_dn=board.D1N,
46+
blue_dp=board.D2P,
47+
blue_dn=board.D2N,
48+
)
49+
50+
# Set up the display. Try 640x240 first (this mode is likely to be added in CircuitPython
51+
# 9.1.x) then 640x480, which works in CircuitPython 9.0.x.
52+
try:
53+
displayio.release_displays()
54+
dvi = picodvi.Framebuffer(640, 240, **dvi_pins, color_depth=1)
55+
except ValueError:
56+
print(
57+
"Note: This version of CircuitPython does not support 640x240\n."
58+
"Display will be compressed vertically."
59+
)
60+
displayio.release_displays()
61+
dvi = picodvi.Framebuffer(640, 480, **dvi_pins, color_depth=1)
62+
63+
# Clear the display
64+
ulab.numpy.frombuffer(dvi, dtype=ulab.numpy.uint8)[:] = 0
65+
66+
# Create the "control stream". The details are discussed in the Learn article
67+
def control_gen():
68+
yield total_lines - 2
69+
for _ in range(blanking_lines):
70+
yield from (1, 0) # 0 active pixels is special-cased
71+
for _ in range(active_lines):
72+
yield from (blanking_pixels - 1, active_pixels - 1)
73+
74+
control = array.array("L", control_gen())
75+
76+
# These little programs are run on the RP2040's PIO co-processor, and handle the pixel
77+
# data and sync pulses.
78+
jmp_0 = adafruit_pioasm.Program("jmp 0")
79+
80+
program = adafruit_pioasm.Program(
81+
f"""
82+
.side_set 1
83+
84+
.wrap_target
85+
out y, 32 ; get total line count
86+
wait 0, pin 2
87+
wait 1, pin 2 ; wait for vsync
88+
89+
wait_line_inactive:
90+
out x, 32 ; get total line count
91+
wait 0, pin 1
92+
wait 1, pin 1; wait for hsync
93+
94+
wait_line_active:
95+
nop [{clocks_per_pixel-2}]
96+
jmp x--, wait_line_active ; count off non-active pixels
97+
98+
out x, 32 [{fine_pixel}] ; get line active pixels & perform fine pixel adjust
99+
jmp !x, wait_line_inactive ; no pixels this line, wait next hsync
100+
101+
capture_active_pixels:
102+
in pins, 1 side 1
103+
jmp x--, capture_active_pixels [{clocks_per_pixel-2}] ; more pixels
104+
jmp y--, wait_line_inactive ; more lines?
105+
.wrap
106+
"""
107+
)
108+
109+
# Set up PIO to transfer pixels from Xerox
110+
pio = rp2pio.StateMachine(
111+
program.assembled,
112+
frequency=pixel_frequency * clocks_per_pixel,
113+
first_in_pin=vdata,
114+
in_pin_count=3,
115+
in_pin_pull_up=True,
116+
first_sideset_pin=pixel_sync_out,
117+
auto_pull=True,
118+
pull_threshold=32,
119+
auto_push=True,
120+
push_threshold=32,
121+
offset=0,
122+
**program.pio_kwargs,
123+
)
124+
125+
# Set up the DVI framebuffer memory as a capture target
126+
words_per_row = 640 // 32
127+
first_row = (dvi.height - 240) // 2 # adjust text to center if in 640x480 mode
128+
buf = memoryview(dvi).cast("L")[
129+
first_row * words_per_row : (first_row + active_lines) * words_per_row
130+
]
131+
assert len(buf) == 4800 # Check that the right amount will be transferred
132+
133+
b = array.array("L", [0])
134+
135+
# Repeatedly transfer pixels from Xerox into DVI framebuffer.
136+
while True:
137+
pio.run(jmp_0.assembled)
138+
pio.clear_rxfifo()
139+
pio.write_readinto(control, buf)

0 commit comments

Comments
 (0)