This simple Arduino sketch is for playing YRG and RSF files from SD card root folder using AY-3-8910/12 YM2149F chips or emulator (in parallel mode) from this site. Sketch contains 2MHz generator for AY/YM chip.
(Updated 12.12.2015 updated RSF to v3)
Connections:
Arduino pins to AY/YM chip:
A0 – DA0, A1 – DA1, A2 – DA2, A3 – DA3, A4 – DA4, 5 – DA5, 6 – DA6, 7 – DA7
3 – BC1, 2 – BDIR
9 – frequency generator 2MHz
SD card:
MOSI – 11, MISO – 12, CLK(SCK) – 13, CS – 4
#include <SPI.h>
#include <SD.h>
const int CS_Pin = 4;
// PC0 PC1 PC2 PC3 PC4 PD5 PD6 PD7
const byte ad[8] = {A0, A1, A2, A3, A4, 5, 6, 7 }; // connect to DA0,1,...,7
const int pinBC1 = 3;
const int pinBDIR = 2;
const byte freqOutputPin = 9; // OCR1A output pin for ATmega328, 2MHz output frequency for AY/YM chip
//Port at which pins BC1/BDIR is
#define __BCPORT__ PORTD
#define __BC1__ 3 // PORT PIN (PD3)
#define __BDIR__ 2 // PORT PIN (PD2)
void initFrequencyGenerator()
{ // Set Timer 1 CTC mode OCR1A toggles on compare match
TCCR1A = 0x40;
TCCR1B = 0x09; // prescaller
// This value determines the output frequency: 0 - 16MHz, 1 - 8MHz, 2 - 4MHz, 3 - 2MHz, 4 - 1MHz
OCR1A = 3;
}
File dataFile;
File entry,root;
void setup()
{
//init pins
for(byte i=0; i < 8; i++) pinMode(ad[i], OUTPUT);
pinMode(CS_Pin, OUTPUT);
pinMode(pinBC1, OUTPUT);
pinMode(pinBDIR, OUTPUT);
//inactive mode
digitalWrite(pinBC1, LOW);
digitalWrite(pinBDIR, LOW);
Serial.begin(57600);
// see if the card is present and can be initialized:
if (!SD.begin(CS_Pin))
{
Serial.println("Card failed, or not present");
return;
}
Serial.println("card initialized.");
root = SD.open("/",FILE_READ);
initFrequencyGenerator();
pinMode(freqOutputPin, OUTPUT);
}
void send_data(byte address, byte data) {
// WRITE REGISTER NUMBER
//write address to DA0-DA7 pins
PORTC |= address & 0x1F; // DA0-DA4
PORTD |= address & 0xE0; // DA5-DA7
//validate addess
//set BC1+BDIR bits, latch address mode
__BCPORT__ |= (1 << __BDIR__) + (1 << __BC1__);
asm("nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop"); //set+hold address delay 558ns (400+100 min)
//clear BC1+BDIR bits, inactive mode
__BCPORT__ &= ~((1 << __BDIR__) + (1 << __BC1__));
// reset pins to tristate mode
PORTC &= ~(address & 0x1F);
PORTD &= ~(address & 0xE0);
// WRITE REGISTER DATA
//write data to pins
PORTC |= data & 0x1F;
PORTD |= data & 0xE0;
//validate data
//set BDIR bit, write to reg mode
__BCPORT__ |= ( 1 << __BDIR__);
asm("nop\nnop\nnop\nnop\nnop"); //310ns delay (250min-500max) nop=62ns on 16MHz
//clear BDIR bit, inactive mode
__BCPORT__ &= ~( 1 << __BDIR__);
// reset pins to tristate mode
PORTC &= ~(data & 0x1F);
PORTD &= ~(data & 0xE0);
}
byte buf[128];
byte buf2[14];
unsigned long t;
void play_yrg()
{
while(dataFile.available())
{
// read 16 x16 regs
byte bframes = dataFile.read(buf,128)/16;
for (byte frame = 0; frame < bframes; frame++)
{
// send diff registers from current frame
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]) send_data(reg, buf[frame*16+reg]);
}
memcpy(buf2,&buf[frame*16],14);
//delay(20);
while(millis() - t < 20);
t = millis();
}
}
}
void play_rsf()
{
//unsigned long frames, loopframe, offset;
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,128)) <= 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;
}
skip = 1;
val = buf[ptr++];
switch(val)
{
case 255:
break;
case 254:
skip = buf[ptr++];
if(ptr >= count) return;
break;
default:
mask2 = val;
mask1 = buf[ptr++];
byte reg = 0;
while( mask1 != 0)
{
if(mask1 & 1) send_data(reg,buf[ptr++]);
mask1 >>= 1;
reg++;
}
reg = 8;
while( mask2 != 0)
{
if(mask2 & 1) send_data(reg,buf[ptr++]);
mask2 >>= 1;
reg++;
}
}
//delay(delay_time);
while(millis() - t < delay_time * skip);
t = millis();
}
}
void loop()
{
byte file_type;
// reset AY
for(int i=0;i<14;i++) send_data(i, 0);
memset(buf2,0,14);
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);
Serial.print("File: ");
Serial.println(entry.name());
t = millis();
switch(file_type)
{
case 1:
play_yrg();
break;
case 2:
play_rsf();
break;
}
dataFile.close();
entry.close();
}
[ad name=”HTML”]