mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #13908 from benpicco/periph/dac_play
drivers/periph_common: dac: add dac_play()
This commit is contained in:
commit
272c8d29cc
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 */
|
||||
/** @} */
|
15
tests/driver_dac_dds/Makefile
Normal file
15
tests/driver_dac_dds/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
BOARD ?= mcb2388
|
||||
|
||||
include ../Makefile.tests_common
|
||||
|
||||
BLOBS += hello.raw
|
||||
|
||||
USEMODULE += dac_dds
|
||||
USEMODULE += shell
|
||||
|
||||
# Disable the greeting sample if the board has too little memory
|
||||
# or flashing takes too long
|
||||
ENABLE_GREETING ?= 1
|
||||
CFLAGS += -DENABLE_GREETING=$(ENABLE_GREETING)
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
1
tests/driver_dac_dds/hello.raw
Normal file
1
tests/driver_dac_dds/hello.raw
Normal file
File diff suppressed because one or more lines are too long
323
tests/driver_dac_dds/main.c
Normal file
323
tests/driver_dac_dds/main.c
Normal file
@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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 tests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief DAC (audio) test application
|
||||
*
|
||||
* Generates Sine, Square and Sawtooth waves using
|
||||
* a DAC.
|
||||
* Connect a speaker or headphones to the DAC output
|
||||
* pins of your board, you should be able to hear the
|
||||
* generated sounds.
|
||||
*
|
||||
* @author Benjamin Valentin <benpicco@beuth-hochschule.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mutex.h"
|
||||
#include "dac_dds.h"
|
||||
#include "dac_dds_params.h"
|
||||
#include "shell.h"
|
||||
|
||||
#include "blob/hello.raw.h"
|
||||
|
||||
#ifndef DAC_DDS_CHAN
|
||||
#define DAC_DDS_CHAN 0
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_GREETING
|
||||
#define ENABLE_GREETING 1
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) > (b) ? (b) : (a))
|
||||
#endif
|
||||
|
||||
#define DAC_BUF_SIZE (2048)
|
||||
|
||||
static bool res_16b = 0;
|
||||
static unsigned sample_rate = 8000;
|
||||
|
||||
#define ISIN_PERIOD (0x7FFF)
|
||||
#define ISIN_MAX (0x1000)
|
||||
|
||||
/**
|
||||
* @brief A sine approximation via a fourth-order cosine approx.
|
||||
* source: https://www.coranac.com/2009/07/sines/
|
||||
*
|
||||
* @param x angle (with 2^15 units/circle)
|
||||
* @return sine value (Q12)
|
||||
*/
|
||||
static int32_t isin(int32_t x)
|
||||
{
|
||||
int32_t c, y;
|
||||
static const int32_t qN = 13,
|
||||
qA = 12,
|
||||
B = 19900,
|
||||
C = 3516;
|
||||
|
||||
c = x << (30 - qN); /* Semi-circle info into carry. */
|
||||
x -= 1 << qN; /* sine -> cosine calc */
|
||||
|
||||
x = x << (31 - qN); /* Mask with PI */
|
||||
x = x >> (31 - qN); /* Note: SIGNED shift! (to qN) */
|
||||
x = x * x >> (2 * qN - 14); /* x=x^2 To Q14 */
|
||||
|
||||
y = B - (x * C >> 14); /* B - x^2*C */
|
||||
y = (1 << qA) /* A - x^2*(B-x^2*C) */
|
||||
- (x * y >> 16);
|
||||
|
||||
return c >= 0 ? y : -y;
|
||||
}
|
||||
|
||||
/* simple function to fill buffer with samples */
|
||||
typedef void (*sample_gen_t)(uint8_t *dst, size_t len, uint16_t period);
|
||||
|
||||
static void _fill_saw_samples_8(uint8_t *buf, size_t len, uint16_t period)
|
||||
{
|
||||
uint8_t x = 0;
|
||||
unsigned step = 0xFF / period;
|
||||
|
||||
for (uint16_t i = 0; i < len; ++i) {
|
||||
x += step;
|
||||
buf[i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
static void _fill_saw_samples_16(uint8_t *buf, size_t len, uint16_t period)
|
||||
{
|
||||
uint16_t x = 0;
|
||||
unsigned step = 0xFFFF / period;
|
||||
|
||||
for (uint16_t i = 0; i < len; ++i) {
|
||||
x += step;
|
||||
buf[i] = x;
|
||||
buf[++i] = x >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void _fill_sine_samples_8(uint8_t *buf, size_t len, uint16_t period)
|
||||
{
|
||||
uint16_t x = 0;
|
||||
unsigned step = ISIN_PERIOD / period;
|
||||
|
||||
for (uint16_t i = 0; i < period; ++i) {
|
||||
x += step;
|
||||
buf[i] = isin(x) >> 5;
|
||||
}
|
||||
|
||||
for (uint16_t i = period; i < len; i += period) {
|
||||
memcpy(&buf[i], &buf[0], period);
|
||||
}
|
||||
}
|
||||
|
||||
static void _fill_sine_samples_16(uint8_t *buf, size_t len, uint16_t period)
|
||||
{
|
||||
uint16_t x = 0;
|
||||
unsigned step = ISIN_PERIOD / period;
|
||||
|
||||
period *= 2;
|
||||
|
||||
for (uint16_t i = 0; i < period; ++i) {
|
||||
x += step;
|
||||
|
||||
uint16_t y = isin(x);
|
||||
buf[i] = y;
|
||||
buf[++i] = y >> 8;
|
||||
}
|
||||
|
||||
for (uint16_t i = period; i < len; i += period) {
|
||||
memcpy(&buf[i], &buf[0], period);
|
||||
}
|
||||
}
|
||||
|
||||
static void _fill_square_samples(uint8_t *buf, size_t len, uint16_t period)
|
||||
{
|
||||
period /= 2;
|
||||
|
||||
if (res_16b) {
|
||||
period *= 2;
|
||||
}
|
||||
|
||||
for (uint8_t *end = buf + len; buf < end; ) {
|
||||
memset(buf, 0xFF, period);
|
||||
buf += period;
|
||||
memset(buf, 0x0, period);
|
||||
buf += period;
|
||||
}
|
||||
}
|
||||
|
||||
static void _unlock(void *arg)
|
||||
{
|
||||
mutex_unlock(arg);
|
||||
}
|
||||
|
||||
static void play_function(uint16_t period, uint32_t samples, sample_gen_t fun)
|
||||
{
|
||||
static uint8_t buf[DAC_BUF_SIZE];
|
||||
mutex_t lock = MUTEX_INIT_LOCKED;
|
||||
|
||||
/* only work with whole wave periods */
|
||||
uint16_t len_aligned = DAC_BUF_SIZE - DAC_BUF_SIZE % period;
|
||||
|
||||
/* 16 bit samples doubles data rate */
|
||||
if (res_16b) {
|
||||
samples *= 2;
|
||||
}
|
||||
|
||||
/* pre-calculate buffer */
|
||||
fun(buf, len_aligned, period);
|
||||
|
||||
/* we want to block until the next buffer can be queued */
|
||||
dac_dds_set_cb(DAC_DDS_CHAN, _unlock, &lock);
|
||||
|
||||
while (samples) {
|
||||
size_t len = min(samples, len_aligned);
|
||||
samples -= len;
|
||||
|
||||
dac_dds_play(DAC_DDS_CHAN, buf, len);
|
||||
|
||||
/* wait for buffer flip */
|
||||
mutex_lock(&lock);
|
||||
}
|
||||
}
|
||||
|
||||
#if IS_USED(ENABLE_GREETING)
|
||||
static int cmd_greeting(int argc, char **argv)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
if (sample_rate != 8000 || res_16b) {
|
||||
puts("Warning: audio clip was recoded with 8bit/8000 Hz");
|
||||
}
|
||||
|
||||
puts("Play Greeting…");
|
||||
|
||||
/* we only want to play a single sample */
|
||||
dac_dds_set_cb(DAC_DDS_CHAN, NULL, NULL);
|
||||
|
||||
dac_dds_play(DAC_DDS_CHAN, hello_raw, hello_raw_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _dac_init(void)
|
||||
{
|
||||
printf("init DAC with %d bit, %u Hz\n", res_16b ? 16 : 8, sample_rate);
|
||||
dac_dds_init(DAC_DDS_CHAN, sample_rate,
|
||||
res_16b ? DAC_FLAG_16BIT : DAC_FLAG_8BIT, NULL, NULL);
|
||||
}
|
||||
|
||||
static int cmd_init(int argc, char **argv)
|
||||
{
|
||||
printf("argc: %d\n", argc);
|
||||
|
||||
if (argc < 2) {
|
||||
printf("usage: %s <freq> <bit>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc > 2) {
|
||||
unsigned bit = atoi(argv[2]);
|
||||
|
||||
if (bit != 8 && bit != 16) {
|
||||
printf("Only 8 and 16 bit samples supported.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
res_16b = bit == 16;
|
||||
}
|
||||
|
||||
sample_rate = atoi(argv[1]);
|
||||
|
||||
_dac_init();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_saw(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
printf("usage: %s <freq> <secs>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned freq = atoi(argv[1]);
|
||||
unsigned secs = atoi(argv[2]);
|
||||
|
||||
play_function((sample_rate + freq/2) / freq, secs * sample_rate,
|
||||
res_16b ? _fill_saw_samples_16 : _fill_saw_samples_8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_sine(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
printf("usage: %s <freq> <secs>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned freq = atoi(argv[1]);
|
||||
unsigned secs = atoi(argv[2]);
|
||||
|
||||
play_function((sample_rate + freq/2) / freq, secs * sample_rate,
|
||||
res_16b ? _fill_sine_samples_16 : _fill_sine_samples_8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_square(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
printf("usage: %s <freq> <secs>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned freq = atoi(argv[1]);
|
||||
unsigned secs = atoi(argv[2]);
|
||||
|
||||
play_function((sample_rate + freq/2) / freq, secs * sample_rate,
|
||||
_fill_square_samples);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const shell_command_t shell_commands[] = {
|
||||
#if IS_USED(ENABLE_GREETING)
|
||||
{ "hello", "Play Greeting", cmd_greeting },
|
||||
#endif
|
||||
{ "init", "Initialize DAC", cmd_init },
|
||||
{ "saw", "Play sawtooth wave", cmd_saw },
|
||||
{ "sine", "Play Sine wave", cmd_sine },
|
||||
{ "square", "Play Square wave", cmd_square },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
dac_init(DAC_DDS_PARAM_DAC);
|
||||
_dac_init();
|
||||
|
||||
/* start the shell */
|
||||
char line_buf[SHELL_DEFAULT_BUFSIZE];
|
||||
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user