Skip to content

Instantly share code, notes, and snippets.

@alx
Created October 4, 2011 13:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alx/1261590 to your computer and use it in GitHub Desktop.
Save alx/1261590 to your computer and use it in GitHub Desktop.
/*
=========================================================================================
_______..__ __. ______ ______ .___________. __ ___ .______
/ || \ | | / __ \ / __ \ | || | / \ | _ \
| (----`| \| | | | | | | | | | `---| |----`| | / ^ \ | |_) |
\ \ | . ` | | | | | | | | | | | | | / /_\ \ | _ <
.----) | | |\ | | `--' | | `--' | | | | `----./ _____ \ | |_) |
|_______/ |__| \__| \______/ \______/ |__| |_______/__/ \__\ |______/
=========================================================================================
================== SNOOTLAB FIRST HACKATHON : October 2011 ======================
MP3 Player, with LCD display and Joystick and SD Slot (for the music).
Recommended use : play it loud with some punk or ska music !
To avoid absolutely : use it in your bath...
WARNING : Done with Snootlab's products sell on their website and an MP3 shield rev1 from Sparkfun, also available on Snootlab
You have to use the SDfat library, and the Deuligne library,
respectively on http://code.google.com/p/sdfatlib/ and https://github.com/Snootlab/Deuligne
Shields used : Deuligne , Memoire, MP3 sparkfun
And an Arduino UNO.
Unplug all the board from the UNO, program it alone then plug the stack on the UNO and finally the power.
The file format MUST BE in "8.3" (ie: "snootlab.fun")
USE INSTRUCTIONS :
LEFT-RIGHT : Vol down/Up
DOWN : Scroll the files stored on the SD
UP : Play the selected file
====================== ALL MY APOLOGIES FOR THIS PIECE OF CODE ! NO TIME, NO COMMENTS, NO CLEAN CODING RULEZ, NO SLEEP...
====================== But Hey, let's say it's a draft ok ... ? :)
*/
#include <Wire.h> // I2C library include
#include <Deuligne.h> // LCD library include
/*
* Open all files in the root dir and print their filename
*/
#include <SdFat.h>
#include <SPI.h>
#define MP3_XCS 9 //Control Chip Select Pin (for accessing SPI Control/Status registers)
#define MP3_XDCS 2 //Data Chip Select / BSYNC Pin
#define MP3_DREQ 3 //Data Request Pin: Player asks for more data
#define TRUE 0
#define FALSE 1
//VS10xx SCI Registers
#define SCI_MODE 0x00
#define SCI_STATUS 0x01
#define SCI_BASS 0x02
#define SCI_CLOCKF 0x03
#define SCI_DECODE_TIME 0x04
#define SCI_AUDATA 0x05
#define SCI_WRAM 0x06
#define SCI_WRAMADDR 0x07
#define SCI_HDAT0 0x08
#define SCI_HDAT1 0x09
#define SCI_AIADDR 0x0A
#define SCI_VOL 0x0B
#define SCI_AICTRL0 0x0C
#define SCI_AICTRL1 0x0D
#define SCI_AICTRL2 0x0E
#define SCI_AICTRL3 0x0F
// SD chip select pin
const uint8_t chipSelect = 10;
// file system object
SdFat sd;
SdFile file;
Deuligne lcd; // lcd object declaration
//Key message
char msgs[5][15] = {
"Right Key OK ",
"Up Key OK ",
"Down Key OK ",
"Left Key OK ",
"Select Key OK" };
int key=-1;
int oldkey=-1;
char name[13];
unsigned char volume_ratio=40;
unsigned char volume[16];
void set_volume(void);
char errorMsg[100]; //This is a generic array used for sprintf of error messages
void setup()
{
Wire.begin(); // join i2c
lcd.init(); // LCD init
lcd.clear(); // Clear Display
lcd.backLight(true); // Backlight ON
lcd.setCursor(5,0); // Place cursor row 6, 1st line (counting from 0)
lcd.print("Setup");
lcd.setCursor(7,1); // Place cursor row 8, 2nd line (counting from 0)
lcd.print("ok");
delay(2000);
lcd.clear();
lcd.print("Move Joystick");
volume[0]='V';
volume[1]='o';
volume[2]='l';
volume[3]=':';
// init SD card
if (!sd.init(SPI_FULL_SPEED, chipSelect)) Serial.println("Error: SD ini");
//=============================
pinMode(MP3_DREQ, INPUT);
pinMode(MP3_XCS, OUTPUT);
pinMode(MP3_XDCS, OUTPUT);
Serial.begin(115200); //Use serial for debugging
Serial.println("MP3 Shield Example");
//Setup SPI for VS1053
pinMode(10, OUTPUT); //Pin 10 must be set as an output for the SPI communication to work
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
//From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz.
//Internal clock multiplier is 1.0x after power up.
//Therefore, max SPI speed is 1.75MHz. We will use 1MHz to be safe.
SPI.setClockDivider(SPI_CLOCK_DIV16); //Set SPI bus speed to 1MHz (16MHz / 16 = 1MHz)
SPI.transfer(0xFF); //Throw a dummy byte at the bus
//Initialize VS1053 chip
digitalWrite(MP3_XCS, HIGH); //Deselect Control
digitalWrite(MP3_XDCS, HIGH); //Deselect Data
Mp3SetVolume(20, 20); //Set initial volume (20 = -10dB)
//Let's check the status of the VS1053
int MP3Mode = Mp3ReadRegister(SCI_MODE);
int MP3Status = Mp3ReadRegister(SCI_STATUS);
int MP3Clock = Mp3ReadRegister(SCI_CLOCKF);
Serial.print("SCI_Mode (0x4800) = 0x");
Serial.println(MP3Mode, HEX);
//Serial.print("SCI_Status (0x48) = 0x");
//Serial.println(MP3Status, HEX);
int vsVersion = (MP3Status >> 4) & 0x000F; //Mask out only the four version bits
Serial.print("VS Version (VS1053 is 4) = ");
Serial.println(vsVersion, DEC); //The 1053B should respond with 4. VS1001 = 0, VS1011 = 1, VS1002 = 2, VS1003 = 3
//Now that we have the VS1053 up and running, increase the VS1053 internal clock multiplier and up our SPI rate
Mp3WriteRegister(SCI_CLOCKF, 0x60, 0x00); //Set multiplier to 3.0x
//From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz.
//Internal clock multiplier is now 3x.
//Therefore, max SPI speed is 5MHz. 4MHz will be safe.
SPI.setClockDivider(SPI_CLOCK_DIV4); //Set SPI bus speed to 4MHz (16MHz / 4 = 4MHz)
MP3Clock = Mp3ReadRegister(SCI_CLOCKF);
Serial.print("SCI_ClockF = 0x");
Serial.println(MP3Clock, HEX);
}
void loop() {
unsigned char indice=0;
unsigned char longueur=0;
key = lcd.get_key(); // read the value from the sensor & convert into key press
if (key != oldkey) // if keypress is detected
{
delay(50); // wait for debounce time
key = lcd.get_key(); // read the value from the sensor & convert into key press
if (key != oldkey)
{
oldkey = key;
if ( key==2)// touches == UP or DOWN
{
if (file.openNext(sd.cwd(), O_READ))
{
file.getFilename(name);
file.close();
} else { lcd.clear();
lcd.print("Back 2 beginning...");
delay(500);
sd.chdir();}
}
if(key==0)
{
//up volume
volume_ratio++;
set_volume();
}
if(key==3)
{
//up volume
volume_ratio--;
set_volume();
}
lcd.clear();
lcd.setCursor(0,0); //
lcd.print(name);
Serial.print(name);
for(indice=0;indice <=15;indice++)
{
lcd.setCursor(indice,1);
lcd.write(volume[indice]);
}
if(key==1)
{
lcd.clear();
lcd.setCursor(0,0); lcd.print(name);
lcd.setCursor(0,1); lcd.print("playing ...");
playMP3(name);
}
}
}
}
void set_volume(void)
{
unsigned char in=0;
if(volume_ratio >=12)volume_ratio=0;
for (in=4;in <=15;in++)
{
volume[in]=' ';
}
for (in=0;in <=volume_ratio;in++)
{
volume[in+4]='#';
Mp3SetVolume((60-(volume_ratio*3)), (60-(volume_ratio*3)));
}
}
//===================================================================
//Write to VS10xx register
//SCI: Data transfers are always 16bit. When a new SCI operation comes in
//DREQ goes low. We then have to wait for DREQ to go high again.
//XCS should be low for the full duration of operation.
void Mp3WriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte){
while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating IC is available
digitalWrite(MP3_XCS, LOW); //Select control
//SCI consists of instruction byte, address byte, and 16-bit data word.
SPI.transfer(0x02); //Write instruction
SPI.transfer(addressbyte);
SPI.transfer(highbyte);
SPI.transfer(lowbyte);
while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating command is complete
digitalWrite(MP3_XCS, HIGH); //Deselect Control
}
//Read the 16-bit value of a VS10xx register
unsigned int Mp3ReadRegister (unsigned char addressbyte){
while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating IC is available
digitalWrite(MP3_XCS, LOW); //Select control
//SCI consists of instruction byte, address byte, and 16-bit data word.
SPI.transfer(0x03); //Read instruction
SPI.transfer(addressbyte);
char response1 = SPI.transfer(0xFF); //Read the first byte
while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating command is complete
char response2 = SPI.transfer(0xFF); //Read the second byte
while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating command is complete
digitalWrite(MP3_XCS, HIGH); //Deselect Control
int resultvalue = response1 << 8;
resultvalue |= response2;
return resultvalue;
}
//Set VS10xx Volume Register
void Mp3SetVolume(unsigned char leftchannel, unsigned char rightchannel){
Mp3WriteRegister(SCI_VOL, leftchannel, rightchannel);
}
void playMP3(char* fileName) {
if (!file.open( fileName, O_READ)) { //Open the file in read mode.
sprintf(errorMsg, "Failed to open %s", fileName);
Serial.println(errorMsg);
return;
}
Serial.println("Track open");
uint8_t mp3DataBuffer[32]; //Buffer of 32 bytes. VS1053 can take 32 bytes at a go.
//track.read(mp3DataBuffer, sizeof(mp3DataBuffer)); //Read the first 32 bytes of the song
int need_data = TRUE;
long replenish_time = millis();
Serial.println("Start MP3 decoding");
while(1) {
while(!digitalRead(MP3_DREQ)) {
//DREQ is low while the receive buffer is full
//You can do something else here, the buffer of the MP3 is full and happy.
//Maybe set the volume or test to see how much we can delay before we hear audible glitches
//If the MP3 IC is happy, but we need to read new data from the SD, now is a great time to do so
if(need_data == TRUE) {
if(!file.read(mp3DataBuffer, sizeof(mp3DataBuffer))) { //Try reading 32 new bytes of the song
//Oh no! There is no data left to read!
//Time to exit
break;
}
need_data = FALSE;
}
//Serial.println("."); //Print a character to show we are doing nothing
//This is here to show how much time is spent transferring new bytes to the VS1053 buffer. Relies on replenish_time below.
Serial.print("Time to replenish buffer: ");
Serial.print(millis() - replenish_time, DEC);
Serial.print("ms");
//Test to see just how much we can do before the audio starts to glitch
long start_time = millis();
//delay(150); //Do NOTHING - audible glitches
//delay(135); //Do NOTHING - audible glitches
//delay(120); //Do NOTHING - barely audible glitches
delay(100); //Do NOTHING - sounds fine
Serial.print(" Idle time: ");
Serial.print(millis() - start_time, DEC);
Serial.println("ms");
//Look at that! We can actually do quite a lot without the audio glitching
//Now that we've completely emptied the VS1053 buffer (2048 bytes) let's see how much
//time the VS1053 keeps the DREQ line high, indicating it needs to be fed
replenish_time = millis();
}
if(need_data == TRUE){ //This is here in case we haven't had any free time to load new data
if(!file.read(mp3DataBuffer, sizeof(mp3DataBuffer))) { //Go out to SD card and try reading 32 new bytes of the song
//Oh no! There is no data left to read!
//Time to exit
break;
}
need_data = FALSE;
}
//Once DREQ is released (high) we now feed 32 bytes of data to the VS1053 from our SD read buffer
digitalWrite(MP3_XDCS, LOW); //Select Data
for(int y = 0 ; y < sizeof(mp3DataBuffer) ; y++) {
SPI.transfer(mp3DataBuffer[y]); // Send SPI byte
}
digitalWrite(MP3_XDCS, HIGH); //Deselect Data
need_data = TRUE; //We've just dumped 32 bytes into VS1053 so our SD read buffer is empty. Set flag so we go get more data
}
while(!digitalRead(MP3_DREQ)) ; //Wait for DREQ to go high indicating transfer is complete
digitalWrite(MP3_XDCS, HIGH); //Deselect Data
file.close(); //Close out this track
sprintf(errorMsg, "Track %s done!", fileName);
Serial.println(errorMsg);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment