/*************************************************** MH2024Player - A Mini MP3 Player for MH2024 MP3 Player module. with console command input control. *************************************************** This example includes all the usable functions of the MH2024Player in 'multiple folder' mode. The program structure could be used with the 'single folder' mode, with afjustments SD Card: - Maximum size 32Gb - Formatted as FAT32 - Directory '01' to '99' - File type mp3 - File names start with 3 digits (001-255). Remainder of name is ignored. Other options (eg more directories) may work Command List: RS Restart. Device is reset and all parameters set to default. EQ xx Set Equalization (0-6) 0: Normal 1: Pop 2: Rock 3: Jazz 4: Classic 5: Bass VO xx Set volume to xx (0-30). PM xx Set play mode 0: Play single 1: Play sequential from current song. Loop to song 1 at end of folder 2: Play sequential from current song. Loop to next folder/song 1 at end of folder. Loop to first folder after all folders 3: Play this song and repeat forever 4: Play random song from current folder. 5: Play random song from any folder (The first time a folder is accessed the 'random' song will be 1) CD xx Set Current Directory to xx. Current song (if any) continues PA Pause. Playback stops until a play command (eg, PL, NX) is issued PL Play current track. Use this when module paused (eg at end of single play) CT xx Play track xx (from current folder). Current song stops and new song starts NX Skip forward in current directory to next track. Loop to first track in directory. PR Skip back in current directory to previous track. Loop to last track in directory. V+ Volume up one step. Maximum is 30. V- Volume down one step. Minimum is 0 (silent). QV Show volume (0-30) QE Show Equalization (0-6) QM Show Mode QD Show number of TF Card directories QT Show number of tracks in current directory QP Show Play status: Mode, Folder, Track } ****************************************************/ /***********Notice and Trouble shooting*************** 1.Connection and Diagram can be found here https://picaxe.com/docs/spe033.pdf Note: MH2024 module is extrmeley sensitive to connections. It may not operate properly with solderless breadboard, and will be very noisy. ZIF socket and positive pressure connections are recommended. Do not overload the output: high impendance earbuds or audio amplifier is required. ****************************************************/ #include "Arduino.h" // UART at pins 10 and 11 #include "SoftwareSerial.h" // OLED 128x64 display at 0x3c #include "SSD1306Ascii.h" #include "SSD1306AsciiAvrI2c.h" #define I2C_ADDRESS 0x3C SSD1306AsciiAvrI2c Display; //#define RECV_PIN 2 //MP3 Player Tx pin //#define BUSY_PIN 3 //MP3 Player Busy SoftwareSerial mySoftwareSerial(10, 11); // MCU RX, TX // pin 10 (Arduino Rx) connects to module pin 3. // Pin 11 (Arduino Tx) connects to module pin 2. In some cases a small value resistor may be required. const uint8_t ACK = 0x41; // Codes const uint8_t NEXT = 0x01; // Play Next const uint8_t PREV = 0x02; // Play Preious const uint8_t TRCK = 0x03; // Play track const uint8_t VOLP = 0x04; // Volume increase const uint8_t VOLM = 0x05; // Volume decrease const uint8_t VOLU = 0x06; // Volume set const uint8_t SPEQ = 0x07; // EQ set const uint8_t SPPM = 0x08; // Mode set const uint8_t SPPS = 0x09; // Source set const uint8_t STBY = 0x0A; // Standby const uint8_t NORM = 0x0B; // Normal const uint8_t REST = 0x0C; // Reset const uint8_t PLAY = 0x0D; // Play (Resume) const uint8_t PAUS = 0x0E; // Pause const uint8_t SPFT = 0x0F; // Play folder and file const uint8_t VADJ = 0x10; // Volume adjust const uint8_t SSRP = 0x11; // Start/Stop Replay const uint8_t UFIN = 0x3A; // U-Disk track finished const uint8_t TFIN = 0x3B; // TF-Card Track Finished const uint8_t FFIN = 0x3C; // Flash Track Finished const uint8_t INIT = 0x3D; // Intialization request const uint8_t ERRR = 0x41; // Error const uint8_t ACKN = 0x41; // Acknowledge const uint8_t QRST = 0x42; // Query Status const uint8_t QRVO = 0x43; // Query Volume const uint8_t QREQ = 0x44; // Query Equalization const uint8_t QRMD = 0x45; // Query Mode const uint8_t QRVR = 0x46; // Query Version const uint8_t QRUT = 0x47; // Query # U_disk tracks const uint8_t QRTT = 0x48; // Query # TF_card tracks const uint8_t QRFT = 0x49; // Query # Flash tracks const uint8_t KEEP = 0x4A; // Keep On const uint8_t QRTC = 0x4B; // Query Current TF-Card Track const uint8_t QRUC = 0x4C; // Query Current U-Disk Track const uint8_t QRFC = 0x4D; // Query Current Flash Track const uint8_t QRCT = 0x4E; // Query Current Directory Tracks const uint8_t QRTF = 0x4F; // Query TF-Card Folders const int pin1A = 2; const int pin1B = 3; const int pin2A = 4; const int pin2B = 5; const int pin3A = 6; const int pin3B = 7; const int pin4A = 8; const int pin4B = 9; int files; // Number of files (total for card) int folder; // Current folder number (1-99) int song; // Current song number (1-folderSizes[folder] int playMode = 5; // Loop/repeat mode (default to random all) int eq; // Equalization uint8_t volume; // Volume uint8_t folderSizes[100]; // [0] Number of folders, [1-99] Files per folder // Button Press Mode bool adjFolder = false; bool adjFile = false; bool adjVolume = false; // Global variables for module uint8_t cmd[10]; // Command array char myData[31]; // Console input for command int ptr = 0; // Receive buffer pointer // =========================================== void setup() { delay(2000); // Startup time for module pinMode(LED_BUILTIN, OUTPUT); pinMode (pin1A, INPUT_PULLUP); pinMode (pin1B, INPUT_PULLUP); pinMode (pin2A, INPUT_PULLUP); pinMode (pin2B, INPUT_PULLUP); pinMode (pin3A, INPUT_PULLUP); pinMode (pin3B, INPUT_PULLUP); pinMode (pin4A, INPUT_PULLUP); pinMode (pin4B, INPUT_PULLUP); Serial.begin(9600); Serial.println(F("GD3200B MP3 Player Interactive Demo")); randomSeed(analogRead(A0)); Display.begin(&Adafruit128x64, I2C_ADDRESS); Display.setFont(Adafruit5x7); Display.clear(); //oled.setFont(X11fixed7x14); Display.setFont(fixed_bold10x15); Display.println(F("MP3 Player")); Display.println(F("Initializing..")); Serial.println(); Serial.println(F("Enter a player command and press Send.")); Serial.println(F("RS Restart. Device is reset and all parameters set to default")); Serial.println(F("EQ xx Set Equalization (0-6)")); Serial.println(F(" 0: Normal 1: Pop 2: Rock")); Serial.println(F(" 3: Jazz 4: Classic 5: Bass")); Serial.println(F("VO xx Set volume to xx (0-30)")); Serial.println(F("PM xx Set play mode")); Serial.println(F(" 0: Play single")); Serial.println(F(" 1: Play this song and repeat forever")); Serial.println(F(" 2: Play sequential from current song. Loop to song 1 at end of folder")); Serial.println(F(" 3: Play sequential from current song. Loop to next folder, song 1 at end of folder. Loop to first folder after all folders")); Serial.println(F(" 4: Play random song from this folder")); Serial.println(F(" 5: Play random song from any folder")); Serial.println(F("CD xx Set Current Directory to xx. Current song (if any) continues")); Serial.println(F("PA Pause. Playback stops until a play command (eg, PL, NX) is issued")); Serial.println(F("PL Play current track. Use this when module paused (eg at end of single play")); Serial.println(F("CT xx Play track xx (from current folder). Current song stops and new song starts")); Serial.println(F("NX Skip forward in current directory to next track.")); Serial.println(F("PR Skip back in current directory to previous track.")); Serial.println(F("V+ Volume up one step")); Serial.println(F("V- Volume down one step")); Serial.println(F("QV Show volume")); Serial.println(F("QE Show Equalization")); Serial.println(F("QM Show Mode")); Serial.println(F("QD Show number of TF Card directories")); Serial.println(F("QT Show number of files in current directory")); Serial.println(F("QP Show Play status: Mode, Folder, Track")); mySoftwareSerial.begin(9600); uint8_t code; // Reset and Wait for initialization // cmdExec(0x0C); // Reset code = 0x3F; // Initialization response do { do {} while (!getResponse()); // 0x3F Initialize if (cmd[3] == code) { Serial.println("Devices = " + String(cmd[6])); break; } } while (true); delay(3000); // Adjust for number of folders (about 1s per 10 folders) Display.println("...complete."); // Set source device cmdExec(0x09, 2); // 0x09 Set device to TF Serial.println("Device# = 2"); // Note: No query for current device! cmdExec(0x06,1); // Suppress weird noise ?? // Query number of TF Files qryExec(0x48); // Query TF files (not used) // Query number of TF Folders qryExec(0x4F); // Query TF folders Serial.println("Folder Count = " + String(folderSizes[0])); // Get track count for folders // Options: // 1. All folders must be same size. Do a single 0x4E query and copy into the whole table. // 2. All folders must be same nominated size (eg, 255) // 3. Hard code the sizes array for a particular TF card // 4. Ignore the problem. Assume 255. A default track might be played if the selected track does not exist. // or it might just hang. // 5. Get size for each folder when it is first accessed (preferred, but undocumented) // Option 5. This is not reliable. It may be due to the number of folders, // the number of files, or that the command is simply faulty. folder = 1; getFolderCount(1); // Get count of files for folder #1 // Set other defaults //cmdExec(0x08, 1); // 0x08 Repeat mode: Folder //cmdExec(0x11, 0); // 0x11 Repeat: Off //cmdExec(0x0B); // 0x0B Paused: Off eq = 1; cmdExec(0x07, eq); // 0x07 Set EQ qryExec(0x44); // 0x44 Query EQ volume = 20; cmdExec(0x06, volume); // 0x09 Set volume qryExec(0x43); // 0x43 Query volume // Set folder number //folder = random(1, folderSizes[0] + 1); Serial.println("Current Folder = " + String(folder)); // Set song number // Note: track 000 is silence, so playable tracks are 1->folderSize-1 song = random(1, folderSizes[folder]); Serial.println("Current Song = " + String(song)); // Play cmdExec(0x0F, folder << 8 | song); showStatus(); } // =========================================== // =========================================== // Global variables for main loop char uCmd[10]; // User console input charaters int uPtr = 0; // Buffer pointer for user input boolean uDone = false; // Flag for user input complete int uData; // Numeric part of user input void loop() { // Check for user data and process. // This segment can be re-written for alternative inputs. The inputs can be converted // to the codes used here, or they can be converted to any other code with // corresponding changes to the selection method in uCmdProcess(). if (Serial.available() > 0) { // Format is AA\n or AA nn...\n // Process User Command char c = Serial.read(); if (c == '\n') { uDone = true; uCmd[uPtr] = '\0'; // for debug print Serial.print(c); } else { uCmd[uPtr] = c; uPtr = (uPtr + 1) % 9; } } if (uDone) { // Extract command uCmd[0] = toupper(uCmd[0]); uCmd[1] = toupper(uCmd[1]); // Extract numeric parameter uData = 0; for (int i = 2; i < uPtr; i++) { if ((uCmd[i] > 47) & (uCmd[i] < 58)) { uData = uData * 10 + (uCmd[i] - 48); } } uCmdProcess(); // Clean up uDone = false; uCmd[0] = '\0'; uPtr = 0; showStatus(); } // Check for button press and process. // A Folder File Volume + // B Reset EQ Mode - if (btnPress(pin1A)) processFolder(); if (btnPress(pin1B)) processReset(); if (btnPress(pin2A)) processFile(); if (btnPress(pin2B)) processEQ(); if (btnPress(pin3A)) processVolume(); if (btnPress(pin3B)) processMode(); if (btnPress(pin4A)) processPlus(); if (btnPress(pin4B)) processMinus(); // Check for incoming message (unrequested - no wait) if (getResponse()) { if (cmd[3] == 0x3D) { // Song end - check for loop/repeat mode if (playMode == 0) cmdExec(0x0e); // 0 Do not Loop/repeat else { // Loop/Repeat is active - check type // 1 Repeat (no change to song #) if (playMode == 2) { // 2 Next in folder song = song % folderSizes[folder] + 1; } if (playMode == 3) { // 3 Next on disk song = song % folderSizes[folder] + 1; if (song == 1) folder = folder % (folderSizes[0] + 1) + 1; if (folderSizes[folder] == 0) getFolderCount(folder); } if (playMode == 4) { // 4 Random this folder song = random(1, folderSizes[folder]); } if (playMode == 5) { // 5 Random any folder folder = random(1, folderSizes[0] + 1); if (folderSizes[folder] == 0) getFolderCount(folder); song = random(1, folderSizes[folder]); } // Play new track cmdExec(0x0F, folder << 8 | song); // Play folder/track Serial.println("Mode = " + String(playMode) + " Folder = " + String(folder) + " Song = " + String(song)); } showStatus(); delay (200); } } } // =========================================== // =========================================== // sendCommand (with overrides) // Form a valid command sequence and transmit it. // No paramaters void sendCommand(uint8_t command) { sendCommand(command, (int) 0); } // Single byte param void sendCommand(uint8_t command, uint8_t arg0) { sendCommand(command, (int) arg0); } // Two byte params void sendCommand(uint8_t command, uint8_t arg0, uint8_t arg1) { sendCommand(command, (int) (arg0 << 8) | arg1); } void sendCommand(uint8_t command, int param) { cmd[0] = 0x7e; cmd[1] = 0xff; cmd[2] = 0x06; cmd[3] = command; cmd[4] = 0x01 ; cmd[5] = param >> 8 & 0xff; // DH cmd[6] = param & 0xff; // DL uint16_t ck = cmdCheckSum(); // Calculate checksum cmd[7] = ck >> 8 & 0xff; // Insert checksum high byte cmd[8] = ck & 0xff; // Insert checksum low byte cmd[9] = 0xef; Serial.print("Sending: "); for (int q = 0; q < 10; q++) { // Show it to the user Serial.print(HEX2(cmd[q])); Serial.print(" "); } // Handle transmit message // Serial.println(" " + sendDetail()); // Print the message info sendDetail(); // Update internal variables Serial.println(); // Send it mySoftwareSerial.write(cmd, 10); // Send the message } // =========================================== // =========================================== // getResponse // Check for received data stream and process // Call this function as required to see if a response is available. If a response is required, // call it in a tight loop until it returns true. // Process: // Examine the serial stream for a character. If none, return false. // If characters are available then look for a command start byte. Otherwise return false. // If a start byte is found, store it and start the state machine. // If state machine is started, confirm this byte is valid for the current state (0,1,2 only), // insert the byte into the array and increment the state. // If not in state machine and not a start byte, then return false. // If byte is invalid for current state (0,1,2 only), reset the state and return false. // If state is complete return true. Array is in cmd[]. // Note: checksum is not checked, terminator is not checked. boolean getResponse() { if (mySoftwareSerial.available() > 0) { uint8_t inData = mySoftwareSerial.read(); switch (ptr) { // Bytes 3 to 9 no checking, just poke case 9: case 8: case 7: case 6: case 5: case 4: case 3: cmd[ptr] = inData; ptr++; break; case 2: // Byte 2 must be 0x06 if (inData == 0x06) { cmd[ptr] = inData; ptr++; } else { // If not, reset point ptr = 0; Serial.println("Discard " + String(HEX2(inData))); } break; case 1: // Byte 1 must be 0xFF if (inData == 0xFF) { cmd[ptr] = inData; ptr++; } else { ptr = 0; Serial.println("Discard " + String(HEX2(inData))); } break; case 0: // Byte 0 must be 0x7E if (inData == 0x7E) { cmd[ptr] = inData; ptr++; } else { ptr = 0; Serial.println("Discard " + String(HEX2(inData))); } break; default: ptr = 0; break; } if (ptr == 10) { // Full 10 bytes received! if (cmd[3] != 0x41) { // Suppres display of Ack Serial.print(F("Receiving: ")); int p = 0; int q = 0; while (q < 10) { Serial.print(HEX2(cmd[q])); // Display it Serial.print(" "); q++; } } Serial.println(" " + recvDetail()); // Action the response information ptr = 0; // Reset the state machine for next message return true; // Get the response code } } return false; } // =========================================== // =========================================== // sendDetail // Process response code (if requied). Also prepare information msg. String sendDetail() { String detail = ""; switch (cmd[3]) { case 0x01: detail = F("Next"); break; case 0x02: detail = F("Previous"); break; case 0x03: detail = "Play Track " + String(cmd[5] << 8 | cmd[6]); break; case 0x04: detail = F("Volume Up"); if (volume++ > 30) volume = 30; break; case 0x05: detail = F("Volume Down"); if (volume-- == 0) volume = 1; break; case 0x06: volume = cmd[6]; detail = "Set Volume " + String(volume); break; case 0x07: detail = "Set EQ " + String(cmd[6]); break; case 0x08: playMode = cmd[6]; detail = "Set Mode " + String(playMode); break; case 0x09: detail = "Set Source " + String(cmd[6]); break; case 0x0A: detail = F("Sleep"); break; case 0x0B: detail = F("Resume"); break; case 0x0C: detail = F("Reset"); break; case 0x0D: detail = F("Play"); break; case 0x0E: detail = F("Pause"); break; case 0x0F: detail = "Set Folder " + String(cmd[5]) + " " + String(cmd[6]); break; case 0x10: detail = "Volume Adjust " + String(cmd[6]); // Force cmd[5] to 0x01 break; case 0x11: detail = "Start/Stop repeat " + String(cmd[6]); break; case 0x3F: detail = F("Query Initialization"); break; case 0x40: detail = F("Error"); break; case 0x41: detail = F("Ack"); break; case 0x42: detail = F("Query Status"); break; case 0x43: detail = F("Query Volume"); break; case 0x44: detail = F("Query EQ"); break; case 0x45: detail = F("Query Mode"); break; case 0x46: detail = F("Query Version"); break; case 0x47: detail = F("Query U_Disk Files"); break; case 0x48: detail = F("Query TF Files"); break; case 0x49: detail = F("Query Flash Files"); break; case 0x4A: detail = F("Continue"); break; case 0x4B: detail = F("Query TF Track"); break; case 0x4C: detail = F("Query U_Disk Track"); break; case 0x4D: detail = F("Query Flash Track"); break; case 0x4E: detail = F("Query Folder files"); break; case 0x4F: detail = F("Query Folder count"); break; default: // if nothing else matches, do the default detail = "Invalid " + String(cmd[3], HEX); break; } return detail; showStatus(); } // =========================================== // =========================================== // recvDetail // Process responses from module (cmd[]) // Form information string and process response (if required). // Acknowledgements are ignored. String recvDetail() { String detail = ""; switch (cmd[3]) { case 0x01: // 0x01 - 0x11 - No returned data. case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: // detail = "Command " + String(cmd[3]) + " " + String(cmd[5] << 8) + String(cmd[6]); break; case 0x3A: detail = "U_Disk/TF insert " + String(cmd[6]); break; case 0x3B: detail = "U_Disk/TF Remove " + String(cmd[6]); break; case 0x3C: detail = "U_Disk Track finish " + String(cmd[5] << 8 | cmd[6]); break; case 0x3D: detail = "TF Track Finish " + String(cmd[5] << 8 | cmd[6]); break; case 0x3E: detail = "Flash Track Finish " + String(cmd[5] << 8 | cmd[6]); break; case 0x3F: // todo: Send reset, reset MCU (wiat for initialization) detail = "Initialization " + String(cmd[6]); break; case 0x40: // todo: Resend last command detail = "Error " + String(cmd[6]); break; case 0x41: // ignore ?? detail = F("Ack") ; break; case 0x42: // Update internal variable ?? detail = "Status " + String(cmd[6]); break; case 0x43: volume = cmd[6]; detail = "Volume " + String(volume); break; case 0x44: // Update internal variable ?? eq = cmd[6]; detail = "EQ " + String(eq); break; case 0x45: // Update internal variable ?? // Not relevant detail = "Mode " + String(playMode); break; case 0x46: // Ignore ?? detail = "Version " + String(cmd[6]); break; case 0x47: detail = "U_disk Files " + String(cmd[5] << 8 | cmd[6]); // Update internal variable ?? break; case 0x48: // Update internal variable ?? detail = "TF Files " + String(cmd[5] << 8 | cmd[6]); break; case 0x49: // Update internal variable ?? detail = "Flash Files " + String(cmd[5] << 8 | cmd[6]); break; case 0x4A: detail = F("Continue"); break; case 0x4B: // Update internal variable ?? detail = "U_Disk Track " + String(cmd[5] << 8 | cmd[6]); break; case 0x4C: // Update internal variable ?? detail = "TF Track " + String(cmd[5] << 8 | cmd[6]); break; case 0x4D: // Update internal variable ?? detail = "Flash Track " + String(cmd[5] << 8 | cmd[6]); break; case 0x4E: // Note: folder number is not included with response! detail = "Folder Files " + String(cmd[6]); folderSizes[folder] = cmd[6]; delay(300); break; case 0x4F: folderSizes[0] = cmd[5] * 256 + cmd[6]; if (folderSizes[0] > 99) folderSizes[0] = 99; // Maximum number of folders allowed detail = "TF Folders " + String(folderSizes[0]); break; default: // if nothing else matches, do the default // Ignore ?? detail = "Invalid " + String(cmd[3], HEX); break; } return detail; } // =========================================== // =========================================== // uCmdProcess - Process User Input // Handle each valid user command // - call the equivalent module command/query, or // - update internal values, or // - both void uCmdProcess() { if (uCmd[0] == 'R' && uCmd[1] == 'S') { // Serial.println("Reset!"); // ... return; } if (uCmd[0] == 'E' && uCmd[1] == 'Q') { // Set Equalization Serial.println("Equalization "); eq = uData; cmdExec(0x07, uData); return; } if (uCmd[0] == 'V' && uCmd[1] == 'O') { Serial.println("Volume Set " + String(uData)); cmdExec(0x06, uData); return; } if (uCmd[0] == 'P' && uCmd[1] == 'M') { playMode = uData % 6; Serial.println("Play Mode " + String(playMode)); return; } if (uCmd[0] == 'C' && uCmd[1] == 'D') { folder = uData % (folderSizes[0] + 1); Serial.print("Current Folder " + String(folder)); return; } if (uCmd[0] == 'C' && uCmd[1] == 'T') { //Set Current Track if (folderSizes[folder] == 0) getFolderCount(folder); if ((uData > 0) && (uData <= folderSizes[folder])) { song = uData; Serial.print("Current Track!" + String(folder) + " " + String(song)); // Play the selected track cmdExec(0x0F, (folder << 8) | song); } return; } if (uCmd[0] == 'P' && uCmd[1] == 'A') { // Pause cmdExec(0x0E); Serial.print("Pause"); return; } if (uCmd[0] == 'P' && uCmd[1] == 'L') { // Play cmdExec(0x0D); Serial.println("Play! Folder " + String(folder) + "Song " + String(song)); return; } if (uCmd[0] == 'N' && uCmd[1] == 'X') { // Next // cmdExec(0x01); song = song % folderSizes[folder] + 1; cmdExec(0x0F, folder << 8 | song); // Play folder/track Serial.println("Next Track"); return; } if (uCmd[0] == 'P' && uCmd[1] == 'R') { // Previous // cmdExec(0x02); song = song - 1; if (song == 0) song = folderSizes[folder]; cmdExec(0x0F, folder << 8 | song); // Play folder/track Serial.print("Previous Track"); return; } if (uCmd[0] == 'V' & uCmd[1] == '+') { // Volume Up cmdExec(0x04); Serial.println("Increase Volume"); return; } if (uCmd[0] == 'V' && uCmd[1] == '-') { // Volume Down cmdExec(0x05); Serial.println("Decrease Volume"); return; } if (uCmd[0] == 'Q' && uCmd[1] == 'V') { // Query Volume qryExec(0x43); return; } if (uCmd[0] == 'Q' && uCmd[1] == 'E') { // Query Equalization Serial.println("Query Equalization "); qryExec(0x44); return; } if (uCmd[0] == 'Q' && uCmd[1] == 'M') { // Query loop/repeat mode Serial.println("Mode = " + String(playMode)); return; } if (uCmd[0] == 'Q' && uCmd[1] == 'T') { qryExec(0x4E, folder); return; } if (uCmd[0] == 'Q' && uCmd[1] == 'D') { qryExec(0x4F); return; } if (uCmd[0] == 'Q' && uCmd[1] == 'P') { Serial.println("Mode = " + String(playMode) + " Folder = " + String(folder) + " Song = " + String(song)); return; } } //=========================================== //=========================================== // cmdExec - prepare a command for sending and wait for Ack void cmdExec(uint8_t c) { cmdExec(c, 0); } void cmdExec(uint8_t c, int param) { sendCommand(c, param); do { do {} while (!getResponse()); if (cmd[3] == ACK) { break; } else sendCommand(c, param); // Re-send???? } while (true); return; } //=========================================== //=========================================== void qryExec(uint8_t c) { qryExec(c, 0); } // qryExec - prepare a query for sending and wait for Ack and response to query void qryExec(uint8_t c, int param) { sendCommand(c, param); // Send command do { do {} while (!getResponse()); if (cmd[3] == c) { // Get response Serial.println(recvDetail()); break; } // Ack will occur before response. if (cmd[3] != ACK) sendCommand(c, param); // Re-send Query } while (true); return; } //=========================================== // Button process //=========================================== void processReset() { Serial.println(F("Resetting....")); cmdExec(0x0c); delay(100); asm volatile ("jmp 0"); } void processMode() { playMode = (playMode + 1) % 6; showStatus(); Serial.println("Mode Adjust " + String(playMode)); } void processEQ() { eq = (eq + 1) % 6; cmdExec(0x07, eq); showStatus(); Serial.println("EQ Adjust " + String(eq)); } void processFolder() { adjFolder = !adjFolder; adjFile = false; adjVolume = false; showStatus(); if (adjFolder) Serial.println(F("Folder Adjust ON")); if (!adjFolder) Serial.println(F("Folder Adjust OFF")); } void processFile() { adjFolder = false; adjFile = !adjFile; adjVolume = false; showStatus(); if (adjFile) Serial.println(F("File Adjust ON")); if (!adjFile) Serial.println(F("File Adjust OFF")); } void processVolume() { adjFolder = false; adjFile = false; adjVolume = !adjVolume; showStatus(); if (adjVolume) Serial.println(F("Volume Adjust ON")); if (!adjVolume) Serial.println(F("Volume Adjust OFF")); } void processPlus() { if (adjFolder) { folder = folder % (folderSizes[0] + 1) + 1; if (folderSizes[folder] == 0) getFolderCount(folder); cmdExec(0x0F, folder << 8 | song); // Play folder/track showStatus(); } else if (adjFile) { song = song % folderSizes[folder] + 1; cmdExec(0x0F, folder << 8 | song); // Play folder/track showStatus(); } else if (adjVolume) { if (volume++ > 30) volume = 30; cmdExec(0x06, volume); showStatus(); } } void processMinus() { if (adjFolder) { folder--; if (folder < 0) folder = 0; cmdExec(0x0F, folder << 8 | song); // Play folder/track showStatus(); } if (adjFile) { song--; if (song < 0) song = 0; cmdExec(0x0F, folder << 8 | song); // Play folder/track showStatus(); } if (adjVolume) { if (volume-- == 0) volume = 1; cmdExec(0x06, volume); showStatus(); } } // Helpers // =========================================== // Convert byte to HEX String HEX2(uint8_t a) { char str[3]; sprintf(str, "%0.2x", a); String myStr = str; myStr.toUpperCase(); return myStr; } // Convert two hex characters to byte (uin8_t) // Hex string is in mydata. Must be upper case // Parameter points to mydata index uint8_t hexToByte(int i) { byte myHex[2]; byte y = myData[i++]; if (y > 0x46) y = y - 0x20; if (y <= 0x39) myHex[0] = y - 0x30; else myHex[0] = y - 0x37; y = myData[i]; if (y <= 0x39) myHex[1] = y - 0x30; else myHex[1] = y - 0x37; return ((unsigned int)(myHex[0] << 4) | (unsigned int)(myHex[1])); } // Calculate checksum for message array in cmd // =========================================== uint16_t cmdCheckSum() { uint16_t sum = 0; for (int i = 1; i < 7; i++) { // From version to param2 sum += cmd[i]; } return -sum; } // Get Folder Count // ============================================ void getFolderCount(uint8_t f) { Serial.println("(Folders)" + String(folderSizes[0])); cmdExec(0x0F, f << 8 | 0); // play track 000 from this folder. Must Exist!! delay(200); // Wait for it to start qryExec(0x4E, f); // Get the folder count folderSizes[f] = cmd[6]; // Update the table Serial.println("Folder Files " + String(f) + " " + String(folderSizes[f])); return; } // Check if a button is pressed bool btnPress(int btn) { if (digitalRead(btn) == LOW) { do { if (digitalRead(btn) == HIGH) { delay(20); if (digitalRead(btn) == HIGH) { delay(20); return true; } } } while (digitalRead(btn) == LOW); } return false; } // Show status on attached i2c display // ============================================ void showStatus() { Display.clear(); Display.println("Mode/EQ:" + String(playMode) + "/" + String(eq)); Display.println("Volume :" + String(volume)); Display.println("Song:" + String(song) + "/" + String(folder)); if (adjVolume) Display.println("Volume +/-"); else if (adjFolder) Display.println("Folder +/-"); else if (adjFile) Display.println("File +/-"); return; }