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);
}