Skip to content

Instantly share code, notes, and snippets.

@Airrr17
Forked from anteo/ay_player.ino
Last active July 11, 2023 17:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Airrr17/21b62fd00c8b57fac0e84ec9947bd462 to your computer and use it in GitHub Desktop.
Save Airrr17/21b62fd00c8b57fac0e84ec9947bd462 to your computer and use it in GitHub Desktop.
Arduino UNO + AY player + OLED
/////////////////////////////////////////////////////////////
//0.96" 128x32 i2c OLED. SDA=A4, SCL=A5
/////////////////////////////////////////////////////////////
#include <SPI.h>
#include "SdFat.h"
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
#define I2C_ADDRESS 0x3C
//#define RST_PIN -1
SSD1306AsciiAvrI2c oled;
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 int bufSize = 300;
const bool demoMode = false;
const int demoLen = 10000;
const int demoFadeLen = 500;
enum AYMode {INACTIVE, WRITE, LATCH};
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();
setupTimer();
init2MhzClock();
resetAY();
oled.begin(&Adafruit128x32, I2C_ADDRESS);
oled.setFont(Adafruit5x7);
oled.clear();
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 setupTimer() {
cli();
TCCR1A = 0;
TCCR1B = _BV(WGM12) | _BV(CS12);
TIMSK1 = _BV(OCIE1A);
TCNT1 = 0;
OCR1A = 1250;
sei();
}
void initSD() {
// Serial.print("ISD.");
if (!sd.begin(pinCS, SD_SCK_MHZ(50))) {
Serial.println("-");
sd.initErrorHalt();
}
// Serial.println("+");
loadDirectory();
// Serial.println("done!");
}
void loadRandomFile() {
SdFile file;
Serial.println("Srf.");
fileNum = random(0, filesCount); //-1
int n = fileNum;
dir.close();
if (!dir.open("/", O_READ)) {
sd.errorHalt("Orf");
}
while (file.openNext(&dir, O_READ)) {
if (checkFile(file)) {
if (n <= 0) {
playFile(file);
return;
}
n--;
}
file.close();
}
sd.errorHalt("Nmf.");
}
void playFile(SdFile entry) {
fp.close();
fp = entry;
// Serial.print("P");
// fp.printName(&Serial);
// Serial.println("...");
loadPos = playPos = totalPos = 0;
///////////////////////////Airrr///////////////////////////////////////////
oled.clear();
oled.print("File: ");
char buf[4];
sprintf (buf, "%03d", fileNum + 1);
oled.print(buf);
oled.print(" of ");
sprintf (buf, "%03d", filesCount);
oled.println(buf);
oled.print("Name: ");
char name[15];
fp.getName(name, 15);
oled.println(name);
oled.print("Size: ");
oled.print(fp.fileSize());
oled.println(" bytes");
////////////////////////////////////Airrr///////////////////////////////////////////
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)) {
filesCount++;
}
file.close();
}
}
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 displayOLED() {
if (playbackFinished) return;
//////////////////////////////////////////Airrr/////////////////////////////////////////////
oled.setCursor(0, 24);
oled.print(" ");
oled.setCursor(volumeC / 1.5, 24);
oled.print(">");
oled.setCursor((122 - volumeA / 1.5), 24);
oled.print("<");
oled.setCursor((62 + volumeB / 1.5), 24);
oled.print("]");
oled.setCursor((57 - volumeB / 1.5), 24);
oled.print("[");
}
///////////////////////////////////////////Airrr////////////////////////////////////////////
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();
displayOLED();
checkDemo();
}
@Airrr17
Copy link
Author

Airrr17 commented Jun 14, 2018

Edited to use with 128x32 oled instead of 16x2 LCD.
Showing selected file# of total files and filesize.

@romeok01
Copy link

Do you have a schematic ?

@Airrr17
Copy link
Author

Airrr17 commented Jun 20, 2021

as i remember everything is mentioned in the code:
pinClock = 3 = AY38910 clock
pinReset = 2 = AY38910 reset
pinBC1 = 8 = AY38910 BC1
pinBDIR = 9 =AY38910 BDIR

pinSHCP = 4 = 74HC595 clock
pinSTCP = 5 = 74HC595 latch
pinDS = 6 = 74HC595 data
74HC595: 8,13 = GND; 16, 10 = Vcc
74HC595: Qx to AY38910 DAx

pinSkip = 7 = button
pinCS = 10 = SD card select (CS)
SDmosi = 11, SDclk = 13, SDmiso = 12.

OLED: A4, A5.

@Airrr17
Copy link
Author

Airrr17 commented Jun 20, 2021

yes. this is it.
AY38910 A8, BC2 to Vcc.

@romeok01
Copy link

@Airrr17
Copy link
Author

Airrr17 commented Jul 11, 2023

The project moved to here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment