Pull to refresh

Олдскул, хардкор, AY-3-8912. «Железный» чиптюн с последовательным входом

DIY


Клона Spectrum 128K, оснащенного музыкальным сопроцессором AY-3-8910 (YM2149F) у меня не было. Был 48K с расширенной клавиатурой и убогим блоком питания, перегревающим внутренности через час-два работы. От этого, помнится. домики посреди моря в Sim City образовывались и другие веселые артефакты. Но к делу данные воспоминания не относятся. Вдохновившись материалом tronix286, я решил восполнить пробел в ретро-образовании и склепать что-нибудь на легендарном (и при этом, легко добываемом и недорогом) чипе.

В ходе изучения различных поделок, идея сформировалась следующая: надо делать модуль с последовательным (UART) входом. Чтобы его уже можно было подключить с минимальными затратами к любому девайсу, добавляя тем самым +146 к чиптюновости. В процессе также было решено освоить пару дополнительных навыков, вроде программирования AVR и изготовления печатных плат с применением фоторезиста.

Сразу опишу результаты: модуль разработан и пиликает, побочные навыки прокачаны, грабли собраны, можно радоваться, расширять и углублять.

Синтезатор

AY-3-8910 — это большой красивый DIP-чип на 40 ножек. Помимо нужных вещей, там еще два 8-разрядных порта, которые для вывода звука нормальными людьми (не пытающимися на них сколхозить адский аналог Covox) не используются. AY-3-8912 не менее красив, но одним «лишним» портом обделен и упакован уже в DIP28. И еще бывает AY-3-8913, вообще без параллельного порта (DIP24). И это только General Instrument / Microchip. Yamaha клепала еще больше вариаций: от YM2149F (аналог AY-3-8910 с делителем тактовой частоты) до YMZ284 (DIP16, один смикшированный выход каналов). Подробнее о чипах на Wiki (англ.).

Управление

Для загрузки данных используется 8-разрядный параллельный порт. Плюс, три управляющих линии (одну, которая BC2, подтягиваем к плюсу питания). Логика следующая:

1. Исходное состояние — BC1=0, BDIR=0.
2. Устанавливаем на ножках порта AY адрес регистра.
3. BC1=1, BDIR=1. Загрузка адреса. Задержка между включением линий должна составлять не более 50 нс, поэтому всякие медленные подергивания ножек (типа ардуинского DigitalWrite) не годятся, надо, например, PORTC |= 0b00110000;
4. BC1=0, BDIR=0.
5. Устанавливаем на ножках порта AY значение регистра.
6. BC1=0, BDIR=1. Загрузка значения.

Повторяем последовательность 14 раз (для каждого нужного регистра). И все это — 50 раз в секунду. Получаем музыку.

Выбор конкретного железа

В моем случае следующий:
1. AY-3-8912 — был дешевле.
2. Atmega8A (DIP28) — доступно, достаточно выводов.
3. Кварцевый генератор на 4 МГц — для тактирования AY.
4. Счетчик К555ИЕ5 — как делитель частоты для тактирования AY.
5. Кварц на 16 МГц — для Atmega8A.
6. Для подключения к ПК — USB UART на FT232R.



Плата разведена именно под это дело. В процессе отладки и более вдумчивого изучения возникли следующие мысли:

1. Если использовать YM2149F, то не нужен счетчик, т.к. в этом чипе есть встроенный делитель частоты на 2.
2. Похоже, что кварц для Atmega тоже не нужен — все прилично работает и от внутреннего генератора на 8 МГц.
3. В теории, можно попробовать вообще избавиться от кварцевого генератора для AY, если поковыряться с аппаратными таймерами и счетчиками Atmega. Но! В этом случае мы сможем тактировать AY только на 2 МГц. А по-хорошему, надо иметь возможность тактирования на 1.7(много цифр) МГц — как это делается в Speccy. У меня кварцевый генератор на 4 МГц стоит в DIP-колодке, чтобы потом его заменить на 3.5(много цифр) МГц.

Выход звука срисован у tronix286, там горстка резисторов и два конденсатора.

Софт

Для вдохновления изучалась вот эта (недо)реализация. Там описан общий принцип работы связки «источник — UART — Atmega — AY», но использование на «меге» загрузчика Arduino в данном случае показалось мне совершенно лишним. Ну и, программа на ПК, написання на C#, мне не понравилась. Шарп здесь примерно так же «нужен», как и Arduino. Формат YM разложен по полочкам здесь.

Прошивка Atmega

Исходный код и hex доступны на гитхабе (ссылка в конце материала), пробегусь просто по основным функциям.

valToPort — запись 8-разрядного значения в «порт», состоящий из половинки порта B и половинки C. Так было удобнее разводить.
sendToAY — запись 8-разрядного значения в регистр AY. Здесь как раз реализована логика, описанная в пункте «Управление».
setup — инициализация портов и UART.
main — зацикленное «получить 16 байт — записать в AY».

Демонстрационный пример на PC



Написан на Python 3 с использованием PySerial. Как и прошивка, лежит на гитхабе. Берет файл 1.ym (несжатый!) из текущей директории, разбирает его и заталкивает в COM6. Ради интереса пример проверен на OS X, работает «из коробки», достаточно только поменять название порта. Подозреваю, что столь же успешно будет работать на Linux, в т.ч. на «малинке».

В выдаче дампа регистров на AY есть один нюанс. Формат YM хранит данные в виде «все значения регистра 0, все значения регистра 1...». Это очень правильно с точки зрения дальнейшего сжатия. Я же работаю с несжатым YM, и мне нужно выдавать пачки байт «регистр 0, регистр 1...». Для PC задача решена в лоб — читаем данные из файла в нужном порядке в большой массив, затем из него последовательно отдаем контроллеру. Когда нужно будет делать «головное устройство» на базе чипа с малым объемом памяти, придется изобретать какие-то буферы.

Итого

Стоит помнить о том, что перед запуском воспроизведения неплохо бы сбросить «мегу» и AY. Потому что если в буфере контроллера что-то есть, при передаче новых данных весь поток сместится, и звуки будут воистину душераздирающие. Пин сброса на модуле предусмотрен, но в демонстрационном примере не используется. Сброс осуществляется тычком провода в сидящий на «земле» корпус кварцевого генератора.

Имеется и недопойманный баг. Который, наверное, прячется где-то в районе «Windows 10 — Python 3 — UART». Периодически скорость обновления падает с 50 Гц до 20 (осциллоскопировано). Системы в явлении не обнаружено, на другом компьютере глюк не воспроизвелся. Если поймаю когда-нибудь, сделаю UPD.

В дальнейшем модуль будет прикручен к находящейся в процессе разработке поделке, об этом в относительно обозримом будущем будет статья. Ну, и к «малинке» надо попробовать подцепить. Простор для экспериментов имеется.

Исходники, разводка платы (SL6), схема модуля (Eagle) на гитхабе.

UPD1. Конвертировать в YM можно с помощью AYEmul.
UPD2. Здесь берется .torrent для скачивания архива Modland.com. Там много.
UPD3. ProjectAY. Там много музыки в AY. На момент публикации сайт лежал.

Теперь все более-менее культурно

UPD4. 18.03.2015 переделал код Atmega и демонстрационный (питоний). Теперь логика такая: Atmega запрашивает у хоста данные, заполняет кольцевой буфер (512 байт), затем, как половину проигрывает, запрашивает еще (т.е. точки запроса 256 и 512 (0)). Хост на запрос реагирует пачкой в 256 байт, Atmega их потребляет через обработчик прерывания. Не все отполировано, но играет куда стабильнее первоначального варианта.

UPD5. Попробовал на малинке. Работает почти из коробки.
0. Отключить консоль на последовательном порту (etc/inittab в конце).
1. comPort = "/dev/ttyAMA0" (последовательный порт)
2. regState = bytes([registerDump[currentPos + currByte]]) (так serial.write работает нормально)
Tags:
Hubs:
Total votes 40: ↑40 and ↓0 +40
Views 31K
Comments Comments 31