DYPlayer library : passing serial port in the begin() function

Please let me know if I'm in the wrong category, or if there is a better one to post this. Thank you !

Hi, I'm trying to reconstruct the DYPlayer library (GitHub - SnijderC/dyplayer: Abstracton for DY-XXXX mp3 player modules over UART.) so that it accept the serial port in the begin(Stream &serial) instead of as an UARTClass argument in the instance declaration.

So I have eliminate the DFPlayerArduino.h and .cpp only to keep the DFPlayer.h and .cpp, then I have removed the DY namespace and moved the serialWrite, serialRead functions in the DFPlayer files.

So when I used the modified library, I declare an instance like this : DFPlayer myDfplayer;

In the setup loop, I use : myDfplayer(Serial); or whaterver serial I want.

Even if everything compile fine, but no communication with the board. If I use the original library, it's all find.

What I'm missing ?
Anybody has an idea on that matters ?
Thank you !

I'm using Arduino nano every with Platformio...
I want to change that library because I'm using it in another class object (in another big code), and it makes everything easier if I can push the serial port at the setup function.

This is my main file:

#include <Arduino.h>

#include <Arduino.h>
#include <SoftwareSerial.h>
#include <DYPlayer.h>


// Initialise on software serial port.
SoftwareSerial SoftSerial(2, 3);
DYPlayer player;

void setup() {
  SoftSerial.begin(9600);
  player.begin(SoftSerial);
  // Also initiate the hardware serial port so we can use it for debug printing
  // to the console..
  Serial.begin(9600);
 // player.setVolume(30); // 50% Volume
  //player.setCycleMode(PlayMode::Repeat); // Play all and repeat.
  player.play();
}

void loop() {

  // Print the number of the sound that is playing.
  Serial.print("Playing sound: ");
  Serial.println((int16_t)player.getPlayingSound());
  delay(500);
}

This is the modified DFPlayer.h:

/**
 * Abstraction of basic features of the DY-SV17F mp3 player board, written for
 * Arduino, should work on other frameworks as well. Instead of DY-SV17F I will
 * from here on refer to it as the "module".
 *
 * There are some virtual methods that MUST be overridden (serialRead and
 * serialWrite) and one that you may override (begin)
 */

#ifndef DYPLAYER_H
#define DYPLAYER_H
#include <Arduino.h>
#include <Stream.h> // Include Stream header for Stream class
#include <stdint.h>

#ifndef DY_PATHS_IN_HEAP
#define DY_PATH_LEN 40
#endif

/**
 * Storage devices reported by module and to choose from when selecting a
 * storage device.
 */
typedef enum class Device : uint8_t
{
  Usb = 0x00,     // USB Storage device.
  Sd = 0x01,      // SD Card.
  Flash = 0x02,   // Onboard flash chip (usually winbond 32, 64Mbit flash).
  Fail = 0xfe,    // UART failure, can't be `-1` (so this can be uint8_t).
  NoDevice = 0xff // No storage device is online.
} device_t;

/**
 * The current module play state.
 */
typedef enum class PlayState : int8_t
{
  Fail = -1, // UART Failure, can be a connection or a CRC problem.
  Stopped = 0,
  Playing = 1,
  Paused = 2
} play_state_t;

/**
 * Equalize settings.
 */
typedef enum class Eq : uint8_t
{
  Normal,
  Pop,
  Rock,
  Jazz,
  Classic
} eq_t;

/**
 * Play modes are basically whatever you commonly find on a media player,
 * i.e.:
 * Repeat 1, Repeat all, Repeat list (dir), playlist (by dir), random play.
 *
 * The default is perhaps somewhat unexpected: `DY::PlayMode::OneOff`. Often
 * these modules will be used in toys or information displays where you can
 * press a button and hear a corresponding sound. To get default media player
 * behaviour, you should probably set `DY::PlayMode::Sequence` to just continue
 * playing the next song until all are played or skipped, then stop.
 */
typedef enum PlayMode : uint8_t
{
  Repeat,      // Play all music in sequence, and repeat.
  RepeatOne,   // Repeat current sound.
  OneOff,      // Play sound file and stop.
  Random,      // Play random sound file.
  RepeatDir,   // Repeat current directory.
  RandomDir,   // Play random sound file in current folder.
  SequenceDir, // Play all sound files in current folder in sequence, and stop.
  Sequence     // Play all sound files on device in sequence, and stop.
} play_mode_t;

/**
 * The `DY::DYPlayer::previousDir()` method expects this type as its argument.
 * Imagine you would press a button on a media player that selects the
 * previous directory/playlist, do you expect it to play the first song of
 * that list, or the last one? Depending on what you find logical or on your
 * requirement, this enumeration allows you to choose what happens when you
 * go to the previous directory.
 */
typedef enum PreviousDir : uint8_t
{
  FirstSound, // When navigating to the previous dir, play the first sound.
  LastSound   // When navigating to the previous dir, play the last sound.
} playDirSound_t;

class DYPlayer
{
public:
  Stream *_port;
  void begin(Stream &port); // Modified begin function
  void serialWrite(uint8_t *buffer, uint8_t len);
  bool serialRead(uint8_t *buffer, uint8_t len);
  /**
   * Virtual method that should implement writing to the module via UART.
   * @param buffer pointer to bytes to send to the module.
   * @param len of buffer.
   */
  //virtual void serialWrite(uint8_t *buffer, uint8_t len) = 0;
  /**
   * Map writing a single byte to the same method as writing a buffer of
   * length 1.
   * Can be overridden to a function that writes directly for performance
   * if required.
   * @param uint8_t byte to write to module.
   */
  void serialWrite(uint8_t byte);

  /**
   * Virtual method that should implement reading from the module via UART.
   * @param buffer pointer to keep data received from the module.
   * @param len of buffer.
   * @return Successful read (true), failure (false).
   */
  //virtual bool serialRead(uint8_t *buffer, uint8_t len) = 0;

  /**
   * Check the current play state can, be called at any time.
   * @return Play status: A [`DY::PlayState`](#typedef-enum-class-dyplay_state_t),
   *         e.g DY::PlayMode::Stopped, DY::PlayMode::Playing, etc.
   */
  play_state_t checkPlayState();

  /**
   * Play the currently selected file from the start.
   */
  void play();

  /**
   * Set the play state to paused.
   */
  void pause();

  /**
   * Set the play state to stopped.
   */
  void stop();

  /**
   * Play the previous file.
   */
  void previous();

  /**
   * Play the next file.
   */
  void next();

  /**
   * Play a sound file by number, number sent as 2 bytes.
   * @param number of the file, e.g. `1` for `00001.mp3`.
   */
  void playSpecified(uint16_t number);

  /**
   * Play a sound file by device and path.
   * Path may consist of up to 2 nested directories of 8 bytes long and a
   * file name of 8 bytes long excluding the extension of 4 bytes long.
   * If your directory names are shorter you can use more nesting. Use no
   * more than 36 bytes for your paths. If you require more, check the
   * readme, chapter: Memory use.
   * @param device A [`DY::Device member`](#typedef-enum-class-dydevice_t),
   *               e.g  `DY::Device::Flash` or `DY::Device::Sd`.
   * @param path pointer to the path of the file (asbsolute).
   */
  void playSpecifiedDevicePath(device_t device, char *path);

  /**
   * Get the storage device that is currently used for playing sound files.
   *
   * @return a [`DY::Device` member](#typedef-enum-class-dydevice_t),
   *         e.g  `DY::Device::Flash` or `DY::Device::Sd`.
   */
  device_t getPlayingDevice();

  /**
   * Set the device number the module should use.
   * Tries to set the device but no guarantee is given, use `getDevice()`
   * to check the actual current storage device.
   * @param device A [`DY::Device` member](#typedef-enum-class-dydevice_t),
   *               e.g  `DY::Device::Flash` or `DY::Device::Sd`.
   */
  void setPlayingDevice(device_t device);

  /**
   * Get the amount of sound files on the current storage device.
   * @return number of sound files.
   */
  uint16_t getSoundCount();

  /**
   * Get the currently playing file by number.
   * @return number of the file currently playing.
   */
  uint16_t getPlayingSound();

  /**
   * Select previous directory and start playing the first or last song.
   * @param song Play `DY::PreviousDir::FirstSound` or
   *             DY::PreviousDir::LastSound
   */
  void previousDir(playDirSound_t song);

  /**
   * Get number of the first song in the currently selected directory.
   * @return number of the first song in the currently selected directory.
   */
  uint16_t getFirstInDir();

  /**
   * Get the amount of sound files in the currently selected directory.
   * NOTE: Excluding files in sub directories.
   * @return number of sound files in currently selected directory.
   */
  uint16_t getSoundCountDir();

  /**
   * Set the playback volume between 0 and 30.
   * Default volume if not set: 20.
   * @param volume to set (0-30)
   */
  void setVolume(uint8_t volume);

  /**
   * Increase the volume.
   */
  void volumeIncrease();

  /**
   * Decrease the volume.
   */
  void volumeDecrease();

  /**
   * Play an interlude file by device and number, number sent as 2 bytes.
   * Note from the manual: "Music interlude" only has level 1. Continuous
   * interlude will cover the previous interlude (the interlude will be
   * played immediately). When the interlude is finished, it will return to
   * the first interlude breakpoint and continue to play.
   * @param device A [`DY::Device member`](#typedef-enum-class-dydevice_t),
   *               e.g  `DY::Device::Flash` or `DY::Device::Sd`.
   * @param number of the file, e.g. `1` for `00001.mp3`.
   */
  void interludeSpecified(device_t device, uint16_t number);

  /**
   * Play an interlude by device and path.
   * Note from the manual: "Music interlude" only has level 1. Continuous
   * interlude will cover the previous interlude (the interlude will be
   * played immediately). When the interlude is finished, it will return to
   * the first interlude breakpoint and continue to play.
   *
   * Path may consist of up to 2 nested directories of 8 bytes long and a
   * file name of 8 bytes long excluding the extension of 4 bytes long.
   * If your directory names are shorter you can use more nesting. Use no
   * more than 36 bytes for your paths. If you require more, check the
   * readme, chapter: Memory use.
   * @param device A [`DY::Device member`](#typedef-enum-class-dydevice_t),
   *               e.g  `DY::Device::Flash` or `DY::Device::Sd`.
   * @param path pointer to the path of the file (asbsolute).
   */
  void interludeSpecifiedDevicePath(device_t device, char *path);

  /**
   * Stop the interlude and continue playing.
   * Will also stop the current sound from playing if interlude is not
   * active.
   */
  void stopInterlude();

  /**
   * Sets the cycle mode.
   * See [`DY::play_state_t`](#typedef-enum-class-dyplay_state_t) for modes
   * and meaning.
   * @param mode The cycle mode to set.
   */
  void setCycleMode(play_mode_t mode);

  /**
   * Set how many cycles to play when in cycle modes 0, 1 or 4 (repeat
   * modes).
   * @param cycles The cycle count for repeat modes.
   */
  void setCycleTimes(uint16_t cycles);

  /**
   * Set the equalizer setting.
   * See [`DY::eq_t`](#typedef-enum-class-dyeq_t) for settings.
   * @param eq The equalizer setting.
   */
  void setEq(eq_t eq);

  /**
   * Select a sound file without playing it.
   * @param number of the file, e.g. `1` for `00001.mp3`.
   */
  void select(uint16_t number);

  /**
   * Combination play allows you to make a playlist of multiple sound files.
   *
   * You could use this to combine numbers e.g.: "fourthy-two" where you
   * have samples for "fourthy" and "two".
   *
   * This feature has a particularly curious parameters, you have to
   * specify the sound files by name, they have to be named by 2 numbers
   * and an extension, e.g.: `01.mp3` and specified by `01`. You should
   * pass them as an array pointer. You need to put the files into a
   * directory that can be called `DY`, `ZH or `XY`, you will have to check
   * the manual that came with your module, or try all of them. There may
   * well be more combinations! Also see
   * [Loading sound files](#loading-sound-files).
   *
   * E.g.
   * ```cpp
   * const char * sounds[2][3] = { "01", "02" };
   * DY::DYPlayer::combinationPlay(sounds, 2);
   * ````
   * @param sounds An array of char[2] containing the names of sounds to
   *               play in order.
   * @param len The length of the passed array.
   */
  void combinationPlay(char *sounds[], uint8_t len);

  /**
   * End combination play.
   */
  void endCombinationPlay();

private:
  /**
   * Calculate the sum of all bytes in a buffer as a simple "CRC".
   * @param data pointer to bytes to calculate the CRC for.
   * @param len of buffer.
   * @return Checksum of the buffer.
   */
  uint8_t inline checksum(uint8_t *data, uint8_t len);

  /**
   * Validate data buffer with CRC byte (last byte should be the CRC byte).
   * @param data pointer to bytes to calculate the CRC for.
   * @param len of data.
   * @return boolean indicating CRC is correct (true) or incorrect (false).
   */
  bool validateCrc(uint8_t *data, uint8_t len);

  /**
   * Send a command to the module, adds a CRC to the passed buffer.
   * @param data pointer to bytes to send to the module.
   * @param len of data.
   */
  void sendCommand(uint8_t *data, uint8_t len);

  /**
   * Send a command to the module, pass a static crc.
   * Use this to optimize speed for static commands.
   * @param data pointer to bytes to send to the module.
   * @param len of data.
   * @param crc Precalculated CRC byte.
   */
  void sendCommand(uint8_t *data, uint8_t len, uint8_t crc);

  /**
   * Get a response to a command.
   * Reads data from UART, validates the CRC, and puts it in the buffer.
   * @param buffer pointer for the bytes to receive.
   * @param len of buffer.
   * @return False on communication failure.
   */
  bool getResponse(uint8_t *buffer, uint8_t len);

  /**
   * Send command with converted paths to  weird format required by the
   * modules.
   *
   * - Any dot in a path should become a star (`*`)
   * - Path ending slashes should be have a star prefix, except root.
   *
   * E.g.: /SONGS1/FILE1.MP3 should become: /SONGS1﹡/FILE1*MP3
   * NOTE: This comment uses a unicode * look-a-alike (﹡) because ﹡/ end the
   * comment.
   * @param command The command to send.
   * @param device A [DY::Device member](#typedef-enum-class-dydevice_t),
   *               e.g  `DY::Device::Flash` or `DY::Device::Sd`.
   * @param path of the file (asbsolute).
   */
  void byPathCommand(uint8_t command, device_t device, char *path);
};

#endif

This is the modified DFPlayer.cpp:

/**
 * Abstraction of basic features of the DY-SV17F mp3 player board, written for
 * Arduino, should work on other frameworks as well. Instead of DY-SV17F I will
 * from here on refer to it as the "module".
 *
 * There are some virtual methods that MUST be overridden (serialRead and
 * serialWrite) and one that you may override (begin)
 */
#include <ctype.h>
#include <string.h>
#include "DYPlayer.h"

void DYPlayer::begin(Stream &port)
{
  _port = &port; // Store the reference to the port
}

void DYPlayer::serialWrite(uint8_t *buffer, uint8_t len)
{
  _port->write(buffer, len);
}

void DYPlayer::serialWrite(uint8_t byte)
{
  uint8_t buffer[1] = {byte};
  serialWrite(buffer, 1);
}

bool DYPlayer::serialRead(uint8_t *buffer, uint8_t len)
  {
    // Serial.setTimeout(1000); // Default timeout 1000ms.
    if (_port->readBytes(buffer, len) > 0)
    {
      return true;
    }
    return false;
  }

uint8_t inline DYPlayer::checksum(uint8_t *data, uint8_t len)
{
  uint8_t sum = 0;
  for (uint8_t i = 0; i < len; i++)
  {
    sum = sum + data[i];
  }
  return sum;
}

bool inline DYPlayer::validateCrc(uint8_t *data, uint8_t len)
{
  uint8_t crc = data[len - 1];
  return checksum(data, len - 1) == crc;
}

void DYPlayer::sendCommand(uint8_t *data, uint8_t len)
{
  uint8_t crc = checksum(data, len);
  serialWrite(data, len);
  serialWrite(crc);
}

void DYPlayer::sendCommand(uint8_t *data, uint8_t len, uint8_t crc)
{
  serialWrite(data, len);
  serialWrite(crc);
}

bool DYPlayer::getResponse(uint8_t *buffer, uint8_t len)
{
  if (serialRead(buffer, len) > 0)
  {
    if (validateCrc(buffer, len))
    {
      return true;
    }
  }
  return false;
}

void DYPlayer::byPathCommand(uint8_t command, device_t device, char *path)
{
  uint8_t len = strlen(path);
  if (len < 1)
    return;
  uint8_t _len = len;
  // Count / in path and, except root slash and determine new length
  for (uint8_t i = 1; i < len; i++)
  {
    if (path[i] == '/')
      _len++;
  }
#ifdef DY_PATHS_IN_HEAP
  uint8_t *_command = new uint8_t[_len + 4];
#else
  uint8_t _command[DY_PATH_LEN + 4];
#endif

  _command[0] = 0xaa;
  _command[1] = command;
  _command[2] = _len + 1;
  _command[3] = (uint8_t)device;
  _command[4] = path[0];
  uint8_t j = 5;
  for (uint8_t i = 1; i < len; i++)
  {
    switch (path[i])
    {
    case '.':
      _command[j] = '*';
      break;
    case '/':
      _command[j] = '*';
      j++;
      // fall-through
    default:
      _command[j] = toupper(path[i]);
    }
    j++;
  }
  sendCommand(_command, _len + 4);
#ifdef DY_PATHS_IN_HEAP
  delete[] _command;
#endif
}

play_state_t DYPlayer::checkPlayState()
{
  uint8_t command[3] = {0xaa, 0x01, 0x00};
  sendCommand(command, 3, 0xab);
  uint8_t buffer[5];
  if (getResponse(buffer, 5))
  {
    return (play_state_t)buffer[3];
  }
  return PlayState::Fail;
}

void DYPlayer::play()
{
  uint8_t command[3] = {0xaa, 0x02, 0x00};
  sendCommand(command, 3, 0xac);
}

void DYPlayer::pause()
{
  uint8_t command[3] = {0xaa, 0x03, 0x00};
  sendCommand(command, 3, 0xad);
}

void DYPlayer::stop()
{
  uint8_t command[3] = {0xaa, 0x04, 0x00};
  sendCommand(command, 3, 0xae);
}

void DYPlayer::previous()
{
  uint8_t command[3] = {0xaa, 0x05, 0x00};
  sendCommand(command, 3, 0xaf);
}

void DYPlayer::next()
{
  uint8_t command[3] = {0xaa, 0x06, 0x00};
  sendCommand(command, 3, 0xb0);
}

void DYPlayer::playSpecified(uint16_t number)
{
  uint8_t command[5] = {0xaa, 0x07, 0x02, 0x00, 0x00};
  command[3] = number >> 8;
  command[4] = number & 0xff;
  sendCommand(command, 5);
}
void DYPlayer::playSpecifiedDevicePath(device_t device, char *path)
{
  byPathCommand(0x08, device, path);
}

device_t DYPlayer::getPlayingDevice()
{
  uint8_t command[3] = {0xaa, 0x0a, 0x00};
  sendCommand(command, 3, 0xb4);
  uint8_t buffer[5];
  if (getResponse(buffer, 5))
  {
    return (device_t)buffer[3];
  }
  return Device::Fail;
}

void DYPlayer::setPlayingDevice(device_t device)
{
  uint8_t command[4] = {0xaa, 0x0b, 0x01, 0x00};
  command[3] = (uint8_t)device;
  sendCommand(command, 4);
}

uint16_t DYPlayer::getSoundCount()
{
  uint8_t command[3] = {0xaa, 0x0c, 0x00};
  sendCommand(command, 3, 0xb6);
  uint8_t buffer[6];
  if (getResponse(buffer, 6))
  {
    return (buffer[3] << 8) | buffer[4];
  }
  return 0;
}

uint16_t DYPlayer::getPlayingSound()
{
  uint8_t command[3] = {0xaa, 0x0d, 0x00};
  sendCommand(command, 3, 0xb7);
  uint8_t buffer[6];
  if (getResponse(buffer, 6))
  {
    return (buffer[3] << 8) | buffer[4];
  }
  return 0;
}

void DYPlayer::previousDir(playDirSound_t song)
{
  if (song == PreviousDir::LastSound)
  {
    uint8_t command[3] = {0xaa, 0x0e, 0x00};
    sendCommand(command, 3, 0xb8);
  }
  else
  {
    uint8_t command[3] = {0xaa, 0x0f, 0x00};
    sendCommand(command, 3, 0xb9);
  }
}

uint16_t DYPlayer::getFirstInDir()
{
  uint8_t command[3] = {0xaa, 0x11, 0x00};
  sendCommand(command, 3, 0xbb);
  uint8_t buffer[6];
  if (getResponse(buffer, 6))
  {
    return (buffer[3] << 8) | buffer[4];
  }
  return 0;
}

uint16_t DYPlayer::getSoundCountDir()
{
  uint8_t command[3] = {0xaa, 0x12, 0x00};
  sendCommand(command, 3, 0xbc);
  uint8_t buffer[6];
  if (getResponse(buffer, 6))
  {
    return (buffer[3] << 8) | buffer[4];
  }
  return 0;
}

void DYPlayer::setVolume(uint8_t volume)
{
  uint8_t command[4] = {0xaa, 0x13, 0x01, 0x00};
  command[3] = volume;
  sendCommand(command, 4);
}

void DYPlayer::volumeIncrease()
{
  uint8_t command[3] = {0xaa, 0x14, 0x00};
  sendCommand(command, 3, 0xbe);
}

void DYPlayer::volumeDecrease()
{
  uint8_t command[3] = {0xaa, 0x15, 0x00};
  sendCommand(command, 3, 0xbf);
}

void DYPlayer::interludeSpecified(device_t device, uint16_t number)
{
  uint8_t command[6] = {0xaa, 0x0b, 0x03, 0x00, 0x00, 0x00};
  command[3] = (uint8_t)device;
  command[4] = number >> 8;
  command[5] = number & 0xff;
  sendCommand(command, 6);
}

void DYPlayer::interludeSpecifiedDevicePath(device_t device, char *path)
{
  byPathCommand(0x17, device, path);
}

void DYPlayer::stopInterlude()
{
  uint8_t command[3] = {0xaa, 0x10, 0x00};
  sendCommand(command, 3, 0xba);
}

void DYPlayer::setCycleMode(play_mode_t mode)
{
  uint8_t command[4] = {0xaa, 0x18, 0x01, 0x00};
  command[3] = mode;
  sendCommand(command, 4);
}
void DYPlayer::setCycleTimes(uint16_t cycles)
{
  uint8_t command[5] = {0xaa, 0x19, 0x02, 0x00, 0x00};
  command[3] = cycles >> 8;
  command[4] = cycles & 0xff;
  sendCommand(command, 5);
}

void DYPlayer::setEq(eq_t eq)
{
  uint8_t command[4] = {0xaa, 0x1a, 0x01, 0x00};
  command[3] = (uint8_t)eq;
  sendCommand(command, 4);
}

void DYPlayer::select(uint16_t number)
{
  uint8_t command[5] = {0xaa, 0x1f, 0x02, 0x00, 0x00};
  command[3] = number >> 8;
  command[4] = number & 0xff;
  sendCommand(command, 5);
}
void DYPlayer::combinationPlay(char *sounds[], uint8_t len)
{
  if (len < 1)
    return;
  // This part of the command can be easily determined already.
  uint8_t command[3] = {0xaa, 0x1b, 0x00};
  command[2] = len * 2;
  // Depends on the length, checksum is a sum so we can add the other values
  // later.
  uint8_t crc = checksum(command, 3);
  // Send the command and length already.
  serialWrite(command, 3);
  // Send each pair of chars containing the file name and add the values of
  // each char to the crc.
  for (uint8_t i = 0; i < len; i++)
  {
    crc += checksum((uint8_t *)sounds[i], 2);
    serialWrite((uint8_t *)sounds[i], 2);
  }
  // Lastly, write the crc value.
  serialWrite(crc);
}

void DYPlayer::endCombinationPlay()
{
  uint8_t command[3] = {0xaa, 0x1c, 0x00};
  sendCommand(command, 3, 0xc6);
}

You're missing the begin call for the SoftSerial instance.

Would it make more sense to pass the SoftSerial instance to the player's constructor, and pass the baud rate to the player's begin method, and adding the missing SoftSerial begin call to the player's begin?

It was a typo... Thanks ! I've corrected my first post.

It's because I have a bigger code that can use different type of players. It's a class object called Player, and it's easier to deal with the serial com in the setup function instead of passing Software or Hardware instance in the constructor...

Ok then. Good luck with your project.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.