| 
  • If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

EE47 MP3 player Final Project Documentation  - Sylvie

Page history last edited by Sylvie 11 years, 8 months ago

 

Design Point of View:

My friend Adib needs a better way to bring the nightclub into his apartment and get people dancing, because his living room is boring (typical Stanford housing) and he wants to transform it into party-central so that people can have a fun time after a long school week.

 

Verplank Diagram:

 

 

Photos of Paper Prototype:

 

I created multiple planning sketches (pictured below) and then 2 prototypes out of cardboard.  The first one wasn't fully assembled and had clear issues with dimensions that needed to change (side panels were too wide; I'd created then to be slightly longer than the hexagon base).  After resolving those and adjusting design, I created a 2nd prototype which is pictured below.  The center tabs and grooves were cut to be of different lengths to test which length would be best for the final product.

 

 

 

State Diagram:

I wanted the controls to be quite straightforward and nothing fancy.  Just on/off, volume up/down and fast forward/rewind.

 

Video of Final Working MP3 Player:

http://youtu.be/90CmwSByoKs

 

Project Code (based off Lab 6, barebones mp3 player. Tab 2 (Id3Tag)and tab 3 (Utilities) had no changes):

/*
 * example sketch to play audio file(s) in a directory, using the mp3 library
 * for playback and the arduino sd library to read files from a microsd card.
 * pins are setup to work well for teensy 2.0. double-check if using arduino.
 *
 * originally based on frank zhao's player: http://frank.circleofcurrent.com/
 * utilities adapted from previous versions of the functions by matthew seal.
 *
 * (c) 2011, 2012 david sirkin sirkin@cdr.stanford.edu
 *                & akil srinivasan akils@stanford.edu
 */

/*
 * Encoder_test
 * Based on code by Paul Badger
 * ----------------------------
 * WGJ 6MAY10: Updated pins, serial speed for Teensyduino
 *
 * Read a rotary encoder with interrupts
 * Encoder hooked up with common to GROUND,
 * encoder0PinA to pin 6, encoder0PinB to pin 7
 * it doesn't matter which encoder pin you use for A or B
 *
 * Uses Arduino pullups on A & B channel outputs
 * turning on the pullups saves having to hook up resistors
 * to the A & B channel outputs

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

#include <SD.h>c
#include <EEPROM.h>

#include <mp3.h>
#include <mp3conf.h>

// include the graphic lcd library. if you're using the lcd, but not graphics
// or bitmap capabilities, making these #define's will save program memory.

#include <nokia_5110_lcd.h>

#define NO_GRAPHICS
#define NO_BITMAP

// uncomment this #define to print out eeprom & sram usage info to the serial
// terminal. note that doing so uses about 850 bytes of program memory.

// #define DEBUG
 
// uncomment this #define to search for id3 tags and (ideally) a title frame.
// note that doing so uses about 950 bytes of program memory.

// #define ID3TAG

// setup microsd and decoder chip select pins, and the decoder-specific pins.

#define sd_cs         12   // 'chip select' line for the microsd card
#define mp3_cs        21   // 'command chip select' connect to cs pin

#define mp3_dcs       20   // 'data chip select' connect to bsync pin
#define mp3_rst       18   // 'reset' connects to decoder's reset pin
#define mp3_dreq      19   // 'data request line' connect to dreq pin

// now assign pins for the graphic lcd (carried over from the etch-a-sketch).

#define lcd_vcc       10        // 'vcc' connects to graphic lcd's vcc pin
#define lcd_sce        9        // 'slave chip select' connect to  sce pin
#define lcd_rst        8        // 'reset' connects to graphic lcd rst pin
#define lcd_d_c        4        // 'data/command input' connect to d/c pin

// 'read_buffer' is the amount of data read from microsd and sent to decoder.
// it's probably best to keep this a factor of 2, up to about 1kb (2kb is the
// max). you might change this if you experienced skips during song playback.

#define read_buffer  512        // size (bytes) of the microsd read buffer
//#define vol 150;
int vol = 125;        // default volume. range min=0 and max=254

// 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

// id3v2 tags have variable-length song titles. that length is indicated in 4
// bytes within the tag. id3v1 tags also have variable-length song titles, up
// to 30 bytes maximum, but the length is not indicated within the tag. using
// 60 bytes here is a compromise between holding most titles and saving sram.

// if you increase this above 255, look for and change 'for' loop index types
// so as to not to overflow the unsigned char data type.

#define max_title_len 60

// you can comment out the following line (and 3 lines at the end of setup())
// to save memory if you're not going to use the graphic lcd. note that doing
// so releases about 550 bytes of sram (!) and 200 bytes of program memory.

// instantiate a graphic lcd object using the pins that we #define'd earlier.

#define encoder0PinA  0  //6?
#define encoder0PinB  1  //7?

volatile unsigned int encoder0Pos = 0;

#define ENC_A 0
#define ENC_B 1
#define RED 10
#define GREEN 0
#define BLUE 14
#define BUTTON 8
#define ENC_PORT PINB
//#include <LiquidCrystal.h>
//LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

Nokia_5110_lcd lcd(lcd_vcc, lcd_d_c, lcd_sce, lcd_rst);

const int buttonPin = 7;
const int ledPin =  9;
const int ledPin2 =  11;
const int ledPin3 =  13;
const int ledPin4 =  15;
const int ledPin5 =  16;
const int ledPin6 =  17;

int buttonState = 0;

// 'File' is a wrapper of the 'SdFile' data type from the sd utility library.

File sd_file;                   // object to represent a file on a microsd

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

unsigned char num_songs = 0, current_song = 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 in 'sd_file.open()'.

char fn[max_name_len];

// an array to hold the current_song's title in ram. it needs 1 extra char to
// hold the '\0' that indicates the end of a character string. the song title
// is found in 'get_title_from_id3tag()'.

char title[max_title_len + 1];

// 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, PAUSED };
volatile state current_state = DIR_PLAY;  //added volatile per lab 6

// you must open a 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 pause(){
  current_state = PAUSED;
}

void replay(){
  current_state = DIR_PLAY;
}

void sd_file_open() {  
  // first, find the file name (that's stored in eeprom) of the current song.
 
  map_current_song_to_fn();
  // then open the file using the name we just found (stored in 'fn' global).
 
  sd_file = SD.open(fn, FILE_READ);
  // optionally find, and print 'current_song's id3tag title to the terminal.
  // if the title is unicode (see comments in 'get_title_from_id3tag'),
 
  #ifdef ID3TAG
    get_title_from_id3tag();
    print_title_to_serial();
  #endif
}

// read a number of bytes from the microsd card, then forward them to the Mp3
// library's 'play' function, which streams them out to the decoder chip.

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

  // first fill the 'bytes' buffer with (up to) 'read_buffer' count of bytes.
  // that happens through the 'sd_file.read()' call, which returns the actual
  // number of bytes that were read (which can be fewer than 'read_buffer' if
  // at the end of the file). then send the retrieved bytes out to be played.
 
  // 'sd_file.read()' manages the index pointer into the file and knows where
  // to start reading the next batch of bytes. 'Mp3.play()' manages the index
  // pointer into the 'bytes' buffer and knows how to send it to the decoder.

  bytes_to_read = sd_file.read(bytes, read_buffer);
  Mp3.play(bytes, bytes_to_read);
 
  // 'bytes_to_read' is only smaller than 'read_buffer' when the song's over.

  if (bytes_to_read < read_buffer) {
    sd_file.close();
    
    // if we've been in the MP3_PLAY state, then we want to pause the player.

    if (current_state == MP3_PLAY) {
      current_state == PAUSED;
    }
  }
}

// continue to play the current (playing) song, until there are no more songs
// in the directory to play. 2 other sd library methods (that we haven't used
// here) can help track your progress while playing songs: 'sd_file.size()' &
// 'sd_file.position()'. you can use these to show say, the percent of a song
// that has already played.
 
void dir_play() {
  if (sd_file) {
    mp3_play();
  }
  else {
    // since 'sd_file' isn't open, the recently playing song must have ended.
    // increment the index, and open the next song, unless it's the last song
    // in the directory. in that case, just set the state to PAUSED.
    if (current_song < (num_songs - 1)) {
      current_song++;
      sd_file_open();
    } else {
      current_state = PAUSED;
    }
  }
}   

void nextSong() {
    current_song++;
 //   File p = dir.openNextFile();
}

void doEncoder() {
  // If pinA and pinB are both high or both low, it is spinning
  // forward. If they're different, it's going backward.
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
    Serial.println("Encoder Pin");
   // Serial.print(encoder0Pos);
    vol=vol+5;
    Mp3.volume(vol);

  } else {
    encoder0Pos--;
        Serial.println("Encoder Pin");
    //    Serial.println(encoder0Pos);
    //vol=vol-5;
    //Mp3.volume(vol);
  }
  Serial.println (encoder0Pos, DEC);
}

void makeWhite(){
  analogWrite(RED, 0);
  analogWrite(GREEN, 0);
  analogWrite(BLUE, 0);
}

/* returns change in encoder state (-1,0,1) */
int read_encoder(){
  static int enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static unsigned char old_AB = 0;
  /**/
  old_AB <<= 2;                   //remember previous state
  old_AB |= ( ENC_PORT & 0x03 );  //add current state
  return ( enc_states[( old_AB & 0x0f )]);
}

  static unsigned long counter4x = 0;      //the SparkFun encoders jump by 4 states from detent to detent
  static unsigned long counter = 0;
  static unsigned long prevCounter = 0;
  int tmpdata;
 
void readRotaryEncoder(){
  if (digitalRead(BUTTON)){
    makeWhite();
    counter4x = 0;
  }
  tmpdata = read_encoder();
  if( tmpdata ) {
    counter4x += tmpdata;
    counter = counter4x/4;
    if (prevCounter != counter){
      //lcd.clear();
      //lcd.setCursor(0,0);
      //lcd.print("Counter value: ");
      //lcd.setCursor(0,1);
      //lcd.print(counter);
      
      /* Light up the LEDs with all possible color combinations - raster scan */
      analogWrite(RED, (counter*5) % 255);
      analogWrite(GREEN, ((counter*5)/255) % 255);
      analogWrite(BLUE, ((counter*5)/255/255) % 255);
    }
    prevCounter = counter;
  }
}


// setup is pretty straightforward. initialize serial communication (used for
// the following error messages), mp3 library, microsd card objects, then the
// graphic lcd. then open the first song in the root library to play.

void setup() {
  Serial.begin(9600);
 
  // if using a graphic lcd, initialize the device now. the parameter '40' is
  // the screen contrast, which ranges from min=0 to max=127.
 
  lcd.init(50);
  lcd.clear();
 
  pinMode(ledPin, OUTPUT);      
  pinMode(buttonPin, INPUT);
  pinMode(ledPin2, OUTPUT);      
  pinMode(ledPin3, OUTPUT);      
  pinMode(ledPin4, OUTPUT);      
  pinMode(ledPin5, OUTPUT);      
  pinMode(ledPin6, OUTPUT);   
digitalWrite(ledPin, HIGH);
digitalWrite(ledPin2, HIGH);
digitalWrite(ledPin3, HIGH);
digitalWrite(ledPin4, HIGH);
digitalWrite(ledPin5, HIGH);
digitalWrite(ledPin6, HIGH);  

  Serial.begin(115200);
  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor
//  attachInterrupt(0, doEncoder, CHANGE);
  Serial.println("Rotary Vol start");  
 
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  pinMode(RED, OUTPUT);
  pinMode(GREEN, OUTPUT);
  pinMode(BLUE, OUTPUT);
  pinMode(BUTTON, INPUT);
  makeWhite();
  Serial.println("rotary encoder start");
 
 // attachInterrupt(1, doEncoder, CHANGE);

 
  // 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 sets the 'dreq' line (automatically) to signal that
  // its input buffer can accommodate 32 more bytes of incoming song data.
 
  // the decoder's default state prevents the spi bus from working with other
  // spi devices, so we initialize it first.
  Mp3.begin(mp3_cs, mp3_dcs, mp3_rst, mp3_dreq);
  Mp3.volume(vol);
 
  // initialize the microsd (which checks the card, volume and root objects).
  sd_card_setup();
 
  // putting all of the root directory's songs into eeprom saves flash space.
  sd_dir_setup();

  // optionally print status info (serial terminal) before opening any files.
  #ifdef DEBUG
    print_status_info();
  #endif
 
  // 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 the lcd still works, now that the other spi devices are setup.
  lcd.writeString(0, 0, "Barebones Mp3!", MODE_NORMAL);
}

// 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 PAUSED when done. the MP3_PLAY state plays one specific song and then
// switches into PAUSED. this sample player 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 DIR_PLAY:
      dir_play();
     // readRotaryEncoder();
    
    //  attachInterrupt(3, pause, RISING);
      attachInterrupt(3, nextSong, RISING);

    //   attachInterrupt(0, doEncoder, CHANGE);
  //  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB))
  //   current_song++;
    
      //Rising so something happens after push->unpush
      //attachInterrupt(0, doEncoder, CHANGE); //encoder pin on interrupt 0-pin 2

 //   buttonState = digitalRead(buttonPin);
  //  if (buttonState == LOW) {     
  //  digitalWrite(ledPin, HIGH);  
  //  }
  //  attachInterrupt(2, replay, CHANGE);    
 // else {
  //  digitalWrite(ledPin, LOW);
 // }
      break;

    case MP3_PLAY:
      mp3_play();
      //readRotaryEncoder();
      attachInterrupt(3, nextSong, RISING);   
      
      //attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
      break;

    case PAUSED:
      //readRotaryEncoder();
     // attachInterrupt(3, replay, RISING);
    //  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
    // Mp3.volume(vol);
      break;
  }
}

 

 

Comments (1)

yusi chen said

at 2:40 pm on Aug 22, 2012

Hi Sylvie,

We do appreciate your awesome project!

We like that you include you include a clear logic chain of design in your final document. You use scenarios, design sketches, mind mapping, state diagram and interaction design prototype to clarify your point, which is fantastic! Also, you give very clear introduction to how to do the product design, including many helpful design details. Great job!
We know that the time is limited for summer quarter final project. Therefore, if you have more time, we do hope you can make your product more “reliable”, so that we can see the functions more clearly in your video.
If you have time to redo your project, we do wish you can add more features in your product. For example, you can use multi-color LED to shine your disco ball. Also, you can try to use different shape of mirrors or add lens to give multi shapes of images on wall. Besides, you can try to analyze the rhyme of the song, so that you can adjust the images on walland the speed of disco ball’s rotation.

Wonderful job!

David, Yusi, Kai and Ben

You don't have permission to comment on this page.