Skip to content

Instantly share code, notes, and snippets.

@anteo
Created February 19, 2018 08:06
Show Gist options
  • Save anteo/0e5d8867df7568a6523d54e19983d8e0 to your computer and use it in GitHub Desktop.
Save anteo/0e5d8867df7568a6523d54e19983d8e0 to your computer and use it in GitHub Desktop.
Arduino UNO + AY player
#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