summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Shawcroft <scott.shawcroft@gmail.com>2017-05-10 18:21:02 -0700
committerScott Shawcroft <scott.shawcroft@gmail.com>2017-05-15 19:52:38 -0700
commit3f5028c666008f58785e86dd477065e3ca99d99f (patch)
tree5b9ff5065012189e028499e15483610aaa36a4bb
parent292ba89ac2cf6b93828597e5c4baff4845432ee8 (diff)
atmel-samd: Support wav file playback. Tested up to 16bit 22.1khz. Must be mono file!
SD card support may work but likely needs buffer tuning. Its untested. Fixes #105
-rw-r--r--atmel-samd/Makefile1
-rw-r--r--atmel-samd/background.c34
-rw-r--r--atmel-samd/background.h32
-rw-r--r--atmel-samd/common-hal/audioio/AudioOut.c369
-rw-r--r--atmel-samd/common-hal/audioio/AudioOut.h18
-rw-r--r--atmel-samd/common-hal/microcontroller/Pin.c2
-rw-r--r--atmel-samd/mpconfigport.h12
-rw-r--r--atmel-samd/mphalport.c27
-rw-r--r--atmel-samd/shared_dma.c1
-rw-r--r--shared-bindings/audioio/AudioOut.c20
10 files changed, 446 insertions, 70 deletions
diff --git a/atmel-samd/Makefile b/atmel-samd/Makefile
index b87078fc0..8afd8b50b 100644
--- a/atmel-samd/Makefile
+++ b/atmel-samd/Makefile
@@ -183,6 +183,7 @@ SRC_ASF = $(addprefix asf/sam0/,\
SRC_C = \
access_vfs.c \
autoreload.c \
+ background.c \
builtin_open.c \
fatfs_port.c \
flash_api.c \
diff --git a/atmel-samd/background.c b/atmel-samd/background.c
new file mode 100644
index 000000000..b43461308
--- /dev/null
+++ b/atmel-samd/background.c
@@ -0,0 +1,34 @@
+/*
+ * 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 "background.h"
+
+#include "asf/common/services/usb/class/msc/device/udi_msc.h"
+#include "common-hal/audioio/AudioOut.h"
+
+void run_background_tasks(void) {
+ audioout_background();
+ udi_msc_process_trans();
+}
diff --git a/atmel-samd/background.h b/atmel-samd/background.h
new file mode 100644
index 000000000..57b00d068
--- /dev/null
+++ b/atmel-samd/background.h
@@ -0,0 +1,32 @@
+/*
+ * 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_ATMEL_SAMD_BACKGROUND_H__
+#define __MICROPY_INCLUDED_ATMEL_SAMD_BACKGROUND_H__
+
+void run_background_tasks(void);
+
+#endif // __MICROPY_INCLUDED_ATMEL_SAMD_BACKGROUND_H__
diff --git a/atmel-samd/common-hal/audioio/AudioOut.c b/atmel-samd/common-hal/audioio/AudioOut.c
index 9d7b922f1..9fb1a6ebe 100644
--- a/atmel-samd/common-hal/audioio/AudioOut.c
+++ b/atmel-samd/common-hal/audioio/AudioOut.c
@@ -25,6 +25,7 @@
*/
#include <stdint.h>
+#include <string.h>
#include "py/gc.h"
#include "py/mperrno.h"
@@ -36,6 +37,7 @@
#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"
@@ -49,8 +51,8 @@ extern uint8_t timer_refcount[TC_INST_NUM + TCC_INST_NUM];
extern const uint16_t prescaler[8];
// This timer is shared amongst all AudioOut objects under the assumption that
-// the code is single threaded. The audioout_tc_instance, audioout_dac_instance,
-// audioout_tc_event, and audioout_dac_event pointers live in
+// 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
@@ -59,17 +61,33 @@ static audioio_audioout_obj_t* active_audioout;
static uint8_t refcount = 0;
+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 audioout_reset(void) {
// Only reset DMA. PWMOut will reset the timer. Other code will reset the DAC.
refcount = 0;
- MP_STATE_VM(audioout_tc_instance) = NULL;
+ MP_STATE_VM(audioout_sample_timer) = NULL;
+ MP_STATE_VM(audioout_block_counter) = NULL;
MP_STATE_VM(audioout_dac_instance) = NULL;
- if (MP_STATE_VM(audioout_tc_event) != NULL) {
- events_detach_user(MP_STATE_VM(audioout_tc_event), EVSYS_ID_USER_DAC_START);
- events_release(MP_STATE_VM(audioout_tc_event));
+ 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(audioout_block_event) != NULL) {
+ events_release(MP_STATE_VM(audioout_block_event));
}
- MP_STATE_VM(audioout_tc_event) = NULL;
+ MP_STATE_VM(audioout_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);
@@ -80,7 +98,130 @@ void audioout_reset(void) {
dma_abort_job(&audio_dma);
}
-static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t* pin, uint32_t frequency) {
+// WARN(tannewt): DO NOT print from here. It calls background tasks and causes a
+// stack overflow.
+void audioout_background(void) {
+ if (MP_STATE_VM(audioout_block_counter) != NULL &&
+ active_audioout != NULL &&
+ active_audioout->last_loaded_block < tc_get_count_value(MP_STATE_VM(audioout_block_counter))) {
+ uint8_t* buffer;
+ if (tc_get_count_value(MP_STATE_VM(audioout_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->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 allocate_block_counter(audioio_audioout_obj_t* self) {
+ // 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) {
+ common_hal_audioio_audioout_deinit(self);
+ mp_raise_RuntimeError("All timers in use");
+ return;
+ }
+ MP_STATE_VM(audioout_block_counter) = gc_alloc(sizeof(struct tc_module), false);
+ if (MP_STATE_VM(audioout_block_counter) == NULL) {
+ common_hal_audioio_audioout_deinit(self);
+ mp_raise_msg(&mp_type_MemoryError, "");
+ }
+
+ // 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(audioout_block_counter), t, &config_tc) != STATUS_OK) {
+ common_hal_audioio_audioout_deinit(self);
+ mp_raise_OSError(MP_EIO);
+ return;
+ };
+
+ 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(audioout_block_counter), &events_tc);
+
+ // Connect the timer overflow event, which happens at the target frequency,
+ // to the DAC conversion trigger.
+ MP_STATE_VM(audioout_block_event) = gc_alloc(sizeof(struct events_resource), false);
+ if (MP_STATE_VM(audioout_block_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 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(audioout_block_event), &config) != STATUS_OK ||
+ events_attach_user(MP_STATE_VM(audioout_block_event), user) != STATUS_OK) {
+ common_hal_audioio_audioout_deinit(self);
+ mp_raise_OSError(MP_EIO);
+ return;
+ }
+
+ tc_enable(MP_STATE_VM(audioout_block_counter));
+ tc_stop_counter(MP_STATE_VM(audioout_block_counter));
+}
+
+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
@@ -124,8 +265,8 @@ 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_tc_instance) = gc_alloc(sizeof(struct tc_module), false);
- if (MP_STATE_VM(audioout_tc_instance) == NULL) {
+ 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, "");
}
@@ -136,7 +277,7 @@ static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t*
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_tc_instance), t, &config_tc) != STATUS_OK) {
+ 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;
@@ -146,15 +287,15 @@ static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t*
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_tc_instance), &events_tc);
+ tc_enable_events(MP_STATE_VM(audioout_sample_timer), &events_tc);
- tc_enable(MP_STATE_VM(audioout_tc_instance));
- tc_stop_counter(MP_STATE_VM(audioout_tc_instance));
+ tc_enable(MP_STATE_VM(audioout_sample_timer));
+ tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
// Connect the timer overflow event, which happens at the target frequency,
// to the DAC conversion trigger.
- MP_STATE_VM(audioout_tc_event) = gc_alloc(sizeof(struct events_resource), false);
- if (MP_STATE_VM(audioout_tc_event) == NULL) {
+ 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, "");
}
@@ -178,8 +319,8 @@ static void shared_construct(audioio_audioout_obj_t* self, const mcu_pin_obj_t*
config.generator = generator;
config.path = EVENTS_PATH_ASYNCHRONOUS;
- if (events_allocate(MP_STATE_VM(audioout_tc_event), &config) != STATUS_OK ||
- events_attach_user(MP_STATE_VM(audioout_tc_event), EVSYS_ID_USER_DAC_START) != STATUS_OK) {
+ 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;
@@ -214,10 +355,11 @@ void common_hal_audioio_audioout_construct_from_buffer(audioio_audioout_obj_t* s
}
if (refcount == 0) {
refcount++;
- shared_construct(self, pin, 8000);
+ shared_construct(self, pin);
}
self->buffer = (uint8_t*) buffer;
+ self->second_buffer = NULL;
// Input len is a count. Internal len is in bytes.
self->len = 2 * len;
self->frequency = 8000;
@@ -226,27 +368,109 @@ void common_hal_audioio_audioout_construct_from_buffer(audioio_audioout_obj_t* s
void common_hal_audioio_audioout_construct_from_file(audioio_audioout_obj_t* self,
const mcu_pin_obj_t* pin,
pyb_file_obj_t* file) {
- mp_raise_NotImplementedError("File playback not supported yet.");
+ self->pin = pin;
+ if (pin != &pin_PA02) {
+ mp_raise_ValueError("Invalid pin");
+ }
+ if (refcount == 0) {
+ refcount++;
+ shared_construct(self, pin);
+ }
+ if (MP_STATE_VM(audioout_block_counter) == NULL) {
+ allocate_block_counter(self);
+ }
+
+ // 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, "");
+ }
}
void common_hal_audioio_audioout_deinit(audioio_audioout_obj_t* self) {
refcount--;
if (refcount == 0) {
- if (MP_STATE_VM(audioout_tc_instance) != NULL) {
- tc_reset(MP_STATE_VM(audioout_tc_instance));
- gc_free(MP_STATE_VM(audioout_tc_instance));
- MP_STATE_VM(audioout_tc_instance) = NULL;
+ 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_tc_event) != NULL) {
- events_detach_user(MP_STATE_VM(audioout_tc_event), EVSYS_ID_USER_DAC_START);
- events_release(MP_STATE_VM(audioout_tc_event));
- gc_free(MP_STATE_VM(audioout_tc_event));
- MP_STATE_VM(audioout_tc_event) = 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));
@@ -267,17 +491,17 @@ static void set_timer_frequency(uint32_t frequency) {
break;
}
}
- uint8_t old_divisor = MP_STATE_VM(audioout_tc_instance)->hw->COUNT16.CTRLA.bit.PRESCALER;
+ uint8_t old_divisor = MP_STATE_VM(audioout_sample_timer)->hw->COUNT16.CTRLA.bit.PRESCALER;
if (new_divisor != old_divisor) {
- tc_disable(MP_STATE_VM(audioout_tc_instance));
- MP_STATE_VM(audioout_tc_instance)->hw->COUNT16.CTRLA.bit.PRESCALER = new_divisor;
- tc_enable(MP_STATE_VM(audioout_tc_instance));
+ 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_tc_instance))) {
+ while (tc_is_syncing(MP_STATE_VM(audioout_sample_timer))) {
/* Wait for sync */
}
- MP_STATE_VM(audioout_tc_instance)->hw->COUNT16.CC[0].reg = new_top;
- while (tc_is_syncing(MP_STATE_VM(audioout_tc_instance))) {
+ 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 */
}
}
@@ -286,35 +510,85 @@ 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_tc_instance));
+ tc_stop_counter(MP_STATE_VM(audioout_sample_timer));
dma_abort_job(&audio_dma);
} else {
dac_enable(MP_STATE_VM(audioout_dac_instance));
}
struct dma_descriptor_config descriptor_config;
dma_descriptor_get_config_defaults(&descriptor_config);
- descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
+ 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 / 2;
+ 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);
- if (loop) {
- descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor);
+ 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;
+ }
+ }
+ }
active_audioout = self;
dma_start_transfer_job(&audio_dma);
+ if (MP_STATE_VM(audioout_block_counter) != NULL) {
+ tc_start_counter(MP_STATE_VM(audioout_block_counter));
+ }
set_timer_frequency(self->frequency);
- tc_start_counter(MP_STATE_VM(audioout_tc_instance));
+ tc_start_counter(MP_STATE_VM(audioout_sample_timer));
}
void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self) {
- if (common_hal_audioio_audioout_get_playing(self)) {
- tc_stop_counter(MP_STATE_VM(audioout_tc_instance));
+ if (active_audioout == self) {
+ if (MP_STATE_VM(audioout_block_counter) != NULL) {
+ tc_stop_counter(MP_STATE_VM(audioout_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));
@@ -323,6 +597,9 @@ void common_hal_audioio_audioout_stop(audioio_audioout_obj_t* self) {
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;
diff --git a/atmel-samd/common-hal/audioio/AudioOut.h b/atmel-samd/common-hal/audioio/AudioOut.h
index 7f4d83040..ffbccfee2 100644
--- a/atmel-samd/common-hal/audioio/AudioOut.h
+++ b/atmel-samd/common-hal/audioio/AudioOut.h
@@ -30,6 +30,7 @@
#include "common-hal/microcontroller/Pin.h"
#include "asf/sam0/drivers/tc/tc.h"
+#include "extmod/vfs_fat_file.h"
#include "py/obj.h"
typedef struct {
@@ -37,11 +38,24 @@ typedef struct {
const mcu_pin_obj_t *pin;
uint32_t frequency;
uint8_t* buffer;
- // TODO(tannewt): Add a second buffer for double buffering from a file system.
- // Length of buffer in bytes.
+
+ // 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;
} audioio_audioout_obj_t;
void audioout_reset(void);
+void audioout_background(void);
+
#endif // __MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_AUDIOIO_AUDIOOUT_H__
diff --git a/atmel-samd/common-hal/microcontroller/Pin.c b/atmel-samd/common-hal/microcontroller/Pin.c
index 37ccb4f9d..d814eb89c 100644
--- a/atmel-samd/common-hal/microcontroller/Pin.c
+++ b/atmel-samd/common-hal/microcontroller/Pin.c
@@ -58,7 +58,7 @@ void reset_all_pins(void) {
// Configure SWD
system_pinmux_get_config_defaults(&config);
config.mux_position = 0x6;
- system_pinmux_group_set_config(&(PORT->Group[0]), PIN_PA30 | PIN_PA31, &config);
+ system_pinmux_group_set_config(&(PORT->Group[0]), PORT_PA30 | PORT_PA31, &config);
#ifdef MICROPY_HW_NEOPIXEL
neopixel_in_use = false;
diff --git a/atmel-samd/mpconfigport.h b/atmel-samd/mpconfigport.h
index a43a68440..dd1d16d05 100644
--- a/atmel-samd/mpconfigport.h
+++ b/atmel-samd/mpconfigport.h
@@ -192,16 +192,18 @@ extern const struct _mp_obj_module_t usb_hid_module;
#define MICROPY_PORT_ROOT_POINTERS \
const char *readline_hist[8]; \
vstr_t *repl_line; \
- struct tc_module* audioout_tc_instance; \
+ struct tc_module* audioout_sample_timer; \
+ struct tc_module* audioout_block_counter; \
struct dac_module* audioout_dac_instance; \
- struct events_resource* audioout_tc_event; \
+ struct events_resource* audioout_sample_event; \
+ struct events_resource* audioout_block_event; \
struct events_resource* audioout_dac_event; \
struct tc_module* pulseout_tc_instance; \
FLASH_ROOT_POINTERS \
-bool udi_msc_process_trans(void);
-#define MICROPY_VM_HOOK_LOOP udi_msc_process_trans();
-#define MICROPY_VM_HOOK_RETURN udi_msc_process_trans();
+void run_background_tasks(void);
+#define MICROPY_VM_HOOK_LOOP run_background_tasks();
+#define MICROPY_VM_HOOK_RETURN run_background_tasks();
#define CIRCUITPY_AUTORELOAD_DELAY_MS 500
#define CIRCUITPY_BOOT_OUTPUT_FILE "/flash/boot_out.txt"
diff --git a/atmel-samd/mphalport.c b/atmel-samd/mphalport.c
index 0694c2b91..8d5e98ca3 100644
--- a/atmel-samd/mphalport.c
+++ b/atmel-samd/mphalport.c
@@ -226,23 +226,18 @@ void mp_hal_stdout_tx_strn(const char *str, size_t len) {
}
void mp_hal_delay_ms(mp_uint_t delay) {
- // If mass storage is enabled measure the time ourselves and run any mass
- // storage transactions in the meantime.
- if (mp_msc_enabled) {
- uint64_t start_tick = ticks_ms;
- uint64_t duration = 0;
- while (duration < delay) {
- #ifdef MICROPY_VM_HOOK_LOOP
- MICROPY_VM_HOOK_LOOP
- #endif
- // Check to see if we've been CTRL-Ced by autoreload or the user.
- if(MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))) {
- break;
- }
- duration = (ticks_ms - start_tick);
+ uint64_t start_tick = ticks_ms;
+ uint64_t duration = 0;
+ while (duration < delay) {
+ #ifdef MICROPY_VM_HOOK_LOOP
+ MICROPY_VM_HOOK_LOOP
+ #endif
+ // Check to see if we've been CTRL-Ced by autoreload or the user.
+ if(MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))) {
+ break;
}
- } else {
- delay_ms(delay);
+ duration = (ticks_ms - start_tick);
+ // TODO(tannewt): Go to sleep for a little while while we wait.
}
}
diff --git a/atmel-samd/shared_dma.c b/atmel-samd/shared_dma.c
index 6b6274d89..9c82b249b 100644
--- a/atmel-samd/shared_dma.c
+++ b/atmel-samd/shared_dma.c
@@ -43,6 +43,7 @@ void init_shared_dma(void) {
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;
dma_allocate(&audio_dma, &config);
// Turn on the transfer complete interrupt so that the job_status changes to done.
g_chan_interrupt_flag[audio_dma.channel_id] |= (1UL << DMA_CALLBACK_TRANSFER_DONE);
diff --git a/shared-bindings/audioio/AudioOut.c b/shared-bindings/audioio/AudioOut.c
index 81117abb2..a765994d5 100644
--- a/shared-bindings/audioio/AudioOut.c
+++ b/shared-bindings/audioio/AudioOut.c
@@ -71,6 +71,26 @@
//| time.sleep(1)
//| sample.stop()
//|
+//| 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)
+//|
+//| f = open("cplay-5.1-16bit-16khz.wav", "rb")
+//
+//| a = audioio.AudioOut(board.A0, f)
+//|
+//| print("playing")
+//| a.play()
+//| 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];