diff --git a/atmel-samd/Makefile b/atmel-samd/Makefile index b87078fc07..8afd8b50bb 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 0000000000..b434613081 --- /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 0000000000..57b00d0688 --- /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 9d7b922f1c..9fb1a6ebed 100644 --- a/atmel-samd/common-hal/audioio/AudioOut.c +++ b/atmel-samd/common-hal/audioio/AudioOut.c @@ -25,6 +25,7 @@ */ #include +#include #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_tc_event) = NULL; + 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_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 7f4d830409..ffbccfee2d 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 37ccb4f9d1..d814eb89ca 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 a43a684408..dd1d16d05a 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 0694c2b91e..8d5e98ca3a 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 6b6274d891..9c82b249ba 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 81117abb27..a765994d5a 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];