Данный простой Arduino скетч позволяет проигрывать YRG и RSF файлы из корня SD карты на эмуляторе с данного сайта в последовательном режиме. (Обновлен 12.12.2015 переход на RSFv3)
Подключение:
Пины на Arduino:
1 (TX pin) -> RX pin на Atmega8
SD карта:
MOSI — 11, MISO — 12, CLK(SCK) — 13, CS — 4
#include <SPI.h> #include <SD.h> const int CS_Pin = 4; File dataFile; File entry,root; void setup() { pinMode(CS_Pin, OUTPUT); // see if the card is present and can be initialized: if (!SD.begin(CS_Pin)) return; root = SD.open("/",FILE_READ); Serial.begin(57600); } #define BUF_MAX 16*15 static byte buf[BUF_MAX]; static byte buf2[29]; unsigned long t; /// PLAY YRG FILE ============================== void play_yrg() { byte regbuf[28]; byte bframes; while(dataFile.available()) { // read 16 x16 regs bframes = dataFile.read(buf,BUF_MAX)/16; for (byte frame = 0; frame < bframes; frame++) { // send diff registers from current frame byte buf_p = 0; for (byte reg = 0; reg < 14; reg++) { if (reg == 13 && buf[frame*16+reg]==255) break; if(reg == 13 || buf2[reg] != buf[frame*16+reg]) { regbuf[buf_p++] = reg; regbuf[buf_p++] = buf[frame*16+reg]; } } Serial.write(regbuf,buf_p); memcpy(buf2,&buf[frame*16],14); //delay(20); while(millis() - t < 20); t = millis(); } } } /// PLAY RSF FILE ============================== void play_rsf() { //unsigned long frames, loopframe,curframe=0; word freq, offset; byte val, zeroes, ptr = 0, count, mask2, mask1, delay_time, skip; if( dataFile.read(buf,4) <= 0 ) return; // short file if(buf[0] != 'R' || buf[1] != 'S' || buf[2] != 'F') return; //not RSF switch(buf[3]) { // reading RSF HEADER v3 only supported! case 3: // RSF ver.3 if( dataFile.read(buf,14) == 0 ) return; // short file memcpy(&freq,&buf[0],sizeof(word)); memcpy(&offset,&buf[2],sizeof(word)); //memcpy(&frames,&buf[4],sizeof(unsigned long)); //memcpy(&loopframe,&buf[8],sizeof(unsigned long)); break; default: return; } if( freq > 1000 ) return; // frequency is too fast delay_time = 1000 / freq; // skip text info dataFile.seek(offset); // play song data if( (count = dataFile.read(buf,BUF_MAX)) <= 0 ) return; for(;;) { if(ptr > count>>1) { // half buffer is already played move it and load more byte msize = count - ptr; memmove(buf,&buf[ptr], msize); if( (count = dataFile.read(&buf[msize],ptr)) <= 0 ) return; count += msize; ptr = 0; } val = buf[ptr++]; skip = 1; switch(val) { case 255: break; case 254: skip = buf[ptr++]; if(ptr >= count) return; break; default: mask2 = val; mask1 = buf[ptr++]; byte reg = 0, reg_p = 0; while( mask1 != 0) { if(mask1 & 1) { buf2[reg_p++] = reg; buf2[reg_p++] = buf[ptr++]; } mask1 >>= 1; reg++; } reg = 8; while(mask2 != 0) { if(mask2 & 1) { buf2[reg_p++] = reg; buf2[reg_p++] = buf[ptr++]; } mask2 >>= 1; reg++; } Serial.write(buf2,reg_p); } //curframe += skip; while(millis() - t < delay_time * skip); // delay t = millis(); } } void loop() { byte file_type; // reset AY memset(buf2,0,29); buf2[0] = 255; for(byte i = 0; i < 14; i++) buf2[i*2+1]=i; Serial.write(buf2,29); memset(buf2,0,29); for(;;) { // find file file_type = 0; if(entry) entry.close(); entry = root.openNextFile(); if(!entry) { // end of files on SD root.rewindDirectory(); entry = root.openNextFile(); } if(entry.isDirectory()) continue; if(strcasestr(entry.name(),".yrg")) { file_type = 1; break; } if(strcasestr(entry.name(),".rsf")) { file_type = 2; break; } } dataFile = SD.open(entry.name(),FILE_READ); t = millis(); switch(file_type) { case 1: play_yrg(); break; case 2: play_rsf(); break; } dataFile.close(); entry.close(); }
Оптимизированный скетч, использующий библиотеку SdFat
#include <SdFat.h> #undef USE_ARDUINO_SPI_LIBRARY #define USE_ARDUINO_SPI_LIBRARY 0 #define CS_Pin 4 #define BAUD_RATE 57600 #define BAUD_PRESCALE (((F_CPU/(BAUD_RATE*16UL)))-1) SdFat SD; SdFile dataFile; void serialwrite(byte* buf,byte bsize) { for(byte i=0 ; i < bsize; i++) { while ((UCSR0A & (1 << UDRE0)) == 0) {}; // Do nothing until UDR is ready for more data to be written to it UDR0 = buf[i]; // Send out the byte value in the variable "ByteToSend" } } void setup() { if (!SD.begin(CS_Pin, SPI_FULL_SPEED)) return;//SD.initErrorHalt(); // initialize serial interface UCSR0B |= (1<<RXEN0) | (1<<TXEN0); UCSR0C |= (1<<UCSZ00) | (1<<UCSZ01); UBRR0H = BAUD_PRESCALE >> 8; UBRR0L = BAUD_PRESCALE; } #define BUF_MAX 16*5 static byte buf[BUF_MAX]; static byte buf2[29]; static unsigned long t; /// PLAY YRG FILE ============================== void play_yrg() { byte regbuf[28]; byte bframes; while((bframes = dataFile.read(buf,BUF_MAX)/16) > 0) { for (byte frame = 0; frame < bframes; frame++) { // send diff registers from current frame byte buf_p = 0; for (byte reg = 0; reg < 14; reg++) { if (reg == 13 && buf[frame*16+reg]==255) break; if(reg == 13 || buf2[reg] != buf[frame*16+reg]) { regbuf[buf_p++] = reg; regbuf[buf_p++] = buf[frame*16+reg]; } } serialwrite(regbuf,buf_p); memcpy(buf2,&buf[frame*16],14); while(millis() - t < 20); //delay(20); t = millis(); } } } /// PLAY RSF FILE ============================== void play_rsf() { //unsigned long frames, loopframe,curframe=0; word freq, offset; byte val, ptr = 0, count, mask2, mask1, delay_time, skip; if( dataFile.read(buf,4) <= 0 ) return; // short file if(buf[0] != 'R' || buf[1] != 'S' || buf[2] != 'F') return; //not RSF switch(buf[3]) { // reading RSF HEADER v3 only supported! case 3: // RSF ver.3 if( dataFile.read(buf,14) == 0 ) return; // short file memcpy(&freq,&buf[0],sizeof(word)); memcpy(&offset,&buf[2],sizeof(word)); //memcpy(&frames,&buf[4],sizeof(unsigned long)); //memcpy(&loopframe,&buf[8],sizeof(unsigned long)); break; default: return; } if( freq > 1000 ) return; // frequency is too fast delay_time = 1000 / freq; // skip text info dataFile.seekSet(offset); // play song data if( (count = dataFile.read(buf,BUF_MAX)) <= 0 ) return; for(;;) { if(ptr > count>>1) { // half buffer is already played move it and load more byte msize = count - ptr; memmove(buf,&buf[ptr], msize); if( (count = dataFile.read(&buf[msize],ptr)) <= 0 ) return; count += msize; ptr = 0; } val = buf[ptr++]; skip = 1; switch(val) { case 255: break; case 254: skip = buf[ptr++]; break; default: mask2 = val; mask1 = buf[ptr++]; byte reg = 0, reg_p = 0; while( mask1 != 0) { if(mask1 & 1) { buf2[reg_p++] = reg; buf2[reg_p++] = buf[ptr++]; } mask1 >>= 1; reg++; } reg = 8; while(mask2 != 0) { if(mask2 & 1) { buf2[reg_p++] = reg; buf2[reg_p++] = buf[ptr++]; } mask2 >>= 1; reg++; } serialwrite(buf2,reg_p); } //curframe += skip; while(millis() - t < delay_time * skip); // delay t = millis(); } } ///===================================================== void loop() { byte file_type; char fname[12]; // using short filenames // reset AY memset(buf2,0,29); buf2[0] = 255; for(byte i = 0; i < 14; i++) buf2[i*2+1]=i; serialwrite(buf2,29); memset(buf2,0,29); for(;;) { // find file file_type = 0; if(!dataFile.openNext(SD.vwd(), O_READ)) dataFile.openRoot(dataFile.volume()); // end of files on SD if(dataFile.isDir()) { dataFile.close(); continue; } dataFile.getFilename(fname); if(strcasestr(fname,".yrg")) { file_type = 1; break; } if(strcasestr(fname,".rsf")) { file_type = 2; break; } } t = millis(); switch(file_type) { case 1: play_yrg(); break; case 2: play_rsf(); break; } dataFile.close(); }
[ad name=»HTML»]
Hello, it looks interesting, but do you have some schematics to understand better how everything is connected (arduino, atmega chip, sd card…)?
Ok, added, but it is written in a sketch
Hi,
It seems I need some more help.
All I get is some «click click click click» or a machine-like noise. With rsf I get click (2 / second), with yrg it’s faster and sounds like a machine working.
I’ve got a pair of Atmega8, I managed to flash them with this command:
avrdude -p atmega8 -c USBasp -U flash:w:AY_Emul_244_2ch_speaker.hex -U eeprom:w:Conf_serial_25MHz_1_75Mhz.hex -U lfuse:w:0xCE:m -U hfuse:w:0xCF:m
then it seems it flashed correctly, I got at the end:
/…/
avrdude: verifying …
avrdude: 1 bytes of hfuse verified
avrdude: safemode: Fuses OK (H:FF, E:CF, L:CE)
avrdude done. Thank you.
I’m using a 25.000H4J crystal, and I’ve double checked the connexions (like on your sketch, I only connected vcc and ground on the sd card reader because I supposed it was necessary to make it work. I’ve only used the sd.h version, not the optimized one. I’ve downloaded the sample songs pack in rsf format from your website (http://www.avray.ru/music_collections/, the last one with 7 songs). I’ve put the tracks at the root of a 1 gb sd card (which i’ve already used for some arduino tests with this sd card reader, it’s in fat16 format).
When I power the arduino (nano), I get the click click / noise sound and the Tx led is synchronised to the sound (it blinks quite fast)
I’ve tried to compile the other sketch with the sdfat lib from greiman’s github, but it’s doesn’t even compile, maybe the version used is another one?
Any idea why I don’t get music?
Hi, if TX pin led flases fast, it look like serial data comes ok. But, you use SPEAKER version, which is for ZX-Spectrum speaker port. Also, I’ve not understand which emulator pin you connected to Arduino, it should be RX pin on emulator and TX pin on Arduino. Not all Atmega8 can work with 25MHz crystall, try 24MHz and 20MHz crystalls, may be them will work, if not, double check all connections.
Check also that config for emulator in same folder.
Thank you, it’s working fine now.
I made a couple of mistake, first my SD card reader was underpowered (3V instead of 5V), then I had only connected VCC + GND on the atmega8, there is also AVCC + the other GND which I’ve forgotten. It seems the arduino can power both the atmega chip and the SD card with its 5V output, I don’t know if it drains too much current though.
I don’t know either if AREF is needed, some sketches show it connected to AVCC as well, but it works without so far.
Now I need to make and wire a proper board for the AY player, fill the SD cards with nice chiptunes, and it will be awesome! Thanks again for the help, and for the whole AVR-AY project.
Thanks! And congratulations!
You need to connect only VCC and GND, no AVCC & AREF needed, also you may use one of two GND by your choice (which is convenient to use in a circuit). And yes, arduino can drive emulator as it use not more than 40mA
I’ve built a complete emulator on prototype board. It’s quite compact. The atmega8 is under the arduino chip, and the sd card reader under the board. It sounds great. Here is a video of it in action:
https://www.youtube.com/watch?v=gzpGbokxwZ4
Next step: making a PCB!
I was trying to record some music from the emulator, and found the output was quite harsh because the separation of the channel A and C is total. I found a simple way to soften it a bit by adding a 680 ohm resistor (or similar) between pins PB1 and PB2. It should also be possible to add a potentiometer to make it even more customizable!
oooooh, thank you very much, it’s perfect this way!! It is very useful. I’ve already ordered my atmega8, it will help me much for assembling everything.
Доброго дня! Собрал Ваш эмулятор на atmege 328 и 25-ом кварце (на 27-ом слышны артефакты). Подключил к Arduino Nano с залитым скетчем «Параллельная загрузка с генератором» (на скетче «Параллельная загрузка» — тишина). Туда же подключил Arduino Mini c этим плеером (второй плеер на комилируется dataFile.getFilename(fname); ошибка exit status ‘class SdFile’ has no member named ‘getFilename’; did you mean ‘getName’?) Две ардуинки для удобства. Да и Mini запитана от Nano, и с нее же идет три вольта на SD (SD карта подключена на прямую без резисторов). Хочешь — с компа либо оффлайн с SD.
Звучит абалденно. Атмега, видимо и не догадывалась на что способна ))).
Можно Вас попросить прикрутить к первому плееру две кнопки — «предыдущая песня» и «следующая песня». И что бы при включении песни играли рандомно. Т.е. не начиналось с первой песни на SD карте, далее по порядку и по кругу.
Эти скетчи приведены для примера, остальное можно реализовать самостоятельно 🙂
Спасибо за хорошие отзывы!
Как можно с Вами связаца?
На zx-pk или в группе ВК
Hi
thanks a million for this brilliant emulator. I really love it. I built Erics Garvuino and am trying to play rsf files from sd card. Most sound great, but on some there is some strange pattern at the start. The RSF files play normal in your AYPlayer.
Here is one I really like that has strange parts at the start.
https://www.file-upload.net/download-14220211/Deflektor.rsf.html
Thamks! Which one sketch are you using? From this site, first or second? Or it is form Garvuino repository? I ask, because they have some differences.
Hi
I tried them all. I believe the first one and the one from the Garvuino repository are pretty much the same. The second one I could not get to work. I did modify this line
dataFile.getFilename(fname);
to
dataFile.getName(fname,12);
as it would not compile otherwise. But it does not appear to play anything.