SkillAgentSearch skills...

Dyplayer

Abstracton for DY-XXXX mp3 player modules over UART.

Install / Use

/learn @SnijderC/Dyplayer
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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:

  1. Define a class that extends the DY::DYPlayer class.
  2. 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.
  3. Define functions for serialWrite() and serialRead() 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 .MP3 or .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

View on GitHub
GitHub Stars135
CategoryDevelopment
Updated6d ago
Forks37

Languages

C++

Security Score

80/100

Audited on Mar 30, 2026

No findings