summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ports/atmel-samd/Makefile30
-rw-r--r--ports/atmel-samd/audio_dma.c363
-rw-r--r--ports/atmel-samd/audio_dma.h89
-rw-r--r--ports/atmel-samd/background.c4
-rw-r--r--ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk2
-rw-r--r--ports/atmel-samd/clocks.c165
-rw-r--r--ports/atmel-samd/clocks.h55
-rw-r--r--ports/atmel-samd/common-hal/audiobusio/I2SOut.c376
-rw-r--r--ports/atmel-samd/common-hal/audiobusio/I2SOut.h51
-rw-r--r--ports/atmel-samd/common-hal/audiobusio/PDMIn.c1
-rw-r--r--ports/atmel-samd/common-hal/audiobusio/PDMIn.h3
-rw-r--r--ports/atmel-samd/common-hal/audioio/AudioOut.c704
-rw-r--r--ports/atmel-samd/common-hal/audioio/AudioOut.h29
-rw-r--r--ports/atmel-samd/common-hal/pulseio/PWMOut.c1
-rw-r--r--ports/atmel-samd/events.c194
-rw-r--r--ports/atmel-samd/events.h46
-rw-r--r--ports/atmel-samd/mpconfigport.h20
-rw-r--r--ports/atmel-samd/shared_dma.c203
-rw-r--r--ports/atmel-samd/shared_dma.h17
-rw-r--r--ports/atmel-samd/supervisor/port.c21
-rw-r--r--ports/atmel-samd/timers.c6
-rw-r--r--ports/atmel-samd/timers.h2
-rw-r--r--shared-bindings/audiobusio/I2SOut.c233
-rw-r--r--shared-bindings/audiobusio/I2SOut.h45
-rw-r--r--shared-bindings/audiobusio/__init__.c5
-rw-r--r--shared-bindings/audioio/AudioOut.c123
-rw-r--r--shared-bindings/audioio/AudioOut.h13
-rw-r--r--shared-bindings/audioio/RawSample.c183
-rw-r--r--shared-bindings/audioio/RawSample.h45
-rw-r--r--shared-bindings/audioio/WaveFile.c157
-rw-r--r--shared-bindings/audioio/WaveFile.h44
-rw-r--r--shared-bindings/audioio/__init__.c5
-rw-r--r--shared-module/audioio/RawSample.c94
-rw-r--r--shared-module/audioio/RawSample.h57
-rw-r--r--shared-module/audioio/WaveFile.c230
-rw-r--r--shared-module/audioio/WaveFile.h68
36 files changed, 2953 insertions, 731 deletions
diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile
index d675e6f2d..7a974a275 100644
--- a/ports/atmel-samd/Makefile
+++ b/ports/atmel-samd/Makefile
@@ -220,7 +220,10 @@ endif
SRC_ASF := $(addprefix asf4/$(CHIP_FAMILY)/, $(SRC_ASF))
SRC_C = \
+ audio_dma.c \
background.c \
+ clocks.c \
+ events.c \
fatfs_port.c \
flash_api.c \
mphalport.c \
@@ -276,7 +279,7 @@ SRC_COMMON_HAL = \
neopixel_write/__init__.c \
os/__init__.c \
storage/__init__.c \
- supervisor/__init__.c \
+ supervisor/__init__.c \
supervisor/Runtime.c \
time/__init__.c \
analogio/__init__.c \
@@ -287,12 +290,11 @@ SRC_COMMON_HAL = \
pulseio/PulseOut.c \
pulseio/PWMOut.c \
usb_hid/__init__.c \
- usb_hid/Device.c
-# audiobusio/__init__.c \
- audiobusio/PDMIn.c \
+ usb_hid/Device.c \
audioio/__init__.c \
audioio/AudioOut.c \
- nvm/__init__.c \
+# nvm/__init__.c \
+ audiobusio/PDMIn.c \
nvm/ByteArray.c \
touchio/__init__.c \
touchio/TouchIn.c \
@@ -338,6 +340,8 @@ SRC_COMMON_HAL_EXPANDED = $(addprefix shared-bindings/, $(SRC_COMMON_HAL)) \
$(addprefix common-hal/, $(SRC_COMMON_HAL))
SRC_SHARED_MODULE = \
+ audioio/RawSample.c \
+ audioio/WaveFile.c \
bitbangio/__init__.c \
bitbangio/I2C.c \
bitbangio/OneWire.c \
@@ -355,6 +359,22 @@ SRC_SHARED_MODULE = \
uheap/__init__.c \
ustack/__init__.c
+ifeq ($(CHIP_FAMILY),samd21)
+SRC_COMMON_HAL = $SRC_COMMON_HAL \
+ audiobusio/__init__.c \
+ audiobusio/I2SOut.c
+endif
+ifeq ($(CHIP_VARIANT),SAMD51G18)
+SRC_COMMON_HAL = $SRC_COMMON_HAL \
+ audiobusio/__init__.c \
+ audiobusio/I2SOut.c
+endif
+ifeq ($(CHIP_VARIANT),SAMD51G19)
+SRC_COMMON_HAL = $SRC_COMMON_HAL \
+ audiobusio/__init__.c \
+ audiobusio/I2SOut.c
+endif
+
SRC_SHARED_MODULE_EXPANDED = $(addprefix shared-bindings/, $(SRC_SHARED_MODULE)) \
$(addprefix shared-module/, $(SRC_SHARED_MODULE))
diff --git a/ports/atmel-samd/audio_dma.c b/ports/atmel-samd/audio_dma.c
new file mode 100644
index 000000000..7030009d9
--- /dev/null
+++ b/ports/atmel-samd/audio_dma.c
@@ -0,0 +1,363 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "audio_dma.h"
+#include "clocks.h"
+#include "events.h"
+#include "shared_dma.h"
+
+#include "shared-bindings/audioio/RawSample.h"
+#include "shared-bindings/audioio/WaveFile.h"
+
+#include "py/mpstate.h"
+
+uint32_t audiosample_sample_rate(mp_obj_t sample_obj) {
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
+ audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
+ return sample->sample_rate;
+ }
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
+ audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
+ return file->sample_rate;
+ }
+ return 16000;
+}
+
+uint8_t audiosample_bits_per_sample(mp_obj_t sample_obj) {
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
+ audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
+ return sample->bits_per_sample;
+ }
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
+ audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
+ return file->bits_per_sample;
+ }
+ return 8;
+}
+
+uint8_t audiosample_channel_count(mp_obj_t sample_obj) {
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
+ audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
+ return sample->channel_count;
+ }
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
+ audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
+ return file->channel_count;
+ }
+ return 1;
+}
+
+void audiosample_reset_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t audio_channel) {
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
+ audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
+ audioio_rawsample_reset_buffer(sample, single_channel, audio_channel);
+ }
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
+ audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
+ audioio_wavefile_reset_buffer(file, single_channel, audio_channel);
+ }
+}
+
+bool audiosample_get_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t channel, uint8_t** buffer, uint32_t* buffer_length) {
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
+ audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
+ return audioio_rawsample_get_buffer(sample, single_channel, channel, buffer, buffer_length);
+ }
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
+ audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
+ return audioio_wavefile_get_buffer(file, single_channel, channel, buffer, buffer_length);
+ }
+ return true;
+}
+
+static void audiosample_get_buffer_structure(mp_obj_t sample_obj, bool single_channel,
+ bool* single_buffer, bool* samples_signed,
+ uint32_t* max_buffer_length, uint8_t* spacing) {
+ if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
+ audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
+ audioio_rawsample_get_buffer_structure(sample, single_channel, single_buffer,
+ samples_signed, max_buffer_length, spacing);
+ } else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
+ audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
+ audioio_wavefile_get_buffer_structure(file, single_channel, single_buffer, samples_signed,
+ max_buffer_length, spacing);
+ }
+}
+
+uint8_t find_free_audio_dma_channel(void) {
+ uint8_t channel;
+ for (channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) {
+ if (!dma_channel_enabled(channel)) {
+ return channel;
+ }
+ }
+ return channel;
+}
+
+audio_dma_t* audio_dma_state[AUDIO_DMA_CHANNEL_COUNT];
+void audio_dma_convert_signed(audio_dma_t* dma, uint8_t* buffer, uint32_t buffer_length,
+ uint8_t** output_buffer, uint32_t* output_buffer_length,
+ uint8_t* output_spacing) {
+ if (dma->first_buffer_free) {
+ *output_buffer = dma->first_buffer;
+ } else {
+ *output_buffer = dma->second_buffer;
+ }
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wcast-align"
+ if (dma->signed_to_unsigned || dma->unsigned_to_signed) {
+ *output_buffer_length = buffer_length / dma->spacing;
+ *output_spacing = 1;
+ uint32_t out_i = 0;
+ if (dma->bytes_per_sample == 1) {
+ for (uint32_t i = 0; i < buffer_length; i += dma->spacing) {
+ if (dma->signed_to_unsigned) {
+ ((uint8_t*) *output_buffer)[out_i] = ((int8_t*) buffer)[i] + 0x80;
+ } else {
+ ((int8_t*) *output_buffer)[out_i] = ((uint8_t*) buffer)[i] - 0x80;
+ }
+ out_i += 1;
+ }
+ } else if (dma->bytes_per_sample == 2) {
+ for (uint32_t i = 0; i < buffer_length / 2; i += dma->spacing) {
+ if (dma->signed_to_unsigned) {
+ ((uint16_t*) *output_buffer)[out_i] = ((int16_t*) buffer)[i] + 0x8000;
+ } else {
+ ((int16_t*) *output_buffer)[out_i] = ((uint16_t*) buffer)[i] - 0x8000;
+ }
+ out_i += 1;
+ }
+ }
+ } else {
+ *output_buffer = buffer;
+ *output_buffer_length = buffer_length;
+ *output_spacing = dma->spacing;
+ }
+ #pragma GCC diagnostic pop
+ dma->first_buffer_free = !dma->first_buffer_free;
+}
+
+void audio_dma_load_next_block(audio_dma_t* dma) {
+ uint8_t* buffer;
+ uint32_t buffer_length;
+ bool last_buffer = audiosample_get_buffer(dma->sample, dma->single_channel, dma->audio_channel,
+ &buffer, &buffer_length);
+
+ DmacDescriptor* descriptor = dma->second_descriptor;
+ if (dma->first_descriptor_free) {
+ descriptor = dma_descriptor(dma->dma_channel);
+ }
+ dma->first_descriptor_free = !dma->first_descriptor_free;
+
+ uint8_t* output_buffer;
+ uint32_t output_buffer_length;
+ uint8_t output_spacing;
+ audio_dma_convert_signed(dma, buffer, buffer_length, &output_buffer, &output_buffer_length,
+ &output_spacing);
+
+ descriptor->BTCNT.reg = output_buffer_length / dma->beat_size / output_spacing;
+ descriptor->SRCADDR.reg = ((uint32_t) output_buffer) + output_buffer_length;
+ if (last_buffer) {
+ if (dma->loop) {
+ audiosample_reset_buffer(dma->sample, dma->single_channel, dma->audio_channel);
+ } else {
+ descriptor->DESCADDR.reg = 0;
+ }
+ }
+ descriptor->BTCTRL.bit.VALID = true;
+}
+
+static void setup_audio_descriptor(DmacDescriptor* descriptor, uint8_t beat_size,
+ uint8_t spacing, uint32_t output_register_address) {
+ uint32_t beat_size_reg = DMAC_BTCTRL_BEATSIZE_BYTE;
+ if (beat_size == 2) {
+ beat_size_reg = DMAC_BTCTRL_BEATSIZE_HWORD;
+ } else if (beat_size == 4) {
+ beat_size_reg = DMAC_BTCTRL_BEATSIZE_WORD;
+ }
+ descriptor->BTCTRL.reg = beat_size_reg |
+ DMAC_BTCTRL_SRCINC |
+ DMAC_BTCTRL_EVOSEL_BLOCK |
+ DMAC_BTCTRL_STEPSIZE(spacing - 1) |
+ DMAC_BTCTRL_STEPSEL_SRC;
+ descriptor->DSTADDR.reg = output_register_address;
+}
+
+// Playback should be shutdown before calling this.
+audio_dma_result audio_dma_setup_playback(audio_dma_t* dma,
+ mp_obj_t sample,
+ bool loop,
+ bool single_channel,
+ uint8_t audio_channel,
+ bool output_signed,
+ uint32_t output_register_address,
+ uint8_t dma_trigger_source) {
+ uint8_t dma_channel = find_free_audio_dma_channel();
+ if (dma_channel >= AUDIO_DMA_CHANNEL_COUNT) {
+ return AUDIO_DMA_DMA_BUSY;
+ }
+
+ dma->sample = sample;
+ dma->loop = loop;
+ dma->single_channel = single_channel;
+ dma->audio_channel = audio_channel;
+ dma->dma_channel = dma_channel;
+ dma->signed_to_unsigned = false;
+ dma->unsigned_to_signed = false;
+ dma->second_descriptor = NULL;
+ dma->spacing = 1;
+ dma->first_descriptor_free = true;
+ audiosample_reset_buffer(sample, single_channel, audio_channel);
+
+ bool single_buffer;
+ bool samples_signed;
+ uint32_t max_buffer_length;
+ audiosample_get_buffer_structure(sample, single_channel, &single_buffer, &samples_signed,
+ &max_buffer_length, &dma->spacing);
+ uint8_t output_spacing = dma->spacing;
+ if (output_signed != samples_signed) {
+ output_spacing = 1;
+ max_buffer_length /= dma->spacing;
+ dma->first_buffer = (uint8_t*) m_malloc(max_buffer_length, false);
+ if (dma->first_buffer == NULL) {
+ return AUDIO_DMA_MEMORY_ERROR;
+ }
+ dma->first_buffer_free = true;
+ if (!single_buffer) {
+ dma->second_buffer = (uint8_t*) m_malloc(max_buffer_length, false);
+ if (dma->second_buffer == NULL) {
+ return AUDIO_DMA_MEMORY_ERROR;
+ }
+ }
+ dma->signed_to_unsigned = !output_signed && samples_signed;
+ dma->unsigned_to_signed = output_signed && !samples_signed;
+ }
+
+ dma->event_channel = 0xff;
+ if (!single_buffer) {
+ dma->second_descriptor = (DmacDescriptor*) m_malloc(sizeof(DmacDescriptor), false);
+ if (dma->second_descriptor == NULL) {
+ return AUDIO_DMA_MEMORY_ERROR;
+ }
+
+ // We're likely double buffering so set up the block interrupts.
+ turn_on_event_system();
+ dma->event_channel = find_sync_event_channel();
+ init_event_channel_interrupt(dma->event_channel, CORE_GCLK, EVSYS_ID_GEN_DMAC_CH_0 + dma_channel);
+ find_sync_event_channel();
+
+ // We keep the audio_dma_t for internal use and the sample as a root pointer because it
+ // contains the audiodma structure.
+ audio_dma_state[dma->dma_channel] = dma;
+ MP_STATE_PORT(playing_audio)[dma->dma_channel] = dma->sample;
+ }
+
+ dma->beat_size = 1;
+ dma->bytes_per_sample = 1;
+ if (audiosample_bits_per_sample(sample) == 16) {
+ dma->beat_size = 2;
+ dma->bytes_per_sample = 2;
+ }
+ // Transfer both channels at once.
+ if (!single_channel && audiosample_channel_count(sample) == 2) {
+ dma->beat_size *= 2;
+ }
+
+ DmacDescriptor* first_descriptor = dma_descriptor(dma_channel);
+ setup_audio_descriptor(first_descriptor, dma->beat_size, output_spacing, output_register_address);
+ if (single_buffer) {
+ first_descriptor->DESCADDR.reg = 0;
+ if (dma->loop) {
+ first_descriptor->DESCADDR.reg = (uint32_t) first_descriptor;
+ }
+ } else {
+ first_descriptor->DESCADDR.reg = (uint32_t) dma->second_descriptor;
+ setup_audio_descriptor(dma->second_descriptor, dma->beat_size, output_spacing, output_register_address);
+ dma->second_descriptor->DESCADDR.reg = (uint32_t) first_descriptor;
+ }
+
+ // Load the first two blocks up front.
+ audio_dma_load_next_block(dma);
+ if (!single_buffer) {
+ audio_dma_load_next_block(dma);
+ }
+
+ dma_configure(dma_channel, dma_trigger_source, true);
+ dma_enable_channel(dma_channel);
+
+ return AUDIO_DMA_OK;
+}
+
+void audio_dma_stop(audio_dma_t* dma) {
+ dma_disable_channel(dma->dma_channel);
+ disable_event_channel(dma->event_channel);
+ MP_STATE_PORT(playing_audio)[dma->dma_channel] = NULL;
+
+ dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
+}
+
+void audio_dma_init(audio_dma_t* dma) {
+ dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
+}
+
+void audio_dma_reset(void) {
+ for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
+ audio_dma_state[i] = NULL;
+ dma_disable_channel(i);
+ dma_descriptor(i)->BTCTRL.bit.VALID = false;
+ MP_STATE_PORT(playing_audio)[i] = NULL;
+ }
+}
+
+bool audio_dma_get_playing(audio_dma_t* dma) {
+ if (dma->dma_channel >= AUDIO_DMA_CHANNEL_COUNT) {
+ return false;
+ }
+ uint32_t status = dma_transfer_status(dma->dma_channel);
+ if ((status & DMAC_CHINTFLAG_TCMPL) != 0) {
+ audio_dma_stop(dma);
+ }
+
+ return status == 0;
+}
+
+// WARN(tannewt): DO NOT print from here. Printing calls background tasks such as this and causes a
+// stack overflow.
+void audio_dma_background(void) {
+ for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
+ audio_dma_t* dma = audio_dma_state[i];
+ if (dma == NULL) {
+ continue;
+ }
+
+ bool block_done = event_interrupt_active(dma->event_channel);
+ if (!block_done) {
+ continue;
+ }
+
+ audio_dma_load_next_block(dma);
+ }
+}
diff --git a/ports/atmel-samd/audio_dma.h b/ports/atmel-samd/audio_dma.h
new file mode 100644
index 000000000..deecd27f3
--- /dev/null
+++ b/ports/atmel-samd/audio_dma.h
@@ -0,0 +1,89 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H
+#define MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H
+
+#include "extmod/vfs_fat_file.h"
+#include "py/obj.h"
+#include "shared-module/audioio/RawSample.h"
+#include "shared-module/audioio/WaveFile.h"
+
+typedef struct {
+ mp_obj_t sample;
+ uint8_t dma_channel;
+ uint8_t event_channel;
+ uint8_t audio_channel;
+ uint8_t bytes_per_sample;
+ uint8_t beat_size;
+ uint8_t spacing;
+ bool loop;
+ bool single_channel;
+ bool signed_to_unsigned;
+ bool unsigned_to_signed;
+ bool first_buffer_free;
+ uint8_t* first_buffer;
+ uint8_t* second_buffer;
+ bool first_descriptor_free;
+ DmacDescriptor* second_descriptor;
+} audio_dma_t;
+
+typedef enum {
+ AUDIO_DMA_OK,
+ AUDIO_DMA_DMA_BUSY,
+ AUDIO_DMA_MEMORY_ERROR,
+} audio_dma_result;
+
+uint32_t audiosample_sample_rate(mp_obj_t sample_obj);
+uint8_t audiosample_bits_per_sample(mp_obj_t sample_obj);
+uint8_t audiosample_channel_count(mp_obj_t sample_obj);
+
+void audio_dma_init(audio_dma_t* dma);
+void audio_dma_reset(void);
+
+// This sets everything up but doesn't start the timer.
+// Sample is the python object for the sample to play.
+// loop is true if we should loop the sample.
+// single_channel is true if we only output a single channel. When false, all channels will be
+// output.
+// audio_channel is the index of the channel to dma. single_channel must be false in this case.
+// output_signed is true if the dma'd data should be signed. False and it will be unsigned.
+// output_register_address is the address to copy data to.
+// dma_trigger_source is the DMA trigger source which cause another copy
+audio_dma_result audio_dma_setup_playback(audio_dma_t* dma,
+ mp_obj_t sample,
+ bool loop,
+ bool single_channel,
+ uint8_t audio_channel,
+ bool output_signed,
+ uint32_t output_register_address,
+ uint8_t dma_trigger_source);
+void audio_dma_stop(audio_dma_t* dma);
+bool audio_dma_get_playing(audio_dma_t* dma);
+
+void audio_dma_background(void);
+
+#endif // MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H
diff --git a/ports/atmel-samd/background.c b/ports/atmel-samd/background.c
index 2fdf05982..92648f42e 100644
--- a/ports/atmel-samd/background.c
+++ b/ports/atmel-samd/background.c
@@ -25,12 +25,12 @@
*/
#include "background.h"
-// #include "common-hal/audioio/AudioOut.h"
+#include "audio_dma.h"
#include "usb.h"
#include "usb_mass_storage.h"
void run_background_tasks(void) {
- // audioout_background();
+ audio_dma_background();
usb_msc_background();
usb_cdc_background();
}
diff --git a/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk
index 61620ffa2..2bfd0f3a0 100644
--- a/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk
+++ b/ports/atmel-samd/boards/feather_m4_express/mpconfigboard.mk
@@ -6,5 +6,5 @@ USB_MANUFACTURER = "Adafruit Industries LLC"
QSPI_FLASH_FILESYSTEM = 1
-CHIP_VARIANT = SAMD51J19A
+CHIP_VARIANT = SAMD51G19A
CHIP_FAMILY = samd51
diff --git a/ports/atmel-samd/clocks.c b/ports/atmel-samd/clocks.c
new file mode 100644
index 000000000..0a945a6e7
--- /dev/null
+++ b/ports/atmel-samd/clocks.c
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "clocks.h"
+
+#include "hpl_gclk_config.h"
+
+#include "shared-bindings/microcontroller/__init__.h"
+
+#include "py/runtime.h"
+
+// TODO(tannewt): Should we have a way of sharing GCLKs based on their speed? Divisor doesn't
+// gaurantee speed because it depends on the source.
+uint8_t find_free_gclk(uint16_t divisor) {
+ if (divisor > (1 << 8)) {
+ if (gclk_enabled(1)) {
+ return 0xff;
+ }
+ return 1;
+ }
+ uint8_t first_8_bit = 2;
+ #ifdef SAMD21
+ first_8_bit = 3;
+ if (divisor <= (1 << 5) && !gclk_enabled(2)) {
+ return 2;
+ }
+ #endif
+ for (uint8_t i = first_8_bit; i < GCLK_GEN_NUM; i++) {
+ if (!gclk_enabled(i)) {
+ return i;
+ }
+ }
+ return 0xff;
+}
+
+bool gclk_enabled(uint8_t gclk) {
+ #ifdef SAMD51
+ return GCLK->GENCTRL[gclk].bit.GENEN;
+ #endif
+ #ifdef SAMD21
+ common_hal_mcu_disable_interrupts();
+ // Explicitly do a byte write so the peripheral knows we're just wanting to read the channel
+ // rather than write to it.
+ *((uint8_t*) &GCLK->GENCTRL.reg) = gclk;
+ while (GCLK->STATUS.bit.SYNCBUSY == 1) {}
+ bool enabled = GCLK->GENCTRL.bit.GENEN;
+ common_hal_mcu_enable_interrupts();
+ return enabled;
+ #endif
+}
+
+void disable_gclk(uint8_t gclk) {
+ #ifdef SAMD51
+ GCLK->GENCTRL[gclk].bit.GENEN = false;
+ while ((GCLK->SYNCBUSY.vec.GENCTRL & (1 << gclk)) != 0) {}
+ #endif
+ #ifdef SAMD21
+ GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(gclk);
+ while (GCLK->STATUS.bit.SYNCBUSY == 1) {}
+ #endif
+}
+
+void connect_gclk_to_peripheral(uint8_t gclk, uint8_t peripheral) {
+ #ifdef SAMD21
+ GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(peripheral) | GCLK_CLKCTRL_GEN(gclk) | GCLK_CLKCTRL_CLKEN;
+ #endif
+ #ifdef SAMD51
+ GCLK->PCHCTRL[peripheral].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(gclk);
+ while(GCLK->SYNCBUSY.reg != 0) {}
+ #endif
+}
+
+void disconnect_gclk_from_peripheral(uint8_t gclk, uint8_t peripheral) {
+ #ifdef SAMD21
+ GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(peripheral) | GCLK_CLKCTRL_GEN(gclk);
+ #endif
+ #ifdef SAMD51
+ GCLK->PCHCTRL[peripheral].reg = 0;
+ #endif
+}
+
+void enable_clock_generator(uint8_t gclk, uint8_t source, uint16_t divisor) {
+ #ifdef SAMD21
+ GCLK->GENDIV.reg = GCLK_GENDIV_ID(gclk) | GCLK_GENDIV_DIV(divisor);
+ GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(gclk) | GCLK_GENCTRL_SRC(source) | GCLK_GENCTRL_GENEN;
+ while (GCLK->STATUS.bit.SYNCBUSY != 0) {}
+ #endif
+ #ifdef SAMD51
+ GCLK->GENCTRL[gclk].reg = GCLK_GENCTRL_SRC(source) | GCLK_GENCTRL_DIV(divisor) | GCLK_GENCTRL_GENEN;
+ while ((GCLK->SYNCBUSY.vec.GENCTRL & (1 << gclk)) != 0) {}
+ #endif
+}
+
+void disable_clock_generator(uint8_t gclk) {
+ #ifdef SAMD21
+ GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(gclk);
+ while (GCLK->STATUS.bit.SYNCBUSY != 0) {}
+ #endif
+ #ifdef SAMD51
+ GCLK->GENCTRL[gclk].reg = 0;
+ while ((GCLK->SYNCBUSY.vec.GENCTRL & (1 << gclk)) != 0) {}
+ #endif
+}
+
+void reset_gclks(void) {
+ // Never reset GCLK0 because its used for the core
+ #if CONF_GCLK_GEN_1_GENEN == 0
+ disable_gclk(1);
+ #endif
+ #if CONF_GCLK_GEN_2_GENEN == 0
+ disable_gclk(2);
+ #endif
+ #if CONF_GCLK_GEN_3_GENEN == 0
+ disable_gclk(3);
+ #endif
+ #if CONF_GCLK_GEN_4_GENEN == 0
+ disable_gclk(4);
+ #endif
+ #if CONF_GCLK_GEN_5_GENEN == 0
+ disable_gclk(5);
+ #endif
+ #if CONF_GCLK_GEN_6_GENEN == 0
+ disable_gclk(6);
+ #endif
+ #if CONF_GCLK_GEN_7_GENEN == 0
+ disable_gclk(7);
+ #endif
+ #ifdef SAMD51
+ #if CONF_GCLK_GEN_8_GENEN == 0
+ disable_gclk(8);
+ #endif
+ #if CONF_GCLK_GEN_9_GENEN == 0
+ disable_gclk(9);
+ #endif
+ #if CONF_GCLK_GEN_10_GENEN == 0
+ disable_gclk(10);
+ #endif
+ #if CONF_GCLK_GEN_11_GENEN == 0
+ disable_gclk(11);
+ #endif
+ #endif
+}
diff --git a/ports/atmel-samd/clocks.h b/ports/atmel-samd/clocks.h
new file mode 100644
index 000000000..99787d4ac
--- /dev/null
+++ b/ports/atmel-samd/clocks.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_ATMEL_SAMD_CLOCKS_H
+#define MICROPY_INCLUDED_ATMEL_SAMD_CLOCKS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "include/sam.h"
+
+#ifdef SAMD51
+#define CLOCK_48MHZ GCLK_GENCTRL_SRC_DFLL_Val
+#endif
+#ifdef SAMD21
+#define CLOCK_48MHZ GCLK_GENCTRL_SRC_DFLL48M_Val
+#endif
+
+#define CORE_GCLK 0
+
+uint8_t find_free_gclk(uint16_t divisor);
+
+bool gclk_enabled(uint8_t gclk);
+void reset_gclks(void);
+
+void connect_gclk_to_peripheral(uint8_t gclk, uint8_t peripheral);
+void disconnect_gclk_from_peripheral(uint8_t gclk, uint8_t peripheral);
+
+void enable_clock_generator(uint8_t gclk, uint8_t source, uint16_t divisor);
+void disable_clock_generator(uint8_t gclk);
+
+#endif // MICROPY_INCLUDED_ATMEL_SAMD_CLOCKS_H
diff --git a/ports/atmel-samd/common-hal/audiobusio/I2SOut.c b/ports/atmel-samd/common-hal/audiobusio/I2SOut.c
new file mode 100644
index 000000000..ec21636d0
--- /dev/null
+++ b/ports/atmel-samd/common-hal/audiobusio/I2SOut.c
@@ -0,0 +1,376 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "extmod/vfs_fat_file.h"
+#include "py/gc.h"
+#include "py/mperrno.h"
+#include "py/runtime.h"
+#include "common-hal/audiobusio/I2SOut.h"
+#include "shared-bindings/audiobusio/I2SOut.h"
+#include "shared-bindings/audioio/RawSample.h"
+#include "shared-bindings/microcontroller/Pin.h"
+
+#include "atmel_start_pins.h"
+#include "hal/include/hal_gpio.h"
+#include "hpl/gclk/hpl_gclk_base.h"
+#include "peripheral_clk_config.h"
+
+#ifdef SAMD21
+#include "hpl/pm/hpl_pm_base.h"
+#endif
+
+#include "audio_dma.h"
+#include "clocks.h"
+#include "events.h"
+#include "samd21_pins.h"
+#include "shared_dma.h"
+#include "timers.h"
+
+#ifdef SAMD21
+#define SERCTRL(name) I2S_SERCTRL_ ## name
+#endif
+
+#ifdef SAMD51
+#define SERCTRL(name) I2S_TXCTRL_ ## name
+#endif
+
+void i2sout_reset(void) {
+ // Make sure the I2S peripheral is running so we can see if the resources we need are free.
+ #ifdef SAMD51
+ // Connect the clock units to the 2mhz clock. It can't disable without it.
+ connect_gclk_to_peripheral(5, I2S_GCLK_ID_0);
+ connect_gclk_to_peripheral(5, I2S_GCLK_ID_1);
+ #endif
+ if (I2S->CTRLA.bit.ENABLE == 1) {
+ I2S->CTRLA.bit.ENABLE = 0;
+ while (I2S->SYNCBUSY.bit.ENABLE == 1) {}
+ }
+
+ // Make sure the I2S peripheral is running so we can see if the resources we need are free.
+ #ifdef SAMD51
+ // Connect the clock units to the 2mhz clock by default. They can't reset without it.
+ disconnect_gclk_from_peripheral(5, I2S_GCLK_ID_0);
+ disconnect_gclk_from_peripheral(5, I2S_GCLK_ID_1);
+
+ hri_mclk_clear_APBDMASK_I2S_bit(MCLK);
+ #endif
+
+ #ifdef SAMD21
+ _pm_disable_bus_clock(PM_BUS_APBC, I2S);
+ #endif
+}
+
+void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self,
+ const mcu_pin_obj_t* bit_clock, const mcu_pin_obj_t* word_select,
+ const mcu_pin_obj_t* data, bool left_justified) {
+ uint8_t serializer = 0xff;
+ uint8_t bc_clock_unit = 0xff;
+ uint8_t ws_clock_unit = 0xff;
+ #ifdef SAMD21
+ if (bit_clock == &pin_PA10
+ #ifdef PIN_PA20
+ || bit_clock == &pin_PA20
+ #endif
+ ) { // I2S SCK[0]
+ bc_clock_unit = 0;
+ }
+ #ifdef PIN_PB11
+ else if (bit_clock == &pin_PB11) { // I2S SCK[1]
+ bc_clock_unit = 1;
+ }
+ #endif
+ if (word_select == &pin_PA11
+ #ifdef PIN_PA21
+ || word_select == &pin_PA21
+ #endif
+ ) { // I2S FS[0]
+ ws_clock_unit = 0;
+ }
+ #ifdef PIN_PB12
+ else if (word_select == &pin_PB12) { // I2S FS[1]
+ ws_clock_unit = 1;
+ }
+ #endif
+
+ if (data == &pin_PA07 || data == &pin_PA19) { // I2S SD[0]
+ serializer = 0;
+ } else if (data == &pin_PA08
+ #ifdef PIN_PB16
+ || data == &pin_PB16
+ #endif
+ ) { // I2S SD[1]
+ serializer = 1;
+ }
+ #endif
+ #ifdef SAMD51
+ // Only clock unit 0 can be used for transmission.
+ if (bit_clock == &pin_PA10 || bit_clock == &pin_PB16) { // I2S SCK[0]
+ bc_clock_unit = 0;
+ }
+ if (word_select == &pin_PA09 || word_select == &pin_PA20) { // I2S FS[0]
+ ws_clock_unit = 0;
+ }
+ if (data == &pin_PA11 || data == &pin_PA21) { // I2S SDO
+ serializer = 0;
+ }
+ #endif
+ if (bc_clock_unit == 0xff) {
+ mp_raise_ValueError("Invalid bit clock pin");
+ }
+ if (ws_clock_unit == 0xff) {
+ mp_raise_ValueError("Invalid bit clock pin");
+ }
+ if (bc_clock_unit != ws_clock_unit) {
+ mp_raise_ValueError("Bit clock and word select must share a clock unit");
+ }
+ if (serializer == 0xff) {
+ mp_raise_ValueError("Invalid data pin");
+ }
+ self->clock_unit = ws_clock_unit;
+ self->serializer = serializer;
+
+ // Make sure the I2S peripheral is running so we can see if the resources we need are free.
+ #ifdef SAMD51
+ hri_mclk_set_APBDMASK_I2S_bit(MCLK);
+
+ // Connect the clock units to the 2mhz clock by default. They can't reset without it.
+ connect_gclk_to_peripheral(5, I2S_GCLK_ID_0);
+ connect_gclk_to_peripheral(5, I2S_GCLK_ID_1);
+ #endif
+
+ #ifdef SAMD21
+ _pm_enable_bus_clock(PM_BUS_APBC, I2S);
+ #endif
+
+ if (I2S->CTRLA.bit.ENABLE == 0) {
+ I2S->CTRLA.bit.SWRST = 1;
+ while (I2S->CTRLA.bit.SWRST == 1) {}
+ } else {
+ #ifdef SAMD21
+ if ((I2S->CTRLA.vec.SEREN & (1 << serializer)) != 0) {
+ mp_raise_RuntimeError("Serializer in use");
+ }
+ #endif
+ #ifdef SAMD51
+ if (I2S->CTRLA.bit.TXEN == 1) {
+ mp_raise_RuntimeError("Serializer in use");
+ }
+ #endif
+ }
+
+ #ifdef SAMD51
+ #define GPIO_I2S_FUNCTION GPIO_PIN_FUNCTION_J
+ #endif
+ #ifdef SAMD21
+ #define GPIO_I2S_FUNCTION GPIO_PIN_FUNCTION_G
+ #endif
+ assert_pin_free(bit_clock);
+ assert_pin_free(word_select);
+ assert_pin_free(data);
+
+ self->bit_clock = bit_clock;
+ self->word_select = word_select;
+ self->data = data;
+
+ claim_pin(bit_clock);
+ claim_pin(word_select);
+ claim_pin(data);
+
+ gpio_set_pin_function(self->bit_clock->pin, GPIO_I2S_FUNCTION);
+ gpio_set_pin_function(self->word_select->pin, GPIO_I2S_FUNCTION);
+ gpio_set_pin_function(self->data->pin, GPIO_I2S_FUNCTION);
+
+ self->left_justified = left_justified;
+ self->playing = false;
+ audio_dma_init(&self->dma);
+}
+
+bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) {
+ return self->bit_clock == mp_const_none;
+}
+
+void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) {
+ if (common_hal_audiobusio_i2sout_deinited(self)) {
+ return;
+ }
+
+ reset_pin(self->bit_clock->pin);
+ self->bit_clock = mp_const_none;
+ reset_pin(self->word_select->pin);
+ self->word_select = mp_const_none;
+ reset_pin(self->data->pin);
+ self->data = mp_const_none;
+}
+
+void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self,
+ mp_obj_t sample, bool loop) {
+ if (common_hal_audiobusio_i2sout_get_playing(self)) {
+ common_hal_audiobusio_i2sout_stop(self);
+ }
+ #ifdef SAMD21
+ if ((I2S->CTRLA.vec.CKEN & (1 << self->clock_unit)) == 1) {
+ mp_raise_RuntimeError("Clock unit in use");
+ }
+ #endif
+ uint8_t bits_per_sample = audiosample_bits_per_sample(sample);
+ // We always output stereo so output twice as many bits.
+ uint16_t bits_per_sample_output = bits_per_sample * 2;
+ uint16_t divisor = 48000000 / (bits_per_sample_output * audiosample_sample_rate(sample));
+ // Find a free GCLK to generate the MCLK signal.
+ uint8_t gclk = find_free_gclk(divisor);
+ if (gclk > GCLK_GEN_NUM) {
+ mp_raise_RuntimeError("Unable to find free GCLK");
+ }
+ self->gclk = gclk;
+
+ uint32_t clkctrl = I2S_CLKCTRL_MCKSEL_GCLK |
+ I2S_CLKCTRL_NBSLOTS(1) |
+ I2S_CLKCTRL_FSWIDTH_HALF;
+ if (self->left_justified) {
+ clkctrl |= I2S_CLKCTRL_BITDELAY_LJ;
+ } else {
+ clkctrl |= I2S_CLKCTRL_FSOUTINV | I2S_CLKCTRL_BITDELAY_I2S;
+ }
+ uint8_t channel_count = audiosample_channel_count(sample);
+ if (channel_count > 2) {
+ mp_raise_ValueError("Too many channels in sample.");
+ }
+ #ifdef SAMD21
+ uint32_t serctrl = (self->clock_unit << I2S_SERCTRL_CLKSEL_Pos) | SERCTRL(SERMODE_TX) | I2S_SERCTRL_TXSAME_SAME | I2S_SERCTRL_EXTEND_MSBIT | I2S_SERCTRL_TXDEFAULT_ONE | I2S_SERCTRL_SLOTADJ_LEFT;
+ #endif
+ #ifdef SAMD51
+ uint32_t serctrl = (self->clock_unit << I2S_RXCTRL_CLKSEL_Pos) | I2S_TXCTRL_TXSAME_SAME;
+ #endif
+ if (audiosample_channel_count(sample) == 1) {
+ serctrl |= SERCTRL(MONO_MONO);
+ } else {
+ serctrl |= SERCTRL(MONO_STEREO);
+ }
+ if (bits_per_sample == 8) {
+ serctrl |= SERCTRL(DATASIZE_8C);
+ clkctrl |= I2S_CLKCTRL_SLOTSIZE_8;
+ } else if (bits_per_sample == 16) {
+ serctrl |= SERCTRL(DATASIZE_16C);
+ clkctrl |= I2S_CLKCTRL_SLOTSIZE_16;
+ }
+
+ // Configure the I2S peripheral
+ I2S->CTRLA.bit.ENABLE = 0;
+ while (I2S->SYNCBUSY.bit.ENABLE == 1) {}
+
+ I2S->CLKCTRL[self->clock_unit].reg = clkctrl;
+ #ifdef SAMD21
+ I2S->SERCTRL[self->serializer].reg = serctrl;
+ #endif
+ #ifdef SAMD51
+ I2S->TXCTRL.reg = serctrl;
+ #endif
+
+ // The DFLL is always a 48mhz clock
+ enable_clock_generator(self->gclk, CLOCK_48MHZ, divisor);
+ connect_gclk_to_peripheral(self->gclk, I2S_GCLK_ID_0 + self->clock_unit);
+
+ I2S->CTRLA.bit.ENABLE = 1;
+ while (I2S->SYNCBUSY.bit.ENABLE == 1) {}
+
+ #ifdef SAMD21
+ uint32_t tx_register = (uint32_t) &I2S->DATA[self->serializer].reg;
+ uint8_t dmac_id = I2S_DMAC_ID_TX_0 + self->serializer;
+ #endif
+ #ifdef SAMD51
+ uint32_t tx_register = (uint32_t) &I2S->TXDATA.reg;
+ uint8_t dmac_id = I2S_DMAC_ID_TX_0;
+ #endif
+ audio_dma_result result = audio_dma_setup_playback(&self->dma, sample, loop, false, 0,
+ true /* output signed */, tx_register, dmac_id);
+
+ if (result == AUDIO_DMA_DMA_BUSY) {
+ common_hal_audiobusio_i2sout_stop(self);
+ mp_raise_RuntimeError("No DMA channel found");
+ } else if (result == AUDIO_DMA_MEMORY_ERROR) {
+ common_hal_audiobusio_i2sout_stop(self);
+ mp_raise_RuntimeError("Unable to allocate buffers for signed conversion");
+ }
+
+ I2S->INTFLAG.reg = I2S_INTFLAG_TXUR0 | I2S_INTFLAG_TXUR1;
+
+ I2S->CTRLA.vec.CKEN = 1 << self->clock_unit;
+ while ((I2S->SYNCBUSY.vec.CKEN & (1 << self->clock_unit)) != 0) {}
+
+ // Init the serializer after the clock. Otherwise, it will never enable because its unclocked.
+ #ifdef SAMD21
+ I2S->CTRLA.vec.SEREN = 1 << self->serializer;
+ while ((I2S->SYNCBUSY.vec.SEREN & (1 << self->serializer)) != 0) {}
+ #endif
+ #ifdef SAMD51
+ I2S->CTRLA.bit.TXEN = 1;
+ while (I2S->SYNCBUSY.bit.TXEN == 1) {}
+ #endif
+
+ self->playing = true;
+}
+
+void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) {
+ audio_dma_stop(&self->dma);
+
+ #ifdef SAMD21
+ I2S->CTRLA.vec.SEREN &= ~(1 << self->serializer);
+ while ((I2S->SYNCBUSY.vec.SEREN & (1 << self->serializer)) != 0) {}
+ #endif
+ #ifdef SAMD51
+ I2S->CTRLA.bit.TXEN = 0;
+ while (I2S->SYNCBUSY.bit.TXEN == 1) {}
+ #endif
+
+ #ifdef SAMD21
+ if (self->clock_unit == 0) {
+ I2S->CTRLA.bit.CKEN0 = 0;
+ while (I2S->SYNCBUSY.bit.CKEN0 == 1) {}
+ } else {
+ I2S->CTRLA.bit.CKEN1 = 0;
+ while (I2S->SYNCBUSY.bit.CKEN1 == 1) {}
+ }
+ #endif
+ disconnect_gclk_from_peripheral(self->gclk, I2S_GCLK_ID_0 + self->clock_unit);
+ disable_clock_generator(self->gclk);
+
+ #ifdef SAMD51
+ connect_gclk_to_peripheral(5, I2S_GCLK_ID_0 + self->clock_unit);
+ #endif
+
+ self->playing = false;
+}
+
+bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) {
+ bool still_playing = audio_dma_get_playing(&self->dma);
+ if (self->playing && !still_playing) {
+ common_hal_audiobusio_i2sout_stop(self);
+ }
+ return still_playing;
+}
diff --git a/ports/atmel-samd/common-hal/audiobusio/I2SOut.h b/ports/atmel-samd/common-hal/audiobusio/I2SOut.h
new file mode 100644
index 000000000..ce88f4de6
--- /dev/null
+++ b/ports/atmel-samd/common-hal/audiobusio/I2SOut.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
+#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
+
+#include "common-hal/microcontroller/Pin.h"
+
+#include "audio_dma.h"
+#include "py/obj.h"
+
+// We don't bit pack because we'll only have two at most. Its better to save code size instead.
+typedef struct {
+ mp_obj_base_t base;
+ bool left_justified;
+ const mcu_pin_obj_t *bit_clock;
+ const mcu_pin_obj_t *word_select;
+ const mcu_pin_obj_t *data;
+ uint8_t clock_unit;
+ uint8_t serializer;
+ uint8_t gclk;
+ bool playing;
+ audio_dma_t dma;
+} audiobusio_i2sout_obj_t;
+
+void i2sout_reset(void);
+
+#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
diff --git a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c
index 50d3b180c..e0ac3f9a3 100644
--- a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c
+++ b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c
@@ -37,7 +37,6 @@
#include "shared-bindings/audiobusio/PDMIn.h"
#include "shared-bindings/microcontroller/Pin.h"
-#include "asf/sam0/drivers/port/port.h"
#include "samd21_pins.h"
#include "shared_dma.h"
diff --git a/ports/atmel-samd/common-hal/audiobusio/PDMIn.h b/ports/atmel-samd/common-hal/audiobusio/PDMIn.h
index 71f36c0d6..50dc60262 100644
--- a/ports/atmel-samd/common-hal/audiobusio/PDMIn.h
+++ b/ports/atmel-samd/common-hal/audiobusio/PDMIn.h
@@ -28,8 +28,6 @@
#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H
#include "common-hal/microcontroller/Pin.h"
-#include "asf/sam0/drivers/i2s/i2s.h"
-#include "asf/sam0/drivers/tc/tc.h"
#include "extmod/vfs_fat_file.h"
#include "py/obj.h"
@@ -39,7 +37,6 @@ typedef struct {
const mcu_pin_obj_t *clock_pin;
const mcu_pin_obj_t *data_pin;
uint32_t frequency;
- struct i2s_module i2s_instance;
uint8_t serializer;
uint8_t clock_unit;
uint8_t bytes_per_sample;
diff --git a/ports/atmel-samd/common-hal/audioio/AudioOut.c b/ports/atmel-samd/common-hal/audioio/AudioOut.c
index 9f6980890..64da34863 100644
--- a/ports/atmel-samd/common-hal/audioio/AudioOut.c
+++ b/ports/atmel-samd/common-hal/audioio/AudioOut.c
@@ -35,155 +35,128 @@
#include "shared-bindings/audioio/AudioOut.h"
#include "shared-bindings/microcontroller/Pin.h"
-#include "asf/sam0/drivers/dac/dac.h"
-#include "asf/sam0/drivers/dma/dma.h"
-#include "asf/sam0/drivers/events/events.h"
-#include "asf/sam0/drivers/port/port.h"
-#include "asf/sam0/drivers/tc/tc.h"
-#include "samd21_pins.h"
-#include "shared_dma.h"
-
-#undef ENABLE
-
-// Shared with PWMOut
-// TODO(tannewt): Factor these out so audioio can exist without PWMOut.
-extern uint32_t target_timer_frequencies[TC_INST_NUM + TCC_INST_NUM];
-extern uint8_t timer_refcount[TC_INST_NUM + TCC_INST_NUM];
-extern const uint16_t prescaler[8];
+#include "atmel_start_pins.h"
+#include "hal/include/hal_gpio.h"
+#include "hpl/gclk/hpl_gclk_base.h"
+#include "peripheral_clk_config.h"
-// This timer is shared amongst all AudioOut objects under the assumption that
-// the code is single threaded. The audioout_sample_timer, audioout_dac_instance,
-// audioout_sample_event, and audioout_dac_event pointers live in
-// MICROPY_PORT_ROOT_POINTERS so they don't get garbage collected.
-
-// The AudioOut object is being currently played. Only it can pause the timer
-// and change its frequency.
-static audioio_audioout_obj_t* active_audioout;
-
-static uint8_t refcount = 0;
+#ifdef SAMD21
+#include "hpl/pm/hpl_pm_base.h"
+#endif
-struct wave_format_chunk {
- uint16_t audio_format;
- uint16_t num_channels;
- uint32_t sample_rate;
- uint32_t byte_rate;
- uint16_t block_align;
- uint16_t bits_per_sample;
- uint16_t extra_params; // Assumed to be zero below.
-};
+#include "audio_dma.h"
+#include "events.h"
+#include "samd21_pins.h"
+#include "shared_dma.h"
+#include "timers.h"
void audioout_reset(void) {
// Only reset DMA. PWMOut will reset the timer. Other code will reset the DAC.
- refcount = 0;
- MP_STATE_VM(audioout_sample_timer) = NULL;
- MP_STATE_VM(audiodma_block_counter) = NULL;
- MP_STATE_VM(audioout_dac_instance) = NULL;
-
- if (MP_STATE_VM(audioout_sample_event) != NULL) {
- events_detach_user(MP_STATE_VM(audioout_sample_event), EVSYS_ID_USER_DAC_START);
- events_release(MP_STATE_VM(audioout_sample_event));
- }
- MP_STATE_VM(audioout_sample_event) = NULL;
-
- if (MP_STATE_VM(audiodma_block_event) != NULL) {
- events_release(MP_STATE_VM(audiodma_block_event));
- }
- MP_STATE_VM(audiodma_block_event) = NULL;
-
- if (MP_STATE_VM(audioout_dac_event) != NULL) {
- events_detach_user(MP_STATE_VM(audioout_dac_event), EVSYS_ID_USER_DMAC_CH_0);
- events_release(MP_STATE_VM(audioout_dac_event));
- }
- MP_STATE_VM(audioout_dac_event) = NULL;
-
- dma_abort_job(&audio_dma);
}
-// WARN(tannewt): DO NOT print from here. It calls background tasks and causes a
-// stack overflow.
-void audioout_background(void) {
- if (MP_STATE_VM(audiodma_block_counter) != NULL &&
- active_audioout != NULL &&
- active_audioout->second_buffer != NULL &&
- active_audioout->last_loaded_block < tc_get_count_value(MP_STATE_VM(audiodma_block_counter))) {
- uint8_t* buffer;
- if (tc_get_count_value(MP_STATE_VM(audiodma_block_counter)) % 2 == 1) {
- buffer = active_audioout->buffer;
- } else {
- buffer = active_audioout->second_buffer;
- }
- uint16_t num_bytes_to_load = active_audioout->len;
- if (num_bytes_to_load > active_audioout->bytes_remaining) {
- num_bytes_to_load = active_audioout->bytes_remaining;
- }
- UINT length_read;
- f_read(&active_audioout->file->fp, buffer, num_bytes_to_load, &length_read);
- active_audioout->bytes_remaining -= length_read;
- active_audioout->last_loaded_block += 1;
-
- if (active_audioout->bytes_remaining == 0) {
- if (active_audioout->loop) {
- // Loop back to the start of the file.
- f_lseek(&active_audioout->file->fp, active_audioout->data_start);
- active_audioout->bytes_remaining = active_audioout->file_length;
- f_read(&active_audioout->file->fp, buffer, active_audioout->len - num_bytes_to_load, &length_read);
- active_audioout->bytes_remaining -= length_read;
- } else {
- DmacDescriptor* descriptor = audio_dma.descriptor;
- if (buffer == active_audioout->second_buffer) {
- descriptor = active_audioout->second_descriptor;
- }
- descriptor->BTCNT.reg = length_read / active_audioout->bytes_per_sample;
- descriptor->SRCADDR.reg = ((uint32_t) buffer) + length_read;
- descriptor->DESCADDR.reg = 0;
- }
- }
-
- if (active_audioout->bytes_per_sample == 2) {
- // Undo twos complement.
- for (uint16_t i = 0; i < length_read / 2; i++) {
- buffer[2 * i + 1] ^= 0x80;
- }
- }
- }
-}
-
-static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t* pin) {
- assert_pin_free(pin);
-
- // Configure the DAC to output on input event and to output an empty event
- // that triggers the DMA to load the next sample.
- MP_STATE_VM(audioout_dac_instance) = gc_alloc(sizeof(struct dac_module), false);
- if (MP_STATE_VM(audioout_dac_instance) == NULL) {
- mp_raise_msg(&mp_type_MemoryError, "");
+void common_hal_audioio_audioout_construct(audioio_audioout_obj_t* self,
+ const mcu_pin_obj_t* left_channel, const mcu_pin_obj_t* right_channel) {
+ #ifdef SAMD21
+ if (right_channel != NULL) {
+ mp_raise_ValueError("Right channel unsupported");
}
- struct dac_config config_dac;
- dac_get_config_defaults(&config_dac);
- config_dac.left_adjust = true;
- config_dac.reference = DAC_REFERENCE_AVCC;
- config_dac.clock_source = GCLK_GENERATOR_0;
- enum status_code status = dac_init(MP_STATE_VM(audioout_dac_instance), DAC, &config_dac);
- if (status != STATUS_OK) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_OSError(MP_EIO);
- return;
+ if (left_channel != &pin_PA02) {
+ mp_raise_ValueError("Invalid pin");
}
-
- struct dac_chan_config channel_config;
- dac_chan_get_config_defaults(&channel_config);
- dac_chan_set_config(MP_STATE_VM(audioout_dac_instance), DAC_CHANNEL_0, &channel_config);
- dac_chan_enable(MP_STATE_VM(audioout_dac_instance), DAC_CHANNEL_0);
-
- struct dac_events events_dac = { .generate_event_on_buffer_empty = true,
- .on_event_start_conversion = true };
- dac_enable_events(MP_STATE_VM(audioout_dac_instance), &events_dac);
-
- // Figure out which timer we are using.
+ assert_pin_free(left_channel);
+ claim_pin(left_channel);
+ #endif
+ #ifdef SAMD51
+ self->right_channel = NULL;
+ if (left_channel != &pin_PA02 && left_channel != &pin_PA05) {
+ mp_raise_ValueError("Invalid pin for left channel");
+ }
+ assert_pin_free(left_channel);
+ if (right_channel != NULL && right_channel != &pin_PA02 && right_channel != &pin_PA05) {
+ mp_raise_ValueError("Invalid pin for right channel");
+ }
+ if (right_channel == left_channel) {
+ mp_raise_ValueError("Cannot output both channels on the same pin");
+ }
+ claim_pin(left_channel);
+ if (right_channel != NULL) {
+ claim_pin(right_channel);
+ self->right_channel = right_channel;
+ gpio_set_pin_function(self->right_channel->pin, GPIO_PIN_FUNCTION_B);
+ audio_dma_init(&self->right_dma);
+ }
+ #endif
+ self->left_channel = left_channel;
+ gpio_set_pin_function(self->left_channel->pin, GPIO_PIN_FUNCTION_B);
+ audio_dma_init(&self->left_dma);
+
+ #ifdef SAMD51
+ hri_mclk_set_APBDMASK_DAC_bit(MCLK);
+ #endif
+
+ #ifdef SAMD21
+ _pm_enable_bus_clock(PM_BUS_APBC, DAC);
+ #endif
+
+ // SAMD21: This clock should be <= 12 MHz, per datasheet section 47.6.3.
+ // SAMD51: This clock should be <= 350kHz, per datasheet table 37-6.
+ _gclk_enable_channel(DAC_GCLK_ID, CONF_GCLK_DAC_SRC);
+
+ // There is a small chance the other output is being used by AnalogOut on the SAMD51 so
+ // only reset if the DAC is disabled.
+ if (DAC->CTRLA.bit.ENABLE == 0) {
+ DAC->CTRLA.bit.SWRST = 1;
+ while (DAC->CTRLA.bit.SWRST == 1) {}
+
+ }
+
+ bool channel0_enabled = true;
+ #ifdef SAMD51
+ channel0_enabled = self->left_channel == &pin_PA02 || self->right_channel == &pin_PA02;
+ bool channel1_enabled = self->left_channel == &pin_PA05 || self->right_channel == &pin_PA05;
+ #endif
+
+ if (channel0_enabled) {
+ #ifdef SAMD21
+ DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI;
+ DAC->CTRLB.reg = DAC_CTRLB_REFSEL_AVCC |
+ DAC_CTRLB_LEFTADJ |
+ DAC_CTRLB_EOEN;
+ #endif
+ #ifdef SAMD51
+ DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI0;
+ DAC->DACCTRL[0].reg = DAC_DACCTRL_CCTRL_CC1M |
+ DAC_DACCTRL_ENABLE |
+ DAC_DACCTRL_LEFTADJ;
+ DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPU;
+ #endif
+ }
+ #ifdef SAMD51
+ if (channel1_enabled) {
+ DAC->EVCTRL.reg |= DAC_EVCTRL_STARTEI1;
+ DAC->DACCTRL[1].reg = DAC_DACCTRL_CCTRL_CC1M |
+ DAC_DACCTRL_ENABLE |
+ DAC_DACCTRL_LEFTADJ;
+ DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPU;
+ }
+ #endif
+
+ // Re-enable the DAC
+ DAC->CTRLA.bit.ENABLE = 1;
+ #ifdef SAMD21
+ while (DAC->STATUS.bit.SYNCBUSY == 1) {}
+ #endif
+ #ifdef SAMD51
+ while (DAC->SYNCBUSY.bit.ENABLE == 1) {}
+ #endif
+
+ // Use a timer to coordinate when DAC conversions occur.
Tc *t = NULL;
- Tc *tcs[TC_INST_NUM] = TC_INSTS;
+ uint8_t tc_index = TC_INST_NUM;
for (uint8_t i = TC_INST_NUM; i > 0; i--) {
- if (tcs[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
- t = tcs[i - 1];
+ if (tc_insts[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
+ t = tc_insts[i - 1];
+ tc_index = i - 1;
break;
}
}
@@ -192,234 +165,77 @@ static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t*
mp_raise_RuntimeError("All timers in use");
return;
}
- MP_STATE_VM(audioout_sample_timer) = gc_alloc(sizeof(struct tc_module), false);
- if (MP_STATE_VM(audioout_sample_timer) == NULL) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_msg(&mp_type_MemoryError, "");
- }
+ self->tc_index = tc_index;
- // Don't bother setting the period. We set it before you playback anything.
- struct tc_config config_tc;
- tc_get_config_defaults(&config_tc);
- config_tc.counter_size = TC_COUNTER_SIZE_16BIT;
- config_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV1;
- config_tc.wave_generation = TC_WAVE_GENERATION_MATCH_FREQ;
- if (tc_init(MP_STATE_VM(audioout_sample_timer), t, &config_tc) != STATUS_OK) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_OSError(MP_EIO);
- return;
- };
+ // Use the 48mhz clocks on both the SAMD21 and 51 because we will be going much slower.
+ uint8_t tc_gclk = 0;
+ #ifdef SAMD51
+ tc_gclk = 1;
+ #endif
- struct tc_events events_tc;
- events_tc.generate_event_on_overflow = true;
- events_tc.on_event_perform_action = false;
- events_tc.event_action = TC_EVENT_ACTION_OFF;
- tc_enable_events(MP_STATE_VM(audioout_sample_timer), &events_tc);
+ turn_on_clocks(true, tc_index, tc_gclk);
- tc_enable(MP_STATE_VM(audioout_sample_timer));
- tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
+ // Don't bother setting the period. We set it before you playback anything.
+ tc_set_enable(t, false);
+ tc_reset(t);
+ #ifdef SAMD51
+ t->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;
+ #endif
+ #ifdef SAMD21
+ t->COUNT16.CTRLA.bit.WAVEGEN = TC_CTRLA_WAVEGEN_MFRQ_Val;
+ #endif
+ t->COUNT16.EVCTRL.reg = TC_EVCTRL_OVFEO;
+ tc_set_enable(t, true);
+ t->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_STOP;
// Connect the timer overflow event, which happens at the target frequency,
- // to the DAC conversion trigger.
- MP_STATE_VM(audioout_sample_event) = gc_alloc(sizeof(struct events_resource), false);
- if (MP_STATE_VM(audioout_sample_event) == NULL) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_msg(&mp_type_MemoryError, "");
- }
- struct events_config config;
- events_get_config_defaults(&config);
-
- uint8_t generator = EVSYS_ID_GEN_TC3_OVF;
- if (t == TC4) {
- generator = EVSYS_ID_GEN_TC4_OVF;
- } else if (t == TC5) {
- generator = EVSYS_ID_GEN_TC5_OVF;
-#ifdef TC6
- } else if (t == TC6) {
- generator = EVSYS_ID_GEN_TC6_OVF;
-#endif
-#ifdef TC7
- } else if (t == TC7) {
- generator = EVSYS_ID_GEN_TC7_OVF;
-#endif
- }
-
- config.generator = generator;
- config.path = EVENTS_PATH_ASYNCHRONOUS;
- if (events_allocate(MP_STATE_VM(audioout_sample_event), &config) != STATUS_OK ||
- events_attach_user(MP_STATE_VM(audioout_sample_event), EVSYS_ID_USER_DAC_START) != STATUS_OK) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_OSError(MP_EIO);
- return;
- }
-
- // Connect the DAC to DMA
- MP_STATE_VM(audioout_dac_event) = gc_alloc(sizeof(struct events_resource), false);
- if (MP_STATE_VM(audioout_dac_event) == NULL) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_msg(&mp_type_MemoryError, "");
- }
- events_get_config_defaults(&config);
- config.generator = EVSYS_ID_GEN_DAC_EMPTY;
- config.path = EVENTS_PATH_ASYNCHRONOUS;
- if (events_allocate(MP_STATE_VM(audioout_dac_event), &config) != STATUS_OK ||
- events_attach_user(MP_STATE_VM(audioout_dac_event), EVSYS_ID_USER_DMAC_CH_0) != STATUS_OK) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_OSError(MP_EIO);
- return;
- }
-
- // Leave the DMA setup to the specific constructor.
-}
-
-void common_hal_audioio_audioout_construct_from_buffer(audioio_audioout_obj_t* self,
- const mcu_pin_obj_t* pin,
- uint16_t* buffer,
- uint32_t len,
- uint8_t bytes_per_sample) {
- self->pin = pin;
- if (pin != &pin_PA02) {
- mp_raise_ValueError("Invalid pin");
- }
- if (refcount == 0) {
- refcount++;
- shared_construct(self, pin);
- }
-
- self->buffer = (uint8_t*) buffer;
- self->second_buffer = NULL;
- self->bytes_per_sample = bytes_per_sample;
- self->len = len;
- self->frequency = 8000;
-}
-
-void common_hal_audioio_audioout_construct_from_file(audioio_audioout_obj_t* self,
- const mcu_pin_obj_t* pin,
- pyb_file_obj_t* file) {
- self->pin = pin;
- if (pin != &pin_PA02) {
- mp_raise_ValueError("Invalid pin");
- }
- if (refcount == 0) {
- refcount++;
- shared_construct(self, pin);
- }
- if (MP_STATE_VM(audiodma_block_counter) == NULL && !allocate_block_counter()) {
- mp_raise_RuntimeError("Unable to allocate audio DMA block counter.");
- }
-
- // Load the wave
- self->file = file;
- uint8_t chunk_header[16];
- f_rewind(&self->file->fp);
- UINT bytes_read;
- f_read(&self->file->fp, chunk_header, 16, &bytes_read);
- if (bytes_read != 16 ||
- memcmp(chunk_header, "RIFF", 4) != 0 ||
- memcmp(chunk_header + 8, "WAVEfmt ", 8) != 0) {
- mp_raise_ValueError("Invalid wave file");
- }
- uint32_t format_size;
- f_read(&self->file->fp, &format_size, 4, &bytes_read);
- if (bytes_read != 4 ||
- format_size > sizeof(struct wave_format_chunk)) {
- mp_raise_ValueError("Invalid format chunk size");
- }
- struct wave_format_chunk format;
- f_read(&self->file->fp, &format, format_size, &bytes_read);
- if (bytes_read != format_size) {
- }
-
- if (format.audio_format != 1 ||
- format.num_channels > 1 ||
- format.bits_per_sample > 16 ||
- (format_size == 18 &&
- format.extra_params != 0)) {
- mp_raise_ValueError("Unsupported format");
- }
- // Get the frequency
- self->frequency = format.sample_rate;
- self->len = 512;
-
- self->bytes_per_sample = format.bits_per_sample / 8;
-
- // TODO(tannewt): Skip any extra chunks that occur before the data section.
-
- uint8_t data_tag[4];
- f_read(&self->file->fp, &data_tag, 4, &bytes_read);
- if (bytes_read != 4 ||
- memcmp((uint8_t *) data_tag, "data", 4) != 0) {
- mp_raise_ValueError("Data chunk must follow fmt chunk");
- }
-
- uint32_t data_length;
- f_read(&self->file->fp, &data_length, 4, &bytes_read);
- if (bytes_read != 4) {
- mp_raise_ValueError("Invalid file");
- }
- self->file_length = data_length;
- self->data_start = self->file->fp.fptr;
-
- // Try to allocate two buffers, one will be loaded from file and the other
- // DMAed to DAC.
- self->buffer = gc_alloc(self->len, false);
- if (self->buffer == NULL) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_msg(&mp_type_MemoryError, "");
- }
-
- self->second_buffer = gc_alloc(self->len, false);
- if (self->second_buffer == NULL) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_msg(&mp_type_MemoryError, "");
- }
-
- self->second_descriptor = gc_alloc(sizeof(DmacDescriptor), false);
- if (self->second_descriptor == NULL) {
- common_hal_audioio_audioout_deinit(self);
- mp_raise_msg(&mp_type_MemoryError, "");
- }
+ // to the DAC conversion trigger(s).
+ #ifdef SAMD21
+ #define FIRST_TC_GEN_ID EVSYS_ID_GEN_TC3_OVF
+ #endif
+ #ifdef SAMD51
+ #define FIRST_TC_GEN_ID EVSYS_ID_GEN_TC0_OVF
+ #endif
+ uint8_t tc_gen_id = FIRST_TC_GEN_ID + 3 * tc_index;
+
+ turn_on_event_system();
+
+ // Find a free event channel. We start at the highest channels because we only need and async
+ // path.
+ uint8_t channel = find_async_event_channel();
+ #ifdef SAMD51
+ connect_event_user_to_channel(EVSYS_ID_USER_DAC_START_1, channel);
+ #define EVSYS_ID_USER_DAC_START EVSYS_ID_USER_DAC_START_0
+ #endif
+ connect_event_user_to_channel(EVSYS_ID_USER_DAC_START, channel);
+ init_async_event_channel(channel, tc_gen_id);
+
+ self->tc_to_dac_event_channel = channel;
+
+ // Leave the DMA setup to playback.
}
bool common_hal_audioio_audioout_deinited(audioio_audioout_obj_t* self) {
- return self->pin == mp_const_none;
+ return self->left_channel == mp_const_none;
}
void common_hal_audioio_audioout_deinit(audioio_audioout_obj_t* self) {
if (common_hal_audioio_audioout_deinited(self)) {
return;
}
- refcount--;
- if (refcount == 0) {
- if (MP_STATE_VM(audioout_sample_timer) != NULL) {
- tc_reset(MP_STATE_VM(audioout_sample_timer));
- gc_free(MP_STATE_VM(audioout_sample_timer));
- MP_STATE_VM(audioout_sample_timer) = NULL;
- }
- if (MP_STATE_VM(audioout_dac_instance) != NULL) {
- dac_reset(MP_STATE_VM(audioout_dac_instance));
- gc_free(MP_STATE_VM(audioout_dac_instance));
- MP_STATE_VM(audioout_dac_instance) = NULL;
- }
- if (MP_STATE_VM(audioout_sample_event) != NULL) {
- events_detach_user(MP_STATE_VM(audioout_sample_event), EVSYS_ID_USER_DAC_START);
- events_release(MP_STATE_VM(audioout_sample_event));
- gc_free(MP_STATE_VM(audioout_sample_event));
- MP_STATE_VM(audioout_sample_event) = NULL;
- }
- if (MP_STATE_VM(audioout_dac_event) != NULL) {
- events_release(MP_STATE_VM(audioout_dac_event));
- gc_free(MP_STATE_VM(audioout_dac_event));
- MP_STATE_VM(audioout_dac_event) = NULL;
- }
- reset_pin(self->pin->pin);
- }
- self->pin = mp_const_none;
+ disable_event_channel(self->tc_to_dac_event_channel);
+
+ reset_pin(self->left_channel->pin);
+ self->left_channel = mp_const_none;
+ #ifdef SAMD51
+ reset_pin(self->right_channel->pin);
+ self->right_channel = mp_const_none;
+ #endif
}
-static void set_timer_frequency(uint32_t frequency) {
- uint32_t system_clock = system_cpu_clock_get_hz();
+static void set_timer_frequency(Tc* timer, uint32_t frequency) {
+ uint32_t system_clock = 48000000;
uint32_t new_top;
uint8_t new_divisor;
for (new_divisor = 0; new_divisor < 8; new_divisor++) {
@@ -428,133 +244,91 @@ static void set_timer_frequency(uint32_t frequency) {
break;
}
}
- uint8_t old_divisor = MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CTRLA.bit.PRESCALER;
+ uint8_t old_divisor = timer->COUNT16.CTRLA.bit.PRESCALER;
if (new_divisor != old_divisor) {
- tc_disable(MP_STATE_VM(audioout_sample_timer));
- MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CTRLA.bit.PRESCALER = new_divisor;
- tc_enable(MP_STATE_VM(audioout_sample_timer));
- }
- while (tc_is_syncing(MP_STATE_VM(audioout_sample_timer))) {
- /* Wait for sync */
- }
- MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CC[0].reg = new_top;
- while (tc_is_syncing(MP_STATE_VM(audioout_sample_timer))) {
- /* Wait for sync */
+ tc_set_enable(timer, false);
+ timer->COUNT16.CTRLA.bit.PRESCALER = new_divisor;
+ tc_set_enable(timer, true);
}
+ tc_wait_for_sync(timer);
+ timer->COUNT16.CC[0].reg = new_top;
+ tc_wait_for_sync(timer);
}
-void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self, bool loop) {
- common_hal_audioio_audioout_get_playing(self);
- // Shut down any active playback.
- if (active_audioout != NULL) {
- tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
- dma_abort_job(&audio_dma);
- } else {
- dac_enable(MP_STATE_VM(audioout_dac_instance));
- }
- switch_audiodma_trigger(DAC_DMAC_ID_EMPTY);
- struct dma_descriptor_config descriptor_config;
- dma_descriptor_get_config_defaults(&descriptor_config);
- if (self->bytes_per_sample == 2) {
- descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
- } else {
- descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
- }
-
- descriptor_config.dst_increment_enable = false;
- // Block transfer count is the number of beats per block (aka descriptor).
- // In this case there are two bytes per beat so divide the length by two.
- descriptor_config.block_transfer_count = self->len / self->bytes_per_sample;
- descriptor_config.source_address = ((uint32_t)self->buffer + self->len);
- descriptor_config.destination_address = ((uint32_t)&DAC->DATABUF.reg + 1);
- descriptor_config.event_output_selection = DMA_EVENT_OUTPUT_BLOCK;
- self->loop = loop;
- if (self->second_buffer == NULL) {
- if (loop) {
- descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor);
- } else {
- descriptor_config.next_descriptor_address = 0;
- }
- } else {
- descriptor_config.next_descriptor_address = ((uint32_t)self->second_descriptor);
- }
- dma_descriptor_create(audio_dma.descriptor, &descriptor_config);
-
- if (self->second_buffer != NULL) {
- // TODO(tannewt): Correctly set the end of this.
- descriptor_config.block_transfer_count = self->len / self->bytes_per_sample;
- descriptor_config.source_address = ((uint32_t)self->second_buffer + self->len);
- descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor);
- dma_descriptor_create(self->second_descriptor, &descriptor_config);
-
- self->last_loaded_block = 0;
- self->bytes_remaining = self->file_length;
-
- f_lseek(&self->file->fp, self->data_start);
- // Seek to the start of the PCM.
- UINT length_read;
- f_read(&self->file->fp, self->buffer, self->len, &length_read);
- self->bytes_remaining -= length_read;
- if (self->bytes_per_sample == 2) {
- // Undo twos complement.
- for (uint16_t i = 0; i < length_read / 2; i++) {
- self->buffer[2 * i + 1] ^= 0x80;
- }
- }
-
- f_read(&self->file->fp, self->second_buffer, self->len, &length_read);
- self->bytes_remaining -= length_read;
- if (self->bytes_per_sample == 2) {
- // Undo twos complement.
- for (uint16_t i = 0; i < length_read / 2; i++) {
- self->second_buffer[2 * i + 1] ^= 0x80;
- }
+void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self,
+ mp_obj_t sample, bool loop) {
+ if (common_hal_audioio_audioout_get_playing(self)) {
+ common_hal_audioio_audioout_stop(self);
+ }
+ audio_dma_result result = AUDIO_DMA_OK;
+ #ifdef SAMD21
+ result = audio_dma_setup_playback(&self->left_dma, sample, loop, true, 0,
+ false /* output unsigned */,
+ (uint32_t) &DAC->DATABUF.reg,
+ DAC_DMAC_ID_EMPTY);
+ #endif
+
+ #ifdef SAMD51
+ uint32_t left_channel_reg = (uint32_t) &DAC->DATABUF[0].reg;
+ uint8_t left_channel_trigger = DAC_DMAC_ID_EMPTY_0;
+ uint32_t right_channel_reg = 0;
+ uint8_t right_channel_trigger = 0;
+ if (self->left_channel == &pin_PA05) {
+ left_channel_reg = (uint32_t) &DAC->DATABUF[1].reg;
+ left_channel_trigger = DAC_DMAC_ID_EMPTY_1;
+ } else if (self->right_channel == &pin_PA05) {
+ right_channel_reg = (uint32_t) &DAC->DATABUF[1].reg;
+ right_channel_trigger = DAC_DMAC_ID_EMPTY_1;
+ }
+ if (self->right_channel == &pin_PA02) {
+ right_channel_reg = (uint32_t) &DAC->DATABUF[0].reg;
+ right_channel_trigger = DAC_DMAC_ID_EMPTY_0;
+ }
+ result = audio_dma_setup_playback(&self->left_dma, sample, loop, true, 0,
+ false /* output unsigned */,
+ left_channel_reg,
+ left_channel_trigger);
+ if (right_channel_reg != 0 && result == AUDIO_DMA_OK) {
+ result = audio_dma_setup_playback(&self->right_dma, sample, loop, true, 1,
+ false /* output unsigned */,
+ right_channel_reg,
+ right_channel_trigger);
+ }
+ #endif
+ if (result != AUDIO_DMA_OK) {
+ audio_dma_stop(&self->left_dma);
+ #ifdef SAMD51
+ audio_dma_stop(&self->right_dma);
+ #endif
+ if (result == AUDIO_DMA_DMA_BUSY) {
+ mp_raise_RuntimeError("No DMA channel found");
+ } else if (result == AUDIO_DMA_MEMORY_ERROR) {
+ mp_raise_RuntimeError("Unable to allocate buffers for signed conversion");
}
}
- active_audioout = self;
- dma_start_transfer_job(&audio_dma);
-
- if (MP_STATE_VM(audiodma_block_counter) != NULL) {
- tc_start_counter(MP_STATE_VM(audiodma_block_counter));
- }
- set_timer_frequency(self->frequency);
- tc_start_counter(MP_STATE_VM(audioout_sample_timer));
+ Tc* timer = tc_insts[self->tc_index];
+ set_timer_frequency(timer, audiosample_sample_rate(sample));
+ timer->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER;
+ while (timer->COUNT16.STATUS.bit.STOP == 1) {}
+ self->playing = true;
}
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self) {
- if (active_audioout == self) {
- if (MP_STATE_VM(audiodma_block_counter) != NULL) {
- tc_stop_counter(MP_STATE_VM(audiodma_block_counter));
- }
- tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
- dma_abort_job(&audio_dma);
- active_audioout = NULL;
- dac_disable(MP_STATE_VM(audioout_dac_instance));
- }
+ Tc* timer = tc_insts[self->tc_index];
+ timer->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_STOP;
+ audio_dma_stop(&self->left_dma);
+ #ifdef SAMD51
+ audio_dma_stop(&self->right_dma);
+ #endif
+
+ // FIXME(tannewt): Do we want to disable? What if we're sharing with an AnalogOut on the 51?
+ // dac_disable(MP_STATE_VM(audioout_dac_instance));
}
bool common_hal_audioio_audioout_get_playing(audioio_audioout_obj_t* self) {
- if (!dma_is_busy(&audio_dma)) {
- if (active_audioout != NULL) {
- common_hal_audioio_audioout_stop(active_audioout);
- }
- active_audioout = NULL;
- }
- return active_audioout == self;
-}
-
-void common_hal_audioio_audioout_set_frequency(audioio_audioout_obj_t* self,
- uint32_t frequency) {
- if (frequency == 0 || frequency > 350000) {
- mp_raise_ValueError("Unsupported playback frequency");
+ bool now_playing = audio_dma_get_playing(&self->left_dma);
+ if (self->playing && !now_playing) {
+ common_hal_audioio_audioout_stop(self);
}
- self->frequency = frequency;
-
- if (common_hal_audioio_audioout_get_playing(self)) {
- set_timer_frequency(frequency);
- }
-}
-
-uint32_t common_hal_audioio_audioout_get_frequency(audioio_audioout_obj_t* self) {
- return self->frequency;
+ return now_playing;
}
diff --git a/ports/atmel-samd/common-hal/audioio/AudioOut.h b/ports/atmel-samd/common-hal/audioio/AudioOut.h
index 78e4ffc77..efbfe2a1d 100644
--- a/ports/atmel-samd/common-hal/audioio/AudioOut.h
+++ b/ports/atmel-samd/common-hal/audioio/AudioOut.h
@@ -29,28 +29,21 @@
#include "common-hal/microcontroller/Pin.h"
-#include "extmod/vfs_fat_file.h"
+#include "audio_dma.h"
#include "py/obj.h"
typedef struct {
mp_obj_base_t base;
- const mcu_pin_obj_t *pin;
- uint32_t frequency;
- uint8_t* buffer;
-
- // File playback specific:
- uint8_t* second_buffer;
- DmacDescriptor* second_descriptor;
- uint32_t file_length; // In bytes
- uint16_t data_start; // Where the data values start
- uint8_t bytes_per_sample;
- bool signed_samples;
- uint16_t last_loaded_block;
- uint32_t bytes_remaining;
-
- bool loop;
- uint32_t len;
- pyb_file_obj_t* file;
+ const mcu_pin_obj_t *left_channel;
+ audio_dma_t left_dma;
+ #ifdef SAMD51
+ const mcu_pin_obj_t *right_channel;
+ audio_dma_t right_dma;
+ #endif
+ uint8_t tc_index;
+
+ uint8_t tc_to_dac_event_channel;
+ bool playing;
} audioio_audioout_obj_t;
void audioout_reset(void);
diff --git a/ports/atmel-samd/common-hal/pulseio/PWMOut.c b/ports/atmel-samd/common-hal/pulseio/PWMOut.c
index c68ea6985..f1464c4b4 100644
--- a/ports/atmel-samd/common-hal/pulseio/PWMOut.c
+++ b/ports/atmel-samd/common-hal/pulseio/PWMOut.c
@@ -48,7 +48,6 @@ static uint32_t tc_periods[TC_INST_NUM];
uint32_t target_tcc_frequencies[TCC_INST_NUM];
uint8_t tcc_refcount[TCC_INST_NUM];
-const uint16_t prescaler[8] = {1, 2, 4, 8, 16, 64, 256, 1024};
// This bitmask keeps track of which channels of a TCC are currently claimed.
#ifdef SAMD21
diff --git a/ports/atmel-samd/events.c b/ports/atmel-samd/events.c
new file mode 100644
index 000000000..1854692ae
--- /dev/null
+++ b/ports/atmel-samd/events.c
@@ -0,0 +1,194 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "events.h"
+
+#include "clocks.h"
+
+#include "py/runtime.h"
+
+#ifdef SAMD21
+#include "hpl/pm/hpl_pm_base.h"
+#endif
+
+#ifdef SAMD51
+#include "hri/hri_mclk_d51.h"
+#endif
+
+void turn_on_event_system(void) {
+ #ifdef SAMD51
+ hri_mclk_set_APBBMASK_EVSYS_bit(MCLK);
+ #endif
+
+ #ifdef SAMD21
+ _pm_enable_bus_clock(PM_BUS_APBC, EVSYS);
+ #endif
+}
+
+void reset_event_system(void) {
+ #ifdef SAMD51
+ EVSYS->CTRLA.bit.SWRST = true;
+ hri_mclk_clear_APBBMASK_EVSYS_bit(MCLK);
+ #endif
+
+ #ifdef SAMD21
+ EVSYS->CTRL.bit.SWRST = true;
+ _pm_disable_bus_clock(PM_BUS_APBC, EVSYS);
+ #endif
+}
+
+static bool channel_free(int8_t channel) {
+ uint8_t generator;
+ #ifdef SAMD51
+ generator = EVSYS->Channel[channel].CHANNEL.bit.EVGEN;
+ #endif
+ #ifdef SAMD21
+ // Explicitly do a byte write so the peripheral knows we're just wanting to read the channel
+ // rather than write to it.
+ *((uint8_t*) &EVSYS->CHANNEL.reg) = channel;
+ generator = (EVSYS->CHANNEL.reg & EVSYS_CHANNEL_EVGEN_Msk) >> EVSYS_CHANNEL_EVGEN_Pos;
+ #endif
+ return generator == 0;
+}
+
+uint8_t find_async_event_channel(void) {
+ int8_t channel;
+ for (channel = EVSYS_CHANNELS - 1; channel > 0; channel--) {
+ if (channel_free(channel)) {
+ break;
+ }
+ }
+ if (channel < 0) {
+ mp_raise_RuntimeError("All event channels in use");
+ }
+ return channel;
+}
+
+#ifdef SAMD21
+#define EVSYS_SYNCH_NUM EVSYS_CHANNELS
+#endif
+uint8_t find_sync_event_channel(void) {
+ int8_t channel;
+ for (channel = 0; channel < EVSYS_SYNCH_NUM; channel++) {
+ if (channel_free(channel)) {
+ break;
+ }
+ }
+ if (channel >= EVSYS_SYNCH_NUM) {
+ mp_raise_RuntimeError("All sync event channels in use");
+ }
+ return channel;
+}
+
+void disable_event_channel(uint8_t channel_number) {
+ #ifdef SAMD51
+ EVSYS->Channel[channel_number].CHANNEL.reg = 0;
+ #endif
+ #ifdef SAMD21
+ EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(channel_number);
+ #endif
+}
+
+void disable_event_user(uint8_t user_number) {
+ #ifdef SAMD51
+ EVSYS->USER[user_number].reg = 0;
+ #endif
+ #ifdef SAMD21
+ EVSYS->USER.reg = EVSYS_USER_USER(user_number);
+ #endif
+}
+
+void connect_event_user_to_channel(uint8_t user, uint8_t channel) {
+ #ifdef SAMD51
+ EVSYS->USER[user].reg = channel + 1;
+ #endif
+ #ifdef SAMD21
+ EVSYS->USER.reg = EVSYS_USER_USER(user) | EVSYS_USER_CHANNEL(channel + 1);
+ #endif
+}
+
+void init_async_event_channel(uint8_t channel, uint8_t generator) {
+ #ifdef SAMD51
+ EVSYS->Channel[channel].CHANNEL.reg = EVSYS_CHANNEL_EVGEN(generator) | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;
+ #endif
+ #ifdef SAMD21
+ EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(channel) | EVSYS_CHANNEL_EVGEN(generator) | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;
+ #endif
+}
+
+void init_event_channel_interrupt(uint8_t channel, uint8_t gclk, uint8_t generator) {
+ connect_gclk_to_peripheral(gclk, EVSYS_GCLK_ID_0 + channel);
+ #ifdef SAMD51
+ EVSYS->Channel[channel].CHANNEL.reg = EVSYS_CHANNEL_EVGEN(generator) |
+ EVSYS_CHANNEL_PATH_SYNCHRONOUS |
+ EVSYS_CHANNEL_EDGSEL_RISING_EDGE;
+ #endif
+ #ifdef SAMD21
+ EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(channel) |
+ EVSYS_CHANNEL_EVGEN(generator) |
+ EVSYS_CHANNEL_PATH_RESYNCHRONIZED |
+ EVSYS_CHANNEL_EDGSEL_RISING_EDGE;
+ if (channel > 7) {
+ uint8_t value = 1 << (channel - 7);
+ EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVDp8(value) | EVSYS_INTFLAG_OVRp8(value);
+ EVSYS->INTENSET.reg = EVSYS_INTENSET_EVDp8(value) | EVSYS_INTENSET_OVRp8(value);
+ } else {
+ uint8_t value = 1 << channel;
+ EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVD(value) | EVSYS_INTFLAG_OVR(value);
+ EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD(value) | EVSYS_INTENSET_OVR(value);
+ }
+ #endif
+}
+
+bool event_interrupt_active(uint8_t channel) {
+ bool active = false;
+ #ifdef SAMD21
+ if (channel > 7) {
+ uint8_t value = 1 << (channel - 7);
+ active = (EVSYS->INTFLAG.reg & EVSYS_INTFLAG_EVDp8(value)) != 0;
+ // Only clear if we know its active, otherwise there is the possibility it becomes active
+ // after we check but before we clear.
+ if (active) {
+ EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVDp8(value) | EVSYS_INTFLAG_OVRp8(value);
+ }
+ } else {
+ uint8_t value = 1 << channel;
+ active = (EVSYS->INTFLAG.reg & EVSYS_INTFLAG_EVD(value)) != 0;
+ if (active) {
+ EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVD(value) | EVSYS_INTFLAG_OVR(value);
+ }
+ }
+ #endif
+ #ifdef SAMD51
+ active = EVSYS->Channel[channel].CHINTFLAG.bit.EVD;
+ // Only clear if we know its active, otherwise there is the possibility it becomes after we
+ // check but before we clear.
+ if (active) {
+ EVSYS->Channel[channel].CHINTFLAG.reg = EVSYS_CHINTFLAG_EVD;
+ }
+ #endif
+ return active;
+}
diff --git a/ports/atmel-samd/events.h b/ports/atmel-samd/events.h
new file mode 100644
index 000000000..69215c3e7
--- /dev/null
+++ b/ports/atmel-samd/events.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_ATMEL_SAMD_EVENTS_H
+#define MICROPY_INCLUDED_ATMEL_SAMD_EVENTS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "include/sam.h"
+
+void turn_on_event_system(void);
+void reset_event_system(void);
+uint8_t find_async_event_channel(void);
+uint8_t find_sync_event_channel(void);
+void disable_event_channel(uint8_t channel_number);
+void disable_event_user(uint8_t user_number);
+void connect_event_user_to_channel(uint8_t user, uint8_t channel);
+void init_async_event_channel(uint8_t channel, uint8_t generator);
+void init_event_channel_interrupt(uint8_t channel, uint8_t gclk, uint8_t generator);
+bool event_interrupt_active(uint8_t channel);
+
+#endif // MICROPY_INCLUDED_ATMEL_SAMD_EVENTS_H
diff --git a/ports/atmel-samd/mpconfigport.h b/ports/atmel-samd/mpconfigport.h
index c66f0d0b3..7dbcd5062 100644
--- a/ports/atmel-samd/mpconfigport.h
+++ b/ports/atmel-samd/mpconfigport.h
@@ -195,12 +195,19 @@ extern const struct _mp_obj_module_t usb_hid_module;
// Scan gamepad every 32ms
#define CIRCUITPY_GAMEPAD_TICKS 0x1f
+ #if defined(__SAMD51G19A__) || defined(__SAMD51G18A__)
+ #define AUDIOBUSIO_MODULE
+ #else
+ #define AUDIOBUSIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_audiobusio), (mp_obj_t)&audiobusio_module },
+ #endif
+
#define EXTRA_BUILTIN_MODULES \
+ { MP_OBJ_NEW_QSTR(MP_QSTR_audioio), (mp_obj_t)&audioio_module }, \
+ AUDIOBUSIO_MODULE \
{ MP_OBJ_NEW_QSTR(MP_QSTR_bitbangio), (mp_obj_t)&bitbangio_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_gamepad),(mp_obj_t)&gamepad_module }
-// { MP_OBJ_NEW_QSTR(MP_QSTR_audioio), (mp_obj_t)&audioio_module },
-// { MP_OBJ_NEW_QSTR(MP_QSTR_audiobusio), (mp_obj_t)&audiobusio_module },
#define EXPRESS_BOARD
+
#else
#define MICROPY_PY_BUILTINS_REVERSED (0)
#define MICROPY_PY_MICROPYTHON_MEM_INFO (0)
@@ -253,15 +260,12 @@ extern const struct _mp_obj_module_t usb_hid_module;
#define MP_STATE_PORT MP_STATE_VM
+#include "shared_dma.h"
+
#define MICROPY_PORT_ROOT_POINTERS \
const char *readline_hist[8]; \
vstr_t *repl_line; \
- struct tc_module* audiodma_block_counter; \
- struct events_resource* audiodma_block_event; \
- struct tc_module* audioout_sample_timer; \
- struct dac_module* audioout_dac_instance; \
- struct events_resource* audioout_sample_event; \
- struct events_resource* audioout_dac_event; \
+ mp_obj_t playing_audio[AUDIO_DMA_CHANNEL_COUNT]; \
FLASH_ROOT_POINTERS \
void run_background_tasks(void);
diff --git a/ports/atmel-samd/shared_dma.c b/ports/atmel-samd/shared_dma.c
index 8a6a606bb..88c89abee 100644
--- a/ports/atmel-samd/shared_dma.c
+++ b/ports/atmel-samd/shared_dma.c
@@ -34,18 +34,10 @@
#include "shared-bindings/microcontroller/__init__.h"
-// We allocate three DMA resources for the entire lifecycle of the board (not the
-// vm) because the general_dma resource will be shared between the REPL and SPI
-// flash. Both uses must block each other in order to prevent conflict.
-COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[3];
+COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT];
// Don't use these directly. They are used by the DMA engine itself.
-COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[3];
-
-#define AUDIO_DMA_CHANNEL 0
-#define SHARED_TX_CHANNEL 1
-#define SHARED_RX_CHANNEL 2
-
+COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT];
#ifdef SAMD21
#define FIRST_SERCOM_RX_TRIGSRC 0x01
@@ -56,51 +48,6 @@ COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[3];
#define FIRST_SERCOM_TX_TRIGSRC 0x05
#endif
-// static void dma_configure_audio(uint8_t channel) {
-// system_interrupt_enter_critical_section();
-// /** Select the DMA channel and clear software trigger */
-// DMAC->CHID.reg = DMAC_CHID_ID(channel);
-// DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
-// DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
-// DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channel));
-// uint32_t event_output_enable = 0;
-// if (output_event) {
-// event_output_enable = DMAC_CHCTRLB_EVOE;
-// }
-// DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(DMA_PRIORITY_LEVEL_0) |
-// DMAC_CHCTRLB_TRIGSRC(trigsrc) |
-// DMAC_CHCTRLB_TRIGACT(DMA_TRIGGER_ACTION_BEAT) |
-// event_output_enable;
-// // config.peripheral_trigger = DAC_DMAC_ID_EMPTY;
-// // config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
-// // config.event_config.input_action = DMA_EVENT_INPUT_TRIG;
-// // config.event_config.event_output_enable = true;
-// system_interrupt_leave_critical_section();
-// }
-
-void init_shared_dma(void) {
- // Turn on the clocks
- #ifdef SAMD51
- MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
- #endif
-
- #ifdef SAMD21
- PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
- PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
- #endif
-
- DMAC->CTRL.reg = DMAC_CTRL_SWRST;
-
- DMAC->BASEADDR.reg = (uint32_t) dma_descriptors;
- DMAC->WRBADDR.reg = (uint32_t) write_back_descriptors;
-
- DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN0;
-
- // This allocates the lowest channel first so make sure the audio is first
- // so it gets the highest priority.
- // dma_configure_audio(0);
-}
-
static uint8_t sercom_index(Sercom* sercom) {
#ifdef SAMD21
return ((uint32_t) sercom - (uint32_t) SERCOM0) / 0x400;
@@ -115,7 +62,7 @@ static uint8_t sercom_index(Sercom* sercom) {
#endif
}
-static void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event) {
+void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event) {
#ifdef SAMD21
common_hal_mcu_disable_interrupts();
/** Select the DMA channel and clear software trigger */
@@ -147,7 +94,7 @@ static void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_e
#endif
}
-static void enable_channel(uint8_t channel_number) {
+void dma_enable_channel(uint8_t channel_number) {
#ifdef SAMD21
common_hal_mcu_disable_interrupts();
/** Select the DMA channel and clear software trigger */
@@ -162,7 +109,38 @@ static void enable_channel(uint8_t channel_number) {
#endif
}
-static uint8_t transfer_status(uint8_t channel_number) {
+void dma_disable_channel(uint8_t channel_number) {
+ #ifdef SAMD21
+ common_hal_mcu_disable_interrupts();
+ /** Select the DMA channel and clear software trigger */
+ DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
+ DMAC->CHCTRLA.bit.ENABLE = false;
+ common_hal_mcu_enable_interrupts();
+ #endif
+
+ #ifdef SAMD51
+ DmacChannel* channel = &DMAC->Channel[channel_number];
+ channel->CHCTRLA.bit.ENABLE = false;
+ #endif
+}
+
+bool dma_channel_enabled(uint8_t channel_number) {
+ #ifdef SAMD21
+ common_hal_mcu_disable_interrupts();
+ /** Select the DMA channel and clear software trigger */
+ DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
+ bool enabled = DMAC->CHCTRLA.bit.ENABLE;
+ common_hal_mcu_enable_interrupts();
+ return enabled;
+ #endif
+
+ #ifdef SAMD51
+ DmacChannel* channel = &DMAC->Channel[channel_number];
+ return channel->CHCTRLA.bit.ENABLE;
+ #endif
+}
+
+uint8_t dma_transfer_status(uint8_t channel_number) {
#ifdef SAMD21
common_hal_mcu_disable_interrupts();
/** Select the DMA channel and clear software trigger */
@@ -181,7 +159,6 @@ static uint8_t transfer_status(uint8_t channel_number) {
static bool channel_free(uint8_t channel_number) {
#ifdef SAMD21
common_hal_mcu_disable_interrupts();
- /** Select the DMA channel and clear software trigger */
DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
bool channel_free = DMAC->CHSTATUS.reg == 0;
common_hal_mcu_enable_interrupts();
@@ -194,6 +171,29 @@ static bool channel_free(uint8_t channel_number) {
#endif
}
+void init_shared_dma(void) {
+ // Turn on the clocks
+ #ifdef SAMD51
+ MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
+ #endif
+
+ #ifdef SAMD21
+ PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
+ PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
+ #endif
+
+ DMAC->CTRL.reg = DMAC_CTRL_SWRST;
+
+ DMAC->BASEADDR.reg = (uint32_t) dma_descriptors;
+ DMAC->WRBADDR.reg = (uint32_t) write_back_descriptors;
+
+ DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN0;
+
+ for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
+ dma_configure(i, 0, true);
+ }
+}
+
// Do write and read simultaneously. If buffer_out is NULL, write the tx byte over and over.
// If buffer_out is a real buffer, ignore tx.
// DMAs buffer_out -> dest
@@ -284,10 +284,10 @@ static int32_t shared_dma_transfer(void* peripheral,
// Start the RX job first so we don't miss the first byte. The TX job clocks
// the output.
if (rx_active) {
- enable_channel(SHARED_RX_CHANNEL);
+ dma_enable_channel(SHARED_RX_CHANNEL);
}
if (tx_active) {
- enable_channel(SHARED_TX_CHANNEL);
+ dma_enable_channel(SHARED_TX_CHANNEL);
}
@@ -309,10 +309,10 @@ static int32_t shared_dma_transfer(void* peripheral,
// Channels cycle between Suspend -> Pending -> Busy and back while transfering. So, we check
// the channels transfer status for an error or completion.
if (rx_active) {
- while ((transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {}
+ while ((dma_transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {}
}
if (tx_active) {
- while ((transfer_status(SHARED_TX_CHANNEL) & 0x3) == 0) {}
+ while ((dma_transfer_status(SHARED_TX_CHANNEL) & 0x3) == 0) {}
}
if (sercom) {
@@ -331,8 +331,8 @@ static int32_t shared_dma_transfer(void* peripheral,
}
}
- if ((!rx_active || transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) &&
- (!tx_active || transfer_status(SHARED_TX_CHANNEL) == DMAC_CHINTFLAG_TCMPL)) {
+ if ((!rx_active || dma_transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) &&
+ (!tx_active || dma_transfer_status(SHARED_TX_CHANNEL) == DMAC_CHINTFLAG_TCMPL)) {
return length;
}
return -2;
@@ -362,75 +362,6 @@ int32_t qspi_dma_read(uint32_t address, uint8_t* buffer, uint32_t length) {
}
#endif
-bool allocate_block_counter() {
-// // Find a timer to count DMA block completions.
-// Tc *t = NULL;
-// Tc *tcs[TC_INST_NUM] = TC_INSTS;
-// for (uint8_t i = TC_INST_NUM; i > 0; i--) {
-// if (tcs[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
-// t = tcs[i - 1];
-// break;
-// }
-// }
-// if (t == NULL) {
-// return false;
-// }
-// MP_STATE_VM(audiodma_block_counter) = gc_alloc(sizeof(struct tc_module), false);
-// if (MP_STATE_VM(audiodma_block_counter) == NULL) {
-// return false;
-// }
-//
-// // Don't bother setting the period. We set it before you playback anything.
-// struct tc_config config_tc;
-// tc_get_config_defaults(&config_tc);
-// config_tc.counter_size = TC_COUNTER_SIZE_16BIT;
-// config_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV1;
-// if (tc_init(MP_STATE_VM(audiodma_block_counter), t, &config_tc) != STATUS_OK) {
-// return false;
-// };
-//
-// struct tc_events events_tc;
-// events_tc.generate_event_on_overflow = false;
-// events_tc.on_event_perform_action = true;
-// events_tc.event_action = TC_EVENT_ACTION_INCREMENT_COUNTER;
-// tc_enable_events(MP_STATE_VM(audiodma_block_counter), &events_tc);
-//
-// // Connect the timer overflow event, which happens at the target frequency,
-// // to the DAC conversion trigger.
-// MP_STATE_VM(audiodma_block_event) = gc_alloc(sizeof(struct events_resource), false);
-// if (MP_STATE_VM(audiodma_block_event) == NULL) {
-// return false;
-// }
-// struct events_config config;
-// events_get_config_defaults(&config);
-//
-// uint8_t user = EVSYS_ID_USER_TC3_EVU;
-// if (t == TC4) {
-// user = EVSYS_ID_USER_TC4_EVU;
-// } else if (t == TC5) {
-// user = EVSYS_ID_USER_TC5_EVU;
-// #ifdef TC6
-// } else if (t == TC6) {
-// user = EVSYS_ID_USER_TC6_EVU;
-// #endif
-// #ifdef TC7
-// } else if (t == TC7) {
-// user = EVSYS_ID_USER_TC7_EVU;
-// #endif
-// }
-//
-// config.generator = EVSYS_ID_GEN_DMAC_CH_0;
-// config.path = EVENTS_PATH_ASYNCHRONOUS;
-// if (events_allocate(MP_STATE_VM(audiodma_block_event), &config) != STATUS_OK ||
-// events_attach_user(MP_STATE_VM(audiodma_block_event), user) != STATUS_OK) {
-// return false;
-// }
-//
-// tc_enable(MP_STATE_VM(audiodma_block_counter));
-// tc_stop_counter(MP_STATE_VM(audiodma_block_counter));
- return true;
-}
-
-void switch_audiodma_trigger(uint8_t trigger_dmac_id) {
- //dma_configure(audio_dma.channel_id, trigger_dmac_id, true);
+DmacDescriptor* dma_descriptor(uint8_t channel_number) {
+ return &dma_descriptors[channel_number];
}
diff --git a/ports/atmel-samd/shared_dma.h b/ports/atmel-samd/shared_dma.h
index 536a95af3..5f51d404f 100644
--- a/ports/atmel-samd/shared_dma.h
+++ b/ports/atmel-samd/shared_dma.h
@@ -32,6 +32,14 @@
#include "include/sam.h"
+// We allocate DMA resources for the entire lifecycle of the board (not the
+// vm) because the general_dma resource will be shared between the REPL and SPI
+// flash. Both uses must block each other in order to prevent conflict.
+#define AUDIO_DMA_CHANNEL_COUNT 3
+#define DMA_CHANNEL_COUNT (AUDIO_DMA_CHANNEL_COUNT + 2)
+#define SHARED_TX_CHANNEL (DMA_CHANNEL_COUNT - 2)
+#define SHARED_RX_CHANNEL (DMA_CHANNEL_COUNT - 1)
+
volatile bool audio_dma_in_use;
void init_shared_dma(void);
@@ -45,8 +53,11 @@ int32_t sercom_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length)
int32_t sercom_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx);
int32_t sercom_dma_transfer(Sercom* sercom, const uint8_t* buffer_out, uint8_t* buffer_in, uint32_t length);
-// Allocate a counter to track how far along we are in a DMA double buffer.
-bool allocate_block_counter(void);
-void switch_audiodma_trigger(uint8_t trigger_dmac_id);
+void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event);
+void dma_enable_channel(uint8_t channel_number);
+void dma_disable_channel(uint8_t channel_number);
+bool dma_channel_enabled(uint8_t channel_number);
+uint8_t dma_transfer_status(uint8_t channel_number);
+DmacDescriptor* dma_descriptor(uint8_t channel_number);
#endif // MICROPY_INCLUDED_ATMEL_SAMD_SHARED_DMA_H
diff --git a/ports/atmel-samd/supervisor/port.c b/ports/atmel-samd/supervisor/port.c
index 23d9bc909..eafb9b785 100644
--- a/ports/atmel-samd/supervisor/port.c
+++ b/ports/atmel-samd/supervisor/port.c
@@ -45,11 +45,16 @@
#include "common-hal/analogio/AnalogIn.h"
#include "common-hal/analogio/AnalogOut.h"
+#include "common-hal/audiobusio/PDMIn.h"
+#include "common-hal/audiobusio/I2SOut.h"
+#include "common-hal/audioio/AudioOut.h"
#include "common-hal/microcontroller/Pin.h"
#include "common-hal/pulseio/PulseIn.h"
#include "common-hal/pulseio/PulseOut.h"
#include "common-hal/pulseio/PWMOut.h"
#include "common-hal/usb_hid/Device.h"
+#include "clocks.h"
+#include "events.h"
#include "shared_dma.h"
#include "tick.h"
@@ -192,15 +197,21 @@ void reset_port(void) {
sercom_instances[i]->SPI.CTRLA.bit.SWRST = 1;
}
-// #ifdef EXPRESS_BOARD
-// audioout_reset();
+#ifdef EXPRESS_BOARD
+ audioout_reset();
+ #if !defined(__SAMD51G19A__) && !defined(__SAMD51G18A__)
+ i2sout_reset();
+ #endif
+ audio_dma_reset();
// touchin_reset();
-// pdmin_reset();
-// #endif
+ //pdmin_reset();
+#endif
pulsein_reset();
pulseout_reset();
pwmout_reset();
+ reset_gclks();
+
analogin_reset();
#ifdef CIRCUITPY_GAMEPAD_TICKS
@@ -209,6 +220,8 @@ void reset_port(void) {
analogout_reset();
+ reset_event_system();
+
reset_all_pins();
// Set up debugging pins after reset_all_pins().
diff --git a/ports/atmel-samd/timers.c b/ports/atmel-samd/timers.c
index 2e8541f24..33518b782 100644
--- a/ports/atmel-samd/timers.c
+++ b/ports/atmel-samd/timers.c
@@ -39,6 +39,8 @@
#include "hri/hri_gclk_d51.h"
#endif
+const uint16_t prescaler[8] = {1, 2, 4, 8, 16, 64, 256, 1024};
+
// This bitmask keeps track of which channels of a TCC are currently claimed.
#ifdef SAMD21
const uint8_t tcc_cc_num[3] = {4, 2, 2};
@@ -73,8 +75,8 @@ const uint8_t tc_gclk_ids[TC_INST_NUM] = {TC0_GCLK_ID,
TC7_GCLK_ID,
#endif
};
-const uint8_t tcc_gclk_ids[TCC_INST_NUM] = {TCC0_GCLK_ID,
- TCC1_GCLK_ID,
+const uint8_t tcc_gclk_ids[TCC_INST_NUM] = {TCC0_GCLK_ID,
+ TCC1_GCLK_ID,
TCC2_GCLK_ID,
#ifdef TCC3_GCLK_ID
TCC3_GCLK_ID,
diff --git a/ports/atmel-samd/timers.h b/ports/atmel-samd/timers.h
index bb3ab5ee7..fa8965784 100644
--- a/ports/atmel-samd/timers.h
+++ b/ports/atmel-samd/timers.h
@@ -28,6 +28,8 @@
#include "include/sam.h"
+const uint16_t prescaler[8];
+
#ifdef SAMD21
const uint8_t tcc_cc_num[3];
const uint8_t tc_gclk_ids[TC_INST_NUM];
diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c
new file mode 100644
index 000000000..6233bb51a
--- /dev/null
+++ b/shared-bindings/audiobusio/I2SOut.c
@@ -0,0 +1,233 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdint.h>
+
+#include "lib/utils/context_manager_helpers.h"
+#include "py/binary.h"
+#include "py/objproperty.h"
+#include "py/runtime.h"
+#include "shared-bindings/microcontroller/Pin.h"
+#include "shared-bindings/audiobusio/I2SOut.h"
+#include "shared-bindings/util.h"
+
+//| .. currentmodule:: audiobusio
+//|
+//| :class:`I2SOut` -- Output an I2S audio signal
+//| ========================================================
+//|
+//| I2S is used to output an audio signal on an I2S bus.
+//|
+//| .. class:: I2SOut(bit_clock, word_select, data, *, left_justified)
+//|
+//| Create a I2SOut object associated with the given pins.
+//|
+//| :param ~microcontroller.Pin bit_clock: The bit clock (or serial clock) pin
+//| :param ~microcontroller.Pin word_select: The word select (or left/right clock) pin
+//| :param ~microcontroller.Pin data: The data pin
+//| :param bool left_justified: True when data bits are aligned with the word select clock. False
+//| when they are shifted by one to match classic I2S protocol.
+//|
+//| Simple 8ksps 440 Hz sine wave on `Metro M0 Express <https://www.adafruit.com/product/3505>`_
+//| using `UDA1334 Breakout <https://www.adafruit.com/product/3678>`_::
+//|
+//| import audiobusio
+//| import audioio
+//| import board
+//| import array
+//| import time
+//| import math
+//|
+//| # Generate one period of sine wav.
+//| length = 8000 // 440
+//| sine_wave = array.array("H", [0] * length)
+//| for i in range(length):
+//| sine_wave[i] = int(math.sin(math.pi * 2 * i / 18) * (2 ** 15) + 2 ** 15)
+//|
+//| sine_wave = audiobusio.RawSample(sine_wave, sample_rate=8000)
+//| i2s = audiobusio.I2SOut(board.D1, board.D0, board.D9)
+//| i2s.play(sine_wave, loop=True)
+//| time.sleep(1)
+//| i2s.stop()
+//|
+//| Playing a wave file from flash::
+//|
+//| import board
+//| import audioio
+//| import audiobusio
+//| import digitalio
+//|
+//|
+//| f = open("cplay-5.1-16bit-16khz.wav", "rb")
+//| wav = audioio.WaveFile(f)
+//|
+//| a = audiobusio.I2SOut(board.D1, board.D0, board.D9)
+//|
+//| print("playing")
+//| a.play(wav)
+//| while a.playing:
+//| pass
+//| print("stopped")
+//|
+STATIC mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) {
+ mp_arg_check_num(n_args, n_kw, 3, 4, true);
+ mp_map_t kw_args;
+ mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args);
+ enum { ARG_bit_clock, ARG_word_select, ARG_data, ARG_left_justified };
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_bit_clock, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_word_select, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_data, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_left_justified, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_bool = false} },
+ };
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ mp_obj_t bit_clock_obj = args[ARG_bit_clock].u_obj;
+ assert_pin(bit_clock_obj, false);
+ const mcu_pin_obj_t *bit_clock = MP_OBJ_TO_PTR(bit_clock_obj);
+
+ mp_obj_t word_select_obj = args[ARG_word_select].u_obj;
+ assert_pin(word_select_obj, false);
+ const mcu_pin_obj_t *word_select = MP_OBJ_TO_PTR(word_select_obj);
+
+ mp_obj_t data_obj = args[ARG_data].u_obj;
+ assert_pin(data_obj, false);
+ const mcu_pin_obj_t *data = MP_OBJ_TO_PTR(data_obj);
+
+ audiobusio_i2sout_obj_t *self = m_new_obj(audiobusio_i2sout_obj_t);
+ self->base.type = &audiobusio_i2sout_type;
+ common_hal_audiobusio_i2sout_construct(self, bit_clock, word_select, data, args[ARG_left_justified].u_bool);
+
+ return MP_OBJ_FROM_PTR(self);
+}
+
+//| .. method:: deinit()
+//|
+//| Deinitialises the I2SOut and releases any hardware resources for reuse.
+//|
+STATIC mp_obj_t audiobusio_i2sout_deinit(mp_obj_t self_in) {
+ audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ common_hal_audiobusio_i2sout_deinit(self);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_deinit_obj, audiobusio_i2sout_deinit);
+
+//| .. method:: __enter__()
+//|
+//| No-op used by Context Managers.
+//|
+// Provided by context manager helper.
+
+//| .. method:: __exit__()
+//|
+//| Automatically deinitializes the hardware when exiting a context. See
+//| :ref:`lifetime-and-contextmanagers` for more info.
+//|
+STATIC mp_obj_t audiobusio_i2sout_obj___exit__(size_t n_args, const mp_obj_t *args) {
+ (void)n_args;
+ common_hal_audiobusio_i2sout_deinit(args[0]);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiobusio_i2sout___exit___obj, 4, 4, audiobusio_i2sout_obj___exit__);
+
+
+//| .. method:: play(sample, *, loop=False)
+//|
+//| Plays the sample once when loop=False and continuously when loop=True.
+//| Does not block. Use `playing` to block.
+//|
+//| Sample must be an `audioio.WaveFile` or `audioio.RawSample`.
+//|
+//| The sample itself should consist of 8 bit or 16 bit samples.
+//|
+STATIC mp_obj_t audiobusio_i2sout_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ enum { ARG_sample, ARG_loop };
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
+ };
+ audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
+ raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ mp_obj_t sample = args[ARG_sample].u_obj;
+ common_hal_audiobusio_i2sout_play(self, sample, args[ARG_loop].u_bool);
+
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_KW(audiobusio_i2sout_play_obj, 1, audiobusio_i2sout_obj_play);
+
+//| .. method:: stop()
+//|
+//| Stops playback.
+//|
+STATIC mp_obj_t audiobusio_i2sout_obj_stop(mp_obj_t self_in) {
+ audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
+ common_hal_audiobusio_i2sout_stop(self);
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_stop_obj, audiobusio_i2sout_obj_stop);
+
+//| .. attribute:: playing
+//|
+//| True when the audio sample is being output. (read-only)
+//|
+STATIC mp_obj_t audiobusio_i2sout_obj_get_playing(mp_obj_t self_in) {
+ audiobusio_i2sout_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ raise_error_if_deinited(common_hal_audiobusio_i2sout_deinited(self));
+ return mp_obj_new_bool(common_hal_audiobusio_i2sout_get_playing(self));
+}
+MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sout_get_playing_obj, audiobusio_i2sout_obj_get_playing);
+
+const mp_obj_property_t audiobusio_i2sout_playing_obj = {
+ .base.type = &mp_type_property,
+ .proxy = {(mp_obj_t)&audiobusio_i2sout_get_playing_obj,
+ (mp_obj_t)&mp_const_none_obj,
+ (mp_obj_t)&mp_const_none_obj},
+};
+
+STATIC const mp_rom_map_elem_t audiobusio_i2sout_locals_dict_table[] = {
+ // Methods
+ { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiobusio_i2sout_deinit_obj) },
+ { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
+ { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiobusio_i2sout___exit___obj) },
+ { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiobusio_i2sout_play_obj) },
+ { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiobusio_i2sout_stop_obj) },
+
+ // Properties
+ { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiobusio_i2sout_playing_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(audiobusio_i2sout_locals_dict, audiobusio_i2sout_locals_dict_table);
+
+const mp_obj_type_t audiobusio_i2sout_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_I2SOut,
+ .make_new = audiobusio_i2sout_make_new,
+ .locals_dict = (mp_obj_dict_t*)&audiobusio_i2sout_locals_dict,
+};
diff --git a/shared-bindings/audiobusio/I2SOut.h b/shared-bindings/audiobusio/I2SOut.h
new file mode 100644
index 000000000..930044054
--- /dev/null
+++ b/shared-bindings/audiobusio/I2SOut.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017, 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOBUSIO_I2SOUT_H
+#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOBUSIO_I2SOUT_H
+
+#include "common-hal/audiobusio/I2SOut.h"
+#include "common-hal/microcontroller/Pin.h"
+
+extern const mp_obj_type_t audiobusio_i2sout_type;
+
+void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self,
+ const mcu_pin_obj_t* bit_clock, const mcu_pin_obj_t* word_select, const mcu_pin_obj_t* data,
+ bool left_justified);
+
+void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self);
+bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self);
+void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, mp_obj_t sample, bool loop);
+void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self);
+bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self);
+
+#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOBUSIO_I2SOUT_H
diff --git a/shared-bindings/audiobusio/__init__.c b/shared-bindings/audiobusio/__init__.c
index bdd999b04..528a8fb31 100644
--- a/shared-bindings/audiobusio/__init__.c
+++ b/shared-bindings/audiobusio/__init__.c
@@ -31,6 +31,7 @@
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-bindings/audiobusio/__init__.h"
+#include "shared-bindings/audiobusio/I2SOut.h"
#include "shared-bindings/audiobusio/PDMIn.h"
//| :mod:`audiobusio` --- Support for audio input and output over digital bus
@@ -50,6 +51,7 @@
//| .. toctree::
//| :maxdepth: 3
//|
+//| I2SOut
//| PDMIn
//|
//| All libraries change hardware state and should be deinitialized when they
@@ -59,7 +61,8 @@
STATIC const mp_rom_map_elem_t audiobusio_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiobusio) },
- { MP_ROM_QSTR(MP_QSTR_PDMIn), MP_ROM_PTR(&audiobusio_pdmin_type) },
+ { MP_ROM_QSTR(MP_QSTR_I2SOut), MP_ROM_PTR(&audiobusio_i2sout_type) },
+ //{ MP_ROM_QSTR(MP_QSTR_PDMIn), MP_ROM_PTR(&audiobusio_pdmin_type) },
};
STATIC MP_DEFINE_CONST_DICT(audiobusio_module_globals, audiobusio_module_globals_table);
diff --git a/shared-bindings/audioio/AudioOut.c b/shared-bindings/audioio/AudioOut.c
index 5bfd7e2a3..189a217d4 100644
--- a/shared-bindings/audioio/AudioOut.c
+++ b/shared-bindings/audioio/AudioOut.c
@@ -32,6 +32,7 @@
#include "py/runtime.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-bindings/audioio/AudioOut.h"
+#include "shared-bindings/audioio/RawSample.h"
#include "shared-bindings/util.h"
//| .. currentmodule:: audioio
@@ -41,18 +42,13 @@
//|
//| AudioOut can be used to output an analog audio signal on a given pin.
//|
-//| .. class:: AudioOut(pin, sample_source)
+//| .. class:: AudioOut(left_channel, right_channel=None)
//|
-//| Create a AudioOut object associated with the given pin. This allows you to
-//| play audio signals out on the given pin. Sample_source must be a `bytes-like object <https://docs.python.org/3/glossary.html#term-bytes-like-object>`_.
+//| Create a AudioOut object associated with the given pin(s). This allows you to
+//| play audio signals out on the given pin(s).
//|
-//| The sample itself should consist of 16 bit samples and be mono.
-//| Microcontrollers with a lower output resolution will use the highest order
-//| bits to output. For example, the SAMD21 has a 10 bit DAC that ignores the
-//| lowest 6 bits when playing 16 bit samples.
-//|
-//| :param ~microcontroller.Pin pin: The pin to output to
-//| :param bytes-like sample_source: The source of the sample
+//| :param ~microcontroller.Pin left_channel: The pin to output the left channel to
+//| :param ~microcontroller.Pin right_channel: The pin to output the right channel to
//|
//| Simple 8ksps 440 Hz sin wave::
//|
@@ -68,8 +64,9 @@
//| for i in range(length):
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / 18) * (2 ** 15) + 2 ** 15)
//|
-//| sample = audioio.AudioOut(board.SPEAKER, sine_wave)
-//| sample.play(loop=True)
+//| dac = audioio.AudioOut(board.SPEAKER)
+//| sine_wave = audioio.RawSample(sine_wave, mono=True, sample_rate=8000)
+//| dac.play(sine_wave, loop=True)
//| time.sleep(1)
//| sample.stop()
//|
@@ -83,41 +80,42 @@
//| speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
//| speaker_enable.switch_to_output(value=True)
//|
-//| f = open("cplay-5.1-16bit-16khz.wav", "rb")
-//
-//| a = audioio.AudioOut(board.A0, f)
+//| wav = audioio.WaveFile("cplay-5.1-16bit-16khz.wav")
+//| a = audioio.AudioOut(board.A0)
//|
//| print("playing")
-//| a.play()
+//| a.play(wav)
//| while a.playing:
//| pass
//| print("stopped")
//|
-STATIC mp_obj_t audioio_audioout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
- mp_arg_check_num(n_args, n_kw, 2, 2, true);
- mp_obj_t pin_obj = args[0];
- assert_pin(pin_obj, false);
- const mcu_pin_obj_t *pin = MP_OBJ_TO_PTR(pin_obj);
- // We explicitly don't check whether the pin is free because multiple
- // AudioOuts may share it.
+STATIC mp_obj_t audioio_audioout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) {
+ mp_arg_check_num(n_args, n_kw, 1, 2, true);
+ mp_map_t kw_args;
+ mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args);
+ enum { ARG_left_channel, ARG_right_channel };
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_left_channel, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_right_channel, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = mp_const_none} },
+ };
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ mp_obj_t left_channel_obj = args[ARG_left_channel].u_obj;
+ assert_pin(left_channel_obj, false);
+ const mcu_pin_obj_t *left_channel_pin = MP_OBJ_TO_PTR(left_channel_obj);
+
+ mp_obj_t right_channel_obj = args[ARG_right_channel].u_obj;
+ const mcu_pin_obj_t *right_channel_pin = NULL;
+ if (right_channel_obj != mp_const_none) {
+ assert_pin(right_channel_obj, false);
+ right_channel_pin = MP_OBJ_TO_PTR(right_channel_obj);
+ }
// create AudioOut object from the given pin
audioio_audioout_obj_t *self = m_new_obj(audioio_audioout_obj_t);
self->base.type = &audioio_audioout_type;
- mp_buffer_info_t bufinfo;
- if (MP_OBJ_IS_TYPE(args[1], &fatfs_type_fileio)) {
- common_hal_audioio_audioout_construct_from_file(self, pin, MP_OBJ_TO_PTR(args[1]));
- } else if (mp_get_buffer(args[1], &bufinfo, MP_BUFFER_READ)) {
- uint8_t bytes_per_sample = 1;
- if (bufinfo.typecode == 'H') {
- bytes_per_sample = 2;
- } else if (bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
- mp_raise_ValueError("sample_source buffer must be a bytearray or array of type 'H' or 'B'");
- }
- common_hal_audioio_audioout_construct_from_buffer(self, pin, ((uint16_t*)bufinfo.buf), bufinfo.len, bytes_per_sample);
- } else {
- mp_raise_TypeError("sample_source must be a file or bytes-like object");
- }
+ common_hal_audioio_audioout_construct(self, left_channel_pin, right_channel_pin);
return MP_OBJ_FROM_PTR(self);
}
@@ -152,30 +150,38 @@ STATIC mp_obj_t audioio_audioout_obj___exit__(size_t n_args, const mp_obj_t *arg
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioio_audioout___exit___obj, 4, 4, audioio_audioout_obj___exit__);
-//| .. method:: play(loop=False)
+//| .. method:: play(sample, *, loop=False)
//|
//| Plays the sample once when loop=False and continuously when loop=True.
//| Does not block. Use `playing` to block.
//|
+//| Sample must be an `audioio.WaveFile` or `audioio.RawSample`.
+//|
+//| The sample itself should consist of 16 bit samples. Microcontrollers with a lower output
+//| resolution will use the highest order bits to output. For example, the SAMD21 has a 10 bit
+//| DAC that ignores the lowest 6 bits when playing 16 bit samples.
+//|
STATIC mp_obj_t audioio_audioout_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
- enum { ARG_loop };
+ enum { ARG_sample, ARG_loop };
static const mp_arg_t allowed_args[] = {
- { MP_QSTR_loop, MP_ARG_BOOL, {.u_bool = false} },
+ { MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
};
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
- common_hal_audioio_audioout_play(self, args[ARG_loop].u_bool);
+ mp_obj_t sample = args[ARG_sample].u_obj;
+ common_hal_audioio_audioout_play(self, sample, args[ARG_loop].u_bool);
+
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(audioio_audioout_play_obj, 1, audioio_audioout_obj_play);
//| .. method:: stop()
//|
-//| Stops playback of this sample. If another sample is playing instead, it
-//| won't be stopped.
+//| Stops playback.
//|
STATIC mp_obj_t audioio_audioout_obj_stop(mp_obj_t self_in) {
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
@@ -187,7 +193,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_stop_obj, audioio_audioout_obj_stop);
//| .. attribute:: playing
//|
-//| True when the audio sample is being output. (read-only)
+//| True when an audio sample is being output. (read-only)
//|
STATIC mp_obj_t audioio_audioout_obj_get_playing(mp_obj_t self_in) {
audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
@@ -203,34 +209,6 @@ const mp_obj_property_t audioio_audioout_playing_obj = {
(mp_obj_t)&mp_const_none_obj},
};
-//| .. attribute:: frequency
-//|
-//| 32 bit value that dictates how quickly samples are loaded into the DAC
-//| in Hertz (cycles per second). When the sample is looped, this can change
-//| the pitch output without changing the underlying sample.
-//|
-STATIC mp_obj_t audioio_audioout_obj_get_frequency(mp_obj_t self_in) {
- audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
- raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
- return MP_OBJ_NEW_SMALL_INT(common_hal_audioio_audioout_get_frequency(self));
-}
-MP_DEFINE_CONST_FUN_OBJ_1(audioio_audioout_get_frequency_obj, audioio_audioout_obj_get_frequency);
-
-STATIC mp_obj_t audioio_audioout_obj_set_frequency(mp_obj_t self_in, mp_obj_t frequency) {
- audioio_audioout_obj_t *self = MP_OBJ_TO_PTR(self_in);
- raise_error_if_deinited(common_hal_audioio_audioout_deinited(self));
- common_hal_audioio_audioout_set_frequency(self, mp_obj_get_int(frequency));
- return mp_const_none;
-}
-MP_DEFINE_CONST_FUN_OBJ_2(audioio_audioout_set_frequency_obj, audioio_audioout_obj_set_frequency);
-
-const mp_obj_property_t audioio_audioout_frequency_obj = {
- .base.type = &mp_type_property,
- .proxy = {(mp_obj_t)&audioio_audioout_get_frequency_obj,
- (mp_obj_t)&audioio_audioout_set_frequency_obj,
- (mp_obj_t)&mp_const_none_obj},
-};
-
STATIC const mp_rom_map_elem_t audioio_audioout_locals_dict_table[] = {
// Methods
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_audioout_deinit_obj) },
@@ -241,7 +219,6 @@ STATIC const mp_rom_map_elem_t audioio_audioout_locals_dict_table[] = {
// Properties
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audioio_audioout_playing_obj) },
- { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&audioio_audioout_frequency_obj) },
};
STATIC MP_DEFINE_CONST_DICT(audioio_audioout_locals_dict, audioio_audioout_locals_dict_table);
diff --git a/shared-bindings/audioio/AudioOut.h b/shared-bindings/audioio/AudioOut.h
index befd4d35e..55958af79 100644
--- a/shared-bindings/audioio/AudioOut.h
+++ b/shared-bindings/audioio/AudioOut.h
@@ -29,21 +29,18 @@
#include "common-hal/audioio/AudioOut.h"
#include "common-hal/microcontroller/Pin.h"
-#include "extmod/vfs_fat_file.h"
+#include "shared-bindings/audioio/RawSample.h"
extern const mp_obj_type_t audioio_audioout_type;
-void common_hal_audioio_audioout_construct_from_buffer(audioio_audioout_obj_t* self,
- const mcu_pin_obj_t* pin, uint16_t* buffer, uint32_t len, uint8_t bytes_per_sample);
-void common_hal_audioio_audioout_construct_from_file(audioio_audioout_obj_t* self,
- const mcu_pin_obj_t* pin, pyb_file_obj_t* file);
+// left_channel will always be non-NULL but right_channel may be for mono output.
+void common_hal_audioio_audioout_construct(audioio_audioout_obj_t* self,
+ const mcu_pin_obj_t* left_channel, const mcu_pin_obj_t* right_channel);
void common_hal_audioio_audioout_deinit(audioio_audioout_obj_t* self);
bool common_hal_audioio_audioout_deinited(audioio_audioout_obj_t* self);
-void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self, bool loop);
+void common_hal_audioio_audioout_play(audioio_audioout_obj_t* self, mp_obj_t sample, bool loop);
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self);
bool common_hal_audioio_audioout_get_playing(audioio_audioout_obj_t* self);
-uint32_t common_hal_audioio_audioout_get_frequency(audioio_audioout_obj_t* self);
-void common_hal_audioio_audioout_set_frequency(audioio_audioout_obj_t* self, uint32_t frequency);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_AUDIOOUT_H
diff --git a/shared-bindings/audioio/RawSample.c b/shared-bindings/audioio/RawSample.c
new file mode 100644
index 000000000..c4342677a
--- /dev/null
+++ b/shared-bindings/audioio/RawSample.c
@@ -0,0 +1,183 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdint.h>
+
+#include "lib/utils/context_manager_helpers.h"
+#include "py/binary.h"
+#include "py/objproperty.h"
+#include "py/runtime.h"
+#include "shared-bindings/microcontroller/Pin.h"
+#include "shared-bindings/audioio/AudioOut.h"
+#include "shared-bindings/util.h"
+
+//| .. currentmodule:: audioio
+//|
+//| :class:`RawSample` -- A raw audio sample buffer
+//| ========================================================
+//|
+//| An in-memory sound sample
+//|
+//| .. class:: RawSample(buffer, *, channel_count=1, sample_rate=8000)
+//|
+//| Create a RawSample based on the given buffer of signed values. If channel_count is more than
+//| 1 then each channel's samples should rotate. In other words, for a two channel buffer, the
+//| first sample will be for channel 1, the second sample will be for channel two, the third for
+//| channel 1 and so on.
+//|
+//| :param array buffer: An `array.array` with samples
+//| :param int channel_count: The number of channels in the buffer
+//| :param int sample_rate: The desired playback sample rate
+//|
+//| Simple 8ksps 440 Hz sin wave::
+//|
+//| import audioio
+//| import board
+//| import array
+//| import time
+//| import math
+//|
+//| # Generate one period of sine wav.
+//| length = 8000 // 440
+//| sine_wave = array.array("h", [0] * length)
+//| for i in range(length):
+//| sine_wave[i] = int(math.sin(math.pi * 2 * i / 18) * (2 ** 15))
+//|
+//| dac = audioio.AudioOut(board.SPEAKER)
+//| sine_wave = audioio.RawSample(sine_wave)
+//| dac.play(sine_wave, loop=True)
+//| time.sleep(1)
+//| sample.stop()
+//|
+STATIC mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) {
+ mp_arg_check_num(n_args, n_kw, 1, 2, true);
+ mp_map_t kw_args;
+ mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args);
+ enum { ARG_buffer, ARG_channel_count, ARG_sample_rate };
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
+ { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
+ };
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ audioio_rawsample_obj_t *self = m_new_obj(audioio_rawsample_obj_t);
+ self->base.type = &audioio_rawsample_type;
+ mp_buffer_info_t bufinfo;
+ if (mp_get_buffer(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ)) {
+ uint8_t bytes_per_sample = 1;
+ bool signed_samples = bufinfo.typecode == 'b' || bufinfo.typecode == 'h';
+ if (bufinfo.typecode == 'h' || bufinfo.typecode == 'H') {
+ bytes_per_sample = 2;
+ } else if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
+ mp_raise_ValueError("sample_source buffer must be a bytearray or array of type 'h', 'H', 'b' or 'B'");
+ }
+ common_hal_audioio_rawsample_construct(self, ((uint8_t*)bufinfo.buf), bufinfo.len,
+ bytes_per_sample, signed_samples, args[ARG_channel_count].u_int,
+ args[ARG_sample_rate].u_int);
+ } else {
+ mp_raise_TypeError("buffer must be a bytes-like object");
+ }
+
+ return MP_OBJ_FROM_PTR(self);
+}
+
+//| .. method:: deinit()
+//|
+//| Deinitialises the AudioOut and releases any hardware resources for reuse.
+//|
+STATIC mp_obj_t audioio_rawsample_deinit(mp_obj_t self_in) {
+ audioio_rawsample_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ common_hal_audioio_rawsample_deinit(self);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(audioio_rawsample_deinit_obj, audioio_rawsample_deinit);
+
+//| .. method:: __enter__()
+//|
+//| No-op used by Context Managers.
+//|
+// Provided by context manager helper.
+
+//| .. method:: __exit__()
+//|
+//| Automatically deinitializes the hardware when exiting a context. See
+//| :ref:`lifetime-and-contextmanagers` for more info.
+//|
+STATIC mp_obj_t audioio_rawsample_obj___exit__(size_t n_args, const mp_obj_t *args) {
+ (void)n_args;
+ common_hal_audioio_rawsample_deinit(args[0]);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioio_rawsample___exit___obj, 4, 4, audioio_rawsample_obj___exit__);
+
+//| .. attribute:: sample_rate
+//|
+//| 32 bit value that dictates how quickly samples are played in Hertz (cycles per second).
+//| When the sample is looped, this can change the pitch output without changing the underlying
+//| sample. This will not change the sample rate of any active playback. Call ``play`` again to
+//| change it.
+//|
+STATIC mp_obj_t audioio_rawsample_obj_get_sample_rate(mp_obj_t self_in) {
+ audioio_rawsample_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ raise_error_if_deinited(common_hal_audioio_rawsample_deinited(self));
+ return MP_OBJ_NEW_SMALL_INT(common_hal_audioio_rawsample_get_sample_rate(self));
+}
+MP_DEFINE_CONST_FUN_OBJ_1(audioio_rawsample_get_sample_rate_obj, audioio_rawsample_obj_get_sample_rate);
+
+STATIC mp_obj_t audioio_rawsample_obj_set_sample_rate(mp_obj_t self_in, mp_obj_t sample_rate) {
+ audioio_rawsample_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ raise_error_if_deinited(common_hal_audioio_rawsample_deinited(self));
+ common_hal_audioio_rawsample_set_sample_rate(self, mp_obj_get_int(sample_rate));
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_2(audioio_rawsample_set_sample_rate_obj, audioio_rawsample_obj_set_sample_rate);
+
+const mp_obj_property_t audioio_rawsample_sample_rate_obj = {
+ .base.type = &mp_type_property,
+ .proxy = {(mp_obj_t)&audioio_rawsample_get_sample_rate_obj,
+ (mp_obj_t)&audioio_rawsample_set_sample_rate_obj,
+ (mp_obj_t)&mp_const_none_obj},
+};
+
+STATIC const mp_rom_map_elem_t audioio_rawsample_locals_dict_table[] = {
+ // Methods
+ { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_rawsample_deinit_obj) },
+ { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
+ { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioio_rawsample___exit___obj) },
+
+ // Properties
+ { MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&audioio_rawsample_sample_rate_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(audioio_rawsample_locals_dict, audioio_rawsample_locals_dict_table);
+
+const mp_obj_type_t audioio_rawsample_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_RawSample,
+ .make_new = audioio_rawsample_make_new,
+ .locals_dict = (mp_obj_dict_t*)&audioio_rawsample_locals_dict,
+};
diff --git a/shared-bindings/audioio/RawSample.h b/shared-bindings/audioio/RawSample.h
new file mode 100644
index 000000000..0d764b77c
--- /dev/null
+++ b/shared-bindings/audioio/RawSample.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
+#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
+
+#include "common-hal/audioio/AudioOut.h"
+#include "common-hal/microcontroller/Pin.h"
+#include "shared-module/audioio/RawSample.h"
+
+extern const mp_obj_type_t audioio_rawsample_type;
+
+void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t* self,
+ uint8_t* buffer, uint32_t len, uint8_t bytes_per_sample, bool samples_signed,
+ uint8_t channel_count, uint32_t sample_rate);
+
+void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t* self);
+bool common_hal_audioio_rawsample_deinited(audioio_rawsample_obj_t* self);
+uint32_t common_hal_audioio_rawsample_get_sample_rate(audioio_rawsample_obj_t* self);
+void common_hal_audioio_rawsample_set_sample_rate(audioio_rawsample_obj_t* self, uint32_t sample_rate);
+
+#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
diff --git a/shared-bindings/audioio/WaveFile.c b/shared-bindings/audioio/WaveFile.c
new file mode 100644
index 000000000..0a28ad10f
--- /dev/null
+++ b/shared-bindings/audioio/WaveFile.c
@@ -0,0 +1,157 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdint.h>
+
+#include "lib/utils/context_manager_helpers.h"
+// #include "py/binary.h"
+#include "py/objproperty.h"
+#include "py/runtime.h"
+// #include "shared-bindings/microcontroller/Pin.h"
+#include "shared-bindings/audioio/WaveFile.h"
+#include "shared-bindings/util.h"
+
+//| .. currentmodule:: audioio
+//|
+//| :class:`WaveFile` -- Load a wave file for audio playback
+//| ========================================================
+//|
+//| A .wav file prepped for audio playback
+//|
+//| .. class:: WaveFile(filename)
+//|
+//| Load a .wav file for playback with `audioio.AudioOut` or `audiobusio.I2SOut`.
+//|
+//| :param bytes-like file: Already opened wave file
+//|
+//| Playing a wave file from flash::
+//|
+//| import board
+//| import audioio
+//| import digitalio
+//|
+//| # Required for CircuitPlayground Express
+//| speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
+//| speaker_enable.switch_to_output(value=True)
+//|
+//| data = open("cplay-5.1-16bit-16khz.wav", "rb")
+//| wav = audioio.WaveFile(data)
+//| a = audioio.AudioOut(board.A0)
+//|
+//| print("playing")
+//| a.play(wav)
+//| while a.playing:
+//| pass
+//| print("stopped")
+//|
+STATIC mp_obj_t audioio_wavefile_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ mp_arg_check_num(n_args, n_kw, 1, 1, true);
+
+ audioio_wavefile_obj_t *self = m_new_obj(audioio_wavefile_obj_t);
+ self->base.type = &audioio_wavefile_type;
+ if (MP_OBJ_IS_TYPE(args[0], &fatfs_type_fileio)) {
+ common_hal_audioio_wavefile_construct(self, MP_OBJ_TO_PTR(args[0]));
+ } else {
+ mp_raise_TypeError("file must be a file opened in byte mode");
+ }
+
+ return MP_OBJ_FROM_PTR(self);
+}
+
+//| .. method:: deinit()
+//|
+//| Deinitialises the WaveFile and releases all memory resources for reuse.
+//|
+STATIC mp_obj_t audioio_wavefile_deinit(mp_obj_t self_in) {
+ audioio_wavefile_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ common_hal_audioio_wavefile_deinit(self);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(audioio_wavefile_deinit_obj, audioio_wavefile_deinit);
+
+//| .. method:: __enter__()
+//|
+//| No-op used by Context Managers.
+//|
+// Provided by context manager helper.
+
+//| .. method:: __exit__()
+//|
+//| Automatically deinitializes the hardware when exiting a context. See
+//| :ref:`lifetime-and-contextmanagers` for more info.
+//|
+STATIC mp_obj_t audioio_wavefile_obj___exit__(size_t n_args, const mp_obj_t *args) {
+ (void)n_args;
+ common_hal_audioio_wavefile_deinit(args[0]);
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioio_wavefile___exit___obj, 4, 4, audioio_wavefile_obj___exit__);
+
+//| .. attribute:: sample_rate
+//|
+//| 32 bit value that dictates how quickly samples are loaded into the DAC
+//| in Hertz (cycles per second). When the sample is looped, this can change
+//| the pitch output without changing the underlying sample.
+//|
+STATIC mp_obj_t audioio_wavefile_obj_get_sample_rate(mp_obj_t self_in) {
+ audioio_wavefile_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ raise_error_if_deinited(common_hal_audioio_wavefile_deinited(self));
+ return MP_OBJ_NEW_SMALL_INT(common_hal_audioio_wavefile_get_sample_rate(self));
+}
+MP_DEFINE_CONST_FUN_OBJ_1(audioio_wavefile_get_sample_rate_obj, audioio_wavefile_obj_get_sample_rate);
+
+STATIC mp_obj_t audioio_wavefile_obj_set_sample_rate(mp_obj_t self_in, mp_obj_t sample_rate) {
+ audioio_wavefile_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ raise_error_if_deinited(common_hal_audioio_wavefile_deinited(self));
+ common_hal_audioio_wavefile_set_sample_rate(self, mp_obj_get_int(sample_rate));
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_2(audioio_wavefile_set_sample_rate_obj, audioio_wavefile_obj_set_sample_rate);
+
+const mp_obj_property_t audioio_wavefile_sample_rate_obj = {
+ .base.type = &mp_type_property,
+ .proxy = {(mp_obj_t)&audioio_wavefile_get_sample_rate_obj,
+ (mp_obj_t)&audioio_wavefile_set_sample_rate_obj,
+ (mp_obj_t)&mp_const_none_obj},
+};
+
+STATIC const mp_rom_map_elem_t audioio_wavefile_locals_dict_table[] = {
+ // Methods
+ { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_wavefile_deinit_obj) },
+ { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
+ { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioio_wavefile___exit___obj) },
+
+ // Properties
+ { MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&audioio_wavefile_sample_rate_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(audioio_wavefile_locals_dict, audioio_wavefile_locals_dict_table);
+
+const mp_obj_type_t audioio_wavefile_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_WaveFile,
+ .make_new = audioio_wavefile_make_new,
+ .locals_dict = (mp_obj_dict_t*)&audioio_wavefile_locals_dict,
+};
diff --git a/shared-bindings/audioio/WaveFile.h b/shared-bindings/audioio/WaveFile.h
new file mode 100644
index 000000000..b6838ce19
--- /dev/null
+++ b/shared-bindings/audioio/WaveFile.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_WAVEFILE_H
+#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_WAVEFILE_H
+
+#include "common-hal/audioio/AudioOut.h"
+#include "common-hal/microcontroller/Pin.h"
+#include "extmod/vfs_fat_file.h"
+
+extern const mp_obj_type_t audioio_wavefile_type;
+
+void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t* self,
+ pyb_file_obj_t* file);
+
+void common_hal_audioio_wavefile_deinit(audioio_wavefile_obj_t* self);
+bool common_hal_audioio_wavefile_deinited(audioio_wavefile_obj_t* self);
+uint32_t common_hal_audioio_wavefile_get_sample_rate(audioio_wavefile_obj_t* self);
+void common_hal_audioio_wavefile_set_sample_rate(audioio_wavefile_obj_t* self, uint32_t sample_rate);
+
+#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_WAVEFILE_H
diff --git a/shared-bindings/audioio/__init__.c b/shared-bindings/audioio/__init__.c
index 07305a45a..8a00b43d8 100644
--- a/shared-bindings/audioio/__init__.c
+++ b/shared-bindings/audioio/__init__.c
@@ -32,6 +32,7 @@
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-bindings/audioio/__init__.h"
#include "shared-bindings/audioio/AudioOut.h"
+#include "shared-bindings/audioio/WaveFile.h"
//| :mod:`audioio` --- Support for audio input and output
//| ======================================================
@@ -48,6 +49,8 @@
//| :maxdepth: 3
//|
//| AudioOut
+//| RawSample
+//| WaveFile
//|
//| All classes change hardware state and should be deinitialized when they
//| are no longer needed if the program continues after use. To do so, either
@@ -58,6 +61,8 @@
STATIC const mp_rom_map_elem_t audioio_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audioio) },
{ MP_ROM_QSTR(MP_QSTR_AudioOut), MP_ROM_PTR(&audioio_audioout_type) },
+ { MP_ROM_QSTR(MP_QSTR_RawSample), MP_ROM_PTR(&audioio_rawsample_type) },
+ { MP_ROM_QSTR(MP_QSTR_WaveFile), MP_ROM_PTR(&audioio_wavefile_type) },
};
STATIC MP_DEFINE_CONST_DICT(audioio_module_globals, audioio_module_globals_table);
diff --git a/shared-module/audioio/RawSample.c b/shared-module/audioio/RawSample.c
new file mode 100644
index 000000000..e7059bf5a
--- /dev/null
+++ b/shared-module/audioio/RawSample.c
@@ -0,0 +1,94 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "shared-bindings/audioio/RawSample.h"
+
+#include <stdint.h>
+
+#include "shared-module/audioio/RawSample.h"
+
+void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t* self,
+ uint8_t* buffer,
+ uint32_t len,
+ uint8_t bytes_per_sample,
+ bool samples_signed,
+ uint8_t channel_count,
+ uint32_t sample_rate) {
+ self->buffer = buffer;
+ self->bits_per_sample = bytes_per_sample * 8;
+ self->samples_signed = samples_signed;
+ self->len = len;
+ self->channel_count = channel_count;
+ self->sample_rate = sample_rate;
+ self->buffer_read = false;
+}
+
+void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t* self) {
+ self->buffer = NULL;
+}
+bool common_hal_audioio_rawsample_deinited(audioio_rawsample_obj_t* self) {
+ return self->buffer == NULL;
+}
+
+uint32_t common_hal_audioio_rawsample_get_sample_rate(audioio_rawsample_obj_t* self) {
+ return self->sample_rate;
+}
+void common_hal_audioio_rawsample_set_sample_rate(audioio_rawsample_obj_t* self,
+ uint32_t sample_rate) {
+ self->sample_rate = sample_rate;
+}
+
+void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t* self,
+ bool single_channel,
+ uint8_t channel) {
+}
+
+bool audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self,
+ bool single_channel,
+ uint8_t channel,
+ uint8_t** buffer,
+ uint32_t* buffer_length) {
+ *buffer_length = self->len;
+ if (single_channel) {
+ *buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8);
+ } else {
+ *buffer = self->buffer;
+ }
+ return true;
+}
+
+void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t* self, bool single_channel,
+ bool* single_buffer, bool* samples_signed,
+ uint32_t* max_buffer_length, uint8_t* spacing) {
+ *single_buffer = true;
+ *samples_signed = self->samples_signed;
+ *max_buffer_length = self->len;
+ if (single_channel) {
+ *spacing = self->channel_count;
+ } else {
+ *spacing = 1;
+ }
+}
diff --git a/shared-module/audioio/RawSample.h b/shared-module/audioio/RawSample.h
new file mode 100644
index 000000000..1604a1725
--- /dev/null
+++ b/shared-module/audioio/RawSample.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_RAWSAMPLE_H
+#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_RAWSAMPLE_H
+
+#include "py/obj.h"
+
+typedef struct {
+ mp_obj_base_t base;
+ uint8_t* buffer;
+ uint32_t len;
+ uint8_t bits_per_sample;
+ bool samples_signed;
+ uint8_t channel_count;
+ uint32_t sample_rate;
+ bool buffer_read;
+} audioio_rawsample_obj_t;
+
+
+// These are not available from Python because it may be called in an interrupt.
+void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t* self,
+ bool single_channel,
+ uint8_t channel);
+bool audioio_rawsample_get_buffer(audioio_rawsample_obj_t* self,
+ bool single_channel,
+ uint8_t channel,
+ uint8_t** buffer,
+ uint32_t* buffer_length); // length in bytes
+void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t* self, bool single_channel,
+ bool* single_buffer, bool* samples_signed,
+ uint32_t* max_buffer_length, uint8_t* spacing);
+
+#endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_RAWSAMPLE_H
diff --git a/shared-module/audioio/WaveFile.c b/shared-module/audioio/WaveFile.c
new file mode 100644
index 000000000..99283937c
--- /dev/null
+++ b/shared-module/audioio/WaveFile.c
@@ -0,0 +1,230 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "shared-bindings/audioio/WaveFile.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "py/runtime.h"
+
+#include "shared-module/audioio/WaveFile.h"
+
+struct wave_format_chunk {
+ uint16_t audio_format;
+ uint16_t num_channels;
+ uint32_t sample_rate;
+ uint32_t byte_rate;
+ uint16_t block_align;
+ uint16_t bits_per_sample;
+ uint16_t extra_params; // Assumed to be zero below.
+};
+
+void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t* self,
+ pyb_file_obj_t* file) {
+ // Load the wave
+ self->file = file;
+ uint8_t chunk_header[16];
+ f_rewind(&self->file->fp);
+ UINT bytes_read;
+ f_read(&self->file->fp, chunk_header, 16, &bytes_read);
+ if (bytes_read != 16 ||
+ memcmp(chunk_header, "RIFF", 4) != 0 ||
+ memcmp(chunk_header + 8, "WAVEfmt ", 8) != 0) {
+ mp_raise_ValueError("Invalid wave file");
+ }
+ uint32_t format_size;
+ f_read(&self->file->fp, &format_size, 4, &bytes_read);
+ if (bytes_read != 4 ||
+ format_size > sizeof(struct wave_format_chunk)) {
+ mp_raise_ValueError("Invalid format chunk size");
+ }
+ struct wave_format_chunk format;
+ f_read(&self->file->fp, &format, format_size, &bytes_read);
+ if (bytes_read != format_size) {
+ }
+
+ if (format.audio_format != 1 ||
+ format.num_channels > 2 ||
+ format.bits_per_sample > 16 ||
+ (format_size == 18 &&
+ format.extra_params != 0)) {
+ mp_raise_ValueError("Unsupported format");
+ }
+ // Get the sample_rate
+ self->sample_rate = format.sample_rate;
+ self->len = 512;
+ self->channel_count = format.num_channels;
+ self->bits_per_sample = format.bits_per_sample;
+
+ // TODO(tannewt): Skip any extra chunks that occur before the data section.
+
+ uint8_t data_tag[4];
+ f_read(&self->file->fp, &data_tag, 4, &bytes_read);
+ if (bytes_read != 4 ||
+ memcmp((uint8_t *) data_tag, "data", 4) != 0) {
+ mp_raise_ValueError("Data chunk must follow fmt chunk");
+ }
+
+ uint32_t data_length;
+ f_read(&self->file->fp, &data_length, 4, &bytes_read);
+ if (bytes_read != 4) {
+ mp_raise_ValueError("Invalid file");
+ }
+ self->file_length = data_length;
+ self->data_start = self->file->fp.fptr;
+
+ // Try to allocate two buffers, one will be loaded from file and the other
+ // DMAed to DAC.
+ self->buffer = m_malloc(self->len, false);
+ if (self->buffer == NULL) {
+ common_hal_audioio_wavefile_deinit(self);
+ mp_raise_msg(&mp_type_MemoryError, "");
+ }
+
+ self->second_buffer = m_malloc(self->len, false);
+ if (self->second_buffer == NULL) {
+ common_hal_audioio_wavefile_deinit(self);
+ mp_raise_msg(&mp_type_MemoryError, "");
+ }
+}
+
+void common_hal_audioio_wavefile_deinit(audioio_wavefile_obj_t* self) {
+ self->buffer = NULL;
+}
+
+bool common_hal_audioio_wavefile_deinited(audioio_wavefile_obj_t* self) {
+ return self->buffer == NULL;
+}
+
+uint32_t common_hal_audioio_wavefile_get_sample_rate(audioio_wavefile_obj_t* self) {
+ return self->sample_rate;
+}
+
+void common_hal_audioio_wavefile_set_sample_rate(audioio_wavefile_obj_t* self,
+ uint32_t sample_rate) {
+ self->sample_rate = sample_rate;
+}
+
+bool audioio_wavefile_samples_signed(audioio_wavefile_obj_t* self) {
+ return self->bits_per_sample > 8;
+}
+
+uint32_t audioio_wavefile_max_buffer_length(audioio_wavefile_obj_t* self) {
+ return 512;
+}
+
+void audioio_wavefile_reset_buffer(audioio_wavefile_obj_t* self,
+ bool single_channel,
+ uint8_t channel) {
+ if (single_channel && channel == 1) {
+ return;
+ }
+ // We don't reset the buffer index in case we're looping and we have an odd number of buffer
+ // loads
+ self->bytes_remaining = self->file_length;
+ f_lseek(&self->file->fp, self->data_start);
+ self->read_count = 0;
+ self->left_read_count = 0;
+ self->right_read_count = 0;
+}
+
+bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self,
+ bool single_channel,
+ uint8_t channel,
+ uint8_t** buffer,
+ uint32_t* buffer_length) {
+ if (!single_channel) {
+ channel = 0;
+ }
+
+ uint32_t channel_read_count = self->left_read_count;
+ if (channel == 1) {
+ channel_read_count = self->right_read_count;
+ }
+
+ bool need_more_data = self->read_count == channel_read_count;
+
+ if (self->bytes_remaining == 0 && need_more_data) {
+ *buffer = NULL;
+ *buffer_length = 0;
+ return true;
+ }
+
+ if (need_more_data) {
+ uint16_t num_bytes_to_load = self->len;
+ if (num_bytes_to_load > self->bytes_remaining) {
+ num_bytes_to_load = self->bytes_remaining;
+ }
+ UINT length_read;
+ if (self->buffer_index % 2 == 1) {
+ *buffer = self->second_buffer;
+ } else {
+ *buffer = self->buffer;
+ }
+ f_read(&self->file->fp, *buffer, num_bytes_to_load, &length_read);
+ *buffer_length = length_read;
+ if (self->buffer_index % 2 == 1) {
+ self->second_buffer_length = length_read;
+ } else {
+ self->buffer_length = length_read;
+ }
+ self->bytes_remaining -= length_read;
+ self->buffer_index += 1;
+ self->read_count += 1;
+ }
+
+ uint32_t buffers_back = self->read_count - 1 - channel_read_count;
+ if ((self->buffer_index - buffers_back) % 2 == 0) {
+ *buffer = self->second_buffer;
+ *buffer_length = self->second_buffer_length;
+ } else {
+ *buffer = self->buffer;
+ *buffer_length = self->buffer_length;
+ }
+
+ if (channel == 0) {
+ self->left_read_count += 1;
+ } else if (channel == 1) {
+ self->right_read_count += 1;
+ *buffer = *buffer + self->bits_per_sample / 8;
+ }
+
+ return self->bytes_remaining == 0;
+}
+
+void audioio_wavefile_get_buffer_structure(audioio_wavefile_obj_t* self, bool single_channel,
+ bool* single_buffer, bool* samples_signed,
+ uint32_t* max_buffer_length, uint8_t* spacing) {
+ *single_buffer = false;
+ *samples_signed = self->bits_per_sample > 8;
+ *max_buffer_length = 512;
+ if (single_channel) {
+ *spacing = self->channel_count;
+ } else {
+ *spacing = 1;
+ }
+}
diff --git a/shared-module/audioio/WaveFile.h b/shared-module/audioio/WaveFile.h
new file mode 100644
index 000000000..9698d3c93
--- /dev/null
+++ b/shared-module/audioio/WaveFile.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_WAVEFILE_H
+#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_WAVEFILE_H
+
+#include "py/obj.h"
+
+typedef struct {
+ mp_obj_base_t base;
+ uint8_t* buffer;
+ uint32_t buffer_length;
+ uint8_t* second_buffer;
+ uint32_t second_buffer_length;
+ uint32_t file_length; // In bytes
+ uint16_t data_start; // Where the data values start
+ uint8_t bits_per_sample;
+ uint16_t buffer_index;
+ uint32_t bytes_remaining;
+
+ uint8_t channel_count;
+ uint16_t sample_rate;
+
+ uint32_t len;
+ pyb_file_obj_t* file;
+
+ uint32_t read_count;
+ uint32_t left_read_count;
+ uint32_t right_read_count;
+} audioio_wavefile_obj_t;
+
+// These are not available from Python because it may be called in an interrupt.
+void audioio_wavefile_reset_buffer(audioio_wavefile_obj_t* self,
+ bool single_channel,
+ uint8_t channel);
+bool audioio_wavefile_get_buffer(audioio_wavefile_obj_t* self,
+ bool single_channel,
+ uint8_t channel,
+ uint8_t** buffer,
+ uint32_t* buffer_length); // length in bytes
+void audioio_wavefile_get_buffer_structure(audioio_wavefile_obj_t* self, bool single_channel,
+ bool* single_buffer, bool* samples_signed,
+ uint32_t* max_buffer_length, uint8_t* spacing);
+
+#endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_WAVEFILE_H