Плеер Arduino Zx Spectrum Ay

Автономный музыкальный проигрыватель от компьютера ZX Spectrum на Arduino с минимальным количеством деталей.



Плеер Arduino ZX Spectrum AY

Это выглядит как Спектр рингтоны навсегда останется в моем сердце, так как я регулярно слушаю любимые композиции с помощью чудесного Лампа-плеер .



Плеер Arduino ZX Spectrum AY

Но быть привязанным к компьютеру не очень удобно.

Я временно решил эту проблему с помощью не менее замечательного EEE PC. Но мне хотелось еще более миниатюрного.



Плеер Arduino ZX Spectrum AY

Поиски в Интернете привели к следующим красавцам:



Плеер Arduino ZX Spectrum AY

Они восхитительны своей элементной базой, вызывающей ностальгические воспоминания, но я знал, что моя лень не позволит мне завершить такой проект. Мне нужно было что-то маленькое.

И вот почти идеальный кандидат: AVR AY-плеер - воспроизводит файлы *.

PSG - поддерживается файловая система FAT16 — количество каталогов в корне диска 32 — количество файлов в каталоге — 42 (всего 32*42=1344 файла) — сортировка каталогов и файлов в каталогах по первым двум буквам названия Схема выглядит вполне приемлемо по размерам:

Плеер Arduino ZX Spectrum AY

Конечно я нашел это Роковая ошибка , что испортило идиллию: нет режима случайного выбора композиции.

(возможно, стоило просто попросить автора добавить в прошивку эту функцию?).

Два года я искал подходящий вариант, терпение кончилось и я решил действовать.

Исходя из своей фантастической лени, я выбрал минимальные телодвижения: 1. Берём Arduino Mini Pro, чтобы не заморачиваться со жгутом.

2. Вам понадобится SD-карта, чтобы где-нибудь хранить музыку.

Итак берем SD-щит. 3. Вам нужен музыкальный сопроцессор.

Самый маленький — AY-3-8912. Был еще вариант программной эмуляции сопроцессора, но мне все равно хотелось «теплого лампового звука».

Для воспроизведения мы будем использовать формат PSG. Структура формата ПСЖ Смещение Число байтов Описание +0 3 Идентификатор «ПСЖ» +3 1 Маркер «Конец текста» (1Ач) +4 1 Номер версии +5 1 Частота игроков (для версий 10+) +6 10 Данные Данные — последовательности пар байтов, записываемые в регистр.

Первый байт — номер регистра (от 0 до 0x0F), второй — значение.

Вместо номера регистра могут быть специальные маркеры: 0xFF, 0xFE или 0xFD. 0xFD — конец композиции.

0xFF — ожидание 20 мс.

0xFE — следующий байт показывает, сколько раз ждать 80 мс.

Как перейти в ПСЖ 1. Установите Лампа-плеер .

2. Откройте плейлист кнопкой [ПЛ] .

3. Добавьте мелодии в плейлист. 4. Выбрать мелодию в списке, нажать правую кнопку мыши, чтобы вызвать меню, в нем Переход в ПСЖ.

5. Желательно сохранять под именем не длиннее 8 символов, иначе оно не будет отображаться полностью.

Начнем с подключения SD-карты.

Лень предложил взять стандартное подключение SD-shield и использовать стандартная библиотека для работы с картами .

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

Плеер Arduino ZX Spectrum AY

Для проверки берем стандартный эскиз : эскиз списка файлов на карте

  
  
  
   

#include <SPI.h> #include <SD.h> void setup() { Serial.begin(9600); Serial.print("Initializing SD card."); if (!SD.begin(10)) { Serial.println("initialization failed!"); return; } Serial.println("initialization done."); File root = SD.open("/"); printDirectory(root); Serial.println("done!"); } void loop() { } void printDirectory(File dir) { while (true) { File entry = dir.openNextFile(); if (!entry) break; Serial.print(entry.name()); if (!entry.isDirectory()) { Serial.print("\t\t"); Serial.println(entry.size(), DEC); } entry.close(); } }

Форматируем карту, записываем туда несколько файлов, запускаем.

не получается! Я так всегда делаю - самая стандартная задача - и сразу ошибки.

Берем другую флешку - (была старая на 32Мб, берем новую на 2Гб) - да, заработало, но через некоторое время.

Полчаса почесать лоб, переставить разъемы поближе к карте (чтобы проводники были короче), развязывающий конденсатор по питанию - и все заработало в 100% случаев.

Хорошо, идем дальше.

Теперь нам нужно приобрести сопроцессор — ему нужна тактовая частота 1,75 МГц.

Вместо того, чтобы паять кварцевый генератор на 14 МГц и ставить делитель, тратим полдня на чтение док-станции для микроконтроллеров и узнайте, что можно сделать жесткие 1,77(7) МГц, используя быстрый ШИМ :

pinMode(3, OUTPUT); TCCR2A = 0x23; TCCR2B = 0x09; OCR2A = 8; OCR2B = 3;

Далее установите сброс музыкального процессора на контакт 2, нижний полубайт шины данных на A0-A3, верхний полубайт на 4,5,6,7, BC1 на контакт 8, BDIR на контакт 9. Для простоты мы будем подключите аудиовыходы в моно режиме:

Плеер Arduino ZX Spectrum AY

По макету:

Плеер Arduino ZX Spectrum AY

Загрузите тестовый скетч (не помню откуда украл массив)

void resetAY(){ pinMode(A0, OUTPUT); // D0 pinMode(A1, OUTPUT); pinMode(A2, OUTPUT); pinMode(A3, OUTPUT); // D3 pinMode(4, OUTPUT); // D4 pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); // D7 pinMode(8, OUTPUT); // BC1 pinMode(9, OUTPUT); // BDIR digitalWrite(8,LOW); digitalWrite(9,LOW); pinMode(2, OUTPUT); digitalWrite(2, LOW); delay(100); digitalWrite(2, HIGH); delay(100); for (int i=0;i<16;i++) ay_out(i,0); } void setupAYclock(){ pinMode(3, OUTPUT); TCCR2A = 0x23; TCCR2B = 0x09; OCR2A = 8; OCR2B = 3; } void setup() { setupAYclock(); resetAY(); } void ay_out(unsigned char port, unsigned char data){ PORTB = PORTB & B11111100; PORTC = port & B00001111; PORTD = PORTD & B00001111; PORTB = PORTB | B00000011; delayMicroseconds(1); PORTB = PORTB & B11111100; PORTC = data & B00001111; PORTD = (PORTD & B00001111) | (data & B11110000); PORTB = PORTB | B00000010; delayMicroseconds(1); PORTB = PORTB & B11111100; } unsigned int cb = 0; byte rawData[] = { 0xFF, 0x00, 0x8E, 0x02, 0x38, 0x03, 0x02, 0x04, 0x0E, 0x05, 0x02, 0x07, 0x1A, 0x08, 0x0F, 0x09, 0x10, 0x0A, 0x0E, 0x0B, 0x47, 0x0D, 0x0E, 0xFF, 0x00, 0x77, 0x04, 0x8E, 0x05, 0x03, 0x07, 0x3A, 0x08, 0x0E, 0x0A, 0x0D, 0xFF, 0x00, 0x5E, 0x04, 0x0E, 0x05, 0x05, 0x0A, 0x0C, 0xFF, 0x04, 0x8E, 0x05, 0x06, 0x07, 0x32, 0x08, 0x00, 0x0A, 0x0A, 0xFF, 0x05, 0x08, 0x0A, 0x07, 0xFF, 0x04, 0x0E, 0x05, 0x0A, 0x0A, 0x04, 0xFF, 0x00, 0x8E, 0x04, 0x8E, 0x05, 0x00, 0x07, 0x1E, 0x08, 0x0F, 0x0A, 0x0B, 0x0D, 0x0E, 0xFF, 0x00, 0x77, 0x08, 0x0E, 0x0A, 0x06, 0xFF, 0x00, 0x5E, 0x07, 0x3E, 0x0A, 0x00, 0xFF, 0x07, 0x36, 0x08, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x8E, 0x07, 0x33, 0x08, 0x0B, 0x0A, 0x0F, 0x0D, 0x0E, 0xFF, 0x04, 0x77, 0x08, 0x06, 0x0A, 0x0E, 0xFF, 0x04, 0x5E, 0x07, 0x3B, 0x08, 0x00, 0xFF, 0x07, 0x1B, 0x0A, 0x00, 0xFF, 0xFF, 0xFF, 0x02, 0x1C, 0x03, 0x01, 0x04, 0x8E, 0x07, 0x33, 0x08, 0x0B, 0x0A, 0x0B, 0x0B, 0x23, 0x0D, 0x0E, 0xFF, 0x04, 0x77, 0x08, 0x06, 0x0A, 0x0A, 0xFF, 0x04, 0x5E, 0x07, 0x3B, 0x08, 0x00, 0x0A, 0x09, 0xFF, 0x07, 0x1B, 0x0A, 0x00, 0xFF, 0xFF, 0xFF, 0x02, 0x8E, 0x03, 0x00, 0x04, 0x0E, 0x05, 0x01, 0x07, 0x18, 0x08, 0x0F, 0x09, 0x0B, 0x0A, 0x0E, 0xFF, 0x00, 0x77, 0x02, 0x77, 0x04, 0x8E, 0x06, 0x01, 0x08, 0x0E, 0x09, 0x0A, 0x0A, 0x0D, 0xFF, 0x00, 0x5E, 0x02, 0x5E, 0x04, 0x0E, 0x05, 0x02, 0x06, 0x02, 0x09, 0x09, 0x0A, 0x0C, 0xFF, 0x02, 0x8E, 0x04, 0x8E, 0x07, 0x30, 0x08, 0x00, 0x09, 0x08, 0x0A, 0x0A, 0xFF, 0x02, 0x77, 0xFF, 0xFF } void pseudoInterrupt(){ while (rawData[cb]<0xFF) { ay_out(rawData[cb],rawData[cb+1]); cb++; cb++; } if (rawData[cb]==0xff) cb++; if (cb>20*12) cb=0; } void loop() { delay(20); pseudoInterrupt(); }

И мы слышим полсекунды какой-то чудесной мелодии! (на самом деле я тут еще два часа искал, как я забыл отпустить сброс после инициализации).

На этом аппаратная часть закончена, а в программном мы добавляем прерывания по 50 Гц, чтение и запись файлов в регистры сопроцессора.

Финальная версия программы

#include <SPI.h> #include <SD.h> void resetAY(){ pinMode(A0, OUTPUT); // D0 pinMode(A1, OUTPUT); pinMode(A2, OUTPUT); pinMode(A3, OUTPUT); // D3 pinMode(4, OUTPUT); // D4 pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); // D7 pinMode(8, OUTPUT); // BC1 pinMode(9, OUTPUT); // BDIR digitalWrite(8,LOW); digitalWrite(9,LOW); pinMode(2, OUTPUT); digitalWrite(2, LOW); delay(100); digitalWrite(2, HIGH); delay(100); for (int i=0;i<16;i++) ay_out(i,0); } void setupAYclock(){ pinMode(3, OUTPUT); TCCR2A = 0x23; TCCR2B = 0x09; OCR2A = 8; OCR2B = 3; } void setup() { Serial.begin(9600); randomSeed(analogRead(4)+analogRead(5)); initFile(); setupAYclock(); resetAY(); setupTimer(); } void setupTimer(){ cli(); TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = 1250; TCCR1B |= (1 << WGM12); TCCR1B |= (1 << CS12); TIMSK1 |= (1 << OCIE1A); sei(); } void ay_out(unsigned char port, unsigned char data){ PORTB = PORTB & B11111100; PORTC = port & B00001111; PORTD = PORTD & B00001111; PORTB = PORTB | B00000011; delayMicroseconds(1); PORTB = PORTB & B11111100; PORTC = data & B00001111; PORTD = (PORTD & B00001111) | (data & B11110000); PORTB = PORTB | B00000010; delayMicroseconds(1); PORTB = PORTB & B11111100; } unsigned int playPos = 0; unsigned int fillPos = 0; const int bufSize = 200; byte playBuf[bufSize]; // 31 bytes per frame max, 50*31 = 1550 per sec, 155 per 0.1 sec File fp; boolean playFinished = false; void loop() { fillBuffer(); if (playFinished){ fp.close(); openRandomFile(); playFinished = false; } } void fillBuffer(){ int fillSz = 0; int freeSz = bufSize; if (fillPos>playPos) { fillSz = fillPos-playPos; freeSz = bufSize - fillSz; } if (playPos>fillPos) { freeSz = playPos - fillPos; fillSz = bufSize - freeSz; } freeSz--; // do not reach playPos while (freeSz>0){ byte b = 0xFD; if (fp.available()){ b = fp.read(); } playBuf[fillPos] = b; fillPos++; if (fillPos==bufSize) fillPos=0; freeSz--; } } void prepareFile(char *fname){ Serial.print("prepare ["); Serial.print(fname); Serial.println("].

"); fp = SD.open(fname); if (!fp){ Serial.println("error opening music file"); return; } while (fp.available()) { byte b = fp.read(); if (b==0xFF) break; } fillPos = 0; playPos = 0; cli(); fillBuffer(); resetAY(); sei(); } File root; int fileCnt = 0; void openRandomFile(){ int sel = random(0,fileCnt-1); Serial.print("File selection = "); Serial.print(sel, DEC); Serial.println(); root.rewindDirectory(); int i = 0; while (true) { File entry = root.openNextFile(); if (!entry) break; Serial.print(entry.name()); if (!entry.isDirectory()) { Serial.print("\t\t"); Serial.println(entry.size(), DEC); if (i==sel) prepareFile(entry.name()); i++; } entry.close(); } } void initFile(){ Serial.print("Initializing SD card."); pinMode(10, OUTPUT); digitalWrite(10, HIGH); if (!SD.begin(10)) { Serial.println("initialization failed!"); return; } Serial.println("initialization done."); root = SD.open("/"); // reset AY fileCnt = countDirectory(root); Serial.print("Files cnt = "); Serial.print(fileCnt, DEC); Serial.println(); openRandomFile(); Serial.print("Buffer size = "); Serial.print(bufSize, DEC); Serial.println(); Serial.print("fillPos = "); Serial.print(fillPos, DEC); Serial.println(); Serial.print("playPos = "); Serial.print(playPos, DEC); Serial.println(); for (int i=0; i<bufSize;i++){ Serial.print(playBuf[i],HEX); Serial.print("-"); if (i==15) Serial.println(); } Serial.println("done!"); } int countDirectory(File dir) { int res = 0; root.rewindDirectory(); while (true) { File entry = dir.openNextFile(); if (!entry) break; Serial.print(entry.name()); if (!entry.isDirectory()) { Serial.print("\t\t"); Serial.println(entry.size(), DEC); res++; } entry.close(); } return res; } int skipCnt = 0; ISR(TIMER1_COMPA_vect){ if (skipCnt>0){ skipCnt--; } else { int fillSz = 0; int freeSz = bufSize; if (fillPos>playPos) { fillSz = fillPos-playPos; freeSz = bufSize - fillSz; } if (playPos>fillPos) { freeSz = playPos - fillPos; fillSz = bufSize - freeSz; } boolean ok = false; int p = playPos; while (fillSz>0){ byte b = playBuf[p]; p++; if (p==bufSize) p=0; fillSz--; if (b==0xFF){ ok = true; break; } if (b==0xFD){ ok = true; playFinished = true; for (int i=0;i<16;i++) ay_out(i,0); break; } if (b==0xFE){ if (fillSz>0){ skipCnt = playBuf[p]; p++; if (p==bufSize) p=0; fillSz--; skipCnt = 4*skipCnt; ok = true; break; } } if (b<=252){ if (fillSz>0){ byte v = playBuf[p]; p++; if (p==bufSize) p=0; fillSz--; if (b<16) ay_out(b,v); } } } // while (fillSz>0) if (ok){ playPos = p; } } // else skipCnt }

Для полной автономности я добавил еще усилитель на TDA2822M, сам плеер потребляет 90 мА, вместе с усилителем - около 200 мА, при желании его можно питать от батареек.



Плеер Arduino ZX Spectrum AY

Обе раскладки вместе:

Плеер Arduino ZX Spectrum AY

На этом этапе я пока остановился, слушаю музыку с макетки, думаю, в каком корпусе хотелось бы его собрать.

Думал подключить индикатор, но как-то не получается.

Я не чувствую необходимости.

Реализация пока сырая, т.к.

устройство находится в стадии разработки, а поскольку.

я могу оставить его на пару лет в таком состоянии, решил не откладывая написать статью.

Вопросы, предложения, замечания, исправления приветствуются в комментариях.

Использованная литература:

Теги: #Сделай сам или Сделай сам #Электроника для начинающих #arduino #spectrum #музыка #плеер #ay-3-8910
Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.