Final Project Design Documentation


Clap-On MP3

 

Video:

 

Features:

  1.  Button to Skip Forward
  2.  Button to Skip Backward
  3.  LCD Screen Displaying the Title
  4.  LCD Screen Displaying the current surrounding noise level
  5.  Clap-On Pause/ Play

 

Components Used:

  1. ArduinoBoardMicro
  2. 16 x 2 LCD Screen - LCM1602A-NSW-BBW
  3. MP3 Decoder - VSP1053
  4. Standard SD Card
  5. 3.5mm Audio Jack
  6.  7 Band Graphic Equalizer - MSGEQ7
  7. Microphone with Auto Gain Control - MAX9814 
  8. Voltage Shifter -HEF 4050BP 

 

 

Initial Vision/ Point of View/ Description of the Process:

     A. Initial Vision

     I wanted to make a device that could listen along with the user and display a graphical feedback to the user that it is also listening to the music. In order to do so I needed to analyze the frequencies received from the microphone in real time.

     Also, the option to use the Nokia 5110 (datasheet here: http://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf) was discarded from the design process because it was not reliable. It was exhibiting some internal electrical failures that I could not fix, so I decided to use the Componenet #2 instead, which was 16x2 LCD Standard LCD Screen. 

 

     B. Difficulty #1

     Initially, I had in mind to perform Fast Fourier Transform directly in the Arduino. However, the library provided by the Arudino Playground was very tricky to successfully process the livestreaming audio from the microphone. Different resolution for the transformation and actually getting understandable values from the process were more difficult than it appeared to be. I have not completely diagnosed what the problem was, but my assumption is that the Analog-Digital Conversion part of the code is not compatible with the ArduinoBoardMicro. Here is a link to the library that I tried to get it to work but couldn't: 

 

http://wiki.openmusiclabs.com/wiki/ArduinoFFT

 

It also recommends a more update/ robust/ faster transformation FHT, but have not gotten around to get it to work either. 

 

     C. Solution #1

     In order to get around performing FFT using the Library, I bought Component #6, which is the Graphic Equalizer Board. It collects data from from the microphone sensor and updates an array of seven values representing the loudness (in decibels) of each frequency bands. The bands were :

 

int FreqVal[7]:     [0]     [1]     [2]    [3]     [4]      [5]        [6] 

Frequency(Hz):    63    160    400   1K    2.5K   6.25K    16K

 

Though it did restrict me to 7 bin resolution, it was much easier to get it work because the product is provided with a standard library configured for the equalizer chip. 

 

     D. Difficulty #2

 

          I had some trouble when I was transferring the circuit from the breadboard to a perfboard. The microphone-equalizer part of the circuit began to exhibit a very odd response. Normally, the microphone is sensitive enough to pick up a person's voice from few feets away and direct contact on the housing (e.g: blowing or tapping on the cap) would create very high frequency values. Also, the interrupts did not exhibit the expected behavior when triggered either. I was down to the last day working on the project and rushing it certainly did not help.

 

     E. Solution #2

     Patiently debug for shorting circuits or for lack of continuities. Just need to have faith in the fact that it was working on the breadboard and as long as you can recreate the board, you should be fine.

 

 

Procession of the Project:

 

 

Finished wiring on the breadboard. Last taken before it was torn apart.

 

Perfboard Schematic Idea #1: this plan was too crowded. Crowded plan only makes the job harder.

 

Perfboard Schematic Idea #2: this plan divides the components into two boards and provides more room to solder. Notice how the LCD displays the song title. It is the best feeling when it works on the first try. Other components required more debugging phases.

 

Though two board idea might have given me more room to work, it required me to solder bunch of jumper cables because many components ran between the boards. Though it allowed me to work on the board easier, it made the volume of the device twice as bigger, which was unintended. I solved this problem by folding the two boards in have after finishing soldering.

 

Tips for Future Students:

 

     GET STARTED EARLY

     Running out of time at the last minute is not the ideal environment to wrap up a project. Soldering and making precise measurements require time and even with the utmost precision bugs will arise. The only way to prevent last minute bugs to hold your project down is to get started early.

     What I had in mind early in the process was not exactly the visual analysis of the surrounding sound. Rather, I was simply trying this thing or that and moving from figuring out this component to another component. Because I spend sometime without a specific goal in mind in the first few days of the time remaining after the completion of Lab 6, I was behind when I made up my mind on this project. So get started as early as possible.

 

 

Room for Improvement:

 

 

Code:

     There are total of three files: 

     1. Main File (with setup and loop methods)

 

// ---- includes and defines ------------------------------------------------

 

#include <SD.h>

#include <SPI.h>

#include <EEPROM.h>

 

#include <mp3.h>

#include <mp3conf.h>

 

#define sd_cs         17        // 'chip select' line for the microsd card

 

#define mp3_cs        A0        // 'command chip select' connect to cs pin

#define mp3_dcs       A1        // 'data chip select' connect to bsync pin

#define mp3_dreq      A2        // 'data request line' connect to dreq pin

#define mp3_rst       -1        // 'reset' connects to decoder's reset pin

 

#define read_buffer  512        // size (bytes) of the microsd read buffer

#define mp3_vol      254        // default volume. range min=0 and max=254

 

#define max_name_len  13

#define max_num_songs 40

 

#define max_title_len 60

 

// ---- global variables ----------------------------------------------------

 

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

 

unsigned char num_songs = 0, current_song = 0;

 

char fn[max_name_len];

 

char title[max_title_len + 1];

 

enum state { DIR_PLAY, MP3_PLAY, PAUSED };

state current_state = DIR_PLAY;

 

// ---- SmallLCD Stuff ------------------------------------------------------

#include <AudioAnalyzer.h>//Version 1.2 for Spectrum analyzer

#include <LiquidCrystal.h>

 

Analyzer Audio = Analyzer(11,12,5);//Strobe pin ->11  RST pin ->12 Analog Pin ->5

LiquidCrystal smallLCD(10, 9, 8, 4, 7, 6);

 

int FreqVal[7];//

 

//---- module functions -----------------------------------------------------

 

void sd_file_open() {  

  get_current_song_as_fn();

 

  sd_file = SD.open(fn, FILE_READ);

 

  print_title_to_lcd();

}

 

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

 

  bytes_to_read = sd_file.read(bytes, read_buffer);

  Mp3.play(bytes, bytes_to_read);

 

  if (bytes_to_read < read_buffer) {

    sd_file.close();

 

    if (current_state == MP3_PLAY) {

      current_state == PAUSED;

    }

  }

}

 

void dir_play() {

  if (sd_file) {

    mp3_play();

  }

  else {

    if (current_song == 255){

      current_song = 0;

      sd_file_open();

    } 

    else if(current_song == 254){

      current_song = num_songs - 1;

      sd_file_open();

    }

    else if (current_song < (num_songs - 1)) {

      current_song++;

      sd_file_open();

    }

    else {

      current_state = PAUSED;

    }

  }

}

 

 

// ---- Audio Interface -----------------------------------------------------

unsigned long lastTimeAboveTHOLD[7];

long thold = 400;

long clapTimeMax = 2000;//thousand milliseconds = 1 second

long clapTimeMin = 100;

 

void checkDoubleClap(){

  unsigned long currentTime = millis();

  boolean clapTriggered = false;

 

  //update times

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

    if(max((FreqVal[i]-100),0) > thold){

      unsigned long temp = lastTimeAboveTHOLD[i];

      if(currentTime - temp < clapTimeMax && currentTime - temp > clapTimeMin){

        clapTriggered = true;

      } 

      lastTimeAboveTHOLD[i] = currentTime;

    }

  }

  if(clapTriggered){

    pause_play();

  }

}

 

 

// ---- smallLCD methods ---------------------------------------------------

void createChars(){

  byte temp[8];

  byte row = B00000;

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

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

      if(8 - i > j){

        temp[j] = row;

      } else {

        temp[j] = ~row;

      }

    }

    smallLCD.createChar(i, temp);

  }

}

void printFreqValue(){

  int total = 700;

  smallLCD.setCursor(0, 1);

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

    int v1 =  8 * max((FreqVal[i]-100),0) / total;    

    smallLCD.write(byte(v1));    

    if(i != 6){

      int v2 = (v1 + (16 * max((FreqVal[i + 1]-100),0) / total)) / 2;

      smallLCD.write(byte(v2));  

    } 

  }

}

 

 

// ---- Interface Methods ---------------------------------------------------

void pause_play() {

  if(current_state == DIR_PLAY) {

    current_state = PAUSED;

  } else {

    current_state = DIR_PLAY;

  }

}

void skip_forward() {

  static unsigned long last_interrupt_time = 0;

  unsigned long interrupt_time = millis();

 

  if (interrupt_time - last_interrupt_time > 200)

  {

    Mp3.cancel_playback();

    sd_file.close();

    if(current_song == num_songs - 1){

      current_song = -1;      

    }

    current_state = DIR_PLAY;    

  }

  last_interrupt_time = interrupt_time;

}

void skip_backward() {

  static unsigned long last_interrupt_time = 0;

  unsigned long interrupt_time = millis();

 

  if (interrupt_time - last_interrupt_time > 200)

  {

    Mp3.cancel_playback();

    sd_file.close();

    if(current_song == 0){

      current_song = 254;       

    } else {

      current_song--;

      current_song--;      

    }

    current_state = DIR_PLAY;

  }

  last_interrupt_time = interrupt_time;

}

 

// ---- setup and loop ------------------------------------------------------

 

void setup() {

 

  smallLCD.begin(16, 2);

  smallLCD.clear();

  smallLCD.print("Barebones Mp3!");

  Audio.Init();//Init module 

  createChars();

 

  Mp3.begin(mp3_cs, mp3_dcs, mp3_rst, mp3_dreq);

  Mp3.volume(mp3_vol);

 

  sd_card_setup();

  sd_dir_setup();

  sd_file_open();

 

  pinMode(5, OUTPUT);

 

  attachInterrupt(1, skip_forward, CHANGE);

  attachInterrupt(0, skip_backward, CHANGE);

}

void loop() {

  switch(current_state) {

 

    case DIR_PLAY:

      dir_play();

      break;

 

    case MP3_PLAY:

      mp3_play();

      break;

 

    case PAUSED:

      break;

  }

  Audio.ReadFreq(FreqVal);//return 7 value of 7 bands pass filiter 

                          //Frequency(Hz):63  160  400  1K  2.5K  6.25K  16K

                          //FreqVal[]:      0    1    2    3    4    5    6  

  printFreqValue(); 

}

 

     2. MP3 File Processing File

void get_title_from_id3tag () {

  unsigned char id3[3];       // pointer to the first 3 characters to read in

 

  sd_file.seek(0);

  sd_file.read(id3, 3);

 

  if (id3[0] == 'I' && id3[1] == 'D' && id3[2] == '3') {

    unsigned char pb[4];       // pointer to the last 4 characters we read in

    unsigned char c;           // the next 1 character in the file to be read

 

    sd_file.read(pb, 3);

    sd_file.read(pb, 4);

 

    unsigned long v2l = ((unsigned long) pb[0] << (7 * 3)) +

                        ((unsigned long) pb[1] << (7 * 2)) +

                        ((unsigned long) pb[2] << (7 * 1)) + pb[3];

 

    sd_file.seek(0);

 

    while (1) {

 

      sd_file.read(&c, 1);

 

      pb[0] = pb[1];

      pb[1] = pb[2];

      pb[2] = pb[3];

      pb[3] = c;

 

      if (pb[0] == 'T' && pb[1] == 'I' && pb[2] == 'T' && pb[3] == '2') {

 

        sd_file.read(pb, 4);

 

        unsigned long tl = ((unsigned long) pb[0] << (8 * 3)) +

                           ((unsigned long) pb[1] << (8 * 2)) +

                           ((unsigned long) pb[2] << (8 * 1)) + pb[3];

        tl--;

 

        sd_file.read(pb, 2);

        sd_file.read(&c, 1);

 

        if (c) {

          sd_file.read(pb, 2);

          tl -= 2;

        }

 

        if (tl > max_title_len) tl = max_title_len;

 

        sd_file.read(title, tl);

        title[tl] = '\0';

        break;

      }

      else

      if (pb[1] == 'T' && pb[2] == 'T' && pb[3] == '2') {

 

        sd_file.read(pb, 4);

 

        unsigned long tl = ((unsigned long) pb[0] << (8 * 2)) +

                           ((unsigned long) pb[1] << (8 * 1)) + pb[2];

        tl--;

 

        if (tl > max_title_len) tl = max_title_len;

 

        sd_file.read(title, tl);

        title[tl] = '\0';

        break;

      }

      else

      if (sd_file.position() == v2l) {

 

        strncpy(title, fn, max_name_len);

        break;

      }

    }

  }

  else {

 

    sd_file.seek(sd_file.size() - 128);

    sd_file.read(id3, 3);

 

    if (id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G') {

 

      sd_file.read(title, 30);

 

      for (char i = 30 - 1; i >= 0; i--) {

        if (title[i] <= ' ' || title[i] > 126) {

          title[i] = '\0';

        }

        else {

          break;

        }

      }

    }

    else {

 

      strncpy(title, fn, max_name_len);

    }

  }

}

 

     3. Utilities File

 

File sd_root;                   // the sd partition's root ('/') directory

 

void sd_card_setup() {

  if (!SD.begin(sd_cs)) {

    smallLCD.clear();

    smallLCD.print("sd card failed");

    return;

  }

  sd_root = SD.open("/");

 

  if (!sd_root) {

    smallLCD.clear();

    smallLCD.print("couldn't root vol");

    return;

  }

}

 

void sd_dir_setup() {

  num_songs = 0;

 

  sd_root.rewindDirectory();

 

  while (num_songs < max_num_songs) {

 

    File p = sd_root.openNextFile();

    if (!p) break;

 

    if (p.name()[0] == '~' || p.name()[0] == '.' || p.isDirectory()) {

      continue;

    }

 

    char i;

 

    for (i = max_name_len - 5; i > 0; i--) {

      if (p.name()[i] == '.') break;

    }

    i++;

 

    if ((p.name()[i] == 'M' && p.name()[i+1] == 'P' && p.name()[i+2] == '3') ||

        (p.name()[i] == 'W' && p.name()[i+1] == 'A' && p.name()[i+2] == 'V')) {

 

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

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

      }

      num_songs++;

    }

  }

}

 

int freeRam () {

  extern int __heap_start, *__brkval; 

  int v;

  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 

}

 

void get_current_song_as_fn() {

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

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

  }

}

 

void print_title_to_lcd() {

  get_title_from_id3tag();

 

  smallLCD.clear();

  smallLCD.print(title);

}