mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
drivers: Added driver for the DFPlayer MP3 player
This commit is contained in:
parent
d2f3cafa45
commit
82bfb66cb8
@ -210,6 +210,12 @@ ifneq (,$(filter dcf77,$(USEMODULE)))
|
||||
USEMODULE += xtimer
|
||||
endif
|
||||
|
||||
ifneq (,$(filter dfplayer,$(USEMODULE)))
|
||||
FEATURES_REQUIRED += periph_uart
|
||||
FEATURES_REQUIRED += periph_gpio
|
||||
USEMODULE += xtimer
|
||||
endif
|
||||
|
||||
ifneq (,$(filter dht,$(USEMODULE)))
|
||||
USEMODULE += xtimer
|
||||
FEATURES_REQUIRED += periph_gpio
|
||||
|
@ -88,6 +88,10 @@ ifneq (,$(filter dcf77,$(USEMODULE)))
|
||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/dcf77/include
|
||||
endif
|
||||
|
||||
ifneq (,$(filter dfplayer,$(USEMODULE)))
|
||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/dfplayer/include
|
||||
endif
|
||||
|
||||
ifneq (,$(filter dht,$(USEMODULE)))
|
||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/dht/include
|
||||
endif
|
||||
|
1
drivers/dfplayer/Makefile
Normal file
1
drivers/dfplayer/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
239
drivers/dfplayer/dfplayer.c
Normal file
239
drivers/dfplayer/dfplayer.c
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_dfplayer
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation DFPlayer Mini Device Driver
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "dfplayer.h"
|
||||
#include "dfplayer_constants.h"
|
||||
#include "dfplayer_internal.h"
|
||||
#include "irq.h"
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/uart.h"
|
||||
#include "thread.h"
|
||||
#include "xtimer.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
int dfplayer_init(dfplayer_t *dev, const dfplayer_params_t *params)
|
||||
{
|
||||
if (!dev || !params) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const mutex_t locked = MUTEX_INIT_LOCKED;
|
||||
memset(dev, 0x00, sizeof(*dev));
|
||||
dev->uart = params->uart;
|
||||
dev->busy_pin = params->busy_pin;
|
||||
mutex_init(&dev->mutex);
|
||||
dev->sync = locked;
|
||||
|
||||
if (dev->busy_pin != GPIO_UNDEF) {
|
||||
if (gpio_init(dev->busy_pin, GPIO_IN)) {
|
||||
DEBUG("[dfplayer] Initializing busy pin failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
int retval = uart_init(dev->uart, DFPLAYER_BAUD, dfplayer_uart_rx_cb, dev);
|
||||
if (retval != UART_OK) {
|
||||
DEBUG("[dfplayer] uart_init() failed (return value %d)\n", retval);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
DEBUG("[dfplayer] Resetting device now\n");
|
||||
retval = dfplayer_reset(dev);
|
||||
|
||||
if (retval) {
|
||||
DEBUG("[dfplayer] Failed to reset device (return value %d)\n", retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
DEBUG("[dfplayer] dfplayer_init almost completed, setting volume\n");
|
||||
|
||||
return dfplayer_set_volume(dev, params->volume);
|
||||
}
|
||||
|
||||
dfplayer_source_set_t dfplayer_get_sources(dfplayer_t *dev)
|
||||
{
|
||||
unsigned state = irq_disable();
|
||||
dfplayer_source_set_t result = dev->srcs;
|
||||
irq_restore(state);
|
||||
return result;
|
||||
}
|
||||
|
||||
int dfplayer_set_callbacks(dfplayer_t *dev, dfplayer_cb_done_t cb_done,
|
||||
dfplayer_cb_src_t cb_src)
|
||||
{
|
||||
if (!dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
unsigned state = irq_disable();
|
||||
dev->cb_done = cb_done;
|
||||
dev->cb_src = cb_src;
|
||||
irq_restore(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dfplayer_get_state(dfplayer_t *dev, dfplayer_state_t *state)
|
||||
{
|
||||
if (!dev || !state) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (dev->busy_pin != GPIO_UNDEF) {
|
||||
if (!gpio_read(dev->busy_pin)) {
|
||||
*state = DFPLAYER_STATE_PLAYING;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t status;
|
||||
int retval = dfplayer_query(dev, &status, DFPLAYER_CMD_GET_STATUS);
|
||||
if (retval) {
|
||||
/* pass error through */
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (status & DFPLAYER_STATUS_PLAYING) {
|
||||
*state = DFPLAYER_STATE_PLAYING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (status & DFPLAYER_STATUS_PAUSE) {
|
||||
*state = DFPLAYER_STATE_PAUSED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*state = DFPLAYER_STATE_STOPPED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dfplayer_play_file(dfplayer_t *dev, uint8_t folder, uint8_t file)
|
||||
{
|
||||
int retval;
|
||||
if (!folder || !file || (folder > DFPLAYER_MAX_FOLDER)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
retval = dfplayer_file_cmd(dev, DFPLAYER_CMD_FILE, folder, file);
|
||||
if (retval) {
|
||||
mutex_unlock(&dev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
dev->file.folder = folder;
|
||||
dev->file.file = file;
|
||||
dev->file.scheme = DFPLAYER_SCHEME_FOLDER_FILE;
|
||||
mutex_unlock(&dev->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dfplayer_play_from_mp3(dfplayer_t *dev, uint16_t number)
|
||||
{
|
||||
int retval;
|
||||
if (!dev || !number || (number > DFPLAYER_MAX_MP3_FILE)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
retval = dfplayer_file_cmd(dev, DFPLAYER_CMD_PLAY_FROM_MP3,
|
||||
(uint8_t)(number >> 8), (uint8_t) number);
|
||||
if (retval) {
|
||||
mutex_unlock(&dev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
dev->file.number = number;
|
||||
dev->file.scheme = DFPLAYER_SCHEME_MP3_FILE;
|
||||
mutex_unlock(&dev->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dfplayer_play_from_advert(dfplayer_t *dev, uint16_t number)
|
||||
{
|
||||
int retval;
|
||||
if (!dev || !number || (number > DFPLAYER_MAX_ADVERT_FILE)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
retval = dfplayer_file_cmd(dev, DFPLAYER_CMD_PLAY_ADVERT,
|
||||
(uint8_t)(number >> 8), (uint8_t) number);
|
||||
mutex_unlock(&dev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int dfplayer_step(dfplayer_t *dev, int step)
|
||||
{
|
||||
int retval;
|
||||
if (!dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
uint8_t cmd, p1, p2;
|
||||
if (dev->file.scheme == DFPLAYER_SCHEME_FOLDER_FILE) {
|
||||
/* Currently using naming scheme <FOLDERNUM>/<FILENUM>.mp3 */
|
||||
if ((dev->file.file + step < 1) ||
|
||||
(dev->file.file + step > (int)UINT8_MAX))
|
||||
{
|
||||
mutex_unlock(&dev->mutex);
|
||||
return -ERANGE;
|
||||
}
|
||||
cmd = DFPLAYER_CMD_FILE;
|
||||
p1 = dev->file.folder;
|
||||
p2 = (uint8_t)(dev->file.file + step);
|
||||
}
|
||||
else {
|
||||
/* Currently using naming scheme MP3/<FILENUM>.mp3 */
|
||||
if ((dev->file.number + step < 1) ||
|
||||
(dev->file.number + step > DFPLAYER_MAX_MP3_FILE))
|
||||
{
|
||||
mutex_unlock(&dev->mutex);
|
||||
return -ERANGE;
|
||||
}
|
||||
cmd = DFPLAYER_CMD_PLAY_FROM_MP3;
|
||||
p1 = (uint8_t)((dev->file.number + step) >> 8);
|
||||
p2 = (uint8_t)(dev->file.number + step);
|
||||
}
|
||||
|
||||
retval = dfplayer_file_cmd(dev, cmd, p1, p2);
|
||||
if (retval) {
|
||||
mutex_unlock(&dev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (dev->file.scheme == DFPLAYER_SCHEME_FOLDER_FILE) {
|
||||
/* Currently using naming scheme <FOLDERNUM>/<FILENUM>.mp3 */
|
||||
dev->file.file = (uint8_t)(dev->file.file + step);
|
||||
}
|
||||
else {
|
||||
/* Currently using naming scheme MP3/<FILENUM>.mp3 */
|
||||
dev->file.number = (uint16_t)(dev->file.number + step);
|
||||
}
|
||||
|
||||
mutex_unlock(&dev->mutex);
|
||||
return 0;
|
||||
}
|
457
drivers/dfplayer/dfplayer_internal.c
Normal file
457
drivers/dfplayer/dfplayer_internal.c
Normal file
@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_dfplayer
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation DFPlayer Mini Device Driver
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "dfplayer.h"
|
||||
#include "dfplayer_constants.h"
|
||||
#include "dfplayer_internal.h"
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/uart.h"
|
||||
#include "thread.h"
|
||||
#include "xtimer.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief Initial value of the frame check sequence
|
||||
*/
|
||||
static const uint16_t fcs_init = -(DFPLAYER_VERSION + DFPLAYER_LEN);
|
||||
|
||||
/**
|
||||
* @brief Identify the source from an insert/eject event
|
||||
*
|
||||
* @param dev DFPlayer device descriptor
|
||||
*
|
||||
* @return The source that was inserted / ejected
|
||||
* @retval DFPLAYER_SOURCE_NUMOF Unknown source
|
||||
*/
|
||||
static dfplayer_source_t _get_inserted_ejected_source(dfplayer_t *dev)
|
||||
{
|
||||
switch (dev->buf[3]) {
|
||||
case DFPLAYER_DEVICE_USB:
|
||||
DEBUG("[dfplayer] Inserted/ejected USB storage device\n");
|
||||
return DFPLAYER_SOURCE_USB;
|
||||
case DFPLAYER_DEVICE_SDCARD:
|
||||
DEBUG("[dfplayer] Inserted/ejected SD card\n");
|
||||
return DFPLAYER_SOURCE_SDCARD;
|
||||
}
|
||||
|
||||
DEBUG("[dfplayer] Insert/eject event with unknown source\n");
|
||||
return DFPLAYER_SOURCE_NUMOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle a playback completed event
|
||||
*
|
||||
* @param dev DFPlayer device descriptor
|
||||
* @param src Medium the track was played from
|
||||
*/
|
||||
static void _handle_playback_completed(dfplayer_t *dev, dfplayer_source_t src)
|
||||
{
|
||||
uint16_t track = (((uint16_t)dev->buf[2]) << 8) | dev->buf[3];
|
||||
DEBUG("[dfplayer] Playback of track %" PRIu16 " on medium %u completed\n",
|
||||
track, (unsigned)src);
|
||||
|
||||
dev->flags |= DFPLAYER_FLAG_NO_ACK_BUG;
|
||||
|
||||
/* Note: At least some revisions report playback completed more than once,
|
||||
* maybe to increase probability of the message reaching the MCU. This
|
||||
* de-duplicates the message by ignoring follow up messages for 100ms.
|
||||
* Filtering by track number and medium wouldn't work here, as the same
|
||||
* song might be played in repeat mode.
|
||||
*/
|
||||
uint32_t now_us = xtimer_now_usec();
|
||||
if (dev->cb_done && (now_us - dev->last_event_us > DFPLAYER_TIMEOUT_MS * US_PER_MS)) {
|
||||
dev->cb_done(dev, src, track);
|
||||
}
|
||||
dev->last_event_us = now_us;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse the bootup completed frame and init available sources
|
||||
*
|
||||
* @param dev DFPlayer device descriptor
|
||||
*/
|
||||
static void _handle_bootup_completed(dfplayer_t *dev)
|
||||
{
|
||||
if (dev->buf[3] & DFPLAYER_MASK_USB) {
|
||||
dev->srcs |= 0x01 << DFPLAYER_SOURCE_USB;
|
||||
}
|
||||
|
||||
if (dev->buf[3] & DFPLAYER_MASK_SDCARD) {
|
||||
dev->srcs |= 0x01 << DFPLAYER_SOURCE_SDCARD;
|
||||
}
|
||||
|
||||
if (dev->buf[3] & DFPLAYER_MASK_FLASH) {
|
||||
dev->srcs |= 0x01 << DFPLAYER_SOURCE_FLASH;
|
||||
}
|
||||
|
||||
/* Unblock caller of dfplayer_reset() */
|
||||
mutex_unlock(&dev->sync);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle a notification message
|
||||
*/
|
||||
static void _handle_event_notification(dfplayer_t *dev)
|
||||
{
|
||||
switch (dev->buf[0]) {
|
||||
case DFPLAYER_NOTIFY_INSERT:
|
||||
DEBUG("[dfplayer] Insert event\n");
|
||||
{
|
||||
dfplayer_source_t src = _get_inserted_ejected_source(dev);
|
||||
if (src < DFPLAYER_SOURCE_NUMOF) {
|
||||
dev->srcs |= (dfplayer_source_set_t)(0x01 << src);
|
||||
}
|
||||
}
|
||||
if (dev->cb_src) {
|
||||
dev->cb_src(dev, dev->srcs);
|
||||
}
|
||||
return;
|
||||
case DFPLAYER_NOTIFY_EJECT:
|
||||
DEBUG("[dfplayer] Eject event\n");
|
||||
{
|
||||
dfplayer_source_t src = _get_inserted_ejected_source(dev);
|
||||
if (src < DFPLAYER_SOURCE_NUMOF) {
|
||||
dev->srcs &= ~((dfplayer_source_set_t)(0x01 << src));
|
||||
}
|
||||
}
|
||||
if (dev->cb_src) {
|
||||
dev->cb_src(dev, dev->srcs);
|
||||
}
|
||||
return;
|
||||
case DFPLAYER_NOTIFY_DONE_USB:
|
||||
_handle_playback_completed(dev, DFPLAYER_SOURCE_USB);
|
||||
return;
|
||||
case DFPLAYER_NOTIFY_DONE_SDCARD:
|
||||
_handle_playback_completed(dev, DFPLAYER_SOURCE_SDCARD);
|
||||
return;
|
||||
case DFPLAYER_NOTIFY_DONE_FLASH:
|
||||
_handle_playback_completed(dev, DFPLAYER_SOURCE_FLASH);
|
||||
return;
|
||||
case DFPLAYER_NOTIFY_READY:
|
||||
_handle_bootup_completed(dev);
|
||||
return;
|
||||
default:
|
||||
DEBUG("[dfplayer] Unknown notification (%02x)\n", dev->buf[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse the frame received from the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer the frame received from
|
||||
*
|
||||
* The frame is stored in the buffer of the device descriptor
|
||||
*/
|
||||
static void _parse_frame(dfplayer_t *dev)
|
||||
{
|
||||
assert(dev->len == DFPLAYER_LEN);
|
||||
switch (dev->buf[0] & DFPLAYER_CLASS_MASK) {
|
||||
case DFPLAYER_CLASS_NOTIFY:
|
||||
_handle_event_notification(dev);
|
||||
return;
|
||||
case DFPLAYER_CLASS_RESPONSE:
|
||||
/* Unblock thread waiting for response */
|
||||
mutex_unlock(&dev->sync);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG("[dfplayer] Got frame of unknown class\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function called when a byte was received over UART (ISR-context)
|
||||
*
|
||||
* @param _dev The corresponding device descriptor
|
||||
* @param data The received byte of data
|
||||
*/
|
||||
void dfplayer_uart_rx_cb(void *_dev, uint8_t data)
|
||||
{
|
||||
dfplayer_t *dev = _dev;
|
||||
switch (dev->state) {
|
||||
case DFPLAYER_RX_STATE_START:
|
||||
if (data == DFPLAYER_START) {
|
||||
dev->state = DFPLAYER_RX_STATE_VERSION;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case DFPLAYER_RX_STATE_VERSION:
|
||||
if (data == DFPLAYER_VERSION) {
|
||||
dev->state = DFPLAYER_RX_STATE_LENGTH;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case DFPLAYER_RX_STATE_LENGTH:
|
||||
if (data == DFPLAYER_LEN) {
|
||||
dev->len = 0;
|
||||
dev->state = DFPLAYER_RX_STATE_DATA;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
DEBUG("[dfplayer] Got frame with length %" PRIu8 ", but all "
|
||||
"frames should have length 6\n", data);
|
||||
}
|
||||
break;
|
||||
case DFPLAYER_RX_STATE_DATA:
|
||||
/* We are a bit more liberal here and allow the end symbol to
|
||||
* appear in the payload of the frame, as the data sheet does not
|
||||
* mention any sort of escaping to prevent it from appearing in the
|
||||
* frame's payload. If bytes get lost and an and of frame symbol
|
||||
* is mistaken for a payload byte, this will be almost certainly
|
||||
* detected, as additionally a second end of frame symbol would
|
||||
* need to appear at the right position *and* the frame check
|
||||
* sequence need to match
|
||||
*/
|
||||
if ((data == DFPLAYER_END) && (dev->len == DFPLAYER_LEN)) {
|
||||
uint16_t fcs_exp = fcs_init;
|
||||
fcs_exp -= dev->buf[0] + dev->buf[1] + dev->buf[2] + dev->buf[3];
|
||||
uint16_t fcs = (((uint16_t)dev->buf[4]) << 8) | dev->buf[5];
|
||||
if (fcs == fcs_exp) {
|
||||
DEBUG("[dfplayer] Got 0x%02x, 0x%02x, 0x%02x, 0x%02x\n",
|
||||
dev->buf[0], dev->buf[1], dev->buf[2],
|
||||
dev->buf[3]);
|
||||
_parse_frame(dev);
|
||||
}
|
||||
else {
|
||||
DEBUG("[dfplayer] Checksum mismatch");
|
||||
}
|
||||
}
|
||||
else if (dev->len < sizeof(dev->buf)) {
|
||||
dev->buf[dev->len++] = data;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
DEBUG("[dfplayer] Frame overflown\n");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dev->state = DFPLAYER_RX_STATE_START;
|
||||
}
|
||||
|
||||
static int _send(dfplayer_t *dev, uint8_t cmd, uint8_t p1, uint8_t p2,
|
||||
uint32_t timeout_us)
|
||||
{
|
||||
int retval;
|
||||
if (dev->flags & DFPLAYER_FLAG_NO_ACK_BUG) {
|
||||
/* Hardware bug: The next command will not be ack'ed, unless it is
|
||||
* a query command. We can clear the flag, as we issue now a fake query,
|
||||
* if needed.
|
||||
*/
|
||||
dev->flags &= ~(DFPLAYER_FLAG_NO_ACK_BUG);
|
||||
if (cmd < DFPLAYER_LOWEST_QUERY) {
|
||||
/* Command is a control command, we query the volume and ignore the
|
||||
* result as work around */
|
||||
retval = _send(dev, DFPLAYER_CMD_GET_VOLUME, 0, 0,
|
||||
DFPLAYER_TIMEOUT_MS * US_PER_MS);
|
||||
if (retval) {
|
||||
/* pass through error */
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t fcs = fcs_init - (cmd + DFPLAYER_ACK + p1 + p2);
|
||||
uint8_t frame[] = {
|
||||
DFPLAYER_START, DFPLAYER_VERSION, DFPLAYER_LEN, cmd, DFPLAYER_ACK,
|
||||
p1, p2, (uint8_t)(fcs>>8), (uint8_t)fcs, DFPLAYER_END
|
||||
};
|
||||
|
||||
for (unsigned i = 0; i < DFPLAYER_RETRIES; i++) {
|
||||
retval = 0;
|
||||
DEBUG("[dfplayer] About to exchange frame\n");
|
||||
/* Enforce that mutex is locked, so that xtimer_mutex_lock_timeout()
|
||||
* will not return immediately. */
|
||||
mutex_trylock(&dev->sync);
|
||||
uart_write(dev->uart, frame, sizeof(frame));
|
||||
|
||||
if (xtimer_mutex_lock_timeout(&dev->sync, timeout_us)) {
|
||||
DEBUG("[dfplayer] Response timed out\n");
|
||||
retval = -ETIMEDOUT;
|
||||
}
|
||||
else {
|
||||
uint8_t code = dev->buf[0];
|
||||
if (code == DFPLAYER_RESPONSE_ERROR) {
|
||||
switch (dev->buf[3]) {
|
||||
case DFPLAYER_ERROR_BUSY:
|
||||
DEBUG("[dfplayer] Error: Module is busy\n");
|
||||
retval = -EAGAIN;
|
||||
break;
|
||||
case DFPLAYER_ERROR_FRAME:
|
||||
DEBUG("[dfplayer] Error: DFPlayer received incomplete "
|
||||
"frame\n");
|
||||
retval = -EIO;
|
||||
break;
|
||||
case DFPLAYER_ERROR_FCS:
|
||||
DEBUG("[dfplayer] Error: DFPlayer received corrupt frame "
|
||||
"(FCS mismatch)\n");
|
||||
retval = -EIO;
|
||||
break;
|
||||
default:
|
||||
DEBUG("[dfplayer] Unknown error!\n");
|
||||
/* This should never be reached according the datasheet */
|
||||
retval = -EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* wait to work around HW bug */
|
||||
xtimer_usleep(DFPLAYER_SEND_DELAY_MS * US_PER_MS);
|
||||
|
||||
if (!retval) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int dfplayer_transceive(dfplayer_t *dev, uint16_t *resp,
|
||||
uint8_t cmd, uint8_t p1, uint8_t p2)
|
||||
{
|
||||
if (!dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
|
||||
int retval = _send(dev, cmd, p1, p2, DFPLAYER_TIMEOUT_MS * US_PER_MS);
|
||||
if (retval) {
|
||||
mutex_unlock(&dev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (resp) {
|
||||
*resp = (((uint16_t)dev->buf[2]) << 8) | (uint16_t)dev->buf[3];
|
||||
}
|
||||
|
||||
mutex_unlock(&dev->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dfplayer_reset(dfplayer_t *dev)
|
||||
{
|
||||
if (!dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->mutex);
|
||||
|
||||
int retval = _send(dev, DFPLAYER_CMD_RESET, 0, 0,
|
||||
DFPLAYER_TIMEOUT_MS * US_PER_MS);
|
||||
|
||||
if (retval) {
|
||||
mutex_unlock(&dev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Enforce that mutex is locked, so that xtimer_mutex_lock_timeout()
|
||||
* will not return immediately. */
|
||||
mutex_trylock(&dev->sync);
|
||||
|
||||
const uint32_t bootup_timeout = DFPLAYER_BOOTUP_TIME_MS * US_PER_MS;
|
||||
if (xtimer_mutex_lock_timeout(&dev->sync, bootup_timeout)) {
|
||||
mutex_unlock(&dev->mutex);
|
||||
DEBUG("[dfplayer] Waiting for device to boot timed out\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
uint8_t code = dev->buf[0];
|
||||
mutex_unlock(&dev->mutex);
|
||||
|
||||
if (code != DFPLAYER_NOTIFY_READY) {
|
||||
DEBUG("[dfplayer] Got unexpected response after reset\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dfplayer_file_cmd(dfplayer_t *dev, uint8_t cmd, uint8_t p1, uint8_t p2)
|
||||
{
|
||||
int retval = _send(dev, cmd, p1, p2, DFPLAYER_TIMEOUT_MS * US_PER_MS);
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Enforce that mutex is locked, so that xtimer_mutex_lock_timeout()
|
||||
* will not return immediately. */
|
||||
mutex_trylock(&dev->sync);
|
||||
|
||||
const uint32_t timeout_us = DFPLAYER_TIMEOUT_MS * US_PER_MS;
|
||||
if (xtimer_mutex_lock_timeout(&dev->sync, timeout_us)) {
|
||||
/* For commands DFPLAYER_CMD_PLAY_FROM_MP3 (0x12) and
|
||||
* DFPLAYER_CMD_PLAY_ADVERT (0x13) a second reply is only generated on
|
||||
* failure. A timeout could be either:
|
||||
* a) Success. DFPlayer is playing the selected file
|
||||
* or
|
||||
* b) Failure, but the reply got lost (or was rejected due to mismatch
|
||||
* of the frame check sequence)
|
||||
*
|
||||
* We just check if the DFPlayer is actually playing
|
||||
*/
|
||||
if (dev->busy_pin != GPIO_UNDEF) {
|
||||
/* Using BUSY pin to check if device is playing */
|
||||
if (gpio_read(dev->busy_pin)) {
|
||||
/* Device not playing, file does not exist */
|
||||
retval = -ENOENT;
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
}
|
||||
else {
|
||||
/* BUSY pin not connected, query status instead */
|
||||
retval = _send(dev, DFPLAYER_CMD_GET_STATUS, 0, 0, timeout_us);
|
||||
|
||||
if (!retval) {
|
||||
uint8_t status = dev->buf[3];
|
||||
retval = (status & DFPLAYER_STATUS_PLAYING) ? 0 : -ENOENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t code = dev->buf[0];
|
||||
uint8_t error = dev->buf[3];
|
||||
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (code == DFPLAYER_RESPONSE_ERROR) {
|
||||
/* The DFPlayer already acknowledged successful reception of the
|
||||
* command, so we expect that the only cause for an error is that the
|
||||
* file was not found. But better check anyway, the device is strange */
|
||||
if (error == DFPLAYER_ERROR_NO_SUCH_FILE) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
DEBUG("[dfplayer] Got unexpected error message\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
189
drivers/dfplayer/include/dfplayer_constants.h
Normal file
189
drivers/dfplayer/include/dfplayer_constants.h
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_dfplayer
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Constants used in the DFPlayer Mini Driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#ifndef DFPLAYER_CONSTANTS_H
|
||||
#define DFPLAYER_CONSTANTS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <periph/gpio.h>
|
||||
#include <mutex.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Constants used in frames send to the DFPlayer Mini
|
||||
*/
|
||||
#define DFPLAYER_START (0x7e) /**< Start symbol */
|
||||
#define DFPLAYER_VERSION (0xff) /**< Value to use in version field */
|
||||
#define DFPLAYER_LEN (0x06) /**< Length of a frame */
|
||||
#define DFPLAYER_NO_ACK (0x00) /**< No acknowledgement of CMD required */
|
||||
#define DFPLAYER_ACK (0x01) /**< Acknowledgement of CMD required */
|
||||
#define DFPLAYER_END (0xef) /**< End symbol */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name UART settings of the DFPlayer Mini
|
||||
*/
|
||||
#define DFPLAYER_BAUD (9600) /**< Symbol rate of the DFPlayer mini */
|
||||
#define DFPLAYER_DATA_BITS (UART_DATA_BITS_8) /**< The DFPlayer uses 8 data bits */
|
||||
#define DFPLAYER_PARITY (UART_PARITY_NONE) /**< The DFPlayer does not use a parity bit */
|
||||
#define DFPLAYER_STOP_BITS (UART_STOP_BITS_1) /**< The DFPlayer uses 1 stop bit */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Commands supported by the DFPlayer Mini
|
||||
*/
|
||||
#define DFPLAYER_CMD_NEXT (0x01) /**< Start playing the next song */
|
||||
#define DFPLAYER_CMD_PREV (0x02) /**< Start playing the next song */
|
||||
#define DFPLAYER_CMD_VOLUME_INC (0x04) /**< Increase volume */
|
||||
#define DFPLAYER_CMD_VOLUME_DEC (0x05) /**< Decrease volume */
|
||||
#define DFPLAYER_CMD_SET_VOLUME (0x06) /**< Set the volume to the given level */
|
||||
#define DFPLAYER_CMD_SET_EQUALIZER (0x07) /**< Set the equalizer to the given setting */
|
||||
#define DFPLAYER_CMD_SET_SOURCE (0x09) /**< Set the source to play files from */
|
||||
#define DFPLAYER_CMD_STANDBY_ENTER (0x0a) /**< Enter low power mode */
|
||||
#define DFPLAYER_CMD_STANDBY_EXIT (0x0b) /**< Exit low power mode, back to normal mode */
|
||||
#define DFPLAYER_CMD_RESET (0x0c) /**< Reset the DFPlayer Mini */
|
||||
#define DFPLAYER_CMD_PLAY (0x0d) /**< Start playing the selected file */
|
||||
#define DFPLAYER_CMD_PAUSE (0x0e) /**< Pause the playback */
|
||||
#define DFPLAYER_CMD_FILE (0x0f) /**< Play the given file */
|
||||
#define DFPLAYER_CMD_PLAY_FROM_MP3 (0x12) /**< Play the given file (1-9999) from the folder `"MP3"` */
|
||||
#define DFPLAYER_CMD_PLAY_ADVERT (0x13) /**< Play the given file (1-9999) from the folder `"ADVERT"`, resume current playback afterwards */
|
||||
#define DFPLAYER_CMD_ABORT_ADVERT (0x15) /**< Play the given file (1-9999) from the folder `"ADVERT"`, resume current playback afterwards */
|
||||
#define DFPLAYER_CMD_REPEAT_FOLDER (0x17) /**< Start repeat-playing the given folder (1-99) */
|
||||
#define DFPLAYER_CMD_RANDOM (0x18) /**< Start playing all files in random order */
|
||||
#define DFPLAYER_CMD_REPEAT (0x19) /**< 0 = repeat currently played file, 1 = stop repeating */
|
||||
#define DFPLAYER_CMD_GET_STATUS (0x42) /**< Retrieve the current status */
|
||||
#define DFPLAYER_CMD_GET_VOLUME (0x43) /**< Retrieve the current volume */
|
||||
#define DFPLAYER_CMD_GET_EQUALIZER (0x44) /**< Retrieve the current equalizer setting */
|
||||
#define DFPLAYER_CMD_GET_MODE (0x45) /**< Retrieve the current playback mode */
|
||||
#define DFPLAYER_CMD_GET_VERSION (0x46) /**< Retrieve the device's software version */
|
||||
#define DFPLAYER_CMD_FILES_USB (0x47) /**< Get the total number of files on USB storage */
|
||||
#define DFPLAYER_CMD_FILES_SDCARD (0x48) /**< Get the total number of files on the SD card */
|
||||
#define DFPLAYER_CMD_FILES_FLASH (0x49) /**< Get the total number of files on NOR flash */
|
||||
#define DFPLAYER_CMD_FILENO_USB (0x4b) /**< Get the currently select file number on the USB storage */
|
||||
#define DFPLAYER_CMD_FILENO_SDCARD (0x4c) /**< Get the currently select file number on the SD-Card */
|
||||
#define DFPLAYER_CMD_FILENO_FLASH (0x4d) /**< Get the currently select file number on the NOR flash */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Classes of messages received from the DFPlayer
|
||||
*/
|
||||
#define DFPLAYER_CLASS_MASK (0xf0) /**< Use this mask to get the class from a response code */
|
||||
#define DFPLAYER_CLASS_NOTIFY (0x30) /**< Message is an event notification (unrelated to any command) */
|
||||
#define DFPLAYER_CLASS_RESPONSE (0x40) /**< Message is a response to the most recent command */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Notification codes send by the DFPlayer Mini
|
||||
*/
|
||||
#define DFPLAYER_NOTIFY_INSERT (0x3a) /**< A USB storage device or an SD card was inserted */
|
||||
#define DFPLAYER_NOTIFY_EJECT (0x3b) /**< A USB storage device or an SD card was ejected */
|
||||
#define DFPLAYER_NOTIFY_DONE_USB (0x3c) /**< Completed playing the indicated track from USB storage */
|
||||
#define DFPLAYER_NOTIFY_DONE_SDCARD (0x3d) /**< Completed playing the indicated track from SD card */
|
||||
#define DFPLAYER_NOTIFY_DONE_FLASH (0x3e) /**< Completed playing the indicated track from flash */
|
||||
/**
|
||||
* @brief The DFPlayer is ready
|
||||
*
|
||||
* This notification is send after boot/reset when the DFPlayer Mini has become
|
||||
* ready. As additional info the 4 least significant bits indicate which
|
||||
* playback sources are available.
|
||||
*/
|
||||
#define DFPLAYER_NOTIFY_READY (0x3f)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Bitmasks identifying the playback sources in the ready notification
|
||||
*/
|
||||
#define DFPLAYER_MASK_USB (0x01) /**< USB stick is connected */
|
||||
#define DFPLAYER_MASK_SDCARD (0x02) /**< SD-Card is connected */
|
||||
#define DFPLAYER_MASK_PC (0x04) /**< Unclear, has something to do with debugging */
|
||||
#define DFPLAYER_MASK_FLASH (0x08) /**< NOR flash is connected */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Response codes codes send by the DFPlayer Mini
|
||||
*/
|
||||
#define DFPLAYER_RESPONSE_ERROR (0x40) /**< While processing the most recent command an error occurred */
|
||||
#define DFPLAYER_RESPONSE_OK (0x41) /**< Last command succeeded */
|
||||
/* Beware: Handle every code of class response (0x4*) that does not indicate
|
||||
* an error (0x40) as success */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Error codes send as parameter of error messages
|
||||
*/
|
||||
#define DFPLAYER_ERROR_BUSY (0x00) /**< Module is busy */
|
||||
#define DFPLAYER_ERROR_FRAME (0x01) /**< Received incomplete frame */
|
||||
#define DFPLAYER_ERROR_FCS (0x02) /**< Frame check sequence of last frame didn't match */
|
||||
/**
|
||||
* @brief File/folder selected for playback (command 0x06) does not exit
|
||||
*
|
||||
* Beware: The DFPlayer Mini will acknowledge the command 0x06 first blindly,
|
||||
* and send a second acknowledgement when the file exists (0x41), or an error
|
||||
* (0x40) if not.
|
||||
*/
|
||||
#define DFPLAYER_ERROR_NO_SUCH_FILE (0x06)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Device identifiers in insert/eject notifications
|
||||
*/
|
||||
#define DFPLAYER_DEVICE_USB (0x01) /**< A USB storage device was inserted/ejected */
|
||||
#define DFPLAYER_DEVICE_SDCARD (0x02) /**< An SD card was inserted/ejected */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Status bitmasks
|
||||
*
|
||||
* These values have been obtained by reverse engineering.
|
||||
*/
|
||||
#define DFPLAYER_STATUS_PLAYING (0x01) /**< The DFPlayer is currently playing a song */
|
||||
#define DFPLAYER_STATUS_PAUSE (0x02) /**< The DFPlayer is paused */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Flags to store info about the driver state
|
||||
*/
|
||||
/**
|
||||
* @brief The next command will be affected by the no-ACK bug
|
||||
*
|
||||
* After playback of a file completed, the next command does not get ack'ed by
|
||||
* the device. Any subsequent command is however normally ack'ed. Luckily,
|
||||
* query commands (0x40 and higher) are not affected. The driver works around
|
||||
* this issue by querying the volume prior to sending a control command when
|
||||
* this flag is set
|
||||
*/
|
||||
#define DFPLAYER_FLAG_NO_ACK_BUG (0x01)
|
||||
/** @} */
|
||||
|
||||
#define DFPLAYER_BOOTUP_TIME_MS (3000) /**< Boot up of the device takes 1.5 to 3 secs */
|
||||
#define DFPLAYER_TIMEOUT_MS (100) /**< Timeout waiting for a replay in milliseconds */
|
||||
#define DFPLAYER_SEND_DELAY_MS (100) /**< Wait 100ms after a cmd to work around hw bug */
|
||||
#ifndef DFPLAYER_RETRIES
|
||||
#define DFPLAYER_RETRIES (5) /**< How often to retry a command on timeout */
|
||||
#endif /* DFPLAYER_RETRIES */
|
||||
#define DFPLAYER_MAX_VOLUME (30) /**< Maximum supported volume */
|
||||
#define DFPLAYER_MAX_FOLDER (99) /**< Highest supported folder number */
|
||||
#define DFPLAYER_MAX_MP3_FILE (9999) /**< Highest supported file number in the `"MP3"` folder */
|
||||
#define DFPLAYER_MAX_ADVERT_FILE (9999) /**< Highest supported file number in the `"ADVERT"` folder */
|
||||
#define DFPLAYER_LOWEST_QUERY (0x40) /**< Query commands are 0x40 or higher */
|
||||
|
||||
#endif /* DFPLAYER_CONSTANTS_H */
|
||||
/** @} */
|
261
drivers/dfplayer/include/dfplayer_implementation.h
Normal file
261
drivers/dfplayer/include/dfplayer_implementation.h
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_dfplayer
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Header-only functions of the DFPlayer Mini Device driver
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
#ifndef DFPLAYER_IMPLEMENTATION_H
|
||||
#define DFPLAYER_IMPLEMENTATION_H
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "dfplayer.h"
|
||||
#include "dfplayer_constants.h"
|
||||
#include "dfplayer_internal.h"
|
||||
#include "dfplayer_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This file contains the implementations of the functions whose signature and
|
||||
* Doxygen documentation is given in `drivers/include/dfplayer.h`. Doxygen
|
||||
* sadly doesn't detect this and creates two entries for every function; with
|
||||
* one entry being undocumented. We just hide the implementations here from
|
||||
* Doxygen to prevent the duplicate entries
|
||||
*/
|
||||
#ifndef DOXYGEN
|
||||
static inline int dfplayer_next(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_step(dev, 1);
|
||||
}
|
||||
|
||||
static inline int dfplayer_prev(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_step(dev, -1);
|
||||
}
|
||||
|
||||
static inline int dfplayer_set_volume(dfplayer_t *dev, uint8_t volume)
|
||||
{
|
||||
if (volume > DFPLAYER_MAX_VOLUME) {
|
||||
volume = DFPLAYER_MAX_VOLUME;
|
||||
}
|
||||
return dfplayer_cmd_1param(dev, DFPLAYER_CMD_SET_VOLUME, volume);
|
||||
}
|
||||
|
||||
static inline int dfplayer_set_equalizer(dfplayer_t *dev,
|
||||
dfplayer_eq_t equalizer)
|
||||
{
|
||||
if ((unsigned)equalizer >= (unsigned)DFPLAYER_EQ_NUMOF) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_cmd_1param(dev, DFPLAYER_CMD_SET_EQUALIZER,
|
||||
(uint8_t)equalizer);
|
||||
}
|
||||
|
||||
static inline int dfplayer_set_source(dfplayer_t *dev, dfplayer_source_t src)
|
||||
{
|
||||
if ((unsigned)src >= (unsigned)DFPLAYER_SOURCE_NUMOF) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_cmd_1param(dev, DFPLAYER_CMD_SET_SOURCE, (uint8_t)src);
|
||||
}
|
||||
|
||||
static inline int dfplayer_enter_standby(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_cmd(dev, DFPLAYER_CMD_STANDBY_ENTER);
|
||||
}
|
||||
|
||||
static inline int dfplayer_exit_standby(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_cmd(dev, DFPLAYER_CMD_STANDBY_EXIT);
|
||||
}
|
||||
|
||||
static inline int dfplayer_play(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_cmd(dev, DFPLAYER_CMD_PLAY);
|
||||
}
|
||||
|
||||
static inline int dfplayer_pause(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_cmd(dev, DFPLAYER_CMD_PAUSE);
|
||||
}
|
||||
|
||||
static inline int dfplayer_stop_advert(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_cmd(dev, DFPLAYER_CMD_ABORT_ADVERT);
|
||||
}
|
||||
|
||||
static inline int dfplayer_repeat_folder(dfplayer_t *dev, uint8_t folder)
|
||||
{
|
||||
return dfplayer_file_cmd(dev, DFPLAYER_CMD_REPEAT_FOLDER, 0, folder);
|
||||
}
|
||||
|
||||
static inline int dfplayer_shuffle_all(dfplayer_t *dev)
|
||||
{
|
||||
return dfplayer_cmd(dev, DFPLAYER_CMD_RANDOM);
|
||||
}
|
||||
|
||||
static inline int dfplayer_repeat(dfplayer_t *dev, bool repeat)
|
||||
{
|
||||
return dfplayer_cmd_1param(dev, DFPLAYER_CMD_REPEAT, (uint8_t)repeat);
|
||||
}
|
||||
|
||||
static inline int dfplayer_get_volume(dfplayer_t *dev, uint8_t *volume)
|
||||
{
|
||||
if (!volume) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t tmp;
|
||||
int retval = dfplayer_query(dev, &tmp, DFPLAYER_CMD_GET_VOLUME);
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
*volume = (uint8_t)tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int dfplayer_get_equalizer(dfplayer_t *dev,
|
||||
dfplayer_eq_t *equalizer)
|
||||
{
|
||||
if (!equalizer) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t tmp;
|
||||
int retval = dfplayer_query(dev, &tmp, DFPLAYER_CMD_GET_EQUALIZER);
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
*equalizer = (dfplayer_eq_t)tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int dfplayer_get_mode(dfplayer_t *dev,
|
||||
dfplayer_mode_t *mode)
|
||||
{
|
||||
if (!mode) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t tmp;
|
||||
int retval = dfplayer_query(dev, &tmp, DFPLAYER_CMD_GET_MODE);
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
*mode = (dfplayer_mode_t)tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int dfplayer_get_version(dfplayer_t *dev, uint16_t *version)
|
||||
{
|
||||
if (!version) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_query(dev, version, DFPLAYER_CMD_GET_VERSION);
|
||||
}
|
||||
|
||||
static inline int dfplayer_files_usb(dfplayer_t *dev, uint16_t *files)
|
||||
{
|
||||
if (!files) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_query(dev, files, DFPLAYER_CMD_FILES_USB);
|
||||
}
|
||||
|
||||
static inline int dfplayer_files_sdcard(dfplayer_t *dev, uint16_t *files)
|
||||
{
|
||||
if (!files) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_query(dev, files, DFPLAYER_CMD_FILES_SDCARD);
|
||||
}
|
||||
|
||||
static inline int dfplayer_files_flash(dfplayer_t *dev, uint16_t *files)
|
||||
{
|
||||
if (!files) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_query(dev, files, DFPLAYER_CMD_FILES_FLASH);
|
||||
}
|
||||
|
||||
static inline int dfplayer_get_fileno_usb(dfplayer_t *dev, uint16_t *fileno)
|
||||
{
|
||||
if (!fileno) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_query(dev, fileno, DFPLAYER_CMD_FILENO_USB);
|
||||
}
|
||||
|
||||
static inline int dfplayer_get_fileno_sdcard(dfplayer_t *dev, uint16_t *fileno)
|
||||
{
|
||||
if (!fileno) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_query(dev, fileno, DFPLAYER_CMD_FILENO_SDCARD);
|
||||
}
|
||||
|
||||
static inline int dfplayer_get_fileno_flash(dfplayer_t *dev, uint16_t *fileno)
|
||||
{
|
||||
if (!fileno) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return dfplayer_query(dev, fileno, DFPLAYER_CMD_FILENO_FLASH);
|
||||
}
|
||||
|
||||
static inline dfplayer_file_t dfplayer_get_played_file(dfplayer_t *dev)
|
||||
{
|
||||
mutex_lock(&dev->mutex);
|
||||
dfplayer_file_t res = dev->file;
|
||||
mutex_unlock(&dev->mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline int dfplayer_source_set_contains(dfplayer_source_set_t set,
|
||||
dfplayer_source_t src)
|
||||
{
|
||||
return (set & (0x01 << src)) ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline void dfplayer_source_set_add(dfplayer_source_set_t set,
|
||||
dfplayer_source_t src)
|
||||
{
|
||||
set |= 0x01 << src;
|
||||
}
|
||||
|
||||
static inline void dfplayer_source_set_rm(dfplayer_source_set_t set,
|
||||
dfplayer_source_t src)
|
||||
{
|
||||
set &= ~((dfplayer_source_set_t)(0x01 << src));
|
||||
}
|
||||
#endif /* !DOXYGEN */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DFPLAYER_IMPLEMENTATION_H */
|
||||
/** @} */
|
214
drivers/dfplayer/include/dfplayer_internal.h
Normal file
214
drivers/dfplayer/include/dfplayer_internal.h
Normal file
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_dfplayer
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Internal functions of DFPlayer Mini Device driver
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#ifndef DFPLAYER_INTERNAL_H
|
||||
#define DFPLAYER_INTERNAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dfplayer_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Exchange a frame with the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param resp On success the response info is stored if `resp != NULL`
|
||||
* @param cmd Command to send
|
||||
* @param p1 First parameter to send
|
||||
* @param p2 Second parameter to send
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid argument
|
||||
* @retval -EAGAIN Device responded with error busy
|
||||
* @retval -EIO Communication error
|
||||
* @retval -ETIMEDOUT Reply from the DFPlayer timed out
|
||||
*
|
||||
* The frame format of the DFPlayer Mini is this:
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* 0 1
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Start | Version |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Length | Command/Code |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Feedback | Parameter 1 |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Parameter 2 | FCS (MSB) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | FCS (LSB) | Stop |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* These are the values of the fields when **sending** a frame to the DFPlayer
|
||||
*
|
||||
* | Field | Value |
|
||||
* |:-------------- |:------------------------------------------------------------- |
|
||||
* | Start | `0x7e` |
|
||||
* | Version | `0xff` |
|
||||
* | Length | Length of the data (always 6) |
|
||||
* | Command/Code | Command to send |
|
||||
* | Feedback | `0x01` if device should acknowledge command, `0x00` otherwise |
|
||||
* | Parameter 1 | First parameter of the command, or `0x00` |
|
||||
* | Parameter 2 | Second parameter of the command, or `0x00` |
|
||||
* | FCS | Frame check sequence, `0 - sum(data)` |
|
||||
* | Stop | `0xef` |
|
||||
*
|
||||
* These are the values of the fields when **receiving** a frame from the DFPlayer
|
||||
*
|
||||
* | Field | Value |
|
||||
* |:-------------- |:------------------------------------------------------------- |
|
||||
* | Start | `0x7e` |
|
||||
* | Version | `0xff` |
|
||||
* | Length | Length of the data (always 6) |
|
||||
* | Command/Code | Response code (0x41 = success, 0x40 = error, 0x3* = event) |
|
||||
* | Feedback | `0x00` |
|
||||
* | Parameter 1 | Additional info (most significant byte) |
|
||||
* | Parameter 2 | Additional info (least significant byte) |
|
||||
* | FCS | Frame check sequence, `0 - sum(data)` |
|
||||
* | Stop | `0xef` |
|
||||
*
|
||||
*/
|
||||
int dfplayer_transceive(dfplayer_t *dev, uint16_t *resp,
|
||||
uint8_t cmd, uint8_t p1, uint8_t p2);
|
||||
|
||||
/**
|
||||
* @brief Send a command selecting a playback file
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param cmd Command starting playback of a file
|
||||
* @param p1 First parameter to send
|
||||
* @param p2 Second parameter to send
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid argument
|
||||
* @retval -EAGAIN Device responded with error busy
|
||||
* @retval -EIO Communication error
|
||||
* @retval -ETIMEDOUT Reply from the DFPlayer timed out
|
||||
* @retval -ENOENT No such file found
|
||||
*
|
||||
* @warning In contrast to all other functions in this header, this command
|
||||
* does not lock and unlock the mutex in `dev->mutex` internally, but
|
||||
* leaves this to the caller. This is required to do the booking of
|
||||
* the currently played file consistent with the device state, even if
|
||||
* multiple threads control the same DFPlayer.
|
||||
*/
|
||||
int dfplayer_file_cmd(dfplayer_t *dev, uint8_t cmd, uint8_t p1, uint8_t p2);
|
||||
|
||||
/**
|
||||
* @brief Reset the DFPlayer Mini MP3 player
|
||||
*
|
||||
* @param dev Device to reset
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid argument
|
||||
* @retval -EAGAIN Device responded with error busy
|
||||
* @retval -EIO Communication error
|
||||
* @retval -ETIMEDOUT Reply from the DFPlayer timed out
|
||||
*/
|
||||
int dfplayer_reset(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief UART-ISR handler of the DFPLayer driver
|
||||
*
|
||||
* @param dev DFPlayer device descriptor for which a byte was received
|
||||
* @param data The byte received over UART
|
||||
*/
|
||||
void dfplayer_uart_rx_cb(void *dev, uint8_t data);
|
||||
|
||||
/**
|
||||
* @brief Send a command with two parameters to the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param cmd Command to send
|
||||
* @param p1 First parameter to send
|
||||
* @param p2 Second parameter to send
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EAGAIN Device responded with error busy
|
||||
* @retval -EIO Communication error
|
||||
* @retval -ETIMEDOUT Reply from the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_cmd_2param(dfplayer_t *dev, uint8_t cmd,
|
||||
uint8_t p1, uint8_t p2)
|
||||
{
|
||||
return dfplayer_transceive(dev, NULL, cmd, p1, p2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a command with one parameter to the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param cmd Command to send
|
||||
* @param param The parameter to send
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EAGAIN Device responded with error busy
|
||||
* @retval -EIO Communication error
|
||||
* @retval -ETIMEDOUT Reply from the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_cmd_1param(dfplayer_t *dev, uint8_t cmd, uint8_t param)
|
||||
{
|
||||
return dfplayer_transceive(dev, NULL, cmd, 0, param);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a command without parameters to the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param cmd Command to send
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EAGAIN Device responded with error busy
|
||||
* @retval -EIO Communication error
|
||||
* @retval -ETIMEDOUT Reply from the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_cmd(dfplayer_t *dev, uint8_t cmd)
|
||||
{
|
||||
return dfplayer_transceive(dev, NULL, cmd, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a query and receive the response
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param resp The response code will be stored here
|
||||
* @param cmd Query-command to send
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EAGAIN Device responded with error busy
|
||||
* @retval -EIO Communication error
|
||||
* @retval -ETIMEDOUT Reply from the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_query(dfplayer_t *dev, uint16_t *resp, uint8_t cmd)
|
||||
{
|
||||
return dfplayer_transceive(dev, resp, cmd, 0, 0);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DFPLAYER_INTERNAL_H */
|
||||
/** @} */
|
73
drivers/dfplayer/include/dfplayer_params.h
Normal file
73
drivers/dfplayer/include/dfplayer_params.h
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_dfplayer
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Default configuration for the DFPlayer Mini driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#ifndef DFPLAYER_PARAMS_H
|
||||
#define DFPLAYER_PARAMS_H
|
||||
|
||||
#include "board.h"
|
||||
#include "dfplayer_types.h"
|
||||
#include "kernel_defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Default configuration parameters for the DFPlayer Mini driver
|
||||
* @{
|
||||
*/
|
||||
#ifndef DFPLAYER_PARAM_UART
|
||||
#define DFPLAYER_PARAM_UART (UART_DEV(0)) /**< The UART device connected to the DFPlayer Mini */
|
||||
#endif
|
||||
|
||||
#ifndef DFPLAYER_PARAM_BUSY_PIN
|
||||
#define DFPLAYER_PARAM_BUSY_PIN (GPIO_UNDEF) /**< The GPIO connected to the busy pin of the DFPlayer Mini */
|
||||
#endif
|
||||
|
||||
#ifndef DFPLAYER_PARAM_VOLUME
|
||||
#define DFPLAYER_PARAM_VOLUME (15) /**< The volume to set during initialization */
|
||||
#endif
|
||||
|
||||
#ifndef DFPLAYER_PARAMS
|
||||
#define DFPLAYER_PARAMS {\
|
||||
.uart = DFPLAYER_PARAM_UART, \
|
||||
.busy_pin = DFPLAYER_PARAM_BUSY_PIN, \
|
||||
.volume = DFPLAYER_PARAM_VOLUME, \
|
||||
}
|
||||
#endif
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @brief DFPlayer Mini configuration
|
||||
*/
|
||||
static const dfplayer_params_t dfplayer_params[] =
|
||||
{
|
||||
DFPLAYER_PARAMS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Number of DFPlayer descriptors present
|
||||
*/
|
||||
#define DFPLAYER_NUMOF ARRAY_SIZE(dfplayer_params)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DFPLAYER_PARAMS_H */
|
||||
/** @} */
|
205
drivers/dfplayer/include/dfplayer_types.h
Normal file
205
drivers/dfplayer/include/dfplayer_types.h
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_dfplayer
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Types used in the DFPlayer Mini Device Driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#ifndef DFPLAYER_TYPES_H
|
||||
#define DFPLAYER_TYPES_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "kernel_types.h"
|
||||
#include "mutex.h"
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/uart.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enumeration of the RX states of the DFPlayer driver
|
||||
*/
|
||||
typedef enum {
|
||||
DFPLAYER_RX_STATE_START, /**< Waiting for start symbol (`0x7e`) */
|
||||
DFPLAYER_RX_STATE_VERSION, /**< Waiting for version (`0xff`) */
|
||||
DFPLAYER_RX_STATE_LENGTH, /**< Waiting for length (`0x06`) */
|
||||
DFPLAYER_RX_STATE_DATA, /**< Receiving data */
|
||||
DFPLAYER_RX_STATE_NUMOF /**< Number of RX states */
|
||||
} dfplayer_rx_state_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of the equalizer settings supported by the DFPlayer
|
||||
*/
|
||||
typedef enum {
|
||||
DFPLAYER_EQ_NORMAL, /**< The "Normal" equalizer setting */
|
||||
DFPLAYER_EQ_POP, /**< The "Pop" equalizer setting */
|
||||
DFPLAYER_EQ_ROCK, /**< The "Rock" equalizer setting */
|
||||
DFPLAYER_EQ_JAZZ, /**< The "Jazz" equalizer setting */
|
||||
DFPLAYER_EQ_CLASSIC, /**< The "Classic" equalizer setting */
|
||||
DFPLAYER_EQ_BASE, /**< The "Base" equalizer setting */
|
||||
DFPLAYER_EQ_NUMOF /**< Number of supported equalizer settings */
|
||||
} dfplayer_eq_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of the playback modes supported by the DFPlayer
|
||||
*/
|
||||
typedef enum {
|
||||
DFPLAYER_MODE_UNKOWN, /**< No idea, the data sheet is vague */
|
||||
DFPLAYER_MODE_REPEAT_DIR, /**< Repeating a directory */
|
||||
DFPLAYER_MODE_REPEAT, /**< Repeating a single file */
|
||||
DFPLAYER_MODE_RANDOM, /**< Playing all files in random order */
|
||||
DFPLAYER_MODE_NORMAL, /**< Normal playback */
|
||||
DFPLAYER_MODE_NUMOF /**< Number of supported playback modes */
|
||||
} dfplayer_mode_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of the different sources for playback supported
|
||||
*/
|
||||
typedef enum {
|
||||
DFPLAYER_SOURCE_USB, /**< Read files from USB storage */
|
||||
DFPLAYER_SOURCE_SDCARD, /**< Read files from SD card */
|
||||
DFPLAYER_SOURCE_AUX, /**< No idea, the data sheet never mentions AUX again */
|
||||
DFPLAYER_SOURCE_SLEEP, /**< No idea, the data sheet is extremely vague on this */
|
||||
DFPLAYER_SOURCE_FLASH, /**< Read files from NOR flash */
|
||||
DFPLAYER_SOURCE_NUMOF /**< Number of supported playback modes */
|
||||
} dfplayer_source_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of the detectable states of the DFPlayer
|
||||
*/
|
||||
typedef enum {
|
||||
DFPLAYER_STATE_PLAYING, /**< Currently playing a file */
|
||||
DFPLAYER_STATE_PAUSED, /**< Playback is paused, can be resumed */
|
||||
DFPLAYER_STATE_STOPPED, /**< No file playing */
|
||||
DFPLAYER_STATE_NUMOF, /**< Number of DFPlayer states supported by the driver */
|
||||
} dfplayer_state_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of the different naming schemes
|
||||
*/
|
||||
typedef enum {
|
||||
DFPLAYER_SCHEME_FOLDER_FILE,/**< Naming scheme `<folder>/<file>` */
|
||||
DFPLAYER_SCHEME_MP3_FILE, /**< Naming scheme `MP3/<number>` */
|
||||
DFPLAYER_SCHEME_NUMOF, /**< Number of naming schemes supported */
|
||||
} dfplayer_scheme_t;
|
||||
|
||||
/**
|
||||
* @brief Set of DFPlayer playback sources
|
||||
*/
|
||||
typedef uint8_t dfplayer_source_set_t;
|
||||
|
||||
/**
|
||||
* @brief A DFPlayer Mini device descriptor
|
||||
*/
|
||||
typedef struct dfplayer dfplayer_t;
|
||||
|
||||
/**
|
||||
* @brief Signature of the function called when a playback of track completed
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer triggering the event
|
||||
* @param src Source medium the track was played from
|
||||
* @param track The number of the track that was played
|
||||
*
|
||||
* @warning This function is called from interrupt context
|
||||
*/
|
||||
typedef void (*dfplayer_cb_done_t)(dfplayer_t *dev, dfplayer_source_t src,
|
||||
uint16_t track);
|
||||
|
||||
/**
|
||||
* @brief Signature of the function called when a medium was inserted/ejected
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer triggering the event
|
||||
* @param srcs Set of sources currently available for playback
|
||||
*
|
||||
* @warning This function is called from interrupt context
|
||||
*/
|
||||
typedef void (*dfplayer_cb_src_t)(dfplayer_t *dev, dfplayer_source_set_t srcs);
|
||||
|
||||
/**
|
||||
* @brief Initialization parameters of a DFPlayer Mini device descriptor
|
||||
*/
|
||||
typedef struct {
|
||||
uart_t uart; /**< UART interface connected to the DFPlayer */
|
||||
gpio_t busy_pin; /**< GPIO connected to the DFPlayer's busy pin */
|
||||
uint8_t volume; /**< Initial volume */
|
||||
} dfplayer_params_t;
|
||||
|
||||
/**
|
||||
* @brief Data structure representing a file on the DFPlayer
|
||||
*
|
||||
* If @ref dfplayer_file_t::scheme is DFPLAYER_SCHEME_MP3_FILE,
|
||||
* @ref dfplayer_file_t::number holds the number of the represented file.
|
||||
* If @ref dfplayer_file_t::scheme is DFPLAYER_SCHEME_FOLDER_FILE,
|
||||
* @ref dfplayer_file_t::folder holds the number of the folder containing
|
||||
* the represented file and @ref dfplayer_file_t::file the number of the file.
|
||||
*
|
||||
* E.g. file `32/123.mp3` would be represented as:
|
||||
*
|
||||
* ```C
|
||||
* dfplayer_file_t file = {
|
||||
* .scheme = DFPLAYER_SCHEME_FOLDER_FILE,
|
||||
* .folder = 32,
|
||||
* .file = 123
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* and `MP3/0042.mp3` as:
|
||||
*
|
||||
* ```C
|
||||
* dfplayer_file_t file = {
|
||||
* .scheme = DFPLAYER_SCHEME_MP3_FILE,
|
||||
* .number = 42
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
typedef struct {
|
||||
union {
|
||||
uint16_t number; /**< Number of the file (naming scheme "MP3/1337.mp3") */
|
||||
struct {
|
||||
uint8_t folder; /**< Folder of the file (naming scheme "42/123.mp3") */
|
||||
uint8_t file; /**< Name of the file (naming scheme "42/123.mp3") */
|
||||
};
|
||||
};
|
||||
dfplayer_scheme_t scheme; /**< Used naming scheme */
|
||||
} dfplayer_file_t;
|
||||
|
||||
/**
|
||||
* @brief A DFPlayer Mini device descriptor
|
||||
*/
|
||||
struct dfplayer {
|
||||
dfplayer_cb_done_t cb_done; /**< Function to call when playing a track completed */
|
||||
dfplayer_cb_src_t cb_src; /**< Function to call when set of available playback sources changes */
|
||||
uint32_t last_event_us; /**< Time stamp of the last event in µs */
|
||||
uint8_t buf[6]; /**< Data buffer for response from DFPlayer */
|
||||
dfplayer_file_t file; /**< Currently played song */
|
||||
uint8_t len; /**< Length of the frame in the buffer */
|
||||
uint8_t flags; /**< Flags storing info about the driver state*/
|
||||
mutex_t mutex; /**< Used to mutual exclusive access */
|
||||
uart_t uart; /**< UART interface connected to the DFPlayer */
|
||||
gpio_t busy_pin; /**< GPIO connected to the DFPlayer's busy pin */
|
||||
mutex_t sync; /**< Used to wait on ISR */
|
||||
dfplayer_source_set_t srcs; /**< Set of available playback sources */
|
||||
dfplayer_rx_state_t state; /**< Current state of the DFPlayer */
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DFPLAYER_TYPES_H */
|
||||
/** @} */
|
777
drivers/include/dfplayer.h
Normal file
777
drivers/include/dfplayer.h
Normal file
@ -0,0 +1,777 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup drivers_dfplayer DFPlayer Mini MP3 Player
|
||||
* @ingroup drivers_multimedia
|
||||
* @brief Driver for the DFPlayer Mini MP3 Player
|
||||
*
|
||||
* The DFPlayer Mini is a super cheap MP3 Player that can be controlled via
|
||||
* UART. It can play files from SD card, USB storage devices and NOR flash.
|
||||
* It has an integrated 3W amplifier that can be used to build a standalone
|
||||
* mono speaker. Alternatively a stereo output can be used that can be
|
||||
* directly connected to headphones, but needs a dedicated amplifier for
|
||||
* connecting to passive speakers.
|
||||
*
|
||||
* ![DFPlayer pinout](https://camo.githubusercontent.com/fd6ed047e8e3ced124b32051ddfff2df9e8afe47/68747470733a2f2f63646e2e73686f706966792e636f6d2f732f66696c65732f312f313530392f313633382f66696c65732f4d50335f706c617965725f6d6f64756c655f70696e6f75745f6469616772616d2e706e67)
|
||||
*
|
||||
* File System Setup
|
||||
* =================
|
||||
*
|
||||
* The SD card or USB storage devices needs to be formatted as FAT32 and the
|
||||
* MP3 files have to be stored using one of the following naming schemes, if
|
||||
* selecting the files efficiently is required:
|
||||
*
|
||||
* Numbered File Inside Numbered Folder Naming Scheme
|
||||
* --------------------------------------------------
|
||||
*
|
||||
* The MP3 files have to be named with three leading decimal digits (e.g.
|
||||
* `001.mp3` or `042 - foo bar.mp3`) and must be placed in in folders named with
|
||||
* two decimal digits (e.g. `01` or `99`). The folder name numbers must be in
|
||||
* the range 1-99 and the file name numbers in the range 1-255. Playback of
|
||||
* these files can be started using the function @ref dfplayer_play_file .
|
||||
*
|
||||
* Examples of valid paths:
|
||||
*
|
||||
* - `01/001.mp3`
|
||||
* - `19/249 - Nothing Else Matters.mp3`
|
||||
*
|
||||
* Examples of ***INVALID*** paths:
|
||||
*
|
||||
* - `01 - Yngwie Malmsteen/042 - Leonardo.mp3`
|
||||
* - The folder name must only consist of two digits, additional chars are
|
||||
* not allowed in the folder name
|
||||
* - `9/42.mp3`
|
||||
* - Leading zeros must be added, e.g. `09/042.mp3` would be valid
|
||||
* - `99/359.mp3`
|
||||
* - Folder number out of range (1-255 is valid)
|
||||
*
|
||||
* Numbered File Inside MP3 Folder Naming Scheme
|
||||
* ---------------------------------------------
|
||||
*
|
||||
* The MP3 files have to be named with four decimal digits and must be placed in
|
||||
* the folder `"MP3"`. Subsequent characters after the four digits are ignored.
|
||||
* Valid names are e.g. `MP3/0001.mp3` or `MP3/0042 - My favorite song.mp3`. The
|
||||
* file numbers 1-9999 are valid. Playback of these files can be started using
|
||||
* the function @ref dfplayer_play_from_mp3 .
|
||||
*
|
||||
* Advertisements
|
||||
* --------------
|
||||
*
|
||||
* MP3 files placed in the folder `"ADVERT"` named with four decimal digits
|
||||
* (e.g. `"0001.mp3"` or `"1337.mp3"`) can be played as advertisements: The
|
||||
* function @ref dfplayer_play_from_advert can be used to start their playback,
|
||||
* but only while a non-advertisement is currently playing. After the
|
||||
* advertisement the normal playback is resumed. This feature has been
|
||||
* implemented in the hope it is used for features like audible user feedback
|
||||
* to controls, rather than playing advertisements.
|
||||
*
|
||||
* Combining Naming Schemes
|
||||
* ------------------------
|
||||
*
|
||||
* All of the above naming schemes can be used on the same storage device.
|
||||
* However, you still have to use to the functions intended to be used with
|
||||
* the naming scheme of a specific file in order to be able to play it.
|
||||
*
|
||||
* Track Numbers
|
||||
* =============
|
||||
*
|
||||
* @warning Track numbers are a bogus unit in the DFPlayer
|
||||
*
|
||||
* Track numbers in the dfplayer revert to the number of the file in the file
|
||||
* system. Without preparing the medium played at a very low level, it will be
|
||||
* hard to map track numbers to their corresponding songs. If you started
|
||||
* playback using @ref dfplayer_play_file or @ref dfplayer_play_from_mp3, you
|
||||
* can use @ref dfplayer_get_played_file to get the currently played filed
|
||||
* according to the used naming scheme (see section above).
|
||||
*
|
||||
* Continuous Playback
|
||||
* ===================
|
||||
*
|
||||
* The are two options for achieving continuous playback with the DFPlayer Mini:
|
||||
* The first is to use @ref dfplayer_shuffle_all or @ref dfplayer_repeat_folder
|
||||
* to schedule shuffled playback of all files or repeat playback of a folder,
|
||||
* respectively. But keep in mind that any playback command will switch back to
|
||||
* normal playback mode.
|
||||
*
|
||||
* The second option is to schedule playback of the next file right after the
|
||||
* playback of a file has completed. This can be implemented most conveniently
|
||||
* by using the callback functions (see @ref dfplayer_set_callbacks). But
|
||||
* beware: The callbacks are called in interrupt context and the API of this
|
||||
* driver ***MUST ONLY*** be used in thread context.
|
||||
*
|
||||
* Known Hardware Bugs
|
||||
* ===================
|
||||
*
|
||||
* Device Bugs Not Handled by the Driver
|
||||
* -------------------------------------
|
||||
*
|
||||
* On some versions of the DFPlayer, any UART communication with the device
|
||||
* during playback results in audible and unpleasant glitches. The only solution
|
||||
* is to not communicate with the device during playback, as the DFPlayer seems
|
||||
* to pause playback for some milliseconds after receiving a command. If the
|
||||
* busy pin of the DFPlayer is connected to your board and configured, the
|
||||
* function @ref dfplayer_get_state will read the busy pin to detect if the
|
||||
* DFPlayer is currently playing. Onlyif according to the busy pin the device is
|
||||
* not currently playing, the UART interface is used to detect whether the
|
||||
* playback is paused or stopped. Thus, @ref dfplayer_get_state can be used
|
||||
* without fearing to cause audio glitches, provided the busy pin is connected
|
||||
* and configured. You can use this to poll the state of the DPlayer and issue
|
||||
* the commands once playback has completed, or use the callbacks (see
|
||||
* @ref dfplayer_set_callbacks) to get notified when playback has completed.
|
||||
*
|
||||
* When playback of a file from the `"ADVERT"` folder is started while the
|
||||
* device not playing, or already playing a file from the `"ADVERT"` folder,
|
||||
* the command fails to execute and the DFPlayer stops all playback (if
|
||||
* currently playing).
|
||||
*
|
||||
* Device Bugs Handled by the Driver
|
||||
* ---------------------------------
|
||||
*
|
||||
* (These bugs are transparently handled by the driver, so users of the driver
|
||||
* can safely skip reading this section.)
|
||||
*
|
||||
* When playing a file from the MP3 folder or from the ADVERT folder, the
|
||||
* DFPlayer acknowledges the command before checking if the file actually
|
||||
* exists. If the file does not exist, a second message to indicate the failure
|
||||
* is send, but no second message is send on success. If the second message is
|
||||
* not received, the driver has to figure out by other means if this is a
|
||||
* communication error or indicates that the playback has started. If the
|
||||
* busy pin of the DFPlayer is connected and configured, it will be used to
|
||||
* verify that the command succeeded. Otherwise, the driver will query the
|
||||
* status of the device via UART to confirm that playback started, which can
|
||||
* cause audible glitches on some revisions. For those revisions, the busy pin
|
||||
* should be really used.
|
||||
*
|
||||
* When the DFPlayer completes playback of a file, the next command received
|
||||
* is not acknowledged, if it is a control command. Query commands are not
|
||||
* affected. The driver works around this bug by querying the volume prior to
|
||||
* issuing a control command directly after playback completed.
|
||||
*
|
||||
* Some versions of the DFPlayer are not able to handle commands send at high
|
||||
* frequency. A delay of 100ms is inserted after every communication with the
|
||||
* device to counter this.
|
||||
*
|
||||
* Some versions of the DFPlayer have a high chance of ignoring commands. The
|
||||
* driver tries up to @ref DFPLAYER_RETRIES (`5` be default) times the command
|
||||
* before giving up. For that reason, non-idempotent commands supported by the
|
||||
* DFPlayer (e.g. increase and decrease volume) are not exposed by the driver,
|
||||
* as retrying could result in them being executed more than once. There are
|
||||
* idempotent commands that can achieve the same (e.g. the set volume command
|
||||
* instead of increase or decrease volume commands) for every non-idempotent
|
||||
* command, so this doesn't restrict the feature set of the driver.
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief DFPlayer Mini Device Driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#ifndef DFPLAYER_H
|
||||
#define DFPLAYER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dfplayer_params.h"
|
||||
#include "dfplayer_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The number of milliseconds to wait after receiving playback of file
|
||||
* has completed before starting playback of the next file
|
||||
*/
|
||||
#define DFPLAYER_WAIT_MS (100U)
|
||||
|
||||
#ifdef MODULE_AUTO_INIT_MULTIMEDIA
|
||||
|
||||
extern dfplayer_t dfplayer_devs[DFPLAYER_NUMOF];
|
||||
|
||||
/**
|
||||
* @brief Get an auto-initialized DFPlayer Mini device descriptor by number
|
||||
* @param num Number of the DFPlayer Mini device descriptor to get
|
||||
* @return The DFPlayer Mini device descriptor at index @p num
|
||||
* @retval `NULL` @p num is out of range
|
||||
*
|
||||
* A value of `0` for @p num is used to retrieve the first device descriptor.
|
||||
*/
|
||||
static inline dfplayer_t * dfplayer_get(unsigned num)
|
||||
{
|
||||
if (num < DFPLAYER_NUMOF) {
|
||||
return &dfplayer_devs[num];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* MODULE_AUTO_INIT_MULTIMEDIA */
|
||||
|
||||
/**
|
||||
* @brief Initialize a DFPlayer Mini device descriptor
|
||||
*
|
||||
* @param dev Device descriptor to initialized
|
||||
* @param params Connection parameters to initialize with
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Invalid parameters
|
||||
* @retval -EIO Failed to initialize UART interface / GPIO pin
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
int dfplayer_init(dfplayer_t *dev, const dfplayer_params_t *params);
|
||||
|
||||
/**
|
||||
* @brief Get the set of available playback sources of the given DFPlayer
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer Mini check
|
||||
*
|
||||
* @return The set of playback sources currently available
|
||||
* @retval 0 If @p dev is `NULL` or no source is available
|
||||
*/
|
||||
dfplayer_source_set_t dfplayer_get_sources(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Set/clear the event callbacks of the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to update
|
||||
* @param cb_done Function to call when playback of a file/track completed
|
||||
* @param cb_src Function to call when a source was inserted/ejected
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameter
|
||||
*
|
||||
* Calling with `NULL` for @p cb_done and/or for @p cb_src clears the
|
||||
* corresponding callback functions.
|
||||
*
|
||||
* @warning The callbacks are called from interrupt context
|
||||
*/
|
||||
int dfplayer_set_callbacks(dfplayer_t *dev, dfplayer_cb_done_t cb_done,
|
||||
dfplayer_cb_src_t cb_src);
|
||||
|
||||
/**
|
||||
* @brief Start playing the next song in the currently used naming scheme
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameter
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ERANGE The next song is out of range (depending to the
|
||||
* currently used naming scheme)
|
||||
* @retval -ENOENT No such file
|
||||
*
|
||||
* If previously @ref dfplayer_play_file was used to start playback of
|
||||
* file "42/113.mp3", this call will try to start "42/114.mp3". If previously
|
||||
* @ref dfplayer_play_from_mp3 was used to start playback of file
|
||||
* "MP3/1337.mp3", this call will try to start "MP3/1338.mp3".
|
||||
*/
|
||||
static inline int dfplayer_next(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Start playing the previous song in the currently used naming scheme
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameter
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ERANGE The next song is out of range (depending to the
|
||||
* currently used naming scheme)
|
||||
* @retval -ENOENT No such file
|
||||
*
|
||||
* If previously @ref dfplayer_play_file was used to start playback of
|
||||
* file "42/113.mp3", this call will try to start "42/112.mp3". If previously
|
||||
* @ref dfplayer_play_from_mp3 was used to start playback of file
|
||||
* "MP3/1337.mp3", this call will try to start "MP3/1336.mp3".
|
||||
*/
|
||||
static inline int dfplayer_prev(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Step forward/backward following the currently used naming scheme
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param step Steps to take (negative for previous files)
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameter
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ERANGE The next song is out of range (depending to the
|
||||
* currently used naming scheme)
|
||||
* @retval -ENOENT No such file
|
||||
*
|
||||
* Calling with a value of `1` for @p step is equivalent to @ref dfplayer_next,
|
||||
* calling with a value of `-1` for @p step is equivalent to @ref dfplayer_prev.
|
||||
* A value of `0` will restart the currently played song
|
||||
*/
|
||||
int dfplayer_step(dfplayer_t *dev, int step);
|
||||
|
||||
/**
|
||||
* @brief Sets the volume to the given value
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param volume Volume to set (max 30)
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*
|
||||
* If @p volume is greater than the maximum allowed volume of 30, 30 will be
|
||||
* send instead.
|
||||
*/
|
||||
static inline int dfplayer_set_volume(dfplayer_t *dev, uint8_t volume);
|
||||
|
||||
/**
|
||||
* @brief Apply the given equalizer setting
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param equalizer The equalizer setting to apply
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_set_equalizer(dfplayer_t *dev,
|
||||
dfplayer_eq_t equalizer);
|
||||
|
||||
/**
|
||||
* @brief Apply the given source
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param src The source to use for playback
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_set_source(dfplayer_t *dev, dfplayer_source_t src);
|
||||
|
||||
/**
|
||||
* @brief Enter standby mode
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_enter_standby(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Exit standby mode
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_exit_standby(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Start/resume playing
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameter
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_play(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Pause the playback
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameter
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_pause(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Start playing the specified file in the specified folder
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param folder Number of the folder
|
||||
* @param file Number of the file
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters (see precondition)
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ENOENT Specified file and/or folder does not exist
|
||||
*
|
||||
* @pre `0 < folder <= 100` and `file > 0`
|
||||
*
|
||||
* E.g. when called with @p folder set to `9` and @p file set to `42`, the
|
||||
* file `"09/042.mp3"` is played. Thus, the folder and file names need to
|
||||
* follow a specific naming convention in order to be selectable with this
|
||||
* function.
|
||||
*/
|
||||
int dfplayer_play_file(dfplayer_t *dev, uint8_t folder, uint8_t file);
|
||||
|
||||
/**
|
||||
* @brief Start playing the specified number in the MP3 folder
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param number Number of the file in the folder `"MP3"` to play
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters (see precondition)
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ENOENT Specified number does not exist
|
||||
*
|
||||
* @pre `0 < number <= 9999`
|
||||
*
|
||||
* E.g. when called with @p number set to `42`, the file `"MP3/0042.mp3"` is
|
||||
* played. Thus, the folder and file names need to follow a specific naming
|
||||
* convention in order to be selectable with this function.
|
||||
*/
|
||||
int dfplayer_play_from_mp3(dfplayer_t *dev, uint16_t number);
|
||||
|
||||
/**
|
||||
* @brief Start playing the specified number in the ADVERT folder
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param number Number of the number in the folder `"ADVERT"` to play
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters (see precondition)
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ENOENT Specified number does not exist
|
||||
*
|
||||
* @pre `0 < number <= 9999`
|
||||
* @warning The playback is only started when the DFPlayer is currently playing
|
||||
* a non-advert file. The current playback is paused, the advert-file
|
||||
* is played, and the previous playback is resumed afterwards
|
||||
* @note While this feature was obviously added to allow playing
|
||||
* advertisements, this function was provided in the best hope it is
|
||||
* not used this way.
|
||||
*
|
||||
* E.g. when called with @p number set to `42`, the file `"ADVERT/0042.mp3"` is
|
||||
* played. Thus, the folder and file names need to follow a specific naming
|
||||
* convention in order to be selectable with this function.
|
||||
*
|
||||
* The most obvious use (apart for advertisements `:-/`) is to use it for
|
||||
* audible feedback to control commands. E.g. when the user changes the volume,
|
||||
* a short "boing" sound could be played. That would allow the user to perceive
|
||||
* the configured volume, even if currently played song is silent while
|
||||
* configuring the volume.
|
||||
*/
|
||||
int dfplayer_play_from_advert(dfplayer_t *dev, uint16_t number);
|
||||
|
||||
/**
|
||||
* @brief Stop playing a file from the ADVERT folder and resume previous playback
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters (see precondition)
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_stop_advert(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Start playing and repeating the specified folder
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param folder Number of the folder to play and repeat
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters (see precondition)
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ENOENT Specified file and/or folder does not exist
|
||||
*
|
||||
* @pre `0 < folder <= 100` and `file > 0`
|
||||
*/
|
||||
static inline int dfplayer_repeat_folder(dfplayer_t *dev, uint8_t folder);
|
||||
|
||||
/**
|
||||
* @brief Launch shuffle playback of all files
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters (see precondition)
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ENOENT Specified file and/or folder does not exist
|
||||
*
|
||||
* @warning Even files in the `"ADVERT"` folder are played
|
||||
*/
|
||||
static inline int dfplayer_shuffle_all(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable repeat playback
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to control
|
||||
* @param repeat Enable repeat playback?
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters (see precondition)
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
* @retval -ENOENT Specified file and/or folder does not exist
|
||||
*/
|
||||
static inline int dfplayer_repeat(dfplayer_t *dev, bool repeat);
|
||||
|
||||
/**
|
||||
* @brief Query the state of the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param state The state will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*
|
||||
* @note This function will work best when the busy pin is connected and
|
||||
* and configured, as this can avoid UART communication if the device
|
||||
* is currently playing. (Remember that UART communication results
|
||||
* in audible glitches during playback...)
|
||||
*/
|
||||
int dfplayer_get_state(dfplayer_t *dev, dfplayer_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Query the current volume of the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param volume The volume will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_get_volume(dfplayer_t *dev, uint8_t *volume);
|
||||
|
||||
/**
|
||||
* @brief Query the current equalizer setting of the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param equalizer The equalizer setting will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_get_equalizer(dfplayer_t *dev,
|
||||
dfplayer_eq_t *equalizer);
|
||||
|
||||
/**
|
||||
* @brief Query the current playback mode of the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param mode The playback mode will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_get_mode(dfplayer_t *dev,
|
||||
dfplayer_mode_t *mode);
|
||||
|
||||
/**
|
||||
* @brief Query the software version running on the DFPlayer Mini
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param version The software version will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_get_version(dfplayer_t *dev, uint16_t *version);
|
||||
|
||||
/**
|
||||
* @brief Query the number of files on the USB storage device
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param files The number of files will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_files_usb(dfplayer_t *dev, uint16_t *files);
|
||||
|
||||
/**
|
||||
* @brief Query the number of files on the SD card
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param files The number of files will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_files_sdcard(dfplayer_t *dev, uint16_t *files);
|
||||
|
||||
/**
|
||||
* @brief Query the number of files on the NOR flash
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param files The number of files will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_files_flash(dfplayer_t *dev, uint16_t *files);
|
||||
|
||||
/**
|
||||
* @brief Query the selected file on the USB storage device
|
||||
*
|
||||
* @warning The file number refers to the internal file system order
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param fileno The selected file will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_get_fileno_usb(dfplayer_t *dev, uint16_t *fileno);
|
||||
|
||||
/**
|
||||
* @brief Query the selected file on the SD card
|
||||
*
|
||||
* @warning The file number refers to the internal file system order
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param fileno The selected file will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_get_fileno_sdcard(dfplayer_t *dev, uint16_t *fileno);
|
||||
|
||||
/**
|
||||
* @brief Query the selected file on the NOR flash
|
||||
*
|
||||
* @warning The file number refers to the internal file system order
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
* @param fileno The selected file will be stored here
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid parameters
|
||||
* @retval -EIO Communication with the DFPlayer Mini failed
|
||||
* @retval -EAGAIN DFPlayer responded with error "Device busy"
|
||||
* @retval -ETIMEDOUT Response of the DFPlayer timed out
|
||||
*/
|
||||
static inline int dfplayer_get_fileno_flash(dfplayer_t *dev, uint16_t *fileno);
|
||||
|
||||
/**
|
||||
* @brief Get the currently played file and the used naming scheme
|
||||
*
|
||||
* @param dev Device descriptor of the DFPlayer to query
|
||||
*
|
||||
* @return The currently played file and the used naming scheme
|
||||
*/
|
||||
static inline dfplayer_file_t dfplayer_get_played_file(dfplayer_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Check if the given source set contains the given source
|
||||
*
|
||||
* @param set Set of sources to check
|
||||
* @param src Source to check for
|
||||
*
|
||||
* @retval 0 @p src is ***NOT*** part of @p set
|
||||
* @retval 1 @p src ***IS*** part of @p set
|
||||
*/
|
||||
static inline int dfplayer_source_set_contains(dfplayer_source_set_t set,
|
||||
dfplayer_source_t src);
|
||||
|
||||
/**
|
||||
* @brief Add the given source to the given source set
|
||||
*
|
||||
* @param set Set of sources to add source to
|
||||
* @param src Source to add
|
||||
*
|
||||
* This function is idempotent
|
||||
*/
|
||||
static inline void dfplayer_source_set_add(dfplayer_source_set_t set,
|
||||
dfplayer_source_t src);
|
||||
|
||||
/**
|
||||
* @brief Remove the given source to the given source set
|
||||
*
|
||||
* @param set Set of sources to remove the source from
|
||||
* @param src Source to remove
|
||||
*
|
||||
* This function is idempotent
|
||||
*/
|
||||
static inline void dfplayer_source_set_rm(dfplayer_source_set_t set,
|
||||
dfplayer_source_t src);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Include implementation of the static inline functions */
|
||||
#include "dfplayer_implementation.h"
|
||||
|
||||
#endif /* DFPLAYER_H */
|
||||
/** @} */
|
@ -18,6 +18,10 @@ ifneq (,$(filter auto_init_loramac,$(USEMODULE)))
|
||||
DIRS += loramac
|
||||
endif
|
||||
|
||||
ifneq (,$(filter auto_init_multimedia,$(USEMODULE)))
|
||||
DIRS += multimedia
|
||||
endif
|
||||
|
||||
ifneq (,$(filter auto_init_usbus,$(USEMODULE)))
|
||||
DIRS += usb
|
||||
endif
|
||||
|
@ -260,5 +260,9 @@ void auto_init(void)
|
||||
|
||||
if (IS_USED(MODULE_AUTO_INIT_MULTIMEDIA)) {
|
||||
LOG_DEBUG("auto_init MULTIMEDIA\n");
|
||||
if (IS_USED(MODULE_DFPLAYER)) {
|
||||
extern void auto_init_dfplayer(void);
|
||||
auto_init_dfplayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
3
sys/auto_init/multimedia/Makefile
Normal file
3
sys/auto_init/multimedia/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
MODULE = auto_init_multimedia
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
50
sys/auto_init/multimedia/auto_init_dfplayer.c
Normal file
50
sys/auto_init/multimedia/auto_init_dfplayer.c
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2019 Marian Buschsieweke
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* @ingroup sys_auto_init_multimedia
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Auto initialization for DFPlayer Mini MP3 player
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsiewke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifdef MODULE_DFPLAYER
|
||||
|
||||
#include "log.h"
|
||||
#include "assert.h"
|
||||
#include "dfplayer.h"
|
||||
#include "dfplayer_params.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief Allocate memory for the device descriptors
|
||||
*/
|
||||
dfplayer_t dfplayer_devs[DFPLAYER_NUMOF];
|
||||
|
||||
void auto_init_dfplayer(void)
|
||||
{
|
||||
DEBUG("[dfplayer] Auto init\n");
|
||||
for (unsigned i = 0; i < DFPLAYER_NUMOF; i++) {
|
||||
if (dfplayer_init(&dfplayer_devs[i], &dfplayer_params[i])) {
|
||||
LOG_ERROR("[auto_init_multimedia] error initializing dfplayer #%u\n", i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
typedef int dont_be_pedantic;
|
||||
#endif /* MODULE_DFPLAYER */
|
Loading…
Reference in New Issue
Block a user