/ Gordon_Cole_MP3_Player / Gordon_Cole_MP3_Player.ino
Gordon_Cole_MP3_Player.ino
1 // SPDX-FileCopyrightText: 2017 Carter Nelson for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 // 6 // Gordon Cole MP3 Player 7 // 8 9 #include <SPI.h> 10 #include <SD.h> 11 #include <Adafruit_VS1053.h> 12 13 // Guide is based on Feather M0 Express 14 // ARDUINO_SAMD_FEATHER_M0 defines only 15 // VS1053 Pins 16 #define VS1053_RESET -1 // VS1053 reset pin (not used!) 17 #define VS1053_CS 6 // VS1053 chip select pin (output) 18 #define VS1053_DCS 10 // VS1053 Data/command select pin (output) 19 #define CARDCS 5 // Card chip select pin 20 #define VS1053_DREQ 9 // VS1053 Data request, ideally an Interrupt pin 21 // Button Pins 22 #define BUTTON_PLAY 13 // PLAY / STOP button 23 #define BUTTON_PAUSE 12 // PAUSE / RESUME button 24 #define BUTTON_NEXT 11 // NEXT button 25 // Status LED 26 #define LED_STATUS 19 // status LED 27 #define BLINK_RATE 500 // blink rate in ms 28 // Volume Control 29 #define KNOB_VOLUME 0 // volume knob 30 #define KNOB_MIN 0 // min ADC value 31 #define KNOB_MAX 1023 // max ADC value 32 #define VOL_MIN 0 // min volume (most loud) 33 #define VOL_MAX 50 // max volume (most quiet) 34 #define VOL_UPDATE 250 // update rate in ms 35 #define VOL_SAMPLES 10 // number of reads for average 36 #define VOL_SAMPLE_RATE 5 // ms delay per sample 37 #define VOL_THRESHOLD 20 // vol must change by this many counts 38 // Maximum number of files (tracks) to load 39 #define TRACKS_MAX 100 40 // Player behavior 41 #define AUTO_PLAY_NEXT true // true to automatically go to next track 42 43 unsigned long currentMillis; 44 unsigned long previousBlinkMillis, previousVolMillis; 45 int currentKnob, previousKnob; 46 int volume; 47 int currentTrack, totalTracks; 48 char trackListing[TRACKS_MAX][13] = {' '}; 49 enum mode { 50 PLAYING, 51 PAUSED, 52 STOPPED 53 } currentMode = STOPPED; 54 55 Adafruit_VS1053_FilePlayer musicPlayer = 56 Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS); 57 58 //----------------------------------------------------------------------------- 59 void setup() { 60 Serial.begin(9600); 61 // Leave commented for standalone operation, uncomment for troubleshooting 62 //while (!Serial) ; 63 64 // Initialize pins 65 pinMode(BUTTON_PLAY, INPUT_PULLUP); 66 pinMode(BUTTON_PAUSE, INPUT_PULLUP); 67 pinMode(BUTTON_NEXT, INPUT_PULLUP); 68 pinMode(LED_STATUS, OUTPUT); 69 70 // Initialize status LED 71 previousBlinkMillis = millis(); 72 digitalWrite(LED_STATUS, LOW); 73 74 Serial.println("\n\nGordon Cole MP3 Player"); 75 76 // Initialize the music player 77 if (! musicPlayer.begin()) { 78 Serial.println(F("Couldn't find VS1053, do you have the right pins defined?")); 79 while (1) { 80 digitalWrite(LED_STATUS, !digitalRead(LED_STATUS)); 81 delay(100); 82 } 83 } 84 Serial.println(F("VS1053 found")); 85 musicPlayer.softReset(); 86 87 // Make a tone to indicate VS1053 is working 88 musicPlayer.sineTest(0x44, 500); 89 90 // Set volume for left, right channels. lower numbers == louder volume! 91 previousVolMillis = millis(); 92 previousKnob = analogRead(KNOB_VOLUME); 93 volume = map(previousKnob, KNOB_MIN, KNOB_MAX, VOL_MIN, VOL_MAX); 94 Serial.print("Volume = "); Serial.println(volume); 95 musicPlayer.setVolume(volume, volume); 96 97 // Initialize the SD card 98 if (!SD.begin(CARDCS)) { 99 Serial.println(F("SD failed, or not present")); 100 while (1) { 101 digitalWrite(LED_STATUS, !digitalRead(LED_STATUS)); 102 delay(100); 103 } 104 } 105 Serial.println("SD OK!"); 106 107 // Load list of tracks 108 Serial.println("Track Listing"); 109 Serial.println("============="); 110 totalTracks = 0; 111 loadTracks(SD.open("/"), 0); 112 currentTrack = 0; 113 114 // Setup interrupts (DREQ) for playback 115 musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT); 116 } 117 118 //----------------------------------------------------------------------------- 119 void loop() { 120 // Check and set volume 121 updateVolume(); 122 123 // Update status LED 124 updateStatusLED(); 125 126 // Auto play next track if feature enabled 127 if (AUTO_PLAY_NEXT) { 128 if (currentMode==PLAYING && musicPlayer.stopped()) { 129 currentTrack = ++currentTrack < totalTracks ? currentTrack : 0; 130 Serial.print("Next "); 131 Serial.print(currentTrack); Serial.print("="); 132 Serial.println(trackListing[currentTrack]); 133 musicPlayer.startPlayingFile(trackListing[currentTrack]); 134 currentMode = PLAYING; 135 } 136 } 137 138 // Start / Stop 139 if (!digitalRead(BUTTON_PLAY)) { 140 if (musicPlayer.stopped()) { 141 Serial.print("Start "); 142 Serial.print(currentTrack); Serial.print("="); 143 Serial.println(trackListing[currentTrack]); 144 musicPlayer.startPlayingFile(trackListing[currentTrack]); 145 currentMode = PLAYING; 146 } else { 147 Serial.println("Stopped."); 148 musicPlayer.stopPlaying(); 149 currentMode = STOPPED; 150 } 151 delay(250); 152 } 153 154 // Pause / Resume 155 if (!digitalRead(BUTTON_PAUSE)) { 156 if (!musicPlayer.stopped()) { 157 if (musicPlayer.paused()) { 158 Serial.println("Resumed"); 159 musicPlayer.pausePlaying(false); 160 currentMode = PLAYING; 161 } else { 162 Serial.println("Paused"); 163 musicPlayer.pausePlaying(true); 164 currentMode = PAUSED; 165 } 166 } 167 delay(250); 168 } 169 170 // Next 171 if (!digitalRead(BUTTON_NEXT)) { 172 if (!musicPlayer.stopped()) { 173 Serial.println("Stopping current playback."); 174 musicPlayer.stopPlaying(); 175 } 176 currentTrack = ++currentTrack < totalTracks ? currentTrack : 0; 177 Serial.print("Next "); 178 Serial.print(currentTrack); Serial.print("="); 179 Serial.println(trackListing[currentTrack]); 180 musicPlayer.startPlayingFile(trackListing[currentTrack]); 181 currentMode = PLAYING; 182 delay(250); 183 } 184 } 185 186 //----------------------------------------------------------------------------- 187 void updateVolume() { 188 // Rate limit 189 currentMillis = millis(); 190 if (currentMillis - previousVolMillis < VOL_UPDATE) return; 191 previousVolMillis = currentMillis; 192 // Get an average reading 193 currentKnob = 0; 194 for (int i=0; i<VOL_SAMPLES; i++) { 195 currentKnob += analogRead(KNOB_VOLUME); 196 delay(VOL_SAMPLE_RATE); 197 } 198 currentKnob /= VOL_SAMPLES; 199 // Only update if it's changed 200 if (abs(currentKnob-previousKnob) > VOL_THRESHOLD) { 201 Serial.print("["); Serial.print(currentKnob); 202 Serial.print(","); Serial.print(previousKnob); 203 Serial.print("] "); 204 previousKnob = currentKnob; 205 volume = map(currentKnob, KNOB_MIN, KNOB_MAX, VOL_MIN, VOL_MAX); 206 Serial.print("Volume set to: "); Serial.println(volume); 207 musicPlayer.setVolume(volume, volume); 208 } 209 } 210 211 //----------------------------------------------------------------------------- 212 void updateStatusLED() { 213 if (musicPlayer.paused()) { 214 // Blink it like a polaroid 215 currentMillis = millis(); 216 if (currentMillis - previousBlinkMillis > BLINK_RATE) { 217 previousBlinkMillis = currentMillis; 218 digitalWrite(LED_STATUS, !digitalRead(LED_STATUS)); 219 } 220 } else if (!musicPlayer.stopped()) { 221 // It's so on again 222 digitalWrite(LED_STATUS, HIGH); 223 } else { 224 // It's so off again 225 digitalWrite(LED_STATUS, LOW); 226 } 227 } 228 229 //----------------------------------------------------------------------------- 230 void loadTracks(File dir, int level) { 231 while (true) { 232 File entry = dir.openNextFile(); 233 if (!entry) return; 234 if (entry.isDirectory()) { 235 // Recursive call to scan next dir level 236 loadTracks(entry, level + 1); 237 } else { 238 // Only add files in root dir 239 if (level == 0) { 240 // And only if they have good names 241 if (nameCheck(entry.name())) { 242 strncpy(trackListing[totalTracks], entry.name(), 12); 243 Serial.print(totalTracks); Serial.print("="); 244 Serial.println(trackListing[totalTracks]); 245 totalTracks++; 246 } 247 } 248 } 249 entry.close(); 250 // Stop scanning if we hit max 251 if (totalTracks >= TRACKS_MAX) return; 252 } 253 } 254 255 //----------------------------------------------------------------------------- 256 bool nameCheck(char* name) { 257 int len = strlen(name); 258 // Check length 259 if (len <= 4) return false; 260 // Check extension 261 char* ext = strrchr(name,'.'); 262 if (!( 263 strcmp(ext,".MP3") == 0 || 264 strcmp(ext,".OGG") == 0 265 )) return false; 266 // Check first character 267 switch(name[0]) { 268 case '_': return false; 269 } 270 return true; 271 }