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

Charles Wang - Final Project

Page history last edited by cvwang@... 12 years, 7 months ago

Charles Wang

13 August 2011

EE47

Final Project - Portable MP3

 

 

 

 

The Process

 

The initial idea for my MP3 project was to design an MP3 for joggers. I wanted something that was small, lightweight and portable. Here is a Verplank Diagram that illustrates the initial idea of my MP3:

The original idea of my jogger's MP3 was to have a headband that had earphones and a wristband with my control interface that would communicate information to the teensy in my headband and play music. At first the idea was that the headband could be used when jogging or sleeping 

 

Here is a brainstorm diagram of my MP3:

 

After brainstorming the functions of my MP3, I began sketching preliminary designs of my MP3:

 

After I got an idea of what I was heading toward in my design, I began wiring all my components together and programming my Teensy to do what I intended. Here are photos of what my breadboard looked like:

 

For my MP3, I knew I would need an LCD screen, an MP3 decoder, a Teensy, a Headphone Jack and at least one push button to play and pause songs. I also bought a slide potentiometer that I intended to use as a volume controller.

 

After I wired and configured those components, I then proceeded to add on 3 more buttons: previous, next, select (for a menu display). I also added another potentiometer to use as a volume control because I thought that the slide potentiometer would be a cool device to use as a menu selector.

 

After I finished my programming and wiring on the breadboard, I went to talk to Marlo, the assistant at Room 36. I told her my MP3 idea and she helped sketch up a design for the box that I could use to house my user interface on my wristband:

 

I then used the laser printer in Room 36 to create my prototype housing for my MP3:

 

However, I realized that there wasn't really much use creating a housing for components and wires that I hadn't even soldered on a perfboard. So I decided to begin the soldering process which was the most tedious and painful process in creating my MP3. When I began soldering, I first soldered wires onto my SD card adapter, LCD, MP3 decoder and Audio Jack. When doing this I made every device have its own set of same colored wires. This decision really helped in the long run when all the devices were coming together. I chose to use a busboard perfboard as I thought this would be the simpler approach to connect my devices. Without much thought put into how I would make my wiring connections, I soldered my Teensy to the busboard. Not long after doing that did I realize that a busboard was not going to work as there were not enough pinholes to connect to my Teensy pins and other devices. Then I began my painstaking 2 hour journey of desoldering my Teensy from the perfboard. When I finally detached my Teensy, I felt like the man in 127 hours who managed to free himself from the rock. One would think that the experience of desoldering my Teensy would have taught me to appreciate the use of female headers, but I evidently did not learn my lesson and decided to solder my Teensy onto a perfboard without the bus lines. This time I clearly thought out my plan to solder and how many pinholes I would need for each Teensy pin. I also created a bus line of ground and power on the small perfboard. My whole MP3 player was connected by 1 10x12 (roughish) perfboard.

 

     

 

These were only with the LCD, SD card and molex connectors for my MP3 decoder. Although I did create molex connectors, I realized that they would be too thick for my compact MP3. However, learning to create molex connectors was a good experience in itself. After, I finally added on the final push buttons, potentiometers, battery and toggle switch (thanks Daniel) and and added some electrical tape to prevent shorts. Now my MP3 was finally functioning by itself without a USB port. This was truly a highlight point in my project.

 

 

I then decided that it was time to laser print my acrylic MP3 cover. Miraculously enough, all the devices naturally just fell into place to take their respective positions in my MP3 player with the help of hot glue to keep them in place.

 

 

However, in the rushed process of trying to get my acrylic printed in the last room 36 session, I somehow managed to lose one of the side cover of my MP3 player. From that point, I improvised and use the elastic wristband and some velcro to cover the backside of my MP3 and create a somewhat presentable final product.

 

As you can see from the photo above, there is a toggle switch sticking out. I fitted the toggle switch through the hole by using a drill to create the right sized hole. And just to demonstrate compactness of my device, here is a photo of the brain power that is going on behind this cased MP3:

 

This was how I created my MP3 portable player.

 

The Code

//mp3.pde: 

/*

 * 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 david sirkin sirkin@cdr.stanford.edu

 *          akil srinivasan akils@stanford.edu

 */

 

#define NO_GRAPHICS

#define NO_BITMAP

#include <nokia_5110_lcd.h>

#include <SD.h>

 

//LCD PINS

#define LCD_PWR   10

#define LCD_SCE   9

#define LCD_RESET 8

#define LCD_DC    4

 

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

 

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

 

#include <SD.h>

#include <EEPROM.h>

#include <Bounce.h>

 

#include <mp3.h>

#include <mp3conf.h>

 

// setup microsd and decoder chip select pins, and the decoder-specific 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

 

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

 

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

#define mp3_vol       175        // 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

 

// 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 for sd_file.open().

 

char fn[max_name_len + 2];

 

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

enum state current_state = IDLE;

 

// Instantiate a Bounce object with a 5 millisecond debounce time

int playPin = 7;

Bounce playBouncer = Bounce(playPin, 10);

int prevPin = 6;

Bounce prevBouncer = Bounce(prevPin, 10);

int nextPin = 5;

Bounce nextBouncer = Bounce(nextPin, 10);

int selectPin = 15;

Bounce selectBouncer = Bounce(selectPin, 10);

 

int analogPin = 14;

int analogValue;

 

int volumeByte = 1000;

int volumePin = 13;

byte volumeLevel;

 

boolean pause = false;

 

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

 

  if(pause==false)

  {

    bytes_to_read = sd_file.read(bytes, read_buffer);

    Mp3.play(bytes, bytes_to_read);

  }  

 

  playBouncer.update ( );

  int value0 = playBouncer.read();

  prevBouncer.update ( );

  int value1 = prevBouncer.read();

  nextBouncer.update ( );

  int value2 = nextBouncer.read();

 

  if(value0==HIGH)

  {

    delay(100);

    if(pause==false)

    {

      pause = true;

    }

    else

    {

      pause = false;

    }

    //pause();

  }

  if(value1==HIGH)

  {

    sd_file.close();

    current_song -= 2;

    current_state = IDLE;

    delay(300);

  }

  if(value2==HIGH)

  {

    sd_file.close();

    current_state = IDLE;

    delay(300);

  }

 

  volumeLevel = int(analogRead(volumePin)/8.18)+130;

  Serial.println(volumeLevel, DEC);

 

  if(EEPROM.read(volumeByte) != volumeLevel)

  {

    EEPROM.write(volumeByte, volumeLevel);

    Mp3.volume(EEPROM.read(volumeByte));

  }

 

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

  }

}

 

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

// in the directory to play. 

 

void dir_play() {

  if (current_song < num_songs) {

     mp3_play();

     Serial.println(current_song, DEC);

     Serial.println(num_songs, DEC);

 

     // 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 (current_state == IDLE) {

       current_song++;

       sd_file_open();

       current_state = DIR_PLAY;

     }

   }

   else {

     current_state = IDLE;

   }

}

 

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

 

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

 

  sd_card_setup();

 

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

 

  lcd.init();

  lcd.clear();

 

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

  Mp3.volume(mp3_vol);

 

  // 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();

}

 

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

 

//here is my menu 

void loop() {

  analogValue = analogRead(analogPin);

  //Serial.println(analogValue, DEC);

 

  selectBouncer.update ( );

  int value0 = selectBouncer.read();

 

  //Serial.println("ram free");

  //Serial.print(get_free_memory(), DEC);  

 

  lcd.writeString(0, 0, "Menu", MODE_NORMAL);

  if(analogValue >= 759 && analogValue < 1024)

  {

    lcd.writeString(10, 1, "Play", MODE_INVERSE);

    if(value0 == LOW)

    {

      current_state = DIR_PLAY;

    }

  }

  else

  {

    lcd.writeString(10, 1, "Play", MODE_NORMAL);

  }

 

  if(analogValue >= 507 && analogValue < 759)

  {

    lcd.writeString(10, 2, "Music", MODE_INVERSE);

    if(value0 == LOW)

    {

      current_state = DIR_PLAY;

    }

  }

  else

  {

    lcd.writeString(10, 2, "Music", MODE_NORMAL);

  }

 

  if(analogValue >= 255 && analogValue < 507)

  {

    lcd.writeString(10, 3, "Shuffle", MODE_INVERSE);

  }

  else

  {

    lcd.writeString(10, 3, "Shuffle", MODE_NORMAL);

  }

 

  if(analogValue >= 0 && analogValue < 255)

  {

    lcd.writeString(10, 4, "Now Playing", MODE_INVERSE);

  }

  else

  {

    lcd.writeString(10, 4, "Now Playing", MODE_NORMAL);

  }

  switch(current_state) {

 

    case DIR_PLAY:

      dir_play();

      break;

 

    case MP3_PLAY:

      mp3_play();

      break;

 

    case IDLE:

      break;

  }

}

 

 

 

//Seperate file Song_Utilities.pde

/*

 * 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 david sirkin sirkin@cdr.stanford.edu

 *          akil srinivasan akils@stanford.edu

 */

 

// check that the microsd card is present, can be initialized and has a valid

// root volume. a pointer to the card's root object is returned as sd_root.

 

void sd_card_setup() {

  if (!SD.begin(sd_cs)) {

    Serial.println("Card failed, or isn't present.");

    return;

  }

  if (!card.init(SPI_HALF_SPEED, sd_cs)) {

    Serial.println("Card found, but initialization failed.");

    return;

  }

  if (!volume.init(card)) {

    Serial.println("Initialized, but couldn't find partition.");

    return;

  }

  if (!sd_root.openRoot(&volume)) {

    Serial.println("Partition found, but couldn't open root");

    return;

  }

}

 

// for each song file in the current directory, store its file name in eeprom

// for later retrieval. this saves on using program memory for the same task,

// which is helpful as you add more functionality to the program.

 

void sd_dir_setup() {

  dir_t p;

  num_songs = 0;

 

  sd_root.rewind();

 

  while (sd_root.readDir(&p) > 0 && num_songs < max_num_songs) {

    // break out of while loop when we wrote all files (past the last entry).

 

    if (p.name[0] == DIR_NAME_FREE) {

      break;

    }

 

    // only store current (not deleted) file entries, and ignore the . and ..

    // sub-directory entries. also ignore any sub-directories.

 

    if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.' || !DIR_IS_FILE(&p)) {

      continue;

    }

 

    // only store mp3 or wav files in eeprom (for now). if you add other file

    // types, you should add their extension here.

 

    // it's okay to hard-code the 8, 9 and 10 as indices here, since SdFatLib

    // pads shorter file names with a ' ' to fill 8 characters. the result is

    // that file extensions are always stored in the last 3 positions.

 

    if ((p.name[8] == 'M' && p.name[9] == 'P' && p.name[10] == '3') ||

        (p.name[8] == 'W' && p.name[9] == 'A' && p.name[10] == 'V') ||

        (p.name[8] == 'M' && p.name[9] == '4' && p.name[10] == 'A')

        ) {

 

      // store each character of the file name into an individual byte in the

      // eeprom. sd_file->name doesn't return the '.' part of the name, so we

      // add that back later when we read the file from eeprom.

 

      unsigned char pos = 0;

 

      for (unsigned char i = 0; i < 11; i++) {

        if (p.name[i] != ' ') {

          EEPROM.write(num_songs * max_name_len + pos, p.name[i]);

          pos++;

        }

      }

 

      // add an 'end of string' character to signal the end of the file name.

 

      EEPROM.write(num_songs * max_name_len + pos, '\0');

      num_songs++;

    }

  }

}

 

// given the numerical index of a particular song to play, go to its location

// in eeprom, retrieve its file name and set the global variable 'fn' to it.

 

void map_current_song_to_fn() {

  int fnln = max_name_len;

 

  // based on the current_song index, get song's name and length from eeprom.

 

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

    fn[i] = EEPROM.read(current_song * max_name_len + i);

 

    // break if we reach the end of the file name, or if we have a directory.

    // keep track of the file name length (fnln), so we can put the '.' back.

 

    if (fn[i] == '\0') {

      fnln = i;

      i = max_name_len;

      break;

    }

    else

    if (fn[i] == '/') {

      fn[0] = '\0';

      fnln = 0;

      i = max_name_len;

      break;

    }

  }

 

  // now restore the '.' that sd_file->name didn't store in its array for us.

 

  if (fnln > 4) {

    fn[fnln + 1] = '\0';

    fn[fnln]     = fn[fnln - 1];

    fn[fnln - 1] = fn[fnln - 2];

    fn[fnln - 2] = fn[fnln - 3];

    fn[fnln - 3] = '.';

  }

}

 

Youtube Video of my portable MP3!

 

Advice for Future EE47 Students

Creating a small MP3 can be very challenging if every aspect isn't thought through. Here are some challenges I came across when creating my MP3 and how I solved them: 

  • Packaging:
    • If you're trying to keep your device small, when hooking up devices, your best bet will be to use the perfboards that have their own individual holes (not the bus board). These perfboards will allow for more freedom when hooking components up to the pins in your Teensy.
    • Give yourself enough wirelength when connecting components. Even though you're creating a small device you never know how far you're going to have to stretch that wire around you're MP3.
    • Never underestimate the power of heat shrink tubing. When you're crunching you're wires into small spaces, connections can easily be shorted so it's always better to use heat shrink (it was also kind of fun making them as well).
    • Female headers aren't always necessary. Although it is always safer to use female headers so you don't have to go through the painful process of desoldering, if you're going for something small, try to stay away from the female headers but make sure you know what you're doing ahead of time.
    • Keep the wiring on yourbreadboard there when transferring to perfboard. It's always good to have a reference to where connections are made in your MP3.
    • Give yourself enough time to move things over to your perfboard and also to make your package. 
  • Coding:
    • ALWAYS save functional code!
    • Having a working Serial Port. 

 

Thanks David, Akil and Ben for all your help this Summer! I've learnt so much and I am definetely going to keep doing these kinds of stuff in the future!

Comments (1)

David S said

at 10:41 am on Aug 16, 2011

We're reluctant to ask how you learned that you could drop your player from a particular height and still have it hold together and work, so we're just not going to ask. ;-)

We really like your documentation of the design process: the design sketches, mind map, breadboard photos, and even narrative of your process. Great job at that. It was also a clever idea to incorporate your original headband once you realized that you had lost your side pieces and it was too late to recover them from Room 36. We also really like your advice for future students.

The sliding potentiometer is a nice interface touch, although by having it control the display (rather than say, adjust the volume), it's only useful if the user is actually looking at the device. We wonder if you could have made that sensor serve several different roles, depending on what mode the player was in. For example, could it select from the menu when no song is playing, but adjust the volume when a song is playing?

We wish that you had included a state diagram, and a bit more detail on your circuit. For example, you didn't provide a link to the manufacturer or part number of the sliding potentiometer. We also would like to have seen a bit more detail on your case design: so, how did you measure cuts so that the parts would snap together, what materials did you try/use, that kind of thing.

Nice job on your player overall.

David, Akil and Ben

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