drivers/dac_dds: add module to play sample buffer over a DAC
This adds an API function to play a buffer of Audio samples using a DAC. Double buffered operation is supported by specifying a callback that will be called when the next buffer can be queued with dac_dds_play().
This commit is contained in:
parent
7946eb766e
commit
ceb3f8443a
1
drivers/dac_dds/Makefile
Normal file
1
drivers/dac_dds/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
2
drivers/dac_dds/Makefile.dep
Normal file
2
drivers/dac_dds/Makefile.dep
Normal file
@ -0,0 +1,2 @@
|
||||
FEATURES_REQUIRED += periph_dac
|
||||
FEATURES_REQUIRED += periph_timer_periodic
|
||||
2
drivers/dac_dds/Makefile.include
Normal file
2
drivers/dac_dds/Makefile.include
Normal file
@ -0,0 +1,2 @@
|
||||
USEMODULE_INCLUDES_dac_dds := $(LAST_MAKEFILEDIR)/include
|
||||
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_dac_dds)
|
||||
143
drivers/dac_dds/dac_dds.c
Normal file
143
drivers/dac_dds/dac_dds.c
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Beuth Hochschule für Technik Berlin
|
||||
*
|
||||
* 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_periph_dac
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Common DAC function fallback implementations
|
||||
*
|
||||
* @author Benjamin Valentin <benpicco@beuth-hochschule.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include "board.h"
|
||||
#include "dac_dds.h"
|
||||
#include "dac_dds_params.h"
|
||||
#include "irq.h"
|
||||
#include "kernel_defines.h"
|
||||
#include "macros/units.h"
|
||||
#include "periph/timer.h"
|
||||
|
||||
static struct dac_ctx {
|
||||
const uint8_t *buffers[2]; /* The two sample buffers */
|
||||
size_t buffer_len[2]; /* Size of the sample buffers */
|
||||
size_t idx; /* Current position in the current buffer */
|
||||
dac_dds_cb_t cb; /* Called when the current buffer is done */
|
||||
void *cb_arg; /* Callback argument */
|
||||
uint16_t sample_ticks; /* Timer ticks per sample */
|
||||
uint8_t cur; /* Active sample buffer */
|
||||
uint8_t playing; /* DAC is playing */
|
||||
uint8_t is_16bit; /* Sample size is 16 instead of 8 bit */
|
||||
} _ctx[DAC_DDS_NUMOF];
|
||||
|
||||
static void _timer_cb(void *arg, int chan)
|
||||
{
|
||||
(void)chan;
|
||||
|
||||
struct dac_ctx *ctx = arg;
|
||||
|
||||
dac_dds_t dac_dds = ctx - _ctx;
|
||||
dac_t dac = dac_dds_params[dac_dds].dac;
|
||||
const uint8_t cur = ctx->cur;
|
||||
const uint8_t *buf = ctx->buffers[cur];
|
||||
const size_t len = ctx->buffer_len[cur];
|
||||
|
||||
if (ctx->is_16bit) {
|
||||
uint8_t l = buf[ctx->idx++];
|
||||
uint8_t h = buf[ctx->idx++];
|
||||
|
||||
dac_set(dac, (h << 8) | l);
|
||||
} else {
|
||||
dac_set(dac, buf[ctx->idx++] << 8);
|
||||
}
|
||||
|
||||
if (ctx->idx >= len) {
|
||||
|
||||
/* invalidate old buffer */
|
||||
ctx->buffer_len[cur] = 0;
|
||||
|
||||
ctx->idx = 0;
|
||||
ctx->cur = !cur;
|
||||
|
||||
/* stop playing if no more samples are queued */
|
||||
if (ctx->buffer_len[!cur] == 0) {
|
||||
ctx->playing = 0;
|
||||
timer_stop(dac_dds_params[dac_dds].timer);
|
||||
/* notify user that next sample buffer can be queued */
|
||||
} else if (ctx->cb) {
|
||||
ctx->cb(ctx->cb_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dac_dds_init(dac_dds_t dac, uint16_t sample_rate, uint8_t flags,
|
||||
dac_dds_cb_t cb, void *cb_arg)
|
||||
{
|
||||
assert(dac < DAC_DDS_NUMOF);
|
||||
|
||||
_ctx[dac].cb = cb;
|
||||
_ctx[dac].cb_arg = cb_arg;
|
||||
_ctx[dac].sample_ticks = dac_dds_params[dac].timer_hz / sample_rate;
|
||||
_ctx[dac].is_16bit = flags & DAC_FLAG_16BIT;
|
||||
|
||||
timer_init(dac_dds_params[dac].timer, dac_dds_params[dac].timer_hz, _timer_cb, &_ctx[dac]);
|
||||
}
|
||||
|
||||
void dac_dds_set_cb(dac_dds_t dac, dac_dds_cb_t cb, void *cb_arg)
|
||||
{
|
||||
unsigned state = irq_disable();
|
||||
|
||||
/* allow to update cb_arg independent of cb */
|
||||
if (cb || cb_arg == NULL) {
|
||||
_ctx[dac].cb = cb;
|
||||
}
|
||||
_ctx[dac].cb_arg = cb_arg;
|
||||
|
||||
irq_restore(state);
|
||||
}
|
||||
|
||||
bool dac_dds_play(dac_dds_t dac, const void *buf, size_t len)
|
||||
{
|
||||
struct dac_ctx *ctx = &_ctx[dac];
|
||||
|
||||
unsigned state = irq_disable();
|
||||
|
||||
uint8_t next = !ctx->cur;
|
||||
ctx->buffers[next] = buf;
|
||||
ctx->buffer_len[next] = len;
|
||||
|
||||
bool is_playing = ctx->playing;
|
||||
irq_restore(state);
|
||||
|
||||
if (is_playing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ctx->playing = 1;
|
||||
ctx->cur = next;
|
||||
|
||||
timer_set_periodic(dac_dds_params[dac].timer, 0, ctx->sample_ticks,
|
||||
TIM_FLAG_RESET_ON_MATCH | TIM_FLAG_RESET_ON_SET);
|
||||
|
||||
/* We can already queue the next buffer */
|
||||
if (ctx->cb) {
|
||||
ctx->cb(ctx->cb_arg);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void dac_dds_stop(dac_dds_t dac)
|
||||
{
|
||||
timer_stop(dac_dds_params[dac].timer);
|
||||
_ctx[dac].playing = 0;
|
||||
}
|
||||
70
drivers/dac_dds/include/dac_dds_params.h
Normal file
70
drivers/dac_dds/include/dac_dds_params.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Beuth Hochschule für Technik Berlin
|
||||
*
|
||||
* 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_periph_dac
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Default configuration for the DAC DDS driver
|
||||
*
|
||||
* @author Benjamin Valentin <benpicco@beuth-hochschule.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef DAC_DDS_PARAMS_H
|
||||
#define DAC_DDS_PARAMS_H
|
||||
|
||||
#include "board.h"
|
||||
#include "macros/units.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Set default configuration parameters for the DAC DDS driver
|
||||
* @{
|
||||
*/
|
||||
#ifndef DAC_DDS_PARAM_DAC
|
||||
#define DAC_DDS_PARAM_DAC DAC_LINE(0)
|
||||
#endif
|
||||
#ifndef DAC_DDS_PARAM_TIMER
|
||||
#define DAC_DDS_PARAM_TIMER (TIMER_NUMOF - 1)
|
||||
#endif
|
||||
#ifndef DAC_DDS_PARAM_TIMER_HZ
|
||||
#define DAC_DDS_PARAM_TIMER_HZ MHZ(1)
|
||||
#endif
|
||||
|
||||
#ifndef DAC_DDS_PARAMS
|
||||
#define DAC_DDS_PARAMS { .dac = DAC_DDS_PARAM_DAC, \
|
||||
.timer = DAC_DDS_PARAM_TIMER, \
|
||||
.timer_hz = DAC_DDS_PARAM_TIMER_HZ, \
|
||||
}
|
||||
#endif
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @brief DAC DDS configuration
|
||||
*/
|
||||
static const dac_dds_params_t dac_dds_params[] =
|
||||
{
|
||||
DAC_DDS_PARAMS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DAC DDS instances
|
||||
*/
|
||||
#define DAC_DDS_NUMOF ARRAY_SIZE(dac_dds_params)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DAC_DDS_PARAMS_H */
|
||||
/** @} */
|
||||
164
drivers/include/dac_dds.h
Normal file
164
drivers/include/dac_dds.h
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Beuth Hochschule für Technik Berlin
|
||||
*
|
||||
* 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_dac_dds DAC Direct Digital Synthesis
|
||||
* @ingroup drivers_periph_dac
|
||||
*
|
||||
* @{
|
||||
* @file
|
||||
* @brief Use a DAC to play a buffer of samples
|
||||
*
|
||||
* A buffer of (audio) samples can be played on a DAC with
|
||||
* a given sample rate.
|
||||
* To supply a steady stream of samples, a double buffer is
|
||||
* used.
|
||||
* While buffer A is played on the DAC the user can fill buffer
|
||||
* B with the next batch of samples by calling @ref dac_dds_play
|
||||
* again. Once buffer A has finished playing, buffer B will
|
||||
* automatically be used instead.
|
||||
*
|
||||
* A callback can be registered that signals when the next buffer
|
||||
* is ready to be filled.
|
||||
* Do not do the actual buffer filling inside the callback as this
|
||||
* is called from the timer context that is used to play the samples.
|
||||
* Instead, use it to wake a thread that then provides the samples
|
||||
* and calls @ref dac_dds_play.
|
||||
* If the next sample buffer is already prepared, you can also call
|
||||
* `dac_dds_play` within the callback, just don't do any I/O or
|
||||
* heavy processing.
|
||||
*
|
||||
* @author Benjamin Valentin <benpicco@beuth-hochschule.de>
|
||||
*/
|
||||
|
||||
#ifndef DAC_DDS_H
|
||||
#define DAC_DDS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "periph/dac.h"
|
||||
#include "periph/timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The callback that will be called when the end of the current sample buffer
|
||||
* has been reached.
|
||||
* Should be used to start filling the next sample buffer with @ref dac_dds_play.
|
||||
*
|
||||
* @note Will be called in interrupt context. Only use the callback to signal a
|
||||
* thread. Don't directly fill the sample buffer in the callback.
|
||||
*/
|
||||
typedef void (*dac_dds_cb_t)(void *arg);
|
||||
|
||||
/**
|
||||
* @brief Configuration struct for a DAC DDS channel
|
||||
* @{
|
||||
*/
|
||||
typedef struct {
|
||||
dac_t dac; /**< The DAC used for playing the sample */
|
||||
tim_t timer; /**< Timer used for periodic DAC events */
|
||||
uint32_t timer_hz; /**< Timer frequency, must be at least 2x sample rate */
|
||||
} dac_dds_params_t;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Index of the DAC DDS channel
|
||||
*/
|
||||
typedef uint8_t dac_dds_t;
|
||||
|
||||
/**
|
||||
* @brief A sample has a resolution of 8 bit
|
||||
*/
|
||||
#ifndef DAC_FLAG_8BIT
|
||||
#define DAC_FLAG_8BIT (0x0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief A sample has a resolution of 16 bit
|
||||
*/
|
||||
#ifndef DAC_FLAG_16BIT
|
||||
#define DAC_FLAG_16BIT (0x1)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize a DAC for playing audio samples
|
||||
* A user defined callback can be provided that will be called when
|
||||
* the next buffer can be queued.
|
||||
* @experimental
|
||||
*
|
||||
* @param[in] dac The DAC to initialize
|
||||
* @param[in] sample_rate The sample rate in Hz
|
||||
* @param[in] flags Optional flags (@ref DAC_FLAG_16BIT)
|
||||
* @param[in] cb Will be called when the next buffer can be queued
|
||||
* @param[in] cb_arg Callback argument
|
||||
*/
|
||||
void dac_dds_init(dac_dds_t dac, uint16_t sample_rate, uint8_t flags,
|
||||
dac_dds_cb_t cb, void *cb_arg);
|
||||
|
||||
/**
|
||||
* @brief Change the 'buffer done' callback.
|
||||
* A user defined callback can be provided that will be called when
|
||||
* the next buffer can be queued.
|
||||
* This function can be used to change the callback on the fly.
|
||||
*
|
||||
* Passing in a @p cb of `NULL` can be used to only update the arg
|
||||
* without updating the @p cb. Conversely, to clear the callback, both
|
||||
* @p cb and @p cb_arg need to be passed in as NULL.
|
||||
* @experimental
|
||||
*
|
||||
* @param[in] dac The DAC to configure
|
||||
* @param[in] cb Called when the played buffer is done
|
||||
* @param[in] cb_arg Callback argument
|
||||
*/
|
||||
void dac_dds_set_cb(dac_dds_t dac, dac_dds_cb_t cb, void *cb_arg);
|
||||
|
||||
/**
|
||||
* @brief Play a buffer of (audio) samples on a DAC
|
||||
*
|
||||
* If this function is called while another buffer is already
|
||||
* being played, the new `buf` will be played when the current
|
||||
* buffer has finished playing.
|
||||
*
|
||||
* The DAC implementations allows one buffer to be queued
|
||||
* (double buffering).
|
||||
*
|
||||
* Whenever a new buffer can be queued, the @ref dac_dds_cb_t
|
||||
* callback function will be executed.
|
||||
*
|
||||
* @experimental
|
||||
*
|
||||
* @param[in] dac The DAC to play the sample on
|
||||
* @param[in] buf A buffer with (audio) samples
|
||||
* @param[in] len Number of bytes to be played
|
||||
*
|
||||
* @return `true` if the buffer was queued while another
|
||||
* buffer is currently playing.
|
||||
* `false` if the new buffer is played immediately.
|
||||
* That means playing was just started or an underrun
|
||||
* occurred.
|
||||
*/
|
||||
bool dac_dds_play(dac_dds_t dac, const void *buf, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Stop playback of the current sample buffer
|
||||
*
|
||||
* @param[in] dac The DAC to stop
|
||||
*/
|
||||
void dac_dds_stop(dac_dds_t dac);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DAC_DDS_H */
|
||||
/** @} */
|
||||
Loading…
x
Reference in New Issue
Block a user