Dyplayer
Abstracton for DY-XXXX mp3 player modules over UART.
Install / Use
/learn @SnijderC/DyplayerREADME
Abstracted UART Control of DY-XXXX mp3 modules
This library abstracts all features described in the manual into a C++ class.
This library does not support the ONE_Line protocol,
more info.
Although all features are implemented and should theoretically work, only those in the examples directory are tested. Please create an issue if you have problems.
This library was written in a hardware independent way. Which means it should work on any device with a serial port, e.g. any Arduino, Espressif, ARM based boards, probably even any computer.
There are Hardware Abstraction Layers (HAL) included for Arduino and ESP-IDF, for other boards you will have to provide one yourself (PR is welcome!).
Note on upgrading from version 3.x.x to version 4.x.x
TL;DR: Change all occurances of enum constants of DY::Device,
DY::PlayState, DY::Eq, DY::PlayMode and DY::PreviousDir from upper case
to CamelCase, e.g.: DY::Device::FLASH to DY::Device::Flash.
One of the problems with C++ I learned about, the hard way is how the namespace can still be polluted despite namespacing everything. As macro's are handled by the preprocessor, any mention of a macro in the source, even if obviously intended as code (well to a human) will be interpreted as a call to a macro.
Embedded devices' headers rely on macro's heavily and they aren't generally
prefixed. So users reported collisions between constants such as USB (it's
actually part of an enum class: DY::Device::USB which makes this even more
frustrating) and a macro defined for a board that has a USB port, also named
USB.
The only way around it is to drop the social convention (that was arguably already advised against) of naming constants in capitals, the compiler doesn't care about this convention, it's just usually done for readability.
Modules should work (not exhaustive)
| Model name | Capacity | SD Card support | Amplifier | Voltage | Tested | | :--------- | :------- | :-------------: | :-------------- | :-----: | :-----: | | DY-SV17F | 32Mbit | No | 3-5W(4Ω/8Ω) | 5VDC | Yes | | DY-SV8F | 64Mbit | No | 3-5W(4Ω/8Ω) | 5VDC | No | | DY-HV20T | NA | Yes, Max. 32GB | 3-5W(4Ω/8Ω) | 5VDC | No | | DY-HV8F | 8Mbit | No | 10W(8Ω)/20W(4Ω) | 6-35VDC | No | | DY-HV20T | NA | Yes, Max. 32GB | 10W(8Ω)/20W(4Ω) | 6-35VDC | No | | DY-SV5W | NA | Yes, Max. 32GB | 3-5W(4Ω/8Ω) | 5VDC | Yes |
NOTE: I cannot guarantee that your board will work with the library. Nor that
a specific feature will work. I only have the DY-SV17F in my possession to
test at the time of writing. If something does not work, make an issue and/or
send me a pull request.
Wiring the module
If you have a board with DIP switches, set CON3 to on, CON1 and CON2
should remain off. If your board doesn't have DIP switches (e.g. DY-SV17F
board), you have to connect 3 resistors of 10KOhm, one from each CON# pin,
to:
| CON pin | Connect to | Via |
| :------ | :--------- | :------- |
| CON1 | GND | 10KOhm |
| CON2 | GND | 10KOhm |
| CON3 | 3.3V | 10KOhm |
The 3.3V pin is exposed by the board so you don't need to provide it yourself.
Further make these connections:
| Pin | Connect to | Via |
| :------- | :------------------------------- | :-------------------------- |
| V? | V+ (voltage depends on module) | |
| GND | GND | |
| IO0/TX | MCU RX | 1KOhm if using a 5V board |
| IO1/RX | MCU TX | 1KOhm if using a 5V board |
| SPK+ | Speaker positive lead | |
| SPK- | Speaker negative lead | |
MCU should be your board or microprocessor, e.g. an Arduino board.
Note the logic levels should be 3.3V or at least limited by a 1KOhm
resistor according to the module manual, or you could damage the module. Some modules appear to
have resistors
soldered onto the board.
Make sure to check the datasheet of your board.
HAL
Arduino tl;dr; If you are using Arduino, skip this chapter and go to Arduino.
ESP32 tl;dr; If you are using ESP-IDF, skip this chapter and go to ESP-IDF.
Because the library is hardware independent you might need to add a Hardware
Abstraction Layer (HAL) which sets up the serial port and that implements a
minimum of 2 functions serialWrite() and serialRead(), there is a HAL for
Arduino included, here's a simplified version of it:
// player.hpp
#include <Arduino.h>
#include "DYPlayer.h"
namespace DY {
class Player: public DYPlayer {
public:
HardwareSerial *port;
Player();
Player(HardwareSerial* port);
void begin();
void serialWrite(uint8_t *buffer, uint8_t len);
bool serialRead(uint8_t *buffer, uint8_t len);
};
}
//player.cpp
#include "player.hpp"
#include "DYPlayerArduino.h"
namespace DY {
Player::Player() {
this->port = &Serial;
}
Player::Player(HardwareSerial* port) {
this->port = port;
}
void Player::begin() {
port->begin(9600);
}
void Player::serialWrite(uint8_t *buffer, uint8_t len) {
port->write(buffer, len);
}
bool Player::serialRead(uint8_t *buffer, uint8_t len) {
// Serial.setTimeout(1000); // Default timeout 1000ms.
if(port->readBytes(buffer, len) > 0) {
return true;
}
return false;
}
}
Note:
This Arduino HAL is simplied, the included HAL does SoftwareSerial
as well which adds some complexity that I don't think should be in an example
like this. You can find the real HAL here.
Steps:
- Define a class that extends the
DY::DYPlayerclass. - Define constructors that set up the serial port. On some platforms you will
need to setup your serial port after some other things are initialized, e.g.
on Arduino, then define an additional
DY::Player::begin()(or e.g.DY::Player::init()) to finish initialisation. - Define functions for
serialWrite()andserialRead()according to the board and the framework you use.
Memory use
This library uses a little memory as possible to play nice with micro controllers with small RAM, such as Atmega328 (used in many Arduino boards), which has 2K RAM.
To keep memory usage low, avoid using the functions that take char *path
arguments. If you do not intend to play sounds by file name, you can skip the
rest of this chapter. If you do, keep reading.
The char *path arguments will always use more RAM than the uint16_t
arguments, obviously but this is compounded by an odd requirement of the player
modules. I.e. the paths to files on flash/SD card have to be defined different
than usual, e.g.
/SONGS/IN/A/PATH/00001.MP3 should be specified as:
/SONGS*/IN*/A*/PATH*/00001*MP3
Analysing this:
- Paths normally end in
/but an additional*is required; - except for the root level.
- Period in before the extension should be replaced by
*as well. - The new path is 4 bytes longer than the specified path.
The conversion is done by the library but this means that the path is allocated twice in memory, once by you, once by the library and the latter needs to have more capacity (in this case 4 bytes). The libray can keep the second string's memory in 2 ways: in heap or in stack memory.
Stack memory is not dynamic, i.e.: the required amount of bytes should be known
at compile time, which means more than the expected amount of bytes should
already be reserved, which is wasteful. Aside from being wastelful, the path
could be really short (most likely, e.g. /00001.MP3 or something like
/SFX/00001.MP3), or it could be really long..
Putting the path in dynamically assigned heap memory fixes all of that, the
library can count the amount of / in the path and make a variable exactly
long enough for the converted path. However we should always be wary of
Heap fragmentation.
In short to assign memory, a contgious block of it needs to be available.
Assigning chunks of memory and then freeing them leaves holes in the memory that
may be be too small to use again later. This will gradually lead to problems.
You may need to reset your device after several minutes, hours or days because
the program can't allocate heap memory any more.
So, by default the library will reserve stack memory. The amount is based on some assumptions:
- The manual of the sound modules states that paths and file names may be up to 8 characters long.
- The library assumes that you will not nest more than 2 directories.
- Extentions can be
.MP3or.WAV, so always 4 bytes.
So we come to:
/ dir */ dir */ file * ext
1 + 8 + 2 + 8 + 2 + 8 + 1 + 4 = 34
Let's round that up to 40 and you could even have some more nesting as long as
the directory names are small enough.
The library will therefore define DY_PATH_LEN as 40, you can override that
if you need more, or if you want to save a few bytes of precious memory. Note
that 40 is the maximum length of the path after conversion to the funcky
format required by the module. The amount of bytes you may use with this default
is set at 36.
Alternatively, if you have a more capable device and/or you can use virtual
memory, you can define DY_PATHS_IN_HEAP to use heap memory instead of reserved
stack memory.
NOTE: On Arduino, you can wrap your strings in F() to tell the compiler you
want the string stored in flash, as opposed to RAM (default), which will save
you even more RAM.
Arduino
Because this is inc
