|
| 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