diff --git a/drivers/dac_dds/Makefile b/drivers/dac_dds/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/dac_dds/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/dac_dds/Makefile.dep b/drivers/dac_dds/Makefile.dep new file mode 100644 index 0000000000..6b811b71f3 --- /dev/null +++ b/drivers/dac_dds/Makefile.dep @@ -0,0 +1,2 @@ +FEATURES_REQUIRED += periph_dac +FEATURES_REQUIRED += periph_timer_periodic diff --git a/drivers/dac_dds/Makefile.include b/drivers/dac_dds/Makefile.include new file mode 100644 index 0000000000..50b100afd7 --- /dev/null +++ b/drivers/dac_dds/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_dac_dds := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_dac_dds) diff --git a/drivers/dac_dds/dac_dds.c b/drivers/dac_dds/dac_dds.c new file mode 100644 index 0000000000..0a48143187 --- /dev/null +++ b/drivers/dac_dds/dac_dds.c @@ -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 + * + * @} + */ + +#include +#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; +} diff --git a/drivers/dac_dds/include/dac_dds_params.h b/drivers/dac_dds/include/dac_dds_params.h new file mode 100644 index 0000000000..dc0db57fc0 --- /dev/null +++ b/drivers/dac_dds/include/dac_dds_params.h @@ -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 + * @} + */ + +#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 */ +/** @} */ diff --git a/drivers/include/dac_dds.h b/drivers/include/dac_dds.h new file mode 100644 index 0000000000..688e51c4a4 --- /dev/null +++ b/drivers/include/dac_dds.h @@ -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 + */ + +#ifndef DAC_DDS_H +#define DAC_DDS_H + +#include +#include +#include + +#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 */ +/** @} */ diff --git a/tests/driver_dac_dds/Makefile b/tests/driver_dac_dds/Makefile new file mode 100644 index 0000000000..f1903bb91f --- /dev/null +++ b/tests/driver_dac_dds/Makefile @@ -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 diff --git a/tests/driver_dac_dds/hello.raw b/tests/driver_dac_dds/hello.raw new file mode 100644 index 0000000000..1cc33fbf70 --- /dev/null +++ b/tests/driver_dac_dds/hello.raw @@ -0,0 +1 @@ +†Š‰Š‰Šˆ‰ˆ‰ˆˆ‰ˆˆˆ‰ˆ‰ˆˆˆ‰ˆ‰ˆˆ‡ˆˆ‡ˆˆ‡ˆˆ‡†‡‡†‡†‡†‡†‡†‡†‡†‡†‡†‡†…†…†…†…†………††…†‡†‡†‡†…‡††‡†‡†…†…†‡†‡††††‡†‡†††……†……†…†……„…„……„†……†……„…„…„…„…„…„„„ƒ„ƒ„ƒƒƒ„ƒ„ƒƒ„ƒ„ƒ„ƒƒ„ƒ„„ƒ„„„…„…„…„…„„…„…„ƒ„„ƒƒ„ƒ„ƒ„ƒ„„„ƒ„ƒ‚‚ƒƒƒ‚ƒ‚ƒƒƒ„ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ„ƒƒ„ƒ‚ƒ‚ƒ‚‚ƒ„ƒ„ƒ„ƒƒ„ƒƒƒ‚ƒ‚ƒƒ„ƒƒ‚ƒƒ„ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚‚‚‚ƒ‚‚ƒ‚ƒ‚ƒ„ƒƒ‚ƒ‚ƒ‚ƒƒ‚ƒƒ‚ƒƒ‚ƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€‚‚‚‚‚‚€€€€‚€€€€‚‚‚‚‚‚‚ƒ‚ƒ‚ƒ‚‚‚ƒ‚ƒ‚ƒ‚‚ƒ‚ƒ‚ƒ‚ƒ‚ƒƒƒ„ƒƒ„ƒ„ƒ„ƒ„ƒ„ƒ„„ƒ„ƒ„„„„ƒƒ„„„ƒ„ƒ„ƒ„„ƒ„ƒ„ƒƒ„ƒ„……„„…„……„………„…„…†…††…†…†‡†‡†‡†‡‡†…†…†……‡†‡†…†…††‡†……†‡†‡‡†‡……‡ˆ††‡ˆ†„‡‰‡…‰ˆ„‰‰ƒ†Š…„‡……†~ŠŒ††‡‰‰~„”‡‹Š}„‡‡„€Ž‚ƒ‰‹ˆw}‘†y‹ƒ}Ž—vh˜—q…uŽ–fˆ¦lk©b†Š}‚…‘}y•…}v”™nx¡‚hŠ•ƒ…†z’kzš{}“t†šr‚¡tn‡vŽ‚‘}~‹‚ˆ‹†x€¦Q{ÀyQ¢¨g›„}¤’d¢fr ‹wŠ“€yŽ‚}—…i—¨po’—ƒxˆ‹€sŸ…i„­X˜£[r¨‹u‰v‹–xh}z‰‰v€ŸŒto‹žzp…™ž`‡±|M‚¶Šb|•Œf€¯ŒgqˆŸ‡[‡²n`ª«fl¢”vk€Ÿ‡lz‘‘{k£“K|¹tL ­`wªzl“¨yY£¡dv•‰ˆ|e‘©l^™§†mq˜¦oN‹©‚foœ¥sa©Pt­‡^x£¦z\‡¥€al™¡qa•¦~l|£ŠT„ªwX‘­sb•£va±qL ºlZŽŽ‡s|œ‚l”¡ta‰§}aªxb˜¡sjБނ\‹¼qLŸ²ch¡ŠoŽŠ|˜‰„˜}qŠo‚–Š…ƒŸ€`”§jh£pr•‰n~¡un¨ž]o£‹n”‰…}{•—vh—žpx‰ˆ†{’‡ywŒ’m¦udŸ™dt›“…oo¤®cTŸ¤nk¡]f³™Ts©˜x~ƒ•–qyŒˆ‚i~§…ZŒ¸_—“}“~h’¤ih«šg‚”’io²“O‹®u‡•o¢„g‰¨{l£—Y~´ƒk‰–…sƒ“…s†“„„~|z”u‰‰~y’…kŠœyo˜qvšƒvˆŠ„‚‚~ˆ‰lTJnÊÎŽ¨À¡†pil_?I{‚pН¯£œŒƒˆuVd~or‘Ÿ—Ÿ ’•€lq{{rr‹™„~œš|‰y|†og‹v{’Š…}‚‰rfaCU¡á“Ï»™…xt[Dd“‚n£Ì·£•”uIR|q_z£­««¨­¡}jy\]wˆ‹Ž–§¨•†‰ˆtfluxx{‡““Љ‰xF<_»Ñi•˨’|xhD?l‘rn°Ôµž›“lAR~l^}ªµ¨¬´´¡{p…wX_}“ °°œŒselwvv}Ž˜–Š…^;\¹Õn‰Æ§‘{ykE=h”qg­Ï²œš–Žh@V€j_€¬´¦©¯¯œwn„tZb€‘Ÿ¬ª–ŠŽ‡ofowyy€Ž™’‰ˆk:K—̈u¿³”†{zlD5\ŽvbžÊ»ž˜•n@O|l]z£±£§­®™vp…vXd‹‘ž¬¦’‡Ž…mepxwz€’•†„Z;]³ÏgŒÅ¢{v`6=oer¸Æ§˜–’‡]Ab|fh‹«¬¤§®¨sykap‡Ž“£©œŒ‹Š|mkw{|}Œ“’†ƒ]Ba¶Ên™À|oxe6@r…j€¯¾°“†“‡VGeuou†¨¹¦›§£‰soutmnƒ–•‘›¡™‹€€~umq{‚„‹ŽzLP‰Ó¨}¯©”…gb}V2Kq„€…Ÿ¼°€Œ„dRZq€|}šµ®™˜“kix{vzŠ˜›–”˜“ˆzxyytu|„…‚wUTƒÍ»©§“†nWqh=B]x“‰¥µ£Œ€}zo]\r„†‡“¥¬£“‡wnpx€‚Œ˜™”Œ‡~vxz|z}~ƒoR`™Ò³¥ž“…kSmgIEXrš¡ˆ™²®œ‡quo\cuˆ”•“¢¯§˜Œ†…„vlq}…‰‰‹”š–ŽŠ†…‚|uxz|{hVf”Ë¿ª£š—‰x`]_YUOZw˜Ÿœ¡¡¦£Žxttrqlm}‘—›ŸŸ¢¤š‹…|{zv{…‰‘’”‘І‚~~|ywxhUfŒ¿Å² –˜‹}lYLZeUUk„ °¤˜¡¤™Œyglxtqu¢¤œ›˜{|}€}‰‘ˆƒ{||u][r—Á¯œ–—Š~oWK[f_^jz–°®¡œš›—‰vkkqz}{˜¢¤˜—“‰y{~ƒ„„Š‘‹‰ˆ†‚€|xgXh„­Ç¿ª˜—“‰oVIXfhlq}“¬µ«¡™•”‚tlnv€†‡‹’›¢£–‘Œ‰‡ƒ|{}ƒ†ˆ‰Š‹ŒŒ‹‡ƒ}waXlˆ¬Âº£“Š‚{nVJS^lvuzŠœ©®¥—‹†„slovŠŽ‘•˜š˜‘‰…€€}}~€ƒ‡ˆ‡ˆ††…„~xeUbx•¹Á®›ˆƒ~ufUFFXkv}ƒ‡’¢¨¥…€|yxvrsxˆ‘”“”’Žˆ„{y{yy€€‚„†‡†…ƒ~u_Xi~œ»½ª™Œƒ~|wk]MGQcvƒ‹Ž–Ÿ£ ™ƒzvvx{{{~ƒŠ‘–—•’ދЇ„‚~|||~€‚ƒƒƒ„…„ƒ€xcZj}˜ºÂ°Ÿ‚|zundTIM\n‚”•–—›™“‰~urtx|€‚…‰“•“Œ‡„ƒ€~{|}~€‚„ƒƒ‚‚q]_r‡§Áº¨™Š~zxrj]MITexŠ“”•–˜˜„ztux}€‚‚…‰Ž“–•’‹‰‡„ƒ}~~ƒ„†………ƒzd]m€š»Á¯Ÿ‘„|zundTJP^p‚•–—™š”‹€yvx|€‚ƒ†‡Œ‘•–”‘Šˆ‡…„‚€€ƒ…‡††††…‚r`gz¯Å»©›Œ‚~{tl_ONYj~™šœ›Ÿ£¢•Šzy|€„…‡‰‘–™˜–“‹‰‡‡…ƒ‚‚ƒ†ˆ‰‰ˆ‡†ƒwddvŠ¨Ã¿­Ÿ„~|vnbRMVew‹•˜™™œ¡¡œ–Œzxz~‚„…‡Š”˜™–“‹‰‡…ƒ€€ƒ…ˆ‰ˆ‰ˆ‡…€l^k}™½Æ´£”†}ztiYHGVkƒš¥£™˜˜“Š~ohlu€Š”˜š›˜‘‰‚{z|~„„†ŠŽŠ…‚~{yjWaw“Á˶£“‚qnkWGKXn°¶ª™ˆ€ztoe^fuˆŸ¬¨Ÿ•‹†yroq{ˆ”››–Š…€{wtv{‚‡ˆsYg}¬Ï½©–~cYciYSf…°¾«˜ƒiW]knoyŠ£¶±ž‰sdhsz~†œ¥¤˜ˆwnow…Š“–”ŠsS\rŸÐì™qPThskkŠ»Â¬›}TG]q…ŠŠœ°«œŠjQTi|˜˜™žš‘‚nbht…•š—”’†y_Uj¸Òº¦jMQiuhl†°Åµ¡ŠfGLb{„…¡­¬ŸŠpXSavˆ’–—–˜•Œnehs€““``w˜ÆÇ¯ž‡dFMmwjl{’²·¥”}\EPex„ŠŒ’ ¤ž“l^alz‰‘’“’““Ž…zpknv{pfs…«ÒȱŸŠnGA^nmry‚ º²¡|^IM[o€‡Œ“š£¥Ÿ‘€n`_iw„Ž““••–“‡zmZVi|”ÃÒ¹§–ƒhTU]dgho€—´¾±¡{fY[erz†‹•¡¨¦Ÿ“…ymiov}ƒ‡ŠŒŽ„niv‡¢ÂÁ­ŽiVR\glmms€“§±¬Ÿobbhpz€„ˆ’›¢£ž˜‚yssx}€ƒƒxnt‚’¯Ìdz£”…r^SUaklnt~Œ¡´µ«Ÿ~na^eox…‰Ž•¤¥¡™‚xporw|zoo|ŠžÁн«œŒ{fTPYdilr{ˆš°·¯£”„td\`ir{ƒ‡Œ’›£§¤œ‘…zqmqvy{qmw†—·Óʵ¤•†r\OQ^ikow¥¶¸® ‘qc^dnw…‰Ž–ž¥§£›…zsqsw|{qp{ŠÁÕÆ²¡’‚mYRXdijpx„•­¼¸¬|nbahpx~ƒˆ™¢«¬¦œ…ztsuy}|oo|‹žÁØÉµ¥•…raWZcfejs‘ª¼½³¤“„ujglruy~ƒ‹– ©­©Ÿ•Š€yxwy||siqާËѽ«›|k_Z]`_^do}‘©»º¯ ‚vnlnnqrtz‚𤍦ž”‹{xvuvurhcm|©ËË·¥”…uh^YYYSR[gv‹¢´·¬Ž‚xrpokiikpz†’œ¢¡š’Šƒ~{wtrpnkban}®ÈÀ­œrg_[[ULMVcs‰Ÿ°³¬ ’‡~xusmhfhmvƒ™ŸŸ›•І‚ytpmka^jz‹¨ÇŲ¢“ƒule_\UKJS`p…𬳝£—އ~yrjeehr~‰“šœš–“‹†€ztnmj`_l|Ž«ÇIJ¡’ƒvohd`YNJQ^o‚–§°¯¦œ•Іypgdfnyƒ”—˜˜—•”’Žˆ‚{uqqnd`k{©Äij£”…yqjfbYMGMYhzŸ«¯¨¢œ—’މ€wnfejs|…‘’”•––•’އzusql`^k{Ž«ÆÃ²¢“…ztnkf]QJNZk}ž§©¦¢Ÿž›—‡|slkpv~…Š’”—™›˜’‡‚~zywn``o€”±ÇÁ±¢”†|wroi_RJMXh{Œš£¦¥£¥¥£ ™„zroquzƒ…ˆ‹”˜››™”މ…}zp`^k|­ÃÀ±£”‡|xtoeWLLTdv†“œ ¡£§ª«ª¤š…|vvwz}€…Š”™šš—“Ї„|o^\hz­Á½¯¡’†€|yundVKJSas‚“—™¤©®¬¦œ‘‡€{yzyxyxz}‚ˆ’”“’‘ŽŒŠ‰†ƒzrbX`p‚š³º°¢–ˆ|zvrk`SKLTaq~†‹’˜Ÿ¦«¬¨¡—އ‚€}ywuuw|†‹ŽŽŒŒŠ‡„€|rb]fvˆ¡µµª‘…~|ytlaVPRZgt}ƒ‡‹‘™¡©¯°«£›”‹‰‡„{xwz~‚†‰‹ŒŽ‹ˆ†ƒ€wlgiz‹ ¯¯¦œ‘ˆƒ‚{vndZUW^isz…Š™£ª¯¯«¤ž˜’ŒŠ…€|xxz~‚…‰‹Œ‘’’‘Ž‹ˆ„‚~rcal|¨¸¶ªž’‡‚€}xsk`WQS\hs{‚‡Œ‘š£ª¯°¬¥Ÿ™”‘Ž‹†|yxy~…‡Š‹‹Ž‘Œˆ„‚€|o_]iz¨¸³¨›…€{wrh\ROR\hsz€…‰™¢ª¯¯©¢›–‘Ž‹‡‚}yutw|€ƒ†‡‰‰‹ŒŽŠ†ƒ€}wh[]l~•®¶®¢•ˆ}{wslbVNMR]it{†‹“œ¥¬°­§ ™’Ž‹‡ƒzvsux{ƒ…‡ˆ‰ŠŒŒ‹ˆ†‚|xo]Wbs‡ ³³¨›Žƒ~|ytof[QKMUamu{…Œ–Ÿ§­¯«£—‘‰†|vrqtw{€ƒ…‡‡ˆŠŒŒ‹‡„~{vi[Zgx¦´¯£—‹}{xslbWNJNWepx~‚‡Ž˜¡©®­§Ÿ—‘‰†|vrppsw|€‚ƒ…‡ˆ‰‹Œ‹ˆ…‚~zvm\T]o„ž³µªƒ}{xsmcWMHJR^jtz†—¡©¯°«£œ•‘‰†€zurquy}‚…†ˆŠ‹ŒŽŽŒ‰†ƒ€|ucV[l€›µ»±¤–‰€}|wrj^RJJQ]ku|ƒ‡• ª±³°¨¡š“Š…€zvtuz~‚‡‰‹Œ‘’‘Їƒzl[Zh|•±¿¹¬…{uncVNKP\jt|ƒˆŽ–¢¬²¶³«¤—“Œ‡‚}xuw{€„ˆŠŒ’”••”Œˆ…{l\^m€›¹Â¹­†ƒ€|vnbWNLUbpz‚‡Œ’œ¨²¸·°¨Ÿ—“Œˆ‚}xvx|‚‡Œ‘’”••”’ŽŠ†ƒ€}{ztdW_sŠªÇÊ»«œŽ†‚}wnbRFFO^o}†“𥰷·²¨œ“‹…‚€}ywwz€ˆ•—˜—––•“‘މ„€}||}€€‚„††‚l^j~˜½Òȵ£’…~{undREEQcxŠ”™› ¨°±®¥˜‹€{y{}~~€ƒ‰—œœ˜•’Œ‰…~||}€‚„†‡ˆ‰Š‰‡†ƒ}yhTXnˆ°ÓϺ¨—ˆ€|vnaN@CRe{Ž–™œŸ¥¬«£™‹~wvx}€€„‰–š™–’ŽŠ‡…~{yz{~€‚„…†……„ƒ‚€~|zz|{||}|}~}nVVm‡²Õθ¥’ƒzwpfUBBQg€“šœž ¥§¤›€vrt{‚ƒ…ˆ–šš˜”ŽŠ†„ƒ€~~}~€ƒ…†…†……„ƒ‚€~}~€€€ƒ„ƒ€rXXoг×Ѻ¨–‡}tjXECQd|“ ¢£¨­ª£—ˆ}xx}‚††ˆ‹—Ÿš”‹‰‡…‚€€‚…ˆŠ‹‰‰ˆ‡ˆ‡†ƒ‚€€‚‚ƒ„ƒ„…††…„‚pX^w“¿Û͹¦”ˆ‚}thUDFWm„˜¢¡¡¥¬®«¢“†}x|ƒ…†‡‰—žŸŸ›•’‹‡‚~€‚†‡†‡ˆˆ‰Šˆ…‚€€€€€€‚ƒ„…„„‚~hTf…²Ó̵¢™•qYDBXjnpuƒŸ¹»²¥š™›’‚vnq|…‡‰Œ‘œ£ š•Ž…}zy|ƒ‚„‡Š‹ˆ…ƒƒ‚€|yz||}}~€rRUy¬Í雤žŽ}dR]eSFMe…Ÿœ™¤°¶­–…„„vln~ޕ𤦛‘Œˆ‚yv|~€‚†ŒŽ‹ˆ‰ˆ†‚}|}}{z{~}gSh È­§®¤’zmtmQ9Aa|yvŠ®À¯šš¢˜‚mgw}qn~‘™˜•¨¡‰‹ˆ€vsz€}{„ŒŒŠŠŒˆ€|~{xwvz||x{cOj·¼x¢Ä­›„x~p?3^hT^u›¯˜“°¯’„‚rd_pyz’¡œ˜žŸ™Š‚‚skw|{…‹‰‹|~zurwvxuygQb±³c¥Ê§•ŒˆvW4]e=H{Œw³·¡•”t\nu^_w‰ˆˆš¬¤”™›‰xy{rlr}†”’Žˆ}}ysqxyvp]YÅ}‚Ͷ›’‘}lJRfC;k„i~®³¢œŸžŠmv`Vo{v|ŽžŸ— §•‡‹‡vpswvt}ˆ‡„’Œ‡Š…~xxxuqut[R}Á~pɼ˜•—ƒu[`iA5hx[v£¥˜¡ª¦Œ}_cyojyŠ•§¢’—™‰~~vpu~}{†ŽŠ‹‘ˆ…ƒƒzvxxjWTŒ²kŒÐ°”ž˜†vfqc8GxaZƒ—•ª³¨–š}l~ydhy€€ƒ•¢—–¤žŽŽŽ…zwz{uz……ƒ‹‘ŽŒ‹†„…}zqZZ¡¡g¦Ï¡™§š‰{y|[?mqMiЉˆž³´¢­•Šon{|wz‹–”¦¤˜œž“‹ˆˆx}ƒ|~‡Š‡‘‘Ž‹…†„qV`§”d±È“Ÿ­™‹€~WQ}bJsƒw…ª¤ž²­’–…y‚~vu|‡……—š“¢™•–“Œƒ„„|z~‚‡‰‰ŠŽŽ‰ŒŠ†}iQ†®lƒÍ¤Œ­¢†ƒiTskB^zjqŠ˜œ—¬·žœª˜†Œ‰ysy€yx‰Œ„›””˜–ŒŒ‰}|x}~}~‚†…‡ŒŠ…†€hQ©cÉ‘­›Œƒ‚€`^{Z@ko\p†ŒŠ”°¦—­©‘”–‰~}€zs~„}ƒŒ”˜”’’‰†‰ƒ}~zz|~~„„ƒ†…iX’¢c™Äˆ”±—Œ‡„|ejvROq_Ys„–¨—œ´¥–¢›‹ˆ‰„ww€yuƒ…ˆŽŽ’•Œ„……}|~|xy}|{€~€}hX‘žc›À˜³–ŽŠ‡zgrrMZnPWmnr|Ž–Ž ®£«—–‰€€tv~wz‚ƒ†ŒŽ‹’Œ‹ˆ„…‚~{}{wy{xzzvbY•b§±y£³”‰xmyiPjeH[i`ex„‰¡—•­¤˜¢Ÿ“‰|~rs|tu}}~†‡…‹Œ‡‰‰…‚„}{|xwywts`Uއ_¥£x§ª‘‹yt}g]q_Paa]et{w‰–˜¥¢ £¤š”˜Ž„ˆƒy{{xvz}{…ƒ„ŠŠˆŠŠ‰‡‡…€€~||yxuoVj˜kv´‰ˆ²Ÿ”“’‰u||bhq[\fbbkvy}Œ‘›¡ž¢¥¢Ÿ››“ŽŽˆ€‰‡wyxzz|‚„„†ˆ‡‰ˆˆˆ†……ƒ€~}zt^\zdž˜„žš‘ˆ“‚vƒvjpmiacodgywy„ŠšŸ™£¦Ÿ¡¡Ÿš•—‰‹‡‚€y~€z~€€‚…„ƒ††„††„ƒ‚ƒyhrwx˜†“˜‘†zˆwvxxtmttksxqx}…‹Ž‹“—”˜š›š››™™˜–”’’ŽŒŠ…††ƒ‚€€€€€€€~u|‰€‰‘ˆ„˜‘‰’Šƒ‹~~‰‚}~ƒ}~ƒƒ‚ƒ†ˆ…‰‹‹ŒŽ‘’‘“’“’’”“’’’Ž‹‹Š‰ˆ‡†…†…ƒ‚ƒ‚€}~‚‚‚€†‡zƒ‰‚ƒ‡‡u„‰ƒƒy…‡‚ƒ…ˆ„†‰‡‰ˆ‰‹‹‹ŒŒŒ‹Œ‹ŒŒŒŒŒŒ‹Š‹‹ŠŠ‰ˆ‰ˆ‡ˆ‡††‡†…††…„…„ƒ„…„…„††…„……ƒƒ„ƒ„ƒ‚ƒ‚ƒ‚‚ƒ‚‚‚ƒ‚ƒ‚ƒ„ƒ„ƒ„„………„„…††††‡†‡†‡‡†‡‡†………†…†……„„…„…„ƒ„ƒ‚„ƒ„ƒ„…ƒƒ„ƒ‚‚ƒ‚‚‚€‚€€€€€€‚‚‚ƒ‚ƒƒ‚ƒƒ„ƒ„…„…„…„„…„ƒ„…„…„„„„ƒ„ƒ„„…„ƒ„ƒƒ‚€‚ƒƒ‚ƒƒ‚ƒ‚ƒ‚‚‚‚‚‚‚‚€}{‹„yƒ‚ˆ|…†~€Œ‚‚…Љ‡„ƒ…ˆ„…ˆƒ………†ƒ…†„†……†…„„……„…ƒ}ƒ…‚‚†„…Žƒ„‰…ˆ‚ƒ‡~~€w‚t{{{zvyxzz|}}~€€€ƒ„††…‡‡‰‰‰Š‰‰ˆŠ‰‡‡ˆˆ‡‡†‡†…„„ƒƒ‚‚€|s|…{}…ˆ„ˆŒ„’|}‡~|wy~ttyvvuwyuxzy~~€‚‚ˆˆ‡ŽŽ‘”‘’•“’“’‘ŒŒŠˆ‡†…ƒ‚€~}}|{ywfk†xt‰‹ŒŠŽ–ŽŒ‰…}wrxrmppomqxtw‚ƒ„‹‘”•›Ÿ¡£¡¢  š›—’‘Ž‹‰‡…ƒ‚€‚ƒƒ„„…‚za{žt†¯”•¨¦šš’|„ƒopsjabib_oqn|‡ˆŽ™ŸŸ¦­©ª¬©¤¢ž™“‘Œ†…~z~}|ƒ„…‡‡ˆˆ‡‡†…ƒ‚~vUe on²‰«ž‰“wt‚hdm_^X]g[l{s‚™¥¦¢­© ¤”“‰ƒ€yw}zz€€ƒ†ˆ‡ŠŒ‡ŠŠ…†„‚€}|ztlIi¡as¾‰‡¹¦—“•‹s~{\emQR`]Zdxvy”•‘¦ª¤ª­©  ‹Ž{€zvx}|{ƒ†‚ˆŒˆŠŒŠ‹Š…‚ƒ{}ywtpUO˜X·Ÿuº®—ž“}tcZsUH`XWao{vŒž¡±£©±©£ž•ˆ†y~}ux{}~ˆ†‡ŽŒ‘Ž‹‹‰„„„}|zxtaK…•Y˜¶x ¹˜–•‘€u€mYocI\`X_nzv…ž“𲩧±­¤Ÿ —ˆŒˆwz}rrzz{}…†…Œ‘‹‰…ƒƒ{|wvulK[žli»„¾ª—˜“ŠtyzZ^pNI`YVbtww‘›£±¥©±©ŸžžŽ…Š€v{yrtyyx†„ˆŽŒŒ‹Š‰‡‚‚{{{wtpWP“‚[¯¨z°´™˜•‘|uhYq_I[a[_qv‡ ”œ²«¨¯¬¤œŸ–‰Œ‰z|~vx|~}‡‡‡ŽŒŽ‘މ‹‹„ƒ„}~}xxxmQrŸg½Œ’½¦š˜–Švz[hrLXlZaoz€˜ž•«³£­²¦¢Ÿ‘‹„z…v€~†‰‡‹ŒŒŽ‹‹‰‡†ƒ‚~~|z|voUlŸmvº“‰²¥—’‘Žux€f`ndV\lje|†}Žœ˜¤©£¢¦ ™—–ˆ‡„|€z„‚…‰‰Š‹Œ‰ŠŒ†„‡‚‚ƒ€‚~~€|zs[z™o…²’‹«£Ž”ˆw}~hfsg^hsnmƒˆ€’œ•œ£¤¡¢“•”‹ŠŠ†ƒƒ‚€„„‚…ˆ‰ˆ‹Š‰‹ˆ†‡„…ƒ‚~|qZzr±™ ¦˜„rw}leimf_pxo|Ї–œ™›£Ÿ™š™’Œ††…ƒƒ€ƒƒ„‚ƒ†ƒ†ˆ„†‡‚‚ƒ€~~}{~~|}~~{yp_|’t€¡–‹’–’‚€‡zmutjiimkisxv€‰ˆŒ’•”””ŠŠ……†ƒ~~€~ƒ€„„ƒ‚€‚€}}~}|}|}z{}vjiˆ‹r‰ Ž†‘“‹}€…vnvuljnsppz~|‰Š‹ŽŒ‹Š‡ˆ‡„†„‚„ƒ€€ƒ‚„‚€‚ƒƒ€„{‚~‚€€|}nw‘…Œ„‹~~‚}wu{}tv~~€ƒˆ‡ˆŒŽŒŽŠŒ‹ŠŠŠˆ†‡†‡†…†‡††‡…†‡……‡†…„…†…‚…‡„„ƒ„…‚ƒƒy€ŽŠ„‹‘‰ˆ‹‚‚‚€‚~|€€~‚ƒ…††ŠŒ‰ŠŒŒŒ‹ŠŠ‰Šˆ†‡‡†‡‡…††…††…†…††…†††……ˆ†ƒ………†ƒ„‡„ƒ†„„„ƒ‚~ƒ‹ˆ‡ŠŒ‰ˆ‰††‡ƒƒ…‚‚‚ƒ„†‡ˆ‰ŠŠ‰‰‹‰‰ˆŠŠ‡‡ˆˆ‡ˆ†‡ˆ…„…†…„††„…………†„ƒ…‡„‚……ƒ„…„ƒ„„„…„„ƒ„‚|‚†„‹‹‹Š‚…‰‚‚…„ƒ€~ƒƒ‚„‡††…‰‰Š‰ˆŠ‰ˆ‰ˆ†……††„†‡…†…††…††…„†……„…†ƒƒƒ……ƒ„‡…„……‡†ƒƒˆ†‚ƒЉ‡ŒŒŠŠˆ‡‡„„…‚‚‚ƒ‚ƒ…ƒ…†…†‰ˆ‡ˆ‰ˆˆˆˆŠˆ†‰‰†††‡………†ˆƒ…†„‡„‚‡†„…„„…„‚„„„ƒ„†‚ƒ…ƒƒ„ƒ‚ƒ€~‚††…†ŠŠ††……„€€~€€‚„……‡„…ˆ‡…‡‡‡‡„‡‡„„…„……ƒ„ƒ„ƒƒ„„ƒ„ƒ„ƒ„ƒƒ„‚ƒ‚ƒƒƒ‚„„‚ƒ„‚€‚ƒ~yƒŠ…‚†ŒŠ…„……‚€€{|~~€„„‚††„†ˆ‡†‡†………†„‚„…ƒ‚†„…„‚„…„†…ƒ‡†………‡„„…„„……„………†…„…„~hf–¬““£¦Ÿ‰}„kcfoytsŒ›‹•š“…z†xt‡‹…†ŒŽ‰Œ‹†„‚……€~‡‰…ƒ…І„…‚€|zƒ}‚ˆ‚~ƒ‚‚~z‚…|~‡„~ybBa§Ð´¡¶£‰g?Q{aOˆÀ´š™”sIIt~r„¥³¬—„‡‚jaq‚•–›ž‘€yxwuwŽ“‘‹‰€vsx}…Š‹‡~{wvy}‚………ƒzweEJ‹ÓÈ—¦¦‰X0E|œa‡Ò¾Ÿ†~waGQ|¼ª‹ª­’iNYv}r‹¼¾ ŽŠƒq^`~™—’ ¥—€ppxxt€˜œ“ŒŠ…{pmw‚‚ŠŽŠ€|yzww{ƒ„pLS’ÒÈŽ£¢‡V2K…¡^ˆÎº›„~|eKS€¿¤‡«®‘kQ_‚t½Á¢‰wce™’£¨šƒsv}xs—™‹Š…~rnv~‰Ž‹…}{xuu~w[NyÃÑ™™©‘n95n—ah¼Ç¨‡ƒwOCc˜—|¢¸¨Žmf}w`o˜ªŸ—™’rcwzz‹Ÿ“ˆ‰Š{lq}‚…“ƒ}sqzƒ‚„‡ˆƒ|}zpQNvÂˤ¯–ƒR2vG^¢Ë¯›˜ž‘a=d€il†®¾¨“Ÿ¢‚dj{|“ª¤’”š~vx‚ƒ}‚‘”ŽŠŒŒ†|y€~~„‡‰…„…†}~}€||ZTÒ´‡¸²žkl‡T4Vv‰‚¡ÆªŽ’”„t^iˆsŠ ¡ž˜›¡’€…ˆ}|ƒŽ‹†Ž”‘Œ‡‰‹ƒ|~€€~ƒ…„‚„„ƒ‚€~}}}€~}eRl¯«€¥¹¯ˆˆ‹ydX`o_Nh…ƒƒ‰š¬ž™›“‡~†ˆ~|…ˆ…†‘މ‰‰†‚€‚ƒ€~€€€‚ƒ‚‚‚‚€€€€€~}q[q™Ž‹•ž° –”Œv{xlihlohjsx}}‚ŒŽ’”˜˜—™™™–”•“‘ŽŒŠ‰ˆ‡†„…„‚ƒ‚‚€€€€€€~~€€€€~zinŽ‹„ˆ’ ”Œ”“ˆƒ‡‚{yxzvqstuuu{~ƒ†‹Ž“”“”––”•”““‘‹Šˆ‡‡†„ƒ„ƒ€€€€~~~}~}~}~}zjjˆ‹‚‡Ž›•‹‘“ކƒyvuvtopsstux}}€„ˆ‹‹‘’’“””“’‘’ŽŒŠ‰ˆ‡†…„„ƒ‚€€~~~~~}~}~}~}}|zqcyŒƒƒ†•œŒ’Œ‚‚{xuuvqpsrssw|}€ƒˆŒŽ‘‘’’“”•”•”’‘ŽŒŠ‰‡†……„ƒ‚€€~~~~~~}}~}~~}|vesކ‚ˆ•ž”’ƒ„~yvwxrostvuu|€ƒ‡ŒŽ‘”•”–——–—––”’’ŽŒŠ‰‡††„„ƒ‚‚€€€~~~}}~}}}{sdxŽ„ƒ‰–œŽ•‘Œ‚‚„|wuuvppsstsv||‚†Š‹Ž’’’”•””“’’Ž‹‰ˆ†…„ƒƒ‚€~}~}~}}||}|{|{{|}{xidƒ‹…‹œ™‹’”‰ƒ‚xvuvuopsstsx|}€‚‡Š‹Ž‘“””•––••””‘‘Šˆ‡†…„ƒƒ€€€€~~€~~~~~}udsˆ…Š– ’•“Ž…ƒ…~zwvxrpuutuy}}€†ˆŠŒ’’”–—˜––—–•“”“‘ŽŠ‹Šˆ‡†……„ƒ‚‚€€€€€€€€€wv„„€‰‹‡‰†ˆ‘Œ€„Œˆ~†…~~ƒ„€ƒ‚‚‚„†‡††ŠŒ‹ŠŽŽ‘Ž‘ŽŽŒŒ‹ŠŠ‰ˆ‡‡††…„ƒƒƒ€€|ƒ}}†ˆ~†…„€ƒ†„„„…†„…†…†…†‡‡‡††‡‡†‡ˆ‰‡ˆˆˆ‰ˆ‰‰ˆŠŠ‰ŠŠ‰Š‰Šˆˆ‰ˆ‰ˆ‡ˆˆ‡ˆ‡†‡‡††…„ƒ…„ƒ‚€ƒ‚‚‚‚‚‚‚€€€€€€€€€€€‚‚‚‚ƒ„…ƒƒ……†††ˆ…ˆ‡‡‰ˆ†‡ˆ‡†‡‡††‡‡†‡†‡††…†„……„„„„„„„‚‰}r’—un–¢sfš¨pc›¨ob™¦ob”ŸpdŽ•ohˆŽsl‡yrz†ƒyx†wŠ–„yš‡~‘™‰„’–‹ˆˆ‡‰†„‚€€}xxvw{|xz‚w|ƒzv}vu|{vy}||~ƒ„‚„††…†‡‡…†‰Šƒ‚‹‹ƒƒ‹‰ƒ†‰†……††…†……ƒƒ‡†ƒˆ„}‡ƒ~„†‡‰‹ˆ€ˆ„|}€~{|{|}}~€€ƒƒ€ƒ‡‚‹ˆ~‚Œ…}†Š€‰‡‚‰…~…‹‚ˆˆ€‹‡}‚‹‡…Œƒ}‡Œ}ŠŒ~|‡„‡††Ž……‹‡ƒƒ„„€€‚„‚ƒ„…†…‡ˆ‡ˆŠˆ‰‹‰…ˆŒ…ƒ‹‹€‚‹ˆ‚ƒˆ…ƒ††‚‡…ƒƒ…„„ƒ„†„‚ƒ…„„…‚ƒ‡†€}„…‚„…Ї‚‚ƒ€€‚„‚„ˆ†…‹‰„‡Œ‰…‡ˆ……‡…†‰ƒ‰†~…‹{‹Œz}‘‰w‚“…xˆ€}ŠŠˆ‡‚ƒ‡ƒsh…¦Ž{Ÿ°ƒ˜’ytximuuv}‚‡ŽŽŽ“”Œ‹‘Œ‚‚†…„……‡Šˆˆ†‡‹€ƒˆ„‚……„ˆ†„…‰††‰…Šˆ~Œˆ}~…^S–Ì„sÅÆ‹‚¢’i_qof`e‡–ƒ«©‡‰Ÿ’zv€ƒy“‡’ž—Ž…}‚†‚€†ˆŠŒŠ‰Œ‹ƒ†…‚„}€…†ƒƒ„‡ƒ}vXMŒÚvÊÀ—‰ƒhNFgz`fŸ±Œ•²¢‰~|xrbiƒˆ~©£••—Žux|yvŒŠ“…‚ƒzy}~‚‡Š……†„{{xzaG]µÑkœÓ©“…„tW6H}_Æ´™ ŸvUayh^Ÿ—§­¥~‚lcvƒ…“œž“Œ‰xryzwy„ŠŒ†ˆŠˆ|z|yspmQOÙ˜rŵ™ˆ}wd?8e‡it¹¿£–—WSyxez ª¡¤¬ª˜}}‡xjwŠ‘” ¦š‹ŒŠzou{}{’Œˆ‹‰vxwwpq]Toýh§¿œŽ~ynM1K‚z^œÆ¯™”‘…dD^xef‹¨¦ ¥¨Ÿƒoz{ce|‰Š˜£Ÿ‡Šmov{z‡’Ž‰ŠŠƒyvuvqmVUvÊ®mµ¸šŒ}xgC2[ˆmk²À§–“}YHkwcn–©¢Ÿ¦¨›}svalˆ‰‘¥œŒ‹‰zpuz}{‚‹’‰‹‹zzxvspXVzΣm¹¶›Œ~zkF3^„hiª½¨—•“†bRsx_h‹™š§® ‡ƒŒ~io„‚ˆ”Ÿš’”ˆ{z}|yz‡‡‡Œ‰ƒƒ‚~yvqXVÆ’u¿· …wM4ixZi¤§œ©œ}}ˆuchv‚‚€–« ˜Ÿ –ˆƒƒwv€†…†Œ“‘ŽŠ„‚ƒ‚|€„ƒ†‡‡„ƒ{`XŒ¹„‹À³¥’ˆŠ~X^qYR[mŒ‡¢±žšž›–‚|‰‚tw‡ˆ…š–”——–Š‹‰€‚‚†…„†ˆˆ†……„‚€€~}r[cšš|ž³­ “~turkXXnh`mz…„‚’œ˜•—ž‘”І†ˆƒ„……„…ˆ‰‡‡ˆˆ‡…†…„„ƒ‚€€~€€~zhf“‰™¤”ˆ—”ˆ‚€ƒ{pstummxuqsy‚‚€‡ŒŽŽ•–”•–˜–””“‘ŽŒ‹‰ˆ†„ƒ‚ƒ‚‚€€€€‚€€€~yir‡„‰”œŽ“‘Œ…„„{yzzuuwyyw{ƒ†‹Ž‘“”••–—–••–”’‘ދЉˆ‡†……ƒ‚ƒ‚ƒ‚‚€€~riŒ‚…‰–•‰Ž†€‚€zxvxuqstutuz}~€„ˆŠ‹Ž‘’’’’“’‘’ŽŒ‹‰ˆ†…„ƒ‚‚€€€~~~~}~~}|vfpŠ„€…˜‹Š‹€zwtuvpoqsssx||}‡ˆŠ‘‘““’‘’ŽŽŒ‰ˆˆ‡…ƒ‚‚€€~~}}|}}|||}|}{{~||„†}|ˆŠ}|‰‹}{ŠŒ~}Š‹~ŠŠ…‹†‚‡Šˆ††‹ŠˆˆŠ‹Š‰Š‹‰‰ˆ‰ˆ‡ˆˆ‡‡†‡†‡††…†…†…………„……„„…„…„…„„…„…„ƒ„„„…„…„………„…†…††…„……„……†………††…†…††…†…†…†…†††…†…„…„…†…†…††…†…†…„…†…„…„…„…„„„ƒ„ƒ„„ƒ„„ƒƒ„„ƒ„„„ƒ„„ƒ„ƒ„ƒ„ƒƒ‚ƒƒ„„ƒ„„ƒƒ„„ƒ„ƒ„ƒ„ƒƒ„ƒ„ƒ„ƒ„ƒ‚ƒ„ƒ„ƒ„ƒƒ„ƒ„ƒ„ƒ„ƒ„„„„„……„…„…„………†…„……„…„„…†…„…„…††…†…†…††…†…„…†…„…„…„……†…„…„……„……„……„†…„„…„……„…„…„…„………†„……………„…„…„…„………„„‡…†ƒƒ‡…ƒƒ†……‚„‡…„„„ˆ‡‚ƒ‡ˆ„…„„Š„€‡……‰ƒƒ‡„ƒ‡‡ƒ„……†ƒ†„†€„‰„ƒ„…†ƒ‚†€~‡†ƒ€€ˆ‡}€„‚…ƒ|ƒˆ‚‚€ƒ†ƒ„|…ˆ~‚„€‚ƒ€…‚€…‡~„ˆƒƒ‡…|~ƒ†‡~}ƒ„†ƒ€€€„„€€„†ƒ{†€„‰‡ƒ„‡ƒ€…‡‚‚‚……ƒ…†‚‚†ƒ‚„ƒ…„‚€„„…„€ƒ‡…ƒ„…ƒ„…‚…„„…}€€oduŒœ­«™”•‹„tcafiongm˜›—–š›–‰€}~~‚~„‹‘–”ŽŽŒƒ{z~€‚…‡‹‹‡„††„…ƒ}}€€ƒƒƒ…‰‹‰ˆˆˆ‡ˆ†„ƒ„……‡‡††ˆ‰ŠŠ‰‰‡†…„„†‡††‡†‡‰ˆˆ‡ˆ††‡…‚ƒƒƒ|dZkˆ²ÏɵŸŽ…zraKAK^uŒ••—šž¢ž“„uhdksx}‚†–Ÿ¡ž—…€}{zyxx|‚‰Ž‹ˆ…ƒ}{zy|„†††‰‰ˆ†…ƒ‚ƒ„„ƒ„…‡‡…v^`w•ÂØÅ®šŒ‚{yrbNKVh€šª«¤™™—pb^gu‚Œ“•—œ¡¡ž•Š~wtuy}~‚‡‘”’‰„~}|{{}€„ˆ‰‰ˆ†…ƒkT\u•Ãש—‰{zr`MJViƒ›©«¦ž¡ž–‰yjgmx„Œ‘’–£¦£›‘‡||~~€‚…Š“’Œˆ…„‚€~{z{}‚…„††…‚~gVe~ŸÅ͸£“‡|yn]MITi–¤¨¡œžŸ›•ˆwkgku€†Š”œŸ˜‡}||}||}€„‰Š‰†„‚€~|{zz{|~€w`Xjƒ§Êǰ‚{xtjZJFTi€–¢¢žžŸ–‰zojnw€…‰‹Ž”™ž™’‹„~}}||‚†‰Œ‹‰‡…„ƒ€~||{{|}~}yeXg~ÃÌ·¤“†}ztj^MDM_t‰˜›ššœŸ š„xppu{€ƒ…ˆ’—›™•Žˆ„‚~{{{}…‡ˆˆ†…„„‚€~||{|{|{vdWdz–½Ê¸¥•†}{vl`OCJZmƒ‘•–—›¡¡›’‡zssuz~€„ˆ“––“ˆ„€~|zz{|~€‚ƒ„ƒƒ‚€}{yxxyxxmYXk¤ÆÄ¯ž‚|xqgXFBN`vŠ“•˜™œ¡¡š‘„xrsw{‚…ŠŽ”—•’ˆ…„‚€~||}‚„……††…„ƒƒ‚~~|}{n]_r…¢Â°¢’…}wrl_RRYbtƒˆŽ’’”œ ŸŸœ“Œ‰…ƒ…‡„„…„„…‡‰‹Œ‹Š‰‡‡††……„ƒƒ‚‚‚‚‚€~xpmwƒ‹™¨©ž˜’Š{||yxyxtpopsty„‡‰ŒŽ’•––—˜˜–••“‘ދЉˆˆ†……„…„ƒ‚‚ƒƒ„ƒƒ‚€€€{torz„š¡¡•‰ƒ||zzxwusqrsv{‚†‰‹’”•–—˜—–••““‘Œ‹‹‰ˆ‡††…„„„…ƒ‚‚‚‚‚€}wrqv‰“¢Ÿ™‘Šƒ}zzyyxxusrrsuy}…ˆŠŒŽ‘“”•––—––•“’’Ž‹‹Šˆ‡†…„…„ƒƒ„ƒ‚ƒƒ‚€~xrrw€Š“œ –‰ƒ{zzyywusqqrtw{ƒ…ˆŠŒŽ‘“”••–•”“’‘Ž‹‰‰‡†…ƒ„„ƒ‚‚€€€~}wqot~‡™ž—‰ƒ}zyyxvusqpopsvy}…‡‰‹Ž’“•”••“’‘ŽŒ‹‰‡††…„ƒƒ‚€€€}~~}}|zvolpy‚Œ–œœ•އ|wwvvtsrpnmmpsw{€ƒ…‡‰ŒŽ‘’““”“‘‘ŽŒ‹Š‰‡†……ƒ‚‚€€€~~~}~}|zunkoz…™Ÿž—ˆ€{wvwvvtsqnoprvz~„‡ˆ‰Œ‘’“”•”“’‘ŽŽ‹‰ˆ††…„„ƒƒ‚‚‚‚€€~}ve^kˆ©±¨£˜‰‰’si_^gg_[ahv…“¤£ž—‘‘Žˆƒ‚„‰ˆ†ˆ‹ŒŠŠŠ‰†ƒ‚‚‚€‚ƒ‚ƒ‚‚€}ePc˜Êª•ª¨Ÿ“djqYBEQh†„~™³­¡—ŽŽŠyimx{|€Š—š–—˜‘‡€~~}|€‡ˆˆŒŒ‹Š†‚|y}|~~€€€oPZ˜Ô…¿°šˆqrtI4U~rn“³¹Ÿ‹“–sW`vrmy”¨ž—£¦’€~‚xpt‚ŠŒ”œ“ˆ‡ˆvw{€‰‰ˆ‡‰€w{iMY¡Ñy‰Ã°š…xZ4Kƒb˜Å²š••ŠiMi|il¨¢œ£©{‡{js‡ŽŽ“ž¥˜~vz€‡‘•ŽŽŒ~{}vwiZi´Ìt¡Ç£“‚~tR2V‰sh«À¦—•€\Ntxexœ¦žŸ§¥z}„piz‹Ž—£ ’ŠŽ†wu|‚Š”‘Œ‚}zzvunY^–ЅŦ’}vZ5F~`’¾©•ŽŽ…fHfzdi¤š £•{u‚ter…‹ŠœŸ“ˆŠ‡wpw}{~„‰‰‹…{zyytr]YzÉ¥o¸´˜Š€|jB5o‡`{¹²›““wQc€gc…š›œ ¨¢‰Šjsƒ†‰“Ÿ—Ž”‘„}~|ˆ‹†‹Š‡„‚~y\SwÀ˜s»¸ …„xJ@upYp”©ž¥©Œ|†„pdk‚„z‰ ž˜™œšŒ‡ˆyx„„„‰‘‘‰ŒŽ‰„ƒ‚ƒ}|€~|i\µŒ€¶³¡Žˆ‰}`_rdTUn†zz™¢™””š“ˆ}vx~‚€ŠŽ‹‹ŽŽˆŠŠ„ƒ‚ƒƒ€€‚‚€‚ƒƒ‚ƒ€|i_†„ž«œ„‘–„xy{vd_pogdo€zw„ŒŽ˜˜’’•ŠŽŠ††ˆ†„„„†…„……„ƒƒ…„ƒƒ„ƒ‚‚|mt‹†‚†–‹‰ˆ€€{xvxwssvwwvz}~„‡‰ŠŒŽŽŽŒ‹‹‰ˆ‡‡††…„„„ƒ‚€ykz‹ƒƒ‡“–ŠŒ‡€‚€zwuwtprtuuuz}~€ƒ‡ˆŠŒŽŽ‘‘ŽŒŒ‹Š‰ˆ‡‡‡††…„ƒƒ‚€vk€Œ„ˆ•”ˆŽŽ‹…~‚xwuxuqttuuu{|}€ƒ‡ˆŠŒŽŽ‘ŽŒ‹Š‰‡‡††……„„ƒ„‚‚€|mrŠ……Ž—‹‰Ž‰€|xvwwsrtuvux|}~…ˆ‰Œ‘’“’‘‘’ŽŽ‹Š‰‰ˆ‡ˆ‡†…†…„„ƒ€qtŠ„‡™Œ‰ŽŠ‚„~zyyzuuwxxx|€„‡Š‹‘‘’“”“’“’‘ŽŒ‹Š‰‰ˆ‡ˆ†…†……„‚€qsŒŠ„ˆ˜Ž‰ŽŠƒƒ~zyyyttwxxw{ƒ‡‰ŠŒŽ‘‘‘ŽŽŒ‹Š‰ˆ‡‡††„ƒ„ƒƒ‚rn‡‰‚…‹–އŒŒ‰‚~|wuuvsqtuvvw|~…ˆ‰‹ŒŽŽŽŽŒ‹‹‰‰ˆ‡††……„ƒ‚‚€~vj}‰‚…‘’†‰Œ‰„}~wututprsttuy{|‚…‡‰‹ŽŽŒŒ‹ŠŠ‰ˆ‡†…„…„ƒƒ‚ƒ‚{mvŠƒ„Ž“ˆ‡ŒŠ†~€zwuwwrsuvwvz~ƒ†ˆ‰‹ŒŽŽŽŽŽŒ‹‹Š‰ˆ‡†…†…„„ƒ‚‚‚€~rq‡‡„Š”Œ†Œ‹ˆ~|wuvxtruvwwy}~€…‡ˆ‰‹ŒŒŽŒŒŒ‹Š‰‰ˆ‡††……„…„„ƒƒ‚xn‰‚‚…‘‡Š‹Š„~€~ywvxwrtvwww{~ƒ†‡ˆŠ‹ŒŒŒŒŒ‹‹Š‰‰ˆ‡†…†…„…„„ƒ‚ƒ‚~qwІƒ…“Šˆ‹ˆ€‚}zxyzvuxyzy|€€„†‰‰‹ŽŽŽŽŽŽŽŒŒ‹‹Š‰ˆˆ‡†‡‡†…†……„ƒƒ‚xs†‹ƒ†Š“‡ŒŒ…„|{{|zwz{|{}‚ƒ…ˆ‹Œ‘‘ŽŽ‹‹Š‰‰ˆ‡ˆ‡‡‡††…„„}s‚…†Š“’‰ŒŒ‡‚„ƒ~|{}{xy{|{|€‚ƒ„‡Š‹ŒŽŽŽŒŒ‹ŠŠ‰‰ˆˆ‡‡ˆ‡†††……ƒ{u…‹…†Š“ˆ‹Œ‹†‚„}|||zxz|{{|€‚‚„†‰Š‹ŒŽŽŽŽŒŒ‹ŠŠŠˆ‡‡†‡†…„…„ƒ„ƒƒ‚€|r}Šƒƒ†Ž‡ˆ‹‰†€‚}{z{{xy{zz{~€€‚„‡‰‰Š‹ŒŒ‹Œ‹‹Š‰‰‰ˆ†††………„ƒ„ƒƒ‚‚€~tw‡…‚„‰ˆ…‰‰†‚€~{z{|yxz{|{~€‚„‡ˆ‰‹ŒŒŒŒ‹‹ŒŒ‹ŠŠ‰‰ˆ‡††‡††…„„„ƒƒ‚zu…‰„…‰Œ†ŠŠˆƒ€‚|zz{ywyz{{|€ƒ…‡ˆ‰Š‹Š‹Š‹‹ŠŠ‹‹Š‰ˆ‡††……„„ƒ„ƒ‚ƒ‚€zr~‡‚…ŒŒ„†‰‡ƒ~zyxyxuvxyxy|}~€‚„……‡‡ˆ‡ˆ‰ˆ‡ˆ‡‡††……„ƒ‚‚€€€€€{s{†‚ƒ‰‹„…ˆ†ƒ~{zyzzwxyz{{~€‚„†‡‡ˆ‰‰Š‰Š‰Š‰Š‰‰ˆ‡ˆ‡†…„…„„ƒ„ƒ„„ƒ‚ƒ‚wz‡…ƒ…Šˆˆ‹‰†‚~|||}{z|}~}‚ƒ…‡ˆ‰ŠŠ‹ŒŒ‹Œ‹Œ‹‹‹Š‰‰ˆˆ‡†††…†…†…„…„…„‚{{ˆˆ…†ŠŠ‡ŠŠˆ„„…~€}}€€„……‡ˆŠŠ‹‹ŒŒŒ‹‹Š‹Š‹ŠŠ‰ˆˆ‡‡†……††…†…„…„…„‚{|‡‡„†ŠŽ‰‡‰ˆ‡ƒ‚‚€~|}~{{|}~€‚ƒ„…‡ˆ‰ˆŠŒ‹Š‹Š‹Š‰Š‰‰ˆ‰ˆ‡‡†…††…†…†……„„„‚{|‡†„…‰Œˆ‡‰ˆ‡‚‚ƒ€~}~}}~€‚ƒ„……‡‰‰‰Š‹Š‹Œ‹‹‹Š‹Š‰‰ˆˆˆ‡†…†‡††…†…††…†…„„ƒ„ƒ„„„ƒ‚}ˆ†††Š‹‡‡‰ˆ‡„„„ƒ‚‚‚‚ƒ‚ƒ„…†…†‡ˆ‰ˆ‰ˆ‰‰Š‰Š‰‰Š‰ˆ‰ˆˆ‡ˆ‡‡†………††…†……†…„…„…„…„……„…„…„…„…„…†…†…††…†…†……†…†…†…†………†…†…†…†…†………„……†…………†…„…„…„…„……„„…„ƒ„…„…„…„…„ƒƒ„„„ƒ„ƒ„„ƒ„ƒ„„ƒ„ƒ„…„ƒƒ„ƒƒ…„ƒ„ƒ„ƒ„ƒ„„ƒ„ƒ„ƒ„……„„…„„ƒ„ƒ„ƒ„…„…„ƒ„ƒƒ„ƒ„ƒ„„ƒ„„ƒ„ƒ„ƒ„ƒ„„„„„ƒ„…„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„„ƒ„ƒ„…„…„…„„ƒ„ƒ„„…„ƒ„ƒ„ƒ„ƒ„„„ƒ„„ƒƒ„„ƒ„ƒƒ„ƒ„ƒ„ƒƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒƒ‚ƒ‚ƒ‚ƒ‚ƒƒ‚‚‚ƒƒ‚ƒ‚ƒ‚‚‚ƒƒ‚ƒƒ‚ƒƒ‚ƒ‚‚‚‚‚‚‚€€€€€€€€‚‚‚€€€‚‚€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒ‚ƒ‚‚‚‚ƒƒƒ„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„ƒ„…„…„…„…„…„…„…†…„…„…„…„…„…„……†…†…†…††…†…†‡†‡†‡†‡‡†‡†‡†‡†‡‡ˆ‡ˆ‡‡†‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆˆˆˆ‰ˆˆ‰ˆ‰‰ˆ‰ˆ‰ˆ‰ˆˆˆ‰ˆˆˆˆ‡ˆˆ‰ˆˆ‰‡ˆ‰ˆ‰‰‰ˆ‰ˆ‰ˆ‰ˆ‰ˆ‰ˆ‰ˆ‰‰ˆ‰‰‰‰‰ˆ‰ˆ‰ˆˆ‰ˆ‡ˆ‰ˆ‰ˆ‰ˆ‡‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‰ˆ‰ˆ‰ˆ‰ˆˆˆˆ‰ˆ‰‰ˆ‡ˆ‡ˆ‰ˆ‰ˆ‰ˆ‰ˆ‰ˆ‰ˆ‰ˆˆ‰ˆˆ‰ˆ‰ˆˆ‡ˆ‡ˆ‡ˆ‡ˆˆ‡ˆ‡ˆˆˆˆ‡ˆˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡ˆ‡†‡‡†‡ˆ‡ˆ‡ˆˆˆˆ‡ˆ‡ˆ‡ˆ‡†‡‡†‡ˆ‡‡‡†‡‡ˆ‡‡‡‡†‡†‡†‡††‡†‡†‡†‡‡†‡††‡†‡†‡†††…†…††…†…†…†…†…††…†…†…„……„…„…„ƒƒ„„ƒ„ƒ„„ƒ„ƒ„ƒ‚ƒ‚ƒ„ƒ„ƒ„ƒƒ‚ƒ‚ƒ‚ƒ‚ƒ‚‚ƒ‚‚ƒƒ‚‚ƒ‚ƒ‚‚‚‚€€€€ \ No newline at end of file diff --git a/tests/driver_dac_dds/main.c b/tests/driver_dac_dds/main.c new file mode 100644 index 0000000000..138ec49203 --- /dev/null +++ b/tests/driver_dac_dds/main.c @@ -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 + * + * @} + */ + +#include +#include +#include + +#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 \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 \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 \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 \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; +}