[Перевод] Проект VG64: добавляем второй монитор к Commodore 64

78e0ad784be13516448361761ba2252f.jpg

После появления идеи добавления второго дисплея к Commodore 64 я довольно быстро реализовал этот проект. Все «железо» уместилось в картридж стандартного размера (вместе с коннектором DE-15). Видеовыход совместим с VGA (31 кГц).

Внутри картриджа — 128 КБ SRAM для кадрового буфера и простой 1-битный ЦАП.

TL; DR


Вот так выглядит плата, размещенная внутри картриджа. Загрузить исходник можно здесь.
614b67ec5947197884efc905f46ef553.jpg

Программный интерфейс


Картридж можно поместить в любую часть 64 КБ адресного пространства, включая I/O1 или I/O2. Есть Verilog код для представления либо в окне в буфере кадра @EXPROM, что заберет 8 КБ памяти Basic, либо основанный на регистрах подход, экономящий оперативную память.

В приведенных примерах для регистров управления используется I/O1 на $DE00. У вас может возникнуть желание изменить поданный пример, если есть конфликт с каким либо другим эддоном (второй SID-чип и т.п.). В целом, существует поддержка специального токена, который позволяет избежать конфликтов, но у меня нет дополнительного ПО, которое эти конфликты вызывает.

Регистры

IOBASE = token
IOBASE+1 = lsb address
IOBASE+2 = msb address
IOBASE+3 = data

Кадровый буфер — линейный, использовать его несложно, подобно собственным режимам C64 с растровым отображением. В SRAM его начало — $00000.

Вывод видео


Вне зависимости от выбранного режима видео выводится с pixel rate в 25 МГц благодаря встроенному генератору 100 МГц. Этот параметр близок к стандарту в 25,175 МГц для экрана с разрешением 640×480 при FPS 60 Гц. Соответственно, любой подключаемый мною дисплей показывал изображение корректно и без проблем. Вертикальная и горизонтальная синхронизация, а также области гашения настроены на правильную полярность и длину для запуска этого режима. Возможны две интерпретации данных кадрового буфера: режим высокого разрешения 640×480 1 бит на пиксель и многоцветный режим низкого разрешения 320×480. Оба режима — palette direct.

Железо


Аппаратное обеспечение достаточно простое: регулятор 3,3 В, CPLD, генератор и SRAM. SRAM тратит половину своего времени на ответы хосту, и еще половину — на загрузку пиксельных данных. Используемый здесь CPLD, Xilinx 95144XL, устойчив к 5 В, поэтому он установлен на шине расширения C64, хотя и запитан от регулятора 3,3 В вместе с остальным оборудованием.
f45c908852cfe79aa0a27bc6a48ba296.png

Используются почти все ресурсы CPLD. Я надеялся поместить один аппаратный спрайт для указателя, но для этого просто не осталось места.

Для тех, кто будет печатать кулеры, в модели STL есть все необходимое, причем в стиле C64.

Важный момент — вам понадобится программатор JTAG для загрузки битового потока в CPLD.

И еще — картридж не работает с платой Ultimate 64. Более того, установка картриджа на эту плату может вызвать повреждение картриджа. Зато все работает со всеми версиями плат C64, C128 и C64 Reloaded. Точно не знаю, совместим ли картридж со всеми версиями C64 или C128, выпущенными Commodore, но я никаких проблем не вижу.

c508a432d159fa06c282952ab7d4f33a.jpg

Технические характеристики


  • 4-х слойная печатная плата. Включены файлы Gerber. Скос на краю значительно увеличивает стоимость, поэтому просто отшлифуйте его вручную (обязательно сделайте это, в противном случае можно повредить female-контакты).
  • Корпус картриджа сверху и снизу. Включены файлы STL.
  • Алюминиевый поляризованный конденсатор, 22 мкФ, 6,6 мм
  • Переключатель мгновенного действия, например pn 430156043726, если нужна кнопка reset для вашего компьютера.
  • Коннекторы .1»
  • резисторы 0603: 2 499R, 3 300R, 2 30R
  • конденсаторы 0603: 10 0,1 мкФ, 7 0,01 мкФ
  • 2 светодиода 3,2×1,6 (полезно для отладки, но не обязательно)
  • XC95144XL-5TQ100C CPLD (скорость не важна)
  • JEDEC 128kx8 SO Async SRAM a la AS6C1008–55PCN (не медленнее)
  • Прямой угловой разъем VGA высокой плотности с отверстиями, гнездовой разъем DE15

Verilog


Я использовал Xilinx ISE 14.5, поскольку не нашел открытого набора инструментов для этих CPLD. Если у кого-то есть такая информация, то поделитесь.

Упаковка пикселей


В режиме высокого разрешения каждый бит соответствует одному пикселю. 1 = белый, 0 = черный. Адреса перемещаются от (0,0) в верхнем левом наиболее видимом положении к нижнему правому (639 479), по столбцу, затем по строке. Бит 7 в каждом байте — это первый пиксель.
В многоцветном режиме пиксели выводятся с той же скоростью, что и в монохромном режиме, но каждый цветовой канал имеет разное разрешение. Зеленый — это ½ pixel rate, а красный и синий — ¼ pixel rate. Сопоставление битового шаблона с цветовым каналом побайтно (фрагментарно) и составляет:

G0 G1 G2 G3 R0 R1 B0 B1

В то время как экранное представление каждого байта буфера кадра выглядит следующим образом:

R0 R0 R0 R0 R1 R1 R1 R1
G0 G0 G1 G1 G2 G2 G3 G3
B0 B0 B0 B0 B1 B1 B1 B1

Преобразование изображений для отображения с помощью ImageMagick, монохромный режим:

convert input.tiff -resize 640×480 -colors 2 -depth 1 output.mono

Цветной режим:

convert input.tiff +dither -posterize 2 -resize 640×480 output.tiff
convert output.tiff -separate channel%d.png

Код написан на Python — мне этот вариант показался самым простым:

from PIL import Image
from array import *
import numpy as np

ir = Image.open("channel0.png")
ig = Image.open("channel1.png")
ib = Image.open("channel2.png")

ir = ir.resize((640,480))
ig = ig.resize((640,480))
ib = ib.resize((640,480))

r = ir.load()
g = ig.load()
b = ib.load()

arr=np.zeros((480,80,8))
out=np.zeros((480,640))

for y in range(0,480):
        for x in range(0,80):

                # 0 1 2 3 is green level
                # 4 5 is red level
                # 6 7 is blue level

                # GREEN
        
                arr[y][x][0]=(g[x*8+0,y]+g[x*8+1,y])/2
                arr[y][x][1]=(g[x*8+2,y]+g[x*8+3,y])/2
                arr[y][x][2]=(g[x*8+4,y]+g[x*8+5,y])/2
                arr[y][x][3]=(g[x*8+6,y]+g[x*8+7,y])/2

                # RED

                arr[y][x][4]=(r[x*8+0,y]+r[x*8+1,y]+r[x*8+2,y]+r[x*8+3,y])/4
                arr[y][x][5]=(r[x*8+4,y]+r[x*8+5,y]+r[x*8+6,y]+r[x*8+7,y])/4

                #BLUE

                arr[y][x][6]=(b[x*8+0,y]+b[x*8+1,y]+b[x*8+2,y]+b[x*8+3,y])/4
                arr[y][x][7]=(b[x*8+4,y]+b[x*8+5,y]+b[x*8+6,y]+b[x*8+7,y])/4

for y in range(0,480):
        for x in range(0,80):
                for bit in range(0,8):

                        arr[y][x][bit] = int(round(round(arr[y][x][bit])/255))

newfile=open("output.bin","wb")

for y in range(0,480):
        for x in range(0,80):

                out[y][x] = int(arr[y][x][0] + arr[y][x][1]*2 + arr[y][x][2]*4 + arr[y][x][3]*8 
+ arr[y][x][4]*16 + arr[y][x][5]*32 + arr[y][x][6]*64 + arr[y][x][7]*128)

                newfile.write(out[y][x].astype(np.ubyte))

newfile.close()

Демонстрационное видео:
Собираем и припаиваем:
e547b24d24b291eed42cee4572844a89.jpg

8647674ee90db51034ff46b2b7abf892.jpg

a331497bad38fae832d379105f8ecbb1.jpg

По этой ссылке можно загрузить все необходимые для работы файлы.

peayzfh745-twugmjk-r2zg-plc.png

© Habrahabr.ru