final project ee47


Design point of view (what are you designing a player for?)

This device is designed for people that don’t want to waste time in learning how to use a device (or don’t have the ability to learn, like somebody drunk), to play the music they want.

 

Verplank diagram

Photos of paper prototype

 

State diagram

Project code

/*

* Edited versión by Javier Ernesto Flores Robles, jeflro@gmail.com

* Last edit August 18, 2012

* originally based on 2011 david sirkin sirkin@cdr.stanford.edu work

* based on frank zhao's player: http://frank.circleofcurrent.com/

* utilities adapted from previous versions of the functions by matthew seal.

*

*/

 

// first step is to include (arduino) sd, eeprom, and (our own) mp3 and lcd libraries.

 

#include <SD.h>

#include <EEPROM.h>

 

#include <mp3.h>

#include <mp3conf.h>

 

//#define ENCODER_OPTIMIZE_INTERRUPTS // not working right

#include <Encoder.h>

 

#include <nokia_5110_lcd.h>

/*IF we are not using the Graphics or Bitmap capabilities

of the LCD, save code space with these #defines*/

#define NO_GRAPHICS

#define NO_BITMAP

 

// setup microsd, decoder, and lcd chip pins

 

#define sd_cs      12      // 'chip select' for microsd card

#define mp3_cs     21      // 'command chip select' to cs pin

 

#define dcs        20      // 'data chip select' to bsync pin

#define rst        18      // 'reset' to decoder's reset pin

#define dreq       19    

// 'data request line' to dreq pin

 

//LCD PINS

#define LCD_PWR   10

#define LCD_SCE   16

#define LCD_RESET 17

#define LCD_DC 4

#define LCD_BL 13

 

//Interrupts PINS

#define redButton   8

#define encButton   7

 

// Color PINS

#define redPin   9

#define bluePin   14

#define greenPin   15

 

//eeprom variables:

#define eeVol 601

#define eeLights 600

#define eeContrast 602

 

// read_buffer is the amount of data read from microsd, then sent to decoder.

 

#define read_buffer   512     // size of the microsd read buffer

int mp3_vol;            // default volume: 0=min, 254=max

 

// file names are 13 bytes max (8 + '.' + 3 + '\0'), and the file list should

// fit into the eeprom. for example, 13 * 40 = 520 bytes of eeprom are needed

// to store a list of 40 songs. if you use shorter file names, or if your mcu

// has more eeprom, you can change these.

 

#define max_name_len  13

#define max_num_songs 40

 

// next steps, declare the variables used later to represent microsd objects.

 

Sd2Card  card;                // top-level represenation of card

SdVolume volume;              // sd partition, not audio volume

SdFile   sd_root, sd_file;    // sd_file is the child of sd_root

 

// declare lcd object as well

Nokia_5110_lcd lcd(LCD_PWR, LCD_DC, LCD_SCE, LCD_RESET);

 

// declare the encoder

Encoder myEnc(6, 5);

 

// store the number of songs in this directory, and the current song to play.

 

unsigned char num_songs = 0, current_song = 0, mp3_selected=0;

 

 

// an array to hold the current_song's file name in ram. every file's name is

// stored longer-term in the eeprom. this array is used for sd_file.open().

 

char fn[max_name_len];

 

// the program runs as a state machine. the 'state' enum includes the states.

// 'current_state' is the default as the program starts. add new states here.

 

enum state { DIR_PLAY, MP3_PLAY, IDLE, PAUSE, MENU, SETTINGS,CHOOSE };

volatile state current_state = MENU;

volatile state paused_state = MENU;

 

//menu states

int selected = 0;

enum menu_state {MENU_PLAY_ALL,MENU_CHOOSING, MENU_SETTINGS };

 

// settings states

int sett_selected = 0;

enum sett_state {SETT_BGLON, SETT_BGLOFF};

 

 

void rgbColor (int Re, int Gr, int Bl) {

  if (EEPROM.read(eeLights)>0) {

analogWrite( redPin ,255-Re);

analogWrite(bluePin  , 255-Bl);

analogWrite(greenPin, 255-Gr);

 }  else {

analogWrite( redPin ,255);

analogWrite(bluePin  , 255);

analogWrite(greenPin, 255);

 }

}

// you must open any song file that you want to play using sd_file_open prior

// to fetching song data from the file. you can only open one file at a time.

 

void sd_file_open() {

 map_current_song_to_fn();

 sd_file.open(&sd_root, fn, FILE_READ);

 

 // if you prefer to work with the current song index (only) instead of file

 // names, this version of the open command should also work for you:

 

 // sd_file.open(&sd_root, current_song, FILE_READ);

}

 

void mp3_play() {

 unsigned char bytes[read_buffer]; // buffer to read and send to the decoder

 unsigned int bytes_to_read;    // number of bytes read from microsd card

 

 // send read_buffer bytes to be played. Mp3.play() tracks the index pointer

 // within the song being played of where to get the next read_buffer bytes.

 

 bytes_to_read = sd_file.read(bytes, read_buffer);

 Mp3.play(bytes, bytes_to_read);

 

 // bytes_to_read should only be less than read_buffer when the song's over.

 

 if(bytes_to_read < read_buffer) {

sd_file.close();

current_state = IDLE;

 }

}

 

 

long oldPosition  = -999;

void checkVol () {

 

 long newPosition = myEnc.read();

 if (newPosition != oldPosition) {

rgbColor(255,30,30);

 

mp3_vol += newPosition - oldPosition;

 

  if (mp3_vol < 1 )

    mp3_vol = 1;

  else if (mp3_vol > 255)

    mp3_vol = 255;

 

  EEPROM.write(eeVol,mp3_vol);

   Mp3.volume(mp3_vol);

  oldPosition = newPosition;

 

 }

 

}

// continue to play the current (playing) song, until there are no more songs

// in the directory to play.

void dir_play() {

 lcd.writeString( 0, 0, "Playing:   ", MODE_NORMAL);

 lcd.writeString( 0, 1, fn, MODE_NORMAL);

 checkVol();

 

 if (current_song < num_songs) {

mp3_play();

 

// if current_state is IDLE, then the currently playing song just ended.

// in that case, increment to get the next song to play, open that file,

// and return to the DIR_PLAY state (which will then play that song).

    // if we played the last part of the last song, we don't do anything,

    // and the current_state is already set to IDLE from mp3_play()

 

if (current_state == IDLE && current_song < (num_songs - 1)) {

   current_song++;

   sd_file_open();

   current_state = DIR_PLAY;

 

}

  }

}

 

//The functions for the buttons

void rightButtonPressed (){

 

 oldPosition = myEnc.read();

//  Serial.println("right int");

 static unsigned long last_interrupt_time = 0;

 unsigned long interrupt_time = millis();

 // If interrupts come faster than 200ms, assume it's a bounce and ignore

 if (interrupt_time - last_interrupt_time > 200)

 {

// Serial.println("button");

switch(current_state) {

 

case MENU:

// Serial.println("menu push");

  lcd.clear();

 

  if (selected == MENU_PLAY_ALL){

    current_state = DIR_PLAY;

    oldPosition  = myEnc.read();

  }

  else if (selected ==  MENU_SETTINGS){

    myEnc.write(sett_selected*4);

    current_state = SETTINGS;

 

  }else if (selected ==  MENU_CHOOSING){

    myEnc.write(mp3_selected*4);

    current_state = CHOOSE;

  }

  break;

 

case DIR_PLAY:

  paused_state = current_state;

  current_state = PAUSE;

  break;

 

case MP3_PLAY:

  paused_state = current_state;

  current_state = PAUSE;

  break;

 

case IDLE:

  break;

 

case PAUSE:

  current_state = paused_state;

  break;

 

case CHOOSE:

 

  sd_file.close();

  Mp3.clear_buffer();

  current_song = mp3_selected;

  sd_file_open();

  current_state = DIR_PLAY;

  break;

 

case SETTINGS:

  if (sett_selected == SETT_BGLON){

    digitalWrite(LCD_BL, HIGH);

    EEPROM.write(eeLights,1);

    rgbColor(150,20,255);

  }

  else if (sett_selected ==  SETT_BGLOFF){

 

    digitalWrite(LCD_BL, LOW);

    EEPROM.write(eeLights,0);

    rgbColor(0,0,0);

  }

  break;

 

}

 }

 last_interrupt_time = interrupt_time;

 

 

}

 

void leftButtonPressed (){

 

 static unsigned long last_interrupt_time = 0;

 unsigned long interrupt_time = millis();

 // If interrupts come faster than 200ms, assume it's a bounce and ignore

 if (interrupt_time - last_interrupt_time > 200)

 {

switch(current_state) {

 

case MENU:

 

  break;

 

case DIR_PLAY:

  current_state = MENU;

  break;

 

case MP3_PLAY:

  current_state = MENU;

  break;

 

case IDLE:

  break;

 

case PAUSE:

 

  break;

 

case SETTINGS:

current_state = MENU;

  break;

 

case CHOOSE:

current_state = MENU;

  break;

 

}

 }

 last_interrupt_time = interrupt_time;

}

 

void settings() {

 if (myEnc.read()%4 == 0){

if (myEnc.read()/4 > sett_selected)

  sett_selected++;

else if (myEnc.read()/4 < sett_selected)

  sett_selected--;

 

if (sett_selected < SETT_BGLON )

  sett_selected = SETT_BGLON;

else if (sett_selected > SETT_BGLOFF)

  sett_selected = SETT_BGLOFF;

 

myEnc.write (sett_selected*4);

 

 

 }

  if (sett_selected == SETT_BGLON)

    lcd.writeString( 0, SETT_BGLON, "Lights ON ", MODE_INVERSE);

else

    lcd.writeString( 0, SETT_BGLON, "Lights ON ", MODE_NORMAL);

 

if (sett_selected == SETT_BGLOFF)

    lcd.writeString( 0, SETT_BGLOFF, "Lights OFF ", MODE_INVERSE);

else

    lcd.writeString( 0, SETT_BGLOFF, "Lights OFF ", MODE_NORMAL);

 

 

}

 

void menu (){

// lcd.clear();

 if (myEnc.read()%4 == 0){

if (myEnc.read()/4 > selected)

  selected++;

else if (myEnc.read()/4 < selected)

  selected--;

 

if (selected < MENU_PLAY_ALL )

  selected = MENU_PLAY_ALL;

else if (selected > MENU_SETTINGS)

  selected = MENU_SETTINGS;

 

myEnc.write (selected*4);

 

 

 }

 if (selected == MENU_PLAY_ALL)

    lcd.writeString( 0, MENU_PLAY_ALL, "Play all   ", MODE_INVERSE);

else

    lcd.writeString( 0, MENU_PLAY_ALL, "Play all   ", MODE_NORMAL);

 

if (selected == MENU_SETTINGS)

    lcd.writeString( 0, MENU_SETTINGS, "Settings   ", MODE_INVERSE);

else

    lcd.writeString( 0, MENU_SETTINGS, "Settings   ", MODE_NORMAL);

 

if (selected == MENU_CHOOSING)

    lcd.writeString( 0, MENU_CHOOSING, "Choose a song ", MODE_INVERSE);

else

    lcd.writeString( 0, MENU_CHOOSING, "Choose a song ", MODE_NORMAL);

}

 

int pastState = current_state;

 

void celarScrn() {

 

 if (pastState != current_state)

 {

pastState = current_state;

lcd.clear();

 }

}

 

void choose(){

 

 celarScrn();

 if (myEnc.read()%4 == 0){

if (myEnc.read()/4 > mp3_selected)

  mp3_selected++;

else if (myEnc.read()/4 < mp3_selected)

  mp3_selected--;

 

if (mp3_selected > num_songs*5 )

  mp3_selected = 0;

else if (mp3_selected > num_songs-1)

  mp3_selected = num_songs-1;

 

myEnc.write (mp3_selected*4);

 

 

 }

 

 for(int i=0; i <num_songs; i ++) {

lcd.gotoXY(0,i);

for (int j = 0; j < max_name_len; j++){

  if (EEPROM.read(i * max_name_len + j) == '\0') {

    break;

  }

  if (mp3_selected == i)

    lcd.writeChar(EEPROM.read(i * max_name_len + j), MODE_INVERSE);

  else

    lcd.writeChar(EEPROM.read(i * max_name_len + j), MODE_NORMAL);

}

 }

 

 

 

}

// setup is pretty straightforward. initialize serial communication (used for

// the following error messages), microsd card objects, mp3 library, and open

// the first song in the root library to play.

 

void setup() {

 

 Serial.begin(9600);

 Serial.println("serial on");

 

 pinMode(SS_PIN, OUTPUT);  //SS_PIN must be output to use SPI

 pinMode(LCD_BL, OUTPUT);

 pinMode(redPin, OUTPUT);

 pinMode(bluePin, OUTPUT);

 pinMode(greenPin, OUTPUT);

 

 // the default state of the mp3 decoder chip keeps the SPI bus from

 // working with other SPI devices, so we have to initialize it first.

 Mp3.begin(mp3_cs, dcs, rst, dreq);

 

 // initialize the microsd (which checks the card, volume and root objects).

 sd_card_setup();

 

 //initialize the LCD

 int contrast = EEPROM.read(eeContrast);

 if (contrast == 0)

contrast = 50;  

 lcd.init(contrast); //parameter is contrast value, between 0 [low] and 127 [high]

 lcd.clear();

 

 // initialize the mp3 library, and set default volume. 'mp3_cs' is the chip

 // select, 'dcs' is data chip select, 'rst' is reset and 'dreq' is the data

 // request. the decoder raises the dreq line (automatically) to signal that

 // it's input buffer can accommodate 32 more bytes of incoming song data.

 // we need to set the SPI speed with the mp3 initialize function since

 // it is the limiting factor, so we call its init function again.

 

 Mp3.begin(mp3_cs, dcs, rst, dreq);

 

 

 // putting all of the root directory's songs into eeprom saves flash space.

 

 sd_dir_setup();

 

 // the program is setup to enter DIR_PLAY mode immediately, so this call to

 // open the root directory before reaching the state machine is needed.

 

 sd_file_open();

 

 //Test that LCD still works with other SPI devices

 

 pinMode(redButton, INPUT_PULLUP);

 pinMode(encButton, INPUT);

 attachInterrupt(2, rightButtonPressed, FALLING  );

 attachInterrupt(3, leftButtonPressed, RISING  );

 

 

 if (EEPROM.read(eeLights)>0) {

digitalWrite(LCD_BL, HIGH);

 } else

rgbColor (0,0,0);

 

 

 mp3_vol = EEPROM.read(eeVol);

 if (mp3_vol == 0)

mp3_vol= 175;  

 

 Mp3.volume(mp3_vol);

}

 

 

// the state machine is setup (at least, at first) to open the microsd card's

// root directory, play all of the songs within it, close the root directory,

// and then stop playing. change these, or add new actions here.

 

// the DIR_PLAY state plays all of the songs in a directory and then switches

// into IDLE when done. the MP3_PLAY state plays one specified song, and then

// switches into IDLE. this example program doesn't enter the MP3_PLAY state,

// as its goal (for now) is just to play all the songs. you can change that.

 

void loop() {

 

 

 switch(current_state) {

 

case MENU:

  celarScrn();

        rgbColor(20,250,250);

  lcd.writeString( 0, 5, "menu   ", MODE_NORMAL);

//   lcd.update();

  menu();

  break;

 

case DIR_PLAY:

  celarScrn();

    rgbColor(20,255,30);

  lcd.writeString( 0, 5, "dir play  ", MODE_NORMAL);

//   lcd.update();

  dir_play();

  break;

 

case MP3_PLAY:

  lcd.writeString( 0, 5, "mp3 play  ", MODE_NORMAL);

//   lcd.update();

  mp3_play();

  break;

 

case IDLE:

  lcd.writeString( 0, 5, "idle ", MODE_NORMAL);

//   lcd.update();

  break;

 

case PAUSE:

  lcd.writeString( 0, 5, "pause   ", MODE_NORMAL);

//   lcd.update();

  break;

 

case SETTINGS:

   rgbColor(255,255,255);

  lcd.writeString( 0, 5, "settings   ", MODE_NORMAL);

//   lcd.update();

  settings();

  break;

 

case CHOOSE:

   rgbColor(0,0,255);

  lcd.writeString( 0, 5, "choosing   ", MODE_NORMAL);

//   lcd.update();

  choose();

  break;

 }

}

 

Video of the final working player in use

 

 

short video of the making process