From 82bfb66cb8b9ce25ce8f2bb5a73e41f502492004 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Thu, 29 Aug 2019 11:20:56 +0200 Subject: [PATCH] drivers: Added driver for the DFPlayer MP3 player --- drivers/Makefile.dep | 6 + drivers/Makefile.include | 4 + drivers/dfplayer/Makefile | 1 + drivers/dfplayer/dfplayer.c | 239 ++++++ drivers/dfplayer/dfplayer_internal.c | 457 ++++++++++ drivers/dfplayer/include/dfplayer_constants.h | 189 +++++ .../include/dfplayer_implementation.h | 261 ++++++ drivers/dfplayer/include/dfplayer_internal.h | 214 +++++ drivers/dfplayer/include/dfplayer_params.h | 73 ++ drivers/dfplayer/include/dfplayer_types.h | 205 +++++ drivers/include/dfplayer.h | 777 ++++++++++++++++++ sys/auto_init/Makefile | 4 + sys/auto_init/auto_init.c | 4 + sys/auto_init/multimedia/Makefile | 3 + sys/auto_init/multimedia/auto_init_dfplayer.c | 50 ++ 15 files changed, 2487 insertions(+) create mode 100644 drivers/dfplayer/Makefile create mode 100644 drivers/dfplayer/dfplayer.c create mode 100644 drivers/dfplayer/dfplayer_internal.c create mode 100644 drivers/dfplayer/include/dfplayer_constants.h create mode 100644 drivers/dfplayer/include/dfplayer_implementation.h create mode 100644 drivers/dfplayer/include/dfplayer_internal.h create mode 100644 drivers/dfplayer/include/dfplayer_params.h create mode 100644 drivers/dfplayer/include/dfplayer_types.h create mode 100644 drivers/include/dfplayer.h create mode 100644 sys/auto_init/multimedia/Makefile create mode 100644 sys/auto_init/multimedia/auto_init_dfplayer.c diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index bf8b4bc5ad..e7551d99e6 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -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 diff --git a/drivers/Makefile.include b/drivers/Makefile.include index 017bd65d67..c4dccaf3d0 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -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 diff --git a/drivers/dfplayer/Makefile b/drivers/dfplayer/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/dfplayer/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/dfplayer/dfplayer.c b/drivers/dfplayer/dfplayer.c new file mode 100644 index 0000000000..96efdaf487 --- /dev/null +++ b/drivers/dfplayer/dfplayer.c @@ -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 + * + * @} + */ +#include +#include +#include +#include +#include + +#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 /.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/.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 /.mp3 */ + dev->file.file = (uint8_t)(dev->file.file + step); + } + else { + /* Currently using naming scheme MP3/.mp3 */ + dev->file.number = (uint16_t)(dev->file.number + step); + } + + mutex_unlock(&dev->mutex); + return 0; +} diff --git a/drivers/dfplayer/dfplayer_internal.c b/drivers/dfplayer/dfplayer_internal.c new file mode 100644 index 0000000000..738af3ad75 --- /dev/null +++ b/drivers/dfplayer/dfplayer_internal.c @@ -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 + * + * @} + */ +#include +#include +#include +#include +#include + +#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; +} diff --git a/drivers/dfplayer/include/dfplayer_constants.h b/drivers/dfplayer/include/dfplayer_constants.h new file mode 100644 index 0000000000..25144980e0 --- /dev/null +++ b/drivers/dfplayer/include/dfplayer_constants.h @@ -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 + */ + +#ifndef DFPLAYER_CONSTANTS_H +#define DFPLAYER_CONSTANTS_H + +#include +#include +#include + +#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 */ +/** @} */ diff --git a/drivers/dfplayer/include/dfplayer_implementation.h b/drivers/dfplayer/include/dfplayer_implementation.h new file mode 100644 index 0000000000..def994743c --- /dev/null +++ b/drivers/dfplayer/include/dfplayer_implementation.h @@ -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 + */ +#ifndef DFPLAYER_IMPLEMENTATION_H +#define DFPLAYER_IMPLEMENTATION_H + +#include + +#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 */ +/** @} */ diff --git a/drivers/dfplayer/include/dfplayer_internal.h b/drivers/dfplayer/include/dfplayer_internal.h new file mode 100644 index 0000000000..92bb572262 --- /dev/null +++ b/drivers/dfplayer/include/dfplayer_internal.h @@ -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 + */ + +#ifndef DFPLAYER_INTERNAL_H +#define DFPLAYER_INTERNAL_H + +#include + +#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 */ +/** @} */ diff --git a/drivers/dfplayer/include/dfplayer_params.h b/drivers/dfplayer/include/dfplayer_params.h new file mode 100644 index 0000000000..668118cef1 --- /dev/null +++ b/drivers/dfplayer/include/dfplayer_params.h @@ -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 + */ + +#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 */ +/** @} */ diff --git a/drivers/dfplayer/include/dfplayer_types.h b/drivers/dfplayer/include/dfplayer_types.h new file mode 100644 index 0000000000..a880bb7162 --- /dev/null +++ b/drivers/dfplayer/include/dfplayer_types.h @@ -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 + */ + +#ifndef DFPLAYER_TYPES_H +#define DFPLAYER_TYPES_H + +#include +#include + +#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 `/` */ + DFPLAYER_SCHEME_MP3_FILE, /**< Naming scheme `MP3/` */ + 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 */ +/** @} */ diff --git a/drivers/include/dfplayer.h b/drivers/include/dfplayer.h new file mode 100644 index 0000000000..5377fe3d19 --- /dev/null +++ b/drivers/include/dfplayer.h @@ -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 + */ + +#ifndef DFPLAYER_H +#define DFPLAYER_H + +#include +#include + +#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 */ +/** @} */ diff --git a/sys/auto_init/Makefile b/sys/auto_init/Makefile index 24b4ecf5ba..850a5287a9 100644 --- a/sys/auto_init/Makefile +++ b/sys/auto_init/Makefile @@ -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 diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index cd99790695..a33274dbff 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -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(); + } } } diff --git a/sys/auto_init/multimedia/Makefile b/sys/auto_init/multimedia/Makefile new file mode 100644 index 0000000000..b53f11d211 --- /dev/null +++ b/sys/auto_init/multimedia/Makefile @@ -0,0 +1,3 @@ +MODULE = auto_init_multimedia + +include $(RIOTBASE)/Makefile.base diff --git a/sys/auto_init/multimedia/auto_init_dfplayer.c b/sys/auto_init/multimedia/auto_init_dfplayer.c new file mode 100644 index 0000000000..60c7a5e424 --- /dev/null +++ b/sys/auto_init/multimedia/auto_init_dfplayer.c @@ -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 + * + * @} + */ + +#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 */