Created
February 19, 2018 08:06
-
-
Save anteo/0e5d8867df7568a6523d54e19983d8e0 to your computer and use it in GitHub Desktop.
Arduino UNO + AY player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <SPI.h> | |
#include "SdFat.h" | |
#include <LiquidCrystal.h> | |
const byte pinClock = 3; // AY38910 clock | |
const byte pinReset = 2; // AY38910 reset | |
const byte pinBC1 = 8; // AY38910 BC1 | |
const byte pinBDIR = 9; // AY38910 BDIR | |
const byte pinSHCP = 4; // 74HC595 clock | |
const byte pinSTCP = 5; // 74HC595 latch | |
const byte pinDS = 6; // 74HC595 data | |
const byte pinSkip = 7; // skip to next random song | |
const byte pinCS = 10; // SD card select (CS) | |
const char *supportedFileExt = ".psg"; | |
const byte fileExtLen = strlen(supportedFileExt); | |
const byte screenWidth = 16; | |
const int bufSize = 300; | |
const bool demoMode = false; | |
const int demoLen = 10000; | |
const int demoFadeLen = 500; | |
enum AYMode {INACTIVE, WRITE, LATCH}; | |
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5); // (RS, E, DB4, DB5, DB6, DB7) | |
SdFat sd; | |
SdFile fp; | |
SdFile dir; | |
byte volumeA; | |
byte volumeB; | |
byte volumeC; | |
byte playBuf[bufSize]; | |
bool playbackFinished = true; | |
bool playbackSkip; | |
int filesCount; | |
int fileNum; | |
int loadPos; | |
int playPos; | |
int skipCnt; | |
int totalPos; | |
float globalVolume; | |
void setup() { | |
Serial.begin(9600); | |
setupPins(); | |
generateSeed(); | |
setupLCD(); | |
setupTimer(); | |
init2MhzClock(); | |
resetAY(); | |
displayLoading(); | |
initSD(); | |
loadRandomFile(); | |
} | |
void setupPins() { | |
pinMode(pinBC1, OUTPUT); | |
pinMode(pinBDIR, OUTPUT); | |
pinMode(pinReset, OUTPUT); | |
pinMode(pinClock, OUTPUT); | |
pinMode(pinSHCP, OUTPUT); | |
pinMode(pinSTCP, OUTPUT); | |
pinMode(pinDS, OUTPUT); | |
pinMode(pinSkip, INPUT); | |
} | |
void generateSeed() { | |
unsigned long seed = seedOut(31); | |
Serial.print("Seed = "); | |
Serial.println(seed); | |
randomSeed(seed); | |
} | |
unsigned int bitOut(void) { | |
static unsigned long firstTime = 1, prev = 0; | |
unsigned long bit1 = 0, bit0 = 0, x = 0, port = 6, limit = 99; | |
if (firstTime) | |
{ | |
firstTime = 0; | |
prev = analogRead(port); | |
} | |
while (limit--) | |
{ | |
x = analogRead(port); | |
bit1 = (prev != x ? 1 : 0); | |
prev = x; | |
x = analogRead(port); | |
bit0 = (prev != x ? 1 : 0); | |
prev = x; | |
if (bit1 != bit0) | |
break; | |
} | |
return bit1; | |
} | |
unsigned long seedOut(unsigned int noOfBits) { | |
// return value with 'noOfBits' random bits set | |
unsigned long seed = 0; | |
for (int i = 0; i < noOfBits; ++i) | |
seed = (seed << 1) | bitOut(); | |
return seed; | |
} | |
void loop() { | |
loadNextByte(); | |
if (playbackFinished) { | |
resetAY(); | |
loadRandomFile(); | |
} | |
} | |
void setupLCD() { | |
lcd.begin(screenWidth, 2); | |
for (int i = 0; i < 8; i++) { | |
byte sym[8]; | |
for (int j = 0; j < 8; j++) { | |
sym[j] = j >= 7 - i ? 0xFF : 0x00; | |
} | |
lcd.createChar(i, sym); | |
} | |
}; | |
void setupTimer() { | |
cli(); | |
TCCR1A = 0; | |
TCCR1B = _BV(WGM12) | _BV(CS12); | |
TIMSK1 = _BV(OCIE1A); | |
TCNT1 = 0; | |
OCR1A = 1250; | |
sei(); | |
} | |
void initSD() { | |
Serial.print("Initializing SD card..."); | |
if (!sd.begin(pinCS, SD_SCK_MHZ(50))) { | |
Serial.println("initialization failed!"); | |
sd.initErrorHalt(); | |
} | |
Serial.println("initialization done."); | |
loadDirectory(); | |
Serial.println("done!"); | |
} | |
void loadRandomFile() { | |
SdFile file; | |
Serial.println("Seeking for random file..."); | |
fileNum = random(0, filesCount - 1); | |
int n = fileNum; | |
dir.close(); | |
if (!dir.open("/", O_READ)) { | |
sd.errorHalt("Open root failed"); | |
} | |
while (file.openNext(&dir, O_READ)) { | |
if (checkFile(file)) { | |
if (n <= 0) { | |
playFile(file); | |
return; | |
} | |
n--; | |
} | |
file.close(); | |
} | |
sd.errorHalt("No more files."); | |
} | |
void displayLoading() { | |
lcd.clear(); | |
lcd.setCursor(3, 0); | |
lcd.print("Loading..."); | |
} | |
void displayFilename() { | |
const int size = screenWidth + fileExtLen; | |
char name[size + 1]; | |
fp.getName(name, size); | |
name[strlen(name) - fileExtLen] = 0; | |
lcd.clear(); | |
lcd.setCursor(0, 0); | |
lcd.print(name); | |
lcd.setCursor(0, 1); | |
sprintf(name, "%03d", fileNum); | |
lcd.print(name); | |
}; | |
void playFile(SdFile entry) { | |
fp.close(); | |
fp = entry; | |
Serial.print("Playing "); | |
fp.printName(&Serial); | |
Serial.println("..."); | |
loadPos = playPos = totalPos = 0; | |
displayFilename(); | |
// ?? | |
while (fp.available()) { | |
byte b = fp.read(); | |
if (b == 0xFF) break; | |
} | |
playbackFinished = false; | |
} | |
bool checkFile(SdFile entry) { | |
char name[256]; | |
entry.getName(name, 255); | |
return !entry.isHidden() && !entry.isDir() && (strlen(name) > fileExtLen && | |
!strcasecmp(name + strlen(name) - fileExtLen, supportedFileExt)); | |
} | |
void loadDirectory() { | |
SdFile file; | |
if (!dir.open("/", O_READ)) { | |
sd.errorHalt("Open root failed"); | |
} | |
while (file.openNext(&dir, O_READ)) { | |
if (checkFile(file)) { | |
file.printName(&Serial); | |
Serial.print("\t"); | |
Serial.print(file.fileSize(), DEC); | |
Serial.println(); | |
filesCount++; | |
} | |
file.close(); | |
} | |
Serial.print("Total files: "); | |
Serial.println(filesCount); | |
} | |
void resetAY() { | |
setAYMode(INACTIVE); | |
volumeA = volumeB = volumeC = 0; | |
globalVolume = 1; | |
digitalWrite(pinReset, LOW); | |
delay(50); | |
digitalWrite(pinReset, HIGH); | |
delay(50); | |
} | |
void init2MhzClock() { | |
const int PERIOD = 9; // 9 CPU cycles ~ 1.778 MHz | |
TCCR2B = 0; // stop timer | |
TCNT2 = 0; // reset timer | |
TCCR2A = _BV(COM2B1) // non-inverting PWM on OC2B | |
| _BV(WGM20) // fast PWM mode, TOP = OCR2A | |
| _BV(WGM21); // ...ditto | |
TCCR2B = _BV(WGM22) | _BV(CS20); // ...ditto | |
OCR2A = PERIOD - 1; | |
OCR2B = PERIOD / 2 - 1; | |
} | |
void setAYMode(AYMode mode) { | |
switch (mode) { | |
case INACTIVE: | |
PORTB &= B11111100; | |
break; | |
case WRITE: | |
PORTB |= B00000010; | |
break; | |
case LATCH: | |
PORTB |= B00000011; | |
break; | |
} | |
} | |
void setVolume(float volume) { | |
globalVolume = volume; | |
writeAY(8, volumeA); | |
writeAY(9, volumeB); | |
writeAY(10, volumeC); | |
} | |
void writeAY(byte port, byte data) { | |
if (port == 8 || port == 9 || port == 10) { | |
if (port == 8) volumeA = data; | |
if (port == 9) volumeB = data; | |
if (port == 10) volumeC = data; | |
data = (byte)(data * globalVolume); | |
} | |
setAYMode(INACTIVE); | |
digitalWrite(pinSTCP, LOW); | |
shiftOut(pinDS, pinSHCP, MSBFIRST, port); | |
digitalWrite(pinSTCP, HIGH); | |
setAYMode(LATCH); | |
setAYMode(INACTIVE); | |
digitalWrite(pinSTCP, LOW); | |
shiftOut(pinDS, pinSHCP, MSBFIRST, data); | |
digitalWrite(pinSTCP, HIGH); | |
setAYMode(WRITE); | |
setAYMode(INACTIVE); | |
} | |
bool loadNextByte() { | |
if (loadPos == playPos - 1 || loadPos == bufSize - 1 && playPos == 0) | |
return false; | |
byte b = fp.available() ? fp.read() : 0xFD; | |
playBuf[loadPos++] = b; | |
if (loadPos == bufSize) loadPos = 0; | |
return true; | |
} | |
bool isNextByteAvailable() { | |
return playPos != loadPos; | |
} | |
byte getNextByte() { | |
if (!isNextByteAvailable()) return 0; | |
byte b = playBuf[playPos++]; | |
if (playPos == bufSize) playPos = 0; | |
totalPos++; | |
return b; | |
} | |
void printVolumeChar(byte col, byte row, int volume) { | |
lcd.setCursor(col, row); | |
if (volume == 0) | |
lcd.print(' '); | |
else | |
lcd.print(char(min(volume - 1, 7))); | |
} | |
void displayVolume(byte col, int volume) { | |
printVolumeChar(col, 0, max(volume - 8, 0)); | |
printVolumeChar(col, 1, volume); | |
} | |
void displayLCD() { | |
if (playbackFinished) return; | |
displayVolume(13, volumeA & 0x0F); | |
displayVolume(14, volumeB & 0x0F); | |
displayVolume(15, volumeC & 0x0F); | |
} | |
void checkDemo() { | |
if (demoMode && totalPos >= demoLen && !playbackFinished) { | |
int demoPos = totalPos - demoLen; | |
setVolume(1.0 - demoPos/(float)demoFadeLen); | |
if (demoPos >= demoFadeLen) playbackFinished = true; | |
} | |
} | |
void playNotes() { | |
if (digitalRead(pinSkip) == LOW) | |
playbackSkip = true; | |
else if (playbackSkip) { | |
playbackSkip = false; | |
playbackFinished = true; | |
} | |
if (playbackFinished || --skipCnt > 0) | |
return; | |
int oldPlayPos = playPos; | |
int oldTotalPos = totalPos; | |
while (isNextByteAvailable()) { | |
byte b = getNextByte(); | |
if (b == 0xFF) { | |
break; | |
} else if (b == 0xFD) { | |
playbackFinished = true; | |
break; | |
} else if (b == 0xFE) { | |
if (isNextByteAvailable()) { | |
skipCnt = getNextByte(); | |
skipCnt *= 4; | |
break; | |
} | |
} else if (b <= 0xFC) { | |
if (isNextByteAvailable()) { | |
byte v = getNextByte(); | |
if (b < 16) writeAY(b, v); | |
} | |
} | |
} | |
if (!isNextByteAvailable()) { | |
playPos = oldPlayPos; | |
totalPos = oldTotalPos; | |
} | |
} | |
ISR(TIMER1_COMPA_vect) { | |
playNotes(); | |
displayLCD(); | |
checkDemo(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment