После появления идеи добавления второго дисплея к Commodore 64 я довольно быстро реализовал этот проект. Все «железо» уместилось в картридж стандартного размера (вместе с коннектором DE-15). Видеовыход совместим с VGA (31 кГц).
Внутри картриджа — 128 КБ SRAM для кадрового буфера и простой 1-битный ЦАП.
TL;DR
Вот так выглядит плата, размещенная внутри картриджа. Загрузить исходник можно здесь.
Программный интерфейс
Картридж можно поместить в любую часть 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 МГц для экрана с разрешением 640x480 при FPS 60 Гц. Соответственно, любой подключаемый мною дисплей показывал изображение корректно и без проблем. Вертикальная и горизонтальная синхронизация, а также области гашения настроены на правильную полярность и длину для запуска этого режима. Возможны две интерпретации данных кадрового буфера: режим высокого разрешения 640x480 1 бит на пиксель и многоцветный режим низкого разрешения 320x480. Оба режима — palette direct.
Железо
Аппаратное обеспечение достаточно простое: регулятор 3,3 В, CPLD, генератор и SRAM. SRAM тратит половину своего времени на ответы хосту, и еще половину — на загрузку пиксельных данных. Используемый здесь CPLD, Xilinx 95144XL, устойчив к 5 В, поэтому он установлен на шине расширения C64, хотя и запитан от регулятора 3,3 В вместе с остальным оборудованием.
Используются почти все ресурсы CPLD. Я надеялся поместить один аппаратный спрайт для указателя, но для этого просто не осталось места.
Для тех, кто будет печатать кулеры, в модели STL есть все необходимое, причем в стиле C64.
Важный момент — вам понадобится программатор JTAG для загрузки битового потока в CPLD.
И еще — картридж не работает с платой Ultimate 64. Более того, установка картриджа на эту плату может вызвать повреждение картриджа. Зато все работает со всеми версиями плат C64, C128 и C64 Reloaded. Точно не знаю, совместим ли картридж со всеми версиями C64 или C128, выпущенными Commodore, но я никаких проблем не вижу.
Технические характеристики
- 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,2x1,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 в каждом байте — это первый пиксель.
В многоцветном режиме пиксели выводятся с той же скоростью, что и в монохромном режиме, но каждый цветовой канал имеет разное разрешение. Зеленый — это 1/2 pixel rate, а красный и синий — 1/4 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 640x480 -colors 2 -depth 1 output.mono
Цветной режим:
convert input.tiff +dither -posterize 2 -resize 640x480 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()
Демонстрационное видео:
Собираем и припаиваем:
По этой ссылке можно загрузить все необходимые для работы файлы.