Pull to refresh

Эмулятор в эмуляторе для проигрывания чиптюн-мелодий на YM2149F

Abnormal programmingProgrammingDebuggingReverse engineeringProgramming microcontrollers

Кто помнит Tetris 2 на Спектруме? Там были куча уровней, возможность играть вдвоём и классная музыка.

Недавно мы сделали 8-битный компьютер для несложных игр, но никаких звуковых возможностей в нём не предусмотрели. И вот захотелось добавить туда какую-нибудь 8-битную музыку. Мне вспомнилась именно мелодия из Tetris 2 (много за ним часов проведено), поэтому я стал с ней ковыряться.

Процессор ATmega328P в нашем компьютере большую часть времени занят отрисовкой изображения, поэтому времени на синтезирование нормальной музыки совсем нет. Значит нам понадобится звуковой процессор YM2149F (он же AY-3-8910), такой же как в ZX Spectrum и других компьютерах.

Подключаем YM2149F к Arduino


Начал я с подключения звукового синтезатора к плате Arduino и с вывода на него простых нот. YM управляется с помощью записи значений в один из 15 (на самом деле мы используем 13) регистров. В регистры записываются частоты звука в каждом из трёх каналов, частота шума, уровень громкости, частота и форма огибающей. Для адресации и передачи данных используется 8 сигналов. Ещё парочка нужны для управления режимом шины — выбор регистра или загрузка значения.


Найденная в интернете схема подключения

Для тактирования YM используется встроенный таймер процессора с делителем частоты. В итоге на вход звукового чипа приходит 2МГц, что неправильно с точки зрения соответствия оригиналу, но для наших тестов пока сойдёт. Мы же программисты, а не железячники.

Код для инициализации генератора тактового сигнала
// Sets a 4MHz clock OC2A (PORTB3)
void set_ym_clock(void) {
  // PB3 - output
  DDRB |= 0x01 << PORTB3;
  // Set Timer 2 CTC mode with no prescaling. OC2A toggles on compare match
  //
  // WGM22:0 = 010: CTC Mode, toggle OC
  // WGM2 bits 1 and 0 are in TCCR2A,
  // WGM2 bit 2 is in TCCR2B
  // COM2A0 sets OC2A (arduino pin 11 on Uno or Duemilanove) to toggle on compare match
  //
  TCCR2A = ((1 << WGM21) | (1 << COM2A0));
  
  // Set Timer 2 No prescaling (i.e. prescale division = 1)
  //
  // CS22:0 = 001: Use CPU clock with no prescaling
  // CS2 bits 2:0 are all in TCCR2B
  TCCR2B = (1 << CS20);
  
  // Make sure Compare-match register A interrupt for timer2 is disabled
  TIMSK2 = 0;

  // Divide the 16MHz clock by 8 -> 2MHz
  OCR2A = 3;
}



Тестовые ноты проигрались успешно и надо было двигаться дальше.

Я раньше не очень интересовался спектрумовской музыкой отдельно от самого Спектрума, но всё же слышал про формат AY. Ещё в одной из статей я видел упоминание формата PSG. Он похож на всякие WAV в MP3 в том смысле, что там содержится линейная последовательность действий с регистрами музыкального сопроцессора. Поэтому файлы получаются большие и в память ATmega не влезают.

Файлы в формате AY намного меньше. В чём же там секрет? А в том, что это куски кода из игр или демок для проигрывания мелодии, а также некоторые массивы данных для этого кода. Обычно проигрыватели просто эмулируют центральный процессор Спектрума, чтобы выполнить эту программу и так проиграть мелодию.

Почему бы не сэмулировать Z80 на AVR?


… подумал я и поискал какую-нибудь библиотеку-симулятор процессора Z80 на языке C. Такой симулятор нашёлся. Ему надо было подсунуть только функции чтения/записи памяти и портов ввода-вывода.

Небольшая сложность возникла с памятью — ведь у ZX Spectrum 48 килобайт ОЗУ, а у ATmega328P всего 2 — напрямую создать массив памяти для функций чтения/записи не получится. Пришлось сделать массив адресов и ячеек, а в нем искать значения при обращениях от процессора.



Оказалось (кто бы мог подумать!), что эмулировать один 8-битный компьютер на другом 8-битном — не очень хорошая идея. Какой-то звук выводится, но всё происходит настолько медленно, что мелодией это назвать сложно. Тогда я решил разобраться с кодом проигрывателя и переписать его на C.

Декомпиляция или закат Солнца вручную


Код оказался немного запутанным. Это был интерпретатор какого-то байт-кода, управляющего музыкальным сопроцессором. В байт-коде даже есть поддержка циклов и подпрограмм. Каждый канал YM управляется отдельной программой с отдельным стеком. Получилось, что изначально я эмулировал компьютер, который эмулирует другой трехпроцессорный компьютер. И хоть программа получалась небольшая (и запускалась лишь 50 раз в секунду), всё равно это было очень медленно.

После того, как я почти раскусил формат этого байт-кода, я случайно наткнулся на его описание. Это был Fuxoft AY Language. Его разработал Frantisek Fuka (Fuxoft), который написал и сам тетрис, и музыку к нему. Этот язык используется в нескольких десятках композиций. И их код даже извлечён из игр в виде файлов FXM. Тот код, что я уже проанализировать, пришлось выкинуть, чтобы начать всё заново (но его по-прежнему можно увидеть в истории изменений репозитория).



Проигрыватель FXM


Краткое описание формата и его декодер мне попались в исходниках бульбовского проигрывателя. Сравнивая его с дизассемблированным кодом, я нашёл всего пару небольших отличий, но семантика структур данных стала намного понятнее.

Теперь проигрыватель работает достаточно быстро. Музыка играется, можно объединять компьютер и прогрыватель. Каждая композиция занимает всего пару килобайт, поэтому если взять Arduino Mega (на процессоре ATmega2560), то можно уместить в его память все существующие мелодии в формате FXM.

Что дальше?


Осталось добавить правильный кварцевый генератор, чтобы частота работы микросхемы была как на Спектруме. Ещё можно написать декодеры других трекерных форматов, добавить SD-карту и получится аппаратный плеер спектрумовской музыки. А когда подсоединим это к нашей игровой коробке — получится настоящий игровой автомат.

Правда пока почему-то не получается, чтобы громкость динамика была нормальной, сейчас всё очень тихо. Может кто-нибудь знает как заставить такой динамик из игрушечного телефона звучать? Китайский телефон с этим справляется, а выходы YM2149F почему-то не очень. Поэтому видео работы плеера я пока не выкладываю.



UPD: Припаял усилитель и записал видео как всё это играет:


Ссылки


  1. Репозиторий к кодом проигрывателя
  2. Как мы подключали светодиодный дисплей 64x64 к Arduino
  3. Управление чипом YM2149F с помощью Arduino
  4. Эмулятор процессора Z80 в виде библиотеки на C
  5. Проигрыватель музыки для AY
  6. Архивы музыки
Tags:ArduinoAYchiptunehappy debuggingzx spectrum
Hubs: Abnormal programming Programming Debugging Reverse engineering Programming microcontrollers
Total votes 14: ↑12 and ↓2+10
Views5.1K

Popular right now

Top of the last 24 hours