YX5300 MP3 Player

The YX5300 MP3 module is easily interfaced to a microcontroller, creating MP3 player with a user interface. Using the MD_YX5300 library and an understanding of how the device works (see the previous posts here and here), this article describes the code for a simple MP3 player and a more complex player with an LCD module display.

The full code for the MP3 players described in this article can be found in the MD_YX5300 library examples folder. This article will describe the principles behind how the players work and will only highlight the code required to illustrate each point.

A simple MP3 player

To keep this version of the MP3 player as simple as possible, we will use a minimum of hardware (one each of tact switch, potentiometer, LED, and the MP3 module) to implement the following functionality:

  • Start/pause track playback with tact switch simple press
  • Play next track with tact switch long press
  • Volume control using the potentiometer
  • Run state shown using LED indicator – on for play, off for pause
Simple MP3 player block diagram

In keeping with the minimalist theme, the code is implemented throughout using synchronous library calls (ie, no unsolicited messages are processed) and the tact switch is managed using the existing MD_UISwitch library.

Additionally, all the tracks to be played are in one folder to further simplify the user interface.

Using the library, loop() code is quite straightforward as we don’t expect or will process any unsolicited messages. The loop() runs the MP3 module event code to process the serial interface and then checks each of the user interface components, dealing with them immediately.

void loop()
{
  mp3.check();        // run the mp3 receiver
  processVolume();    // set the volume if required
  processSwitch();    // process the user switch
  setStatusLED();     // set the status LED to current status
}

The function processVolume() reads the pot, scales the volume within legal bounds and then, if the volume has changed, synchronously tells the MP3 module to set that volume. The special parameter bForce can be used to force writing the volume whether it has changed or not – useful to synch up the current value during initialisation, such as in setup().

void processVolume(bool bForce = false)
// read the volume pot and set the volume if it has changed or forced
{
  static uint8_t vol;   // current audio volume
  uint8_t newVolume = map(analogRead(POT_VOLUME), 0, 1023, 0, 
mp3.volumeMax());

  if (newVolume != vol || bForce)
  {
    vol = newVolume;
    mp3.volume(vol);
  }
}

The processSwitch() function is slightly more complicated but similarly deals with setting the next song or pausing the playback inside the function.

Adding complexity and an LCD display

While the simple player is is useful, it is limited in how it can handle the changes within the MP3 player and the status LED is not that informative.

A more complex example needs to be able to handle unsolicited messages and includes the following features:

  • Add a Hitachi type LCD module (1602 LCD display) to display the current playback state.
  • Detects when the SD card has been removed/inserted in the YX5300 and handles the change appropriately.
  • One tact switch to change player mode:
    • Start/pause track playback with simple press.
    • Random/loop/single playback track progression with long press.
  • A rotary encoder,using built in switch to toggle control of
    • Play next/previous track.
    • Volume level.
LCD MP3 player block diagram

To efficiently manage the LCD display, it is updated only when displayed data has changed. Any code that changes displayable data also sets a global flag needUpdate that is checked once through each loop() to call the update function. This keeps the LCD flicker free.

The display interface shows the following information when the MP3 player is active

LCD status display

and changes to this when the SD card is ejected

LCD display showing SD card removed

The total number of tracks and the currently playing track number are requested from the MP3 module and received as unsolicited data. To process the unsolicited messages this code uses the callback function cbResponse(). The same function could be used in non-callback mode as messages are process in identical fashion.

The main structure of cbResponse() is a case statement to perform an action (ie, do something or save data) when the message is received.

void cbResponse(const MD_YX5300::cbData *status)
// Callback function used to process device unsolicited messages
// or responses to data requests
{
  switch (status->code)
  {
  case MD_YX5300::STS_FILE_END:   // track has ended
    selectNextSong();
    break;

  case MD_YX5300::STS_TF_INSERT:  // card has been inserted
    S.initializing = initData(true);
    break;

  case MD_YX5300::STS_TF_REMOVE:  // card has been removed
    S.playMode = M_EJECTED;
    S.playStatus = S_STOPPED;
    S.needUpdate = true;
    break;

  case MD_YX5300::STS_PLAYING:   // current track index 
    S.curTrack = status->data;
    S.needUpdate = true;
    break;

  case MD_YX5300::STS_FLDR_FILES: // number of files in the folder
    S.numTracks = status->data;
    S.needUpdate = true;
    break;
  }
  S.waiting = false;
}

One slight complication when mixing synchronous calls and unsolicited messages is that the unsolicited message must be processed by the sketch before the next synchronous call, otherwise the synchronous call will process the unsolicited message as the response to the synchronous request. Messages processed from the queue then get out of sequence.

An example of this would occur when the SD card is removed. Loading the status data for the display requires a sequence of requests with unsolicited responses. In the sketch, synchronization is maintained by setting a flag called initializing when an unsolicited message is expected. The code in loop() is modified to block normal processing until this initialization is complete.

void loop()
{
  mp3.check();        // run the mp3 receiver

  // Initialization must preserve the unsolicited queue order so it 
  // stops any normal synchronous calls from happening
  if (S.initializing && !S.waiting)
    S.initializing = initData();
  else
  {
    processEncoder();   // rotary encoder in current mode
    processPlayMode();  // set the current play mode (switch selection)
  }

  // Finally update the display if anything changed - this minimises 
  // the updates to just what is needed for a more readable display
  if (S.needUpdate)
    displayUpdate();
}

The actual initialization code in initData() is a safe combination of synchronous commands/requests in a Finite State Machine that runs to completion when invoked repeatedly from loop().

The remainder of the code is similar to the simple player with the added wrinkle that the pot is replaced by the rotary encoder.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s