Автономный музыкальный проигрыватель от компьютера ZX Spectrum на Arduino с минимальным количеством деталей.
Это выглядит как Спектр рингтоны навсегда останется в моем сердце, так как я регулярно слушаю любимые композиции с помощью чудесного Лампа-плеер .
Но быть привязанным к компьютеру не очень удобно.
Я временно решил эту проблему с помощью не менее замечательного EEE PC. Но мне хотелось еще более миниатюрного.
Поиски в Интернете привели к следующим красавцам:
Они восхитительны своей элементной базой, вызывающей ностальгические воспоминания, но я знал, что моя лень не позволит мне завершить такой проект. Мне нужно было что-то маленькое.
И вот почти идеальный кандидат: AVR AY-плеер - воспроизводит файлы *.
PSG
- поддерживается файловая система FAT16
— количество каталогов в корне диска 32
— количество файлов в каталоге — 42 (всего 32*42=1344 файла)
— сортировка каталогов и файлов в каталогах по первым двум буквам названия
Схема выглядит вполне приемлемо по размерам:
Конечно я нашел это Роковая ошибка , что испортило идиллию: нет режима случайного выбора композиции.
(возможно, стоило просто попросить автора добавить в прошивку эту функцию?).
Два года я искал подходящий вариант, терпение кончилось и я решил действовать.
Исходя из своей фантастической лени, я выбрал минимальные телодвижения: 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 в качестве сигнала выбора карты:
Для проверки берем стандартный эскиз : эскиз списка файлов на карте
Форматируем карту, записываем туда несколько файлов, запускаем.#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. Для простоты мы будем подключите аудиовыходы в моно режиме:
По макету:
Загрузите тестовый скетч (не помню откуда украл массив) 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 мА, при желании его можно питать от батареек.
Обе раскладки вместе:
На этом этапе я пока остановился, слушаю музыку с макетки, думаю, в каком корпусе хотелось бы его собрать.
Думал подключить индикатор, но как-то не получается.
Я не чувствую необходимости.
Реализация пока сырая, т.к.
устройство находится в стадии разработки, а поскольку.
я могу оставить его на пару лет в таком состоянии, решил не откладывая написать статью.
Вопросы, предложения, замечания, исправления приветствуются в комментариях.
Использованная литература:
- Генерация тактовой частоты 1–4 МГц на Arduino
- Воспроизведение чиптюнов с помощью YM2149 и оптимизация Arduino
- Генератор звука YM2149, Arduino и быстрое переключение контактов
- Адаптер ZX Spectrum AY
- Старая школа, хардкор, AY-3-8912. «Железный» чиптюн с последовательным входом
- Звук на чипе AY-3-8910 (или Yamaha YM2149F) с ZX Spectrum на ПК через LPT порт
- Самодельный экран SD-карты для Arduino
- Программное обеспечение AY-плееров
- AY38910, управляемый Arduino – основные подключения
- Эмулятор AY на Arduino
- AY-плеер на AVR Atmega8 [ASM/Algorithm Builder]
-
Работа По Вводу Данных Онлайн — Преимущества
19 Oct, 24 -
Анатомия Linux-Тролля С Добрыми Намерениями
19 Oct, 24 -
Обои: «Когда Умрет Ie6...»
19 Oct, 24 -
Работа С Данными В Yaml
19 Oct, 24