Catalex serial MP3 player -- WIP software

If all you want is a very simple serial control MP3 player with SD slot (controller can’t get to the SD through the module) then these are cheap and work.

This Catalex tool sketch is to let the user see what is sent to the player, what comes back, and example code to do whatever I’m trying or got to work.

If you enter n it will skip to or start the next tune. A string of n’s will skip that many songs ahead. It took a while to figure out waiting for the player to acknowledge the last command before sending the next. I’ll see about tightening that code up some day but it works for now. – Once I get my OTW Daoki MP3 modules I may ditch putting time into this Catalex tool, use the code as you will, it’s non-blocking task-based code.

// catalex tool ver 5/24/17 11PM EST
// Work in progress, not a final form, a frame to try things out on with a few working parts.
// looks for serial monitor chars. n is next, p is prev, # still not working.
// Serial monitor (115200 baud) shows command and response sequences.
// Compiled with Arduino IDE 1.8.2

#include "Arduino.h"

#include <SoftwareSerial.h>
SoftwareSerial Catalex( 10, 11 ); // RX, TX

uint8_t chr, str, playNext;
int8_t replyCount;

int8_t doNext, doNow, isAuto, isAck, skipCount;
enum doState 
{ 
  doAuto = -1, doIdle = 0, doPlayNext, doPlayPrev, doWaitForReply, doMoar
};

const char *doNames[] = // doAuto is for isAuto, special case 
{
  "doIdle", "doPlayNext", "doPlayPrev", "doWaitForReply", "doMoar"
};


/* --- from Catalex doc:
 Command bytes: $S VER Len CMD Feedback data $O
 Mark Byte Byte description
 $S   0x7E Every command should start with $(0x7E)
 VER  0xFF Version information
 Len  0xxx The number of bytes of the command without starting byte and ending
 byte
 CMD  0xxx Such as PLAY and PAUSE and so on
 Feedback 0xxx 0x00 = not feedback, 0x01 = feedback 
 data  The length of the data is not limit and usually it has two bytes
 $O 0xEF Ending byte of the command
 */

uint8_t resetPlayer[8] = 
{
  0x7E, 0xFF, 6, 0x0C, 0, 0, 0, 0xEF
};

uint8_t startSinglePlay[8] =
{
  0x7E, 0xFF, 6, 0x19, 0, 0, 0, 0xEF
};

uint8_t endSinglePlay[8] =
{
  0x7E, 0xFF, 6, 0x19, 0, 0, 1, 0xEF
};

uint8_t playNextSong[8] = 
{ 
  0x7E, 0xFF, 06, 01, 01, 0, 0, 0xEF 
};

uint8_t playPrevSong[8] = 
{ 
  0x7E, 0xFF, 6, 2, 1, 0, 0, 0xEF 
};

uint8_t prePlayFolder[5] =
{
  0x7E, 0xFF, 0x06, 0x17, 0x00 // 01 02 EF
};

uint8_t prePlayFolderAndSong[5] = 
{ 
  0x7E, 0xFF, 06, 0x0F, 0x0 
};

void writeCatalex( uint8_t byt )
{
  Catalex.write( byt ); 
  Serial.print( "0x" ); 
  if ( chr < 0x10 ) Serial.print( "0" ); 
  Serial.print( byt, HEX ); 
}

void setup(void)
{
  Serial.begin( 115200 );
  Serial.println( F( "Catalex tool" ));
  Serial.println( F( "Catalex tool" ));
  Serial.println( F( "Catalex tool\n" ));

  Catalex.begin( 9600 );

  Serial.println( F( "Reset player:" ));
  for ( byte i = 0; i < 8; i++ )
  {
    chr = resetPlayer[ i ];
    writeCatalex( chr );
    if ( i < 7 ) Serial.print( ", " ); 
  }
  Serial.println( ); 

  isAuto = doAuto;
  doNext = doPlayNext;
}

void loop()
{
  // User Input task
  if ( Serial.available())
  {
    chr = Serial.read();
    if ((chr | 32 ) == 'a' )
    {
      Serial.println( F( "\nAutoplay commanded" ));
      isAuto = doAuto;
      if ( doNow == doIdle ) 
      {        
        doNext = doPlayNext;
      }
    }
    else if ((chr | 32 ) == 's' )
    {
      Serial.println( F( "\nSingleplay commanded" ));
      doNext = isAuto = doIdle;
    }    
    else if (( chr | 32 ) == 'n' )
    {
      Serial.println( F( "\nPlay Next commanded" ));
      skipCount++;
      doNext = doPlayNext;
    }
    else if (( chr | 32 ) == 'p' )
    {
      Serial.println( F( "\nPlay Prev commanded" ));
      skipCount--;
      doNext = doPlayPrev;
    } 
    else if (( chr >= '0' ) && ( chr <= '9' )) // experimenting, still don't work
    {
      Serial.println( ); 
      for ( byte i = 0; i < 5; i++ )
      {
        str = prePlayFolder[ i ];
        writeCatalex( str );
        Serial.print( ", " ); 
      }
      writeCatalex( chr );
      Serial.print( ", " ); 
      str = 2;
      writeCatalex( str );
      Serial.print( ", " ); 
      str = 0xEF;
      writeCatalex( str );
      Serial.println( );
    } // else the read char was 'illegal'
  }
  // End of User Input task

  // Catalex Input task
  if ( Catalex.available())
  {
    if ( replyCount < 0 )
    {
      replyCount = 0;
    }
    chr = Catalex.read();
    Serial.print( "0x" ); 
    if ( chr < 0x10 ) Serial.print( "0" ); 
    Serial.print( chr, HEX );

    if (( isAuto == doAuto ) || ( isAuto > 0 ))
    {
      if (( replyCount == 3 ) && ( chr == 0x3D ))
      {
        playNext++;
      } 
    }
    if ( chr == 0xEF )
    {
      Serial.println( );
      replyCount = -1;
      isAck = 1;
    } 
    else
    {
      replyCount++;
      Serial.print( "  " ); 
    }
  }
  // End of Catalex Input task

  // Process tasks
  if ( skipCount != 0 )
  {
    if (( doNow == doIdle ) || ( isAck ))
    {
      if ( skipCount > 0 )
      {
        doNow = doPlayNext;
        skipCount--;
      }
      else if ( skipCount < 0 )
      {
        doNow = doPlayPrev;
        skipCount++;
      }
    }
  }  
  else if (( doNow == doIdle ) && ( doNext != doIdle ))
  {
    doNow = doNext;
  }  
  else if (( doNow == doIdle ) && isAuto )
  {
    doNow = doPlayNext;
  }
  // End of Process tasks


  // Yank Catalex's Chain task
  if ( doNow > doIdle )
  {
    switch ( doNow )
    {
    case doPlayNext :
      Serial.println( ); 
      Serial.println( F( "play next" )); 
      for ( byte i = 0; i < 8; i++ )
      {
        str = playNextSong[ i ];
        writeCatalex( str );
        if ( i < 7 ) Serial.print( ", " ); 
      }
      Serial.println( );
      replyCount = 0; // forces doWaitForReply to wait for next EF
      isAck = 0;
      doNow = doWaitForReply;
      break;

    case doPlayPrev :
      Serial.println( ); 
      Serial.println( F( "play prev" )); 
      for ( byte i = 0; i < 8; i++ )
      {
        str = playPrevSong[ i ];
        writeCatalex( str );
        if ( i < 7 ) Serial.print( ", " ); 
      }
      Serial.println( );
      replyCount = 0; // forces doWaitForReply to wait for next EF
      isAck = 0;
      doNow = doWaitForReply;
      break;

    case doWaitForReply : // waiting for end of Catalex's reply
      if ( isAck )
      {
        Serial.println( ); 
        Serial.println( F( " Reply End" )); 
        replyCount = 0;
        if ( isAuto == doAuto )
        {
          doNow = doMoar;
        }
        else if ( skipCount > 0 )
        {
          doNow = doMoar;
        }
        else
        {
          doNow = doIdle;
        }
      }
      break;

    case doMoar : // waiting for Catalex to signal song play finish
      if (( isAuto == doAuto ) || ( skipCount != 0 ))
      {
        if ( playNext >= 2 ) // Catalex sed song finished playing
        {
          if ( skipCount > 0 )
          {
            skipCount--;
          }
          else // must be < 0
          {
            skipCount++;
          }

          playNext = 0;
          doNow = doPlayNext;
        }  // no else, just settle in and wait
      }
      else
      {
        doNow = doIdle;
      }
    }
  }
  // End of Yank Catalex's Chain task
}

Next version adds 2 buttons, very expandable, put the attached small library files in the project folder.
Ground pin 7 and it skips ahead 12 songs. Ground pin 6 and it skips back 1 song.

catalexTool

// catalex tool ver 5/25/17 5PM EST
// looks for serial monitor chars. n is next, p is prev, # still not working.
// Serial monitor (115200 baud) shows command and response sequences.
// 

#include "Arduino.h"
#include "button.h" 

#include <SoftwareSerial.h>
SoftwareSerial Catalex( 10, 11 ); // RX, TX

uint8_t chr, str, playNext;
int8_t replyCount;

int skipCount;
int8_t doNext, doNow, isAuto, isAck;

enum doState 
{ 
  doAuto = -1, doIdle = 0, doPlayNext, doPlayPrev, doWaitForReply, doMoar
};

const char *doNames[] = // doAuto is for isAuto, special case 
{
  "doIdle", "doPlayNext", "doPlayPrev", "doWaitForReply", "doMoar"
};

const byte howManyButtons = 2;
byte buttonIdx, buttonStatus;
byte buttonPin[ howManyButtons ] = { 6, 7 };
button user[ howManyButtons ]; // button array

/* --- from Catalex doc:
 Command bytes: $S VER Len CMD Feedback data $O
 Mark Byte Byte description
 $S   0x7E Every command should start with $(0x7E)
 VER  0xFF Version information
 Len  0xxx The number of bytes of the command without starting byte and ending
 byte
 CMD  0xxx Such as PLAY and PAUSE and so on
 Feedback 0xxx 0x00 = not feedback, 0x01 = feedback 
 data  The length of the data is not limit and usually it has two bytes
 $O 0xEF Ending byte of the command
 */

uint8_t resetPlayer[8] = 
{
  0x7E, 0xFF, 6, 0x0C, 0, 0, 0, 0xEF
};

uint8_t startSinglePlay[8] =
{
  0x7E, 0xFF, 6, 0x19, 0, 0, 0, 0xEF
};

uint8_t endSinglePlay[8] =
{
  0x7E, 0xFF, 6, 0x19, 0, 0, 1, 0xEF
};

uint8_t playNextSong[8] = 
{ 
  0x7E, 0xFF, 06, 01, 01, 0, 0, 0xEF 
};

uint8_t playPrevSong[8] = 
{ 
  0x7E, 0xFF, 6, 2, 1, 0, 0, 0xEF 
};

uint8_t prePlayFolder[5] =
{
  0x7E, 0xFF, 0x06, 0x17, 0x00 // 01 02 EF
};

uint8_t prePlayFolderAndSong[5] = 
{ 
  0x7E, 0xFF, 06, 0x0F, 0x0 
};

void writeCatalex( uint8_t byt )
{
  Catalex.write( byt ); 
  Serial.print( "0x" ); 
  if ( chr < 0x10 ) Serial.print( "0" ); 
  Serial.print( byt, HEX ); 
}

void setup(void)
{
  Serial.begin( 115200 );
  Serial.println( F( "Catalex tool" ));
  Serial.println( F( "Catalex tool" ));
  Serial.println( F( "Catalex tool\n" ));

  for ( buttonIdx = 0; buttonIdx < howManyButtons; buttonIdx++ )
  {
    user[ buttonIdx ].setButton( buttonPin[ buttonIdx ], 5 ); // buttons on pins 4,5,6,7
  }
  buttonIdx = 0;

  Catalex.begin( 9600 );

  Serial.println( F( "Reset player:" ));
  for ( byte i = 0; i < 8; i++ )
  {
    chr = resetPlayer[ i ];
    writeCatalex( chr );
    if ( i < 7 ) Serial.print( ", " ); 
  }
  Serial.println( ); 

  isAuto = doAuto;
  doNext = doPlayNext;
}

void loop()
{
  // button input task
  buttonStatus = user[ buttonIdx ].runButton(); // turns the crank once, make sure it runs often!

  if ( buttonStatus == 2 ) // just pressed
  {
    switch ( buttonIdx )
    {
      case 0 :
      skipCount--;
      break;
      
      case 1 :
      skipCount += 12;
      break;
    }
  }  
  
  if ( ++buttonIdx >= howManyButtons )
  {
    buttonIdx = 0;
  }  
  // end of button input task
  
  // User Input task
  if ( Serial.available())
  {
    chr = Serial.read();
    if ((chr | 32 ) == 'a' )
    {
      Serial.println( F( "\nAutoplay commanded" ));
      isAuto = doAuto;
      if ( doNow == doIdle ) 
      {        
        doNext = doPlayNext;
      }
    }
    else if ((chr | 32 ) == 's' )
    {
      Serial.println( F( "\nSingleplay commanded" ));
      doNext = isAuto = doIdle;
    }    
    else if (( chr | 32 ) == 'n' )
    {
      Serial.println( F( "\nPlay Next commanded" ));
      skipCount++;
      doNext = doPlayNext;
    }
    else if (( chr | 32 ) == 'p' )
    {
      Serial.println( F( "\nPlay Prev commanded" ));
      skipCount--;
      doNext = doPlayPrev;
    } 
    else if (( chr >= '0' ) && ( chr <= '9' )) // experimenting, still don't work
    {
      Serial.println( ); 
      for ( byte i = 0; i < 5; i++ )
      {
        str = prePlayFolder[ i ];
        writeCatalex( str );
        Serial.print( ", " ); 
      }
      writeCatalex( chr );
      Serial.print( ", " ); 
      str = 2;
      writeCatalex( str );
      Serial.print( ", " ); 
      str = 0xEF;
      writeCatalex( str );
      Serial.println( );
    } // else the read char was 'illegal'
  }
  // End of User Input task

  // Catalex Input task
  if ( Catalex.available())
  {
    if ( replyCount < 0 )
    {
      replyCount = 0;
    }
    chr = Catalex.read();
    Serial.print( "0x" ); 
    if ( chr < 0x10 ) Serial.print( "0" ); 
    Serial.print( chr, HEX );

    if (( isAuto == doAuto ) || ( isAuto > 0 ))
    {
      if (( replyCount == 3 ) && ( chr == 0x3D ))
      {
        playNext++;
      } 
    }
    if ( chr == 0xEF )
    {
      Serial.println( );
      replyCount = -1;
      isAck = 1;
    } 
    else
    {
      replyCount++;
      Serial.print( "  " ); 
    }
  }
  // End of Catalex Input task

  // Process tasks
  if ( skipCount != 0 )
  {
    if (( doNow == doIdle ) || ( isAck ))
    {
      if ( skipCount > 0 )
      {
        doNow = doPlayNext;
        skipCount--;
      }
      else if ( skipCount < 0 )
      {
        doNow = doPlayPrev;
        skipCount++;
      }
    }
  }  
  else if (( doNow == doIdle ) && ( doNext != doIdle ))
  {
    doNow = doNext;
  }  
  else if (( doNow == doIdle ) && isAuto )
  {
    doNow = doPlayNext;
  }
  // End of Process tasks


  // Yank Catalex's Chain task
  if ( doNow > doIdle )
  {
    switch ( doNow )
    {
    case doPlayNext :
      Serial.println( ); 
      Serial.println( F( "play next" )); 
      for ( byte i = 0; i < 8; i++ )
      {
        str = playNextSong[ i ];
        writeCatalex( str );
        if ( i < 7 ) Serial.print( ", " ); 
      }
      Serial.println( );
      replyCount = 0; // forces doWaitForReply to wait for next EF
      isAck = 0;
      doNow = doWaitForReply;
      break;

    case doPlayPrev :
      Serial.println( ); 
      Serial.println( F( "play prev" )); 
      for ( byte i = 0; i < 8; i++ )
      {
        str = playPrevSong[ i ];
        writeCatalex( str );
        if ( i < 7 ) Serial.print( ", " ); 
      }
      Serial.println( );
      replyCount = 0; // forces doWaitForReply to wait for next EF
      isAck = 0;
      doNow = doWaitForReply;
      break;

    case doWaitForReply : // waiting for end of Catalex's reply
      if ( isAck )
      {
        Serial.println( ); 
        Serial.println( F( " Reply End" )); 
        replyCount = 0;
        if ( isAuto == doAuto )
        {
          doNow = doMoar;
        }
        else if ( skipCount > 0 )
        {
          doNow = doMoar;
        }
        else
        {
          doNow = doIdle;
        }
      }
      break;

    case doMoar : // waiting for Catalex to signal song play finish
      if (( isAuto == doAuto ) || ( skipCount != 0 ))
      {
        if ( playNext >= 2 ) // Catalex sed song finished playing
        {
          if ( skipCount > 0 )
          {
            skipCount--;
          }
          else // must be < 0
          {
            skipCount++;
          }

          playNext = 0;
          doNow = doPlayNext;
        }  // no else, just settle in and wait
      }
      else
      {
        doNow = doIdle;
      }
    }
  }
  // End of Yank Catalex's Chain task
}

button.cpp (2.2 KB)

button.h (1.01 KB)

Ha! Got my new MP3 modules in and the chip might be same or newer release of the Catalex player.

New players are DFPlayer Minis with complete doc online in spe033.pdf and on the DFRobot Wiki.