diff options
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 |
