Part 1: Wav Music Player Using Arduino Uno and SD Card

Introduction

Few days ago, I am thinking if I could create a mini karaoke using an Arduino Uno, LCD for the lyrics display, SD card for the sound files and the lyrics, and some switches for the playback functions. I tried to search through google and youtube for some resources but even the basic functionality of music playing is not available. Lyrics display with Arduino is also a challenge, so I decided to strip the lyrics display functionality for the meantime and focus first with basic music playback. So here it is.

Bill Of Materials

  1. Arduino Uno for the main board microcontroller.
  2. Keypad and LCD shield
  • the LCD will be used to display the current wav file being played. It will also be used for displaying symbols of Play, Pause, Play Next, Play Previous, Volume UP, and Volume DOWN.
  • the button switches functions as basic playback control.
  1. SD card module which will be access by SPI communication protocol.
  2. SD card with converted wav files. It should be
  • 8 bit resolution
  • 16, 000 Hz sampling rate
  • mono audio channels
  • unsigned 8 bit PCM format

Hardware Instruction

  1. Connect the LCD/Keypad shield above the Arduino Uno.
  2. the speaker is connected to Arduino Uno digital pin 3 and GND. I am using this speaker box for amplification of the sound. But you may also try to connect it directly to a speaker without an amplifier, sound will be very weak.
  3. the SD card module is connected as follows:
  • SD card Chip Select or pin 1 is connected to Arduino Uno digital pin 2 by this white wire.
  • SD card MOSI or pin 2 is connected to Arduino Uno digital pin 11 by this black wire.
  • SD card GND or pin 3 is connected to Arduino Uno GND pin by this brown wire.
  • SD card 5V or pin 4 is connected to Arduino Uno 5V pin by this red wire.
  • SD card SCLK or pin 5 is connected to Arduino Uno digital pin 13 by this orange wire.
  • SD card MISO or pin 7 is connected to Arduino Uno digital pin 12 by this yellow wire.
  1. The sound files is accessed using the Arduino SD and SPI libraries.
  2. The sound is played to digital pin 3 by Pulse Width Modulation (PWM) using the Arduino TMRPCM library. The default speaker output of TMRPCM is via digital pin 9. But since I am using a Keypad/LCD shield, digital pin 9 is already use as LCD enable pin. To make this work, you need to edit the pcmConfig.h under the TMRPCM library and uncomment by removing the double forward slash before the #define USE_TIMER2. This will enable digital pin 3 as speaker output.
  • pressing the SELECT button functions as Play or Pause the current music
  • pressing the LEFT button functions as Play Previous music
  • pressing the RIGHT button functions as Play Next music
  • pressing the UP button functions as Volume UP or increase the volume. The TMRPCM library allows a volume upto 7 but a volume of 4 is the maximum this setup could allow, above this is just a noise.
  • pressing the DOWN button functions as Volume DOWN or decrease the volume.

Video Demonstration

Source Code

  1/*
  2 * PROJECT: Arduino Mini Karaoke
  3 *      1. wav files and lyrics is located in SD card
  4 *      2. Lyrics will be displayed to 16x2 LCD - not yet
  5 *      3. 
  6 *      
  7 * LINKS:
  8 *  https://github.com/TMRh20/TMRpcm/wiki
  9 *  https://github.com/TMRh20/TMRpcm/wiki/Advanced-Features
 10 *  https://github.com/TMRh20/TMRpcm
 11 *  
 12 *  https://audio.online-convert.com/convert-to-wav
 13 */
 14#include "TMRpcm.h"
 15#include "SD.h"
 16#include "SPI.h"
 17#include "LiquidCrystal.h"
 18
 19TMRpcm audio;
 20LiquidCrystal lcd( 8,  9,  4,  5,  6,  7);
 21File root;
 22
 23#define SD_CS_PIN     2
 24#define SPEAKER_PIN   3
 25#define TMRPCM_VOLUME 4
 26
 27// ####################################
 28// PROTOTHREADING DEFINITIONS HERE:
 29// ####################################
 30unsigned long checkKeypadTaskTimer = 0;
 31const unsigned long checkKeypadTaskInterval = 200;
 32
 33unsigned long playMusicTaskTimer = 0;
 34const unsigned long playMusicTaskInterval = 100;
 35
 36unsigned long updateLCDTaskTimer = 0;
 37const unsigned long updateLCDTaskInterval = 5000;
 38
 39// ####################################
 40// GLOBAL VARIABLES HERE:
 41// ####################################
 42String wavFiles[8];
 43byte wavCnt = 0;
 44byte wavCurrPos = 0;
 45int leftState, rightState, upState, downState, selectState = LOW;
 46long lastDebounceTime = 0;  // the last time the output pin was toggled
 47long debounceDelay = 500;    // the debounce time; increase if the output flickers
 48boolean isPlaying = false;
 49boolean isPaused = false;
 50boolean isPlayNOW = false;
 51boolean isPlayNEXT = false;
 52boolean isPlayPREVIOUS = false;
 53boolean isVolumeUP = false;
 54boolean isVolumeDOWN = false;
 55boolean isBusy = true;
 56int currVolume = TMRPCM_VOLUME;
 57// variables for LCD custom char
 58byte bPLAY[8] = {0x00, 0x00, 0x08, 0x0C, 0x0E, 0x0C, 0x08, 0x00};
 59byte bPAUSE[8] = {0x00, 0x00, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x00};
 60byte bNEXT[8] = {0x00, 0x00, 0x11, 0x19, 0x1D, 0x19, 0x11, 0x00};
 61byte bPREVIOUS[8] = {0x00, 0x00, 0x11, 0x13, 0x17, 0x13, 0x11, 0x00};
 62byte bUPVOLUME[8] = {0x02, 0x07, 0x02, 0x11, 0x11, 0x1B, 0x0E, 0x04};
 63byte bDOWNVOLUME[8] = {0x00, 0x07, 0x00, 0x11, 0x11, 0x1B, 0x0E, 0x04};
 64
 65// ####################################
 66// PROTOTYPES HERE:
 67// ####################################
 68void getWAVFiles(File dir, int numTabs);
 69int cntChar(String str, char chr);
 70char* strToChar(String str);
 71String parseString(int idSeparator, char separator, String str);
 72int get_key(unsigned int input);
 73
 74// #########################################################
 75//  Put code here for initializations
 76// #########################################################
 77void setup()
 78{
 79  // Initialized LCD
 80  lcd.begin(16, 2);
 81  lcd.createChar(0, bPLAY);
 82  lcd.createChar(1, bPAUSE);
 83  lcd.createChar(2, bNEXT);
 84  lcd.createChar(3, bPREVIOUS);
 85  lcd.createChar(4, bUPVOLUME);
 86  lcd.createChar(5, bDOWNVOLUME);
 87  lcd.setCursor(0,0);
 88  lcd.print(" tech-to-tinker ");
 89
 90  // Initialized TMRPCM
 91  audio.speakerPin = SPEAKER_PIN;
 92  if(!SD.begin(SD_CS_PIN))
 93  {
 94    //Serial.println("SD fail");
 95    return;
 96  }
 97  audio.setVolume(TMRPCM_VOLUME);   // This is the maximum volume 
 98
 99  // Get the wav files available
100  root = SD.open("/");
101  getWAVFiles(root, 0);
102  lcd.setCursor(1,1);
103  lcd.write((byte)1);
104  lcd.setCursor(3,1);
105  lcd.print(wavFiles[wavCurrPos]);
106}
107
108// #########################################################
109//  Put your main code here, to run repeatedly:
110// #########################################################
111void loop() {
112  if(millis() >= checkKeypadTaskTimer + checkKeypadTaskInterval){
113    checkKeypadTaskTimer += checkKeypadTaskInterval;
114    checkKeypad();
115  }
116
117  if(millis() >= playMusicTaskTimer + playMusicTaskInterval){
118    playMusicTaskTimer += playMusicTaskInterval;
119    playMusic();
120  }
121
122  if(millis() >= updateLCDTaskTimer + updateLCDTaskInterval){
123    updateLCDTaskTimer += updateLCDTaskInterval;
124    updateLCD();
125  }
126}
127
128void updateLCD() {
129  if ( audio.isPlaying() ) {
130    if (isPaused) { // currently in paused
131      lcd.setCursor(1,1);
132      lcd.write((byte)1);
133    } else {  // from pause to play
134      lcd.setCursor(1,1);
135      lcd.write((byte)0);
136    }
137  }
138}
139
140void playMusic() {
141  if (isPlayNEXT) {
142    audio.disable();  // this is necessary to prevent overrun
143    lcd.setCursor(1,1);
144    lcd.write((byte)2);
145    isPlayNEXT = false;
146    isPlaying = false;
147    isPlayNOW = true;
148    isPaused = false;
149
150    if ( wavCurrPos < (wavCnt-1) ) {
151      wavCurrPos = wavCurrPos + 1;       // increase music pointer
152    } else {
153      wavCurrPos = 0;     // set music pointer to first
154    }
155  }
156
157  if (isPlayPREVIOUS) {
158    audio.disable();  // this is necessary to prevent overrun
159    lcd.setCursor(1,1);
160    lcd.write((byte)3);
161    isPlayPREVIOUS = false;
162    isPlaying = false;
163    isPlayNOW = true;
164    isPaused = false;
165
166    if ( wavCurrPos == 0 ) {
167      wavCurrPos = (wavCnt-1);  // set pointer to last array
168    } else {
169      wavCurrPos = wavCurrPos - 1;           // decrease pointer
170    }
171  }
172
173  if ( audio.isPlaying() ) {
174
175  } else {
176    if (isPlaying) {
177      audio.disable();  // this is necessary to prevent overrun
178      isPlayNEXT = false;
179      isPlaying = false;
180      isPlayNOW = true;
181      isPaused = false;
182  
183      if ( wavCurrPos < (wavCnt-1) ) {
184        wavCurrPos = wavCurrPos + 1;       // increase music pointer
185      } else {
186        wavCurrPos = 0;     // set music pointer to first
187      }
188    }
189  }
190
191  if (isPlayNOW) {  // Select button pressed
192    isPlayNOW = false;  // disable immediately to prevent playing again 
193    if ( audio.isPlaying() ) {
194      // pause playing
195      audio.pause();  // pauses/unpauses playback
196      if (isPaused) { // currently in paused
197        isPaused = false;
198      } else {  // from pause to play
199        isPaused = true;
200      } 
201    } else {
202      // start playing
203      audio.play( strToChar( wavFiles[wavCurrPos] ) );    //plays a file 
204      lcd.setCursor(3,1);
205      lcd.print("             ");
206      lcd.setCursor(3,1);
207      lcd.print(wavFiles[wavCurrPos]);
208      isPlaying = true;
209    }
210  } 
211  
212  if ( (isVolumeUP) && (currVolume<4) ) {  
213   lcd.setCursor(1,1);  
214   lcd.write((byte)4);  
215   currVolume++;  
216   audio.setVolume(currVolume);  
217   isVolumeUP = false;  
218  }  
219  if ( (isVolumeDOWN) && (currVolume>-1) ) {  
220   lcd.setCursor(1,1);  
221   lcd.write((byte)5);  
222   currVolume--;  
223   audio.setVolume(currVolume);  
224   isVolumeDOWN = false;  
225  }  
226}
227
228int adc_key_val[5] ={50, 200, 400, 600, 800 };  // Original, works after actually reading the analog0
229int NUM_KEYS = 5;
230int adc_key_in;
231int key=-1;  
232
233void checkKeypad() {
234  adc_key_in = analogRead(0);                 // Read the value from the sensor
235  key = get_key(adc_key_in);                  // Convert into key press
236  switch(key) {
237    case 0: // RIGHT
238      isPlayNEXT = true;
239      break;
240    case 1: // UP
241      isVolumeUP = true;
242      break;
243    case 2: // DOWN
244      isVolumeDOWN = true;
245      break;
246    case 3: // LEFT
247      isPlayPREVIOUS = true;
248      break;
249    case 4: // SELECT
250      if (isPlayNOW) {
251        isPlayNOW = false;
252      } else {
253        isPlayNOW = true;
254      }
255      break;
256    default:  // OTHERS
257      break;
258  }
259}
260
261int get_key(unsigned int input) {
262    int k;
263    for (k = 0; k < NUM_KEYS; k++)    // Start counting
264    {
265      //if (input == adc_key_val[k])          // Works, but doesn't allow for deviations
266      if (input < adc_key_val[k])             // should be more reliable as it looks for a range instead of a fixed value.
267      {
268            return k;                         // Breaks the for loop
269      }
270   }
271    if (k >= NUM_KEYS)k = -1;                 // No valid key pressed
272    return k;                                 // So break the loop and look for another keypress
273}
274
275void getWAVFiles(File dir, int numTabs) {
276  byte fileCnt = 0;
277  while (true) {
278    File entry =  dir.openNextFile();
279    if (! entry) {
280      // no more files
281      break;
282    }
283    //Serial.println(entry.name());
284    String ext = parseString(cntChar(entry.name(), '.'), '.', entry.name());
285    if (ext == "WAV") 
286    {
287      wavFiles[wavCnt] = entry.name();
288      wavCnt++;
289    }
290    
291    entry.close();
292  }
293
294  if (wavCnt > 0) {
295    for (byte i = 0; i < wavCnt; i++) {
296      Serial.println(wavFiles[i]);
297    }
298  }   
299  Serial.print("wavCnt :");
300  Serial.println(wavCnt,HEX);
301}
302
303int cntChar(String str, char chr) {
304  int n = 0;
305  for (int i = 0; i < str.length(); i++)
306  {
307    if (str[i] == chr) n++;
308  }
309  return n;
310}
311
312char* strToChar(String str) {
313  int len = str.length() + 1;
314  char* buf = new char[len];
315  strcpy(buf, str.c_str());
316  return buf;
317}
318
319String parseString(int idSeparator, char separator, String str) { // like a split JS
320  String output = "";
321  int separatorCout = 0;
322  for (int i = 0; i < str.length(); i++)
323  {
324    if ((char)str[i] == separator)
325    {
326      separatorCout++;
327    }
328    else
329    {
330      if (separatorCout == idSeparator)
331      {
332        output += (char)str[i];
333      }
334      else if (separatorCout > idSeparator)
335      {
336        break;
337      }
338    }
339  }
340  return output;
341}

Call To Action

That’s all everyone. I hope you enjoy watching this video. Please consider supporting my channel by SUBSCRIBING. Click the LIKE button and leave your comments and suggestions in the comment box below.

Thank you and have a good day. Happy tinkering.

Up next, I will try to sync the lyrics and display to LCD.
George signing out, bye.



Posts in this series



No comments yet!

GitHub-flavored Markdown & a sane subset of HTML is supported.