From cfea51ec68504710ee683e77fada7adf4c3cffd4 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Thu, 26 Apr 2018 12:51:37 -0700 Subject: [PATCH] Re-enable PDMIn without ASF and using the helpers added with I2SOut. The API is almost the same except the frequency attribute has been renamed to sample_rate so that its less likely to be confused with frequencies within the audio itself. Fixes #263. --- ports/atmel-samd/Makefile | 14 +- ports/atmel-samd/audio_dma.c | 1 - ports/atmel-samd/audio_dma.h | 2 + .../atmel-samd/common-hal/audiobusio/I2SOut.c | 22 +- .../atmel-samd/common-hal/audiobusio/PDMIn.c | 298 +++++++++++------- .../atmel-samd/common-hal/audiobusio/PDMIn.h | 3 +- ports/atmel-samd/events.c | 23 +- ports/atmel-samd/events.h | 1 + ports/atmel-samd/i2s.c | 84 +++++ ports/atmel-samd/i2s.h | 41 +++ shared-bindings/audiobusio/PDMIn.c | 42 +-- shared-bindings/audiobusio/PDMIn.h | 4 +- shared-bindings/audiobusio/__init__.c | 2 +- 13 files changed, 370 insertions(+), 167 deletions(-) create mode 100644 ports/atmel-samd/i2s.c create mode 100644 ports/atmel-samd/i2s.h diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile index 6b661cd849..bb2d92b4a5 100644 --- a/ports/atmel-samd/Makefile +++ b/ports/atmel-samd/Makefile @@ -299,13 +299,13 @@ SRC_COMMON_HAL = \ usb_hid/Device.c \ audioio/__init__.c \ audioio/AudioOut.c \ -# audiobusio/PDMIn.c \ - touchio/__init__.c \ +# touchio/__init__.c \ touchio/TouchIn.c \ ifeq ($(INTERNAL_LIBM),1) SRC_LIBM = $(addprefix lib/,\ libm/math.c \ + libm/roundf.c \ libm/fmodf.c \ libm/nearbyintf.c \ libm/ef_sqrt.c \ @@ -363,16 +363,14 @@ SRC_SHARED_MODULE = \ uheap/__init__.c \ ustack/__init__.c -ifeq ($(CHIP_FAMILY),samd21) -SRC_COMMON_HAL += \ - audiobusio/__init__.c \ - audiobusio/I2SOut.c -endif +# The smallest SAMD51 packages don't have I2S. Everything else does. ifneq ($(CHIP_VARIANT),SAMD51G18A) ifneq ($(CHIP_VARIANT),SAMD51G19A) SRC_COMMON_HAL += \ audiobusio/__init__.c \ - audiobusio/I2SOut.c + audiobusio/I2SOut.c \ + audiobusio/PDMIn.c + SRC_C += i2s.c endif endif diff --git a/ports/atmel-samd/audio_dma.c b/ports/atmel-samd/audio_dma.c index 7030009d98..227a2e1881 100644 --- a/ports/atmel-samd/audio_dma.c +++ b/ports/atmel-samd/audio_dma.c @@ -267,7 +267,6 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t* dma, 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. diff --git a/ports/atmel-samd/audio_dma.h b/ports/atmel-samd/audio_dma.h index deecd27f30..fd2e6a153e 100644 --- a/ports/atmel-samd/audio_dma.h +++ b/ports/atmel-samd/audio_dma.h @@ -64,6 +64,8 @@ uint8_t audiosample_channel_count(mp_obj_t sample_obj); void audio_dma_init(audio_dma_t* dma); void audio_dma_reset(void); +uint8_t find_free_audio_dma_channel(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. diff --git a/ports/atmel-samd/common-hal/audiobusio/I2SOut.c b/ports/atmel-samd/common-hal/audiobusio/I2SOut.c index ec21636d00..65dae5dcbd 100644 --- a/ports/atmel-samd/common-hal/audiobusio/I2SOut.c +++ b/ports/atmel-samd/common-hal/audiobusio/I2SOut.c @@ -48,7 +48,8 @@ #include "audio_dma.h" #include "clocks.h" #include "events.h" -#include "samd21_pins.h" +#include "i2s.h" +#include "pins.h" #include "shared_dma.h" #include "timers.h" @@ -155,18 +156,7 @@ void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self, 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 + turn_on_i2s(); if (I2S->CTRLA.bit.ENABLE == 0) { I2S->CTRLA.bit.SWRST = 1; @@ -281,8 +271,7 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, } // Configure the I2S peripheral - I2S->CTRLA.bit.ENABLE = 0; - while (I2S->SYNCBUSY.bit.ENABLE == 1) {} + i2s_set_enable(false); I2S->CLKCTRL[self->clock_unit].reg = clkctrl; #ifdef SAMD21 @@ -296,8 +285,7 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, 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) {} + i2s_set_enable(true); #ifdef SAMD21 uint32_t tx_register = (uint32_t) &I2S->DATA[self->serializer].reg; diff --git a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c index e0ac3f9a3e..7788335d1c 100644 --- a/ports/atmel-samd/common-hal/audiobusio/PDMIn.c +++ b/ports/atmel-samd/common-hal/audiobusio/PDMIn.c @@ -37,17 +37,32 @@ #include "shared-bindings/audiobusio/PDMIn.h" #include "shared-bindings/microcontroller/Pin.h" -#include "samd21_pins.h" +#include "atmel_start_pins.h" +#include "hal/include/hal_gpio.h" +#include "hal/utils/include/utils.h" +#include "audio_dma.h" +#include "clocks.h" +#include "events.h" +#include "i2s.h" +#include "pins.h" #include "shared_dma.h" #include "tick.h" #define OVERSAMPLING 64 -#define SAMPLES_PER_BUFFER 32 +#define SAMPLES_PER_BUFFER 64 // MEMS microphones must be clocked at at least 1MHz. #define MIN_MIC_CLOCK 1000000 +#ifdef SAMD21 +#define SERCTRL(name) I2S_SERCTRL_ ## name +#endif + +#ifdef SAMD51 +#define SERCTRL(name) I2S_RXCTRL_ ## name +#endif + void pdmin_reset(void) { while (I2S->SYNCBUSY.reg & I2S_SYNCBUSY_ENABLE) {} I2S->INTENCLR.reg = I2S_INTENCLR_MASK; @@ -60,20 +75,33 @@ void pdmin_reset(void) { void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self, const mcu_pin_obj_t* clock_pin, const mcu_pin_obj_t* data_pin, - uint32_t frequency, + uint32_t sample_rate, uint8_t bit_depth, bool mono, uint8_t oversample) { self->clock_pin = clock_pin; // PA10, PA20 -> SCK0, PB11 -> SCK1 - if (clock_pin == &pin_PA10 - #ifdef PIN_PA20 - || clock_pin == &pin_PA20 + #ifdef SAMD21 + if (clock_pin == &pin_PA10 + #ifdef PIN_PA20 + || clock_pin == &pin_PA20 + #endif + ) { + self->clock_unit = 0; + #ifdef PIN_PB11 + } else if (clock_pin == &pin_PB11) { + self->clock_unit = 1; + #endif #endif + #ifdef SAMD51 + if (clock_pin == &pin_PA10 || clock_pin == &pin_PB16) { + self->clock_unit = 0; + } else if (clock_pin == &pin_PB12 + #ifdef PIN_PB28 + || data_pin == &pin_PB28) { + #else ) { - self->clock_unit = 0; - #ifdef PIN_PB11 - } else if (clock_pin == &pin_PB11) { - self->clock_unit = 1; + #endif + self->clock_unit = 1; #endif } else { mp_raise_ValueError("Invalid clock pin"); @@ -81,80 +109,108 @@ void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self, self->data_pin = data_pin; // PA07, PA19 -> SD0, PA08, PB16 -> SD1 + #ifdef SAMD21 if (data_pin == &pin_PA07 || data_pin == &pin_PA19) { self->serializer = 0; } else if (data_pin == &pin_PA08 - #ifdef PB16 + #ifdef PIN_PB16 || data_pin == &pin_PB16) { #else ) { #endif self->serializer = 1; + #endif + #ifdef SAMD51 + if (data_pin == &pin_PB10 || data_pin == &pin_PA22) { + self->serializer = 1; + #endif } else { mp_raise_ValueError("Invalid data pin"); } - claim_pin(clock_pin); - claim_pin(data_pin); - - if (MP_STATE_VM(audiodma_block_counter) == NULL && - !allocate_block_counter()) { - mp_raise_RuntimeError("Unable to allocate audio DMA block counter."); - } - if (!(bit_depth == 16 || bit_depth == 8) || !mono || oversample != OVERSAMPLING) { mp_raise_NotImplementedError("Only 8 or 16 bit mono with " MP_STRINGIFY(OVERSAMPLING) "x oversampling is supported."); } - // TODO(tannewt): Use the DPLL to get a more precise sampling rate. - // DFLL -> GCLK (/600 for 8khz, /300 for 16khz and /150 for 32khz) -> DPLL (*(63 + 1)) -> GCLK ( / 10) -> 512khz + turn_on_i2s(); - i2s_init(&self->i2s_instance, I2S); - struct i2s_clock_unit_config config_clock_unit; - i2s_clock_unit_get_config_defaults(&config_clock_unit); - config_clock_unit.clock.gclk_src = GCLK_GENERATOR_3; - - config_clock_unit.clock.mck_src = I2S_MASTER_CLOCK_SOURCE_GCLK; - config_clock_unit.clock.mck_out_enable = false; - - config_clock_unit.clock.sck_src = I2S_SERIAL_CLOCK_SOURCE_MCKDIV; - uint32_t clock_divisor = (uint32_t) roundf( 8000000.0f / frequency / oversample); - config_clock_unit.clock.sck_div = clock_divisor; - float mic_clock_freq = 8000000.0f / clock_divisor; - self->frequency = mic_clock_freq / oversample; - if (mic_clock_freq < MIN_MIC_CLOCK || clock_divisor == 0 || clock_divisor > 255) { - mp_raise_ValueError("sampling frequency out of range"); + 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 << self->serializer)) != 0) { + mp_raise_RuntimeError("Serializer in use"); + } + #endif + #ifdef SAMD51 + if (I2S->CTRLA.bit.RXEN == 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(clock_pin); + assert_pin_free(data_pin); - config_clock_unit.frame.number_slots = 2; - config_clock_unit.frame.slot_size = I2S_SLOT_SIZE_16_BIT; - config_clock_unit.frame.data_delay = I2S_DATA_DELAY_0; + uint32_t clock_divisor = (uint32_t) roundf( 48000000.0f / sample_rate / oversample); + float mic_clock_freq = 48000000.0f / clock_divisor; + self->sample_rate = mic_clock_freq / oversample; + if (mic_clock_freq < MIN_MIC_CLOCK || clock_divisor == 0) { + mp_raise_ValueError("sampling rate out of range"); + } + // Find a free GCLK to generate the MCLK signal. + uint8_t gclk = find_free_gclk(clock_divisor); + if (gclk > GCLK_GEN_NUM) { + mp_raise_RuntimeError("Unable to find free GCLK"); + } + self->gclk = gclk; - config_clock_unit.frame.frame_sync.width = I2S_FRAME_SYNC_WIDTH_SLOT; + enable_clock_generator(self->gclk, CLOCK_48MHZ, clock_divisor); + connect_gclk_to_peripheral(self->gclk, I2S_GCLK_ID_0 + self->clock_unit); - config_clock_unit.mck_pin.enable = false; - config_clock_unit.sck_pin.enable = true; - config_clock_unit.sck_pin.gpio = self->clock_pin->pin; - // Mux is always the same. - config_clock_unit.sck_pin.mux = 6L; - config_clock_unit.fs_pin.enable = false; - i2s_clock_unit_set_config(&self->i2s_instance, self->clock_unit, &config_clock_unit); + // Clock unit configuration - struct i2s_serializer_config config_serializer; - i2s_serializer_get_config_defaults(&config_serializer); - config_serializer.clock_unit = self->clock_unit; - config_serializer.mode = I2S_SERIALIZER_PDM2; - config_serializer.data_size = I2S_DATA_SIZE_32BIT; - config_serializer.data_pin.gpio = self->data_pin->pin; - // Mux is always the same. - config_serializer.data_pin.mux = 6L; - config_serializer.data_pin.enable = true; - i2s_serializer_set_config(&self->i2s_instance, self->serializer, &config_serializer); - i2s_enable(&self->i2s_instance); + uint32_t clkctrl = I2S_CLKCTRL_MCKSEL_GCLK | + I2S_CLKCTRL_NBSLOTS(2) | + I2S_CLKCTRL_FSWIDTH_SLOT | + I2S_CLKCTRL_SLOTSIZE_16; + + // Serializer configuration + #ifdef SAMD21 + uint32_t serctrl = (self->clock_unit << I2S_SERCTRL_CLKSEL_Pos) | SERCTRL(SERMODE_PDM2) | SERCTRL(DATASIZE_32); + #endif + #ifdef SAMD51 + uint32_t serctrl = (self->clock_unit << I2S_RXCTRL_CLKSEL_Pos) | SERCTRL(SERMODE_PDM2) | SERCTRL(DATASIZE_32); + #endif + + // Configure the I2S peripheral + i2s_set_enable(false); + + I2S->CLKCTRL[self->clock_unit].reg = clkctrl; + #ifdef SAMD21 + I2S->SERCTRL[self->serializer].reg = serctrl; + #endif + #ifdef SAMD51 + I2S->RXCTRL.reg = serctrl; + #endif + + i2s_set_enable(true); // Run the serializer all the time. This eliminates startup delay for the microphone. - i2s_clock_unit_enable(&self->i2s_instance, self->clock_unit); - i2s_serializer_enable(&self->i2s_instance, self->serializer); + i2s_set_clock_unit_enable(self->clock_unit, true); + i2s_set_serializer_enable(self->serializer, true); + + claim_pin(clock_pin); + claim_pin(data_pin); + + gpio_set_pin_function(self->clock_pin->pin, GPIO_I2S_FUNCTION); + gpio_set_pin_function(self->data_pin->pin, GPIO_I2S_FUNCTION); self->bytes_per_sample = oversample >> 3; self->bit_depth = bit_depth; @@ -168,10 +224,15 @@ void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t* self) { if (common_hal_audiobusio_pdmin_deinited(self)) { return; } - i2s_disable(&self->i2s_instance); - i2s_serializer_disable(&self->i2s_instance, self->serializer); - i2s_clock_unit_disable(&self->i2s_instance, self->clock_unit); - i2s_reset(&self->i2s_instance); + + i2s_set_serializer_enable(self->serializer, false); + i2s_set_clock_unit_enable(self->clock_unit, false); + + i2s_set_enable(false); + + disconnect_gclk_from_peripheral(self->gclk, I2S_GCLK_ID_0 + self->clock_unit); + disable_clock_generator(self->gclk); + reset_pin(self->clock_pin->pin); reset_pin(self->data_pin->pin); self->clock_pin = mp_const_none; @@ -182,65 +243,67 @@ uint8_t common_hal_audiobusio_pdmin_get_bit_depth(audiobusio_pdmin_obj_t* self) return self->bit_depth; } -uint32_t common_hal_audiobusio_pdmin_get_frequency(audiobusio_pdmin_obj_t* self) { - return self->frequency; +uint32_t common_hal_audiobusio_pdmin_get_sample_rate(audiobusio_pdmin_obj_t* self) { + return self->sample_rate; } static void setup_dma(audiobusio_pdmin_obj_t* self, uint32_t length, - DmacDescriptor* second_descriptor, - uint8_t words_per_buffer, uint8_t words_per_sample, - uint32_t* first_buffer, uint32_t* second_buffer) { - // Set up the DMA - struct dma_descriptor_config descriptor_config; - dma_descriptor_get_config_defaults(&descriptor_config); - descriptor_config.beat_size = DMA_BEAT_SIZE_WORD; - descriptor_config.step_selection = DMA_STEPSEL_SRC; - descriptor_config.source_address = (uint32_t)&I2S->DATA[self->serializer]; - descriptor_config.src_increment_enable = false; + DmacDescriptor* descriptor, + DmacDescriptor* second_descriptor, + uint32_t words_per_buffer, uint8_t words_per_sample, + uint32_t* first_buffer, uint32_t* second_buffer) { + descriptor->BTCTRL.reg = DMAC_BTCTRL_VALID | + DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_EVOSEL_BLOCK | + DMAC_BTCTRL_DSTINC | + DMAC_BTCTRL_BEATSIZE_WORD; + // 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. uint16_t block_transfer_count = words_per_buffer; if (length * words_per_sample < words_per_buffer) { block_transfer_count = length * words_per_sample; } - descriptor_config.block_transfer_count = block_transfer_count; - descriptor_config.destination_address = ((uint32_t) first_buffer + sizeof(uint32_t) * block_transfer_count); - descriptor_config.event_output_selection = DMA_EVENT_OUTPUT_BLOCK; - descriptor_config.next_descriptor_address = 0; + + descriptor->BTCNT.reg = block_transfer_count; + descriptor->DSTADDR.reg = ((uint32_t) first_buffer + sizeof(uint32_t) * block_transfer_count); + descriptor->DESCADDR.reg = 0; if (length * words_per_sample > words_per_buffer) { - descriptor_config.next_descriptor_address = ((uint32_t)second_descriptor); + descriptor->DESCADDR.reg = ((uint32_t)second_descriptor); } - dma_descriptor_create(audio_dma.descriptor, &descriptor_config); + #ifdef SAMD21 + descriptor->SRCADDR.reg = (uint32_t)&I2S->DATA[self->serializer]; + #endif + #ifdef SAMD51 + descriptor->SRCADDR.reg = (uint32_t)&I2S->RXDATA; + #endif // Do we need more values than will fit in the first buffer? // If so, set up a second buffer chained to be filled after the first buffer. if (length * words_per_sample > words_per_buffer) { block_transfer_count = words_per_buffer; - descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor); + second_descriptor->DESCADDR.reg = ((uint32_t)descriptor); if (length * words_per_sample < 2 * words_per_buffer) { // Length needed is more than one buffer but less than two. // Subtract off the size of the first buffer, and what remains is the count we need. block_transfer_count = length * words_per_sample - words_per_buffer; - descriptor_config.next_descriptor_address = 0; + second_descriptor->DESCADDR.reg = 0; } - descriptor_config.block_transfer_count = block_transfer_count; - descriptor_config.destination_address = ((uint32_t) second_buffer + sizeof(uint32_t) * block_transfer_count); - dma_descriptor_create(second_descriptor, &descriptor_config); + second_descriptor->DSTADDR.reg = ((uint32_t) second_buffer + sizeof(uint32_t) * block_transfer_count); + + second_descriptor->BTCNT.reg = block_transfer_count; + #ifdef SAMD21 + second_descriptor->SRCADDR.reg = (uint32_t)&I2S->DATA[self->serializer]; + #endif + #ifdef SAMD51 + second_descriptor->SRCADDR.reg = (uint32_t)&I2S->RXDATA; + #endif + second_descriptor->BTCTRL.reg = DMAC_BTCTRL_VALID | + DMAC_BTCTRL_BLOCKACT_NOACT | + DMAC_BTCTRL_EVOSEL_BLOCK | + DMAC_BTCTRL_DSTINC | + DMAC_BTCTRL_BEATSIZE_WORD; } - - switch_audiodma_trigger(I2S_DMAC_ID_RX_0 + self->serializer); -} - -void start_dma(audiobusio_pdmin_obj_t* self) { - dma_start_transfer_job(&audio_dma); - tc_start_counter(MP_STATE_VM(audiodma_block_counter)); - I2S->DATA[1].reg = I2S->DATA[1].reg; -} - -void stop_dma(audiobusio_pdmin_obj_t* self) { - // Shutdown the DMA: serializer keeps running. - tc_stop_counter(MP_STATE_VM(audiodma_block_counter)); - dma_abort_job(&audio_dma); } // a windowed sinc filter for 44 khz, 64 samples @@ -290,20 +353,27 @@ static uint16_t filter_sample(uint32_t pdm_samples[4]) { // output_buffer_length is the number of slots, not the number of bytes. uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* self, uint16_t* output_buffer, uint32_t output_buffer_length) { + uint8_t dma_channel = find_free_audio_dma_channel(); + uint8_t event_channel = find_sync_event_channel(); + // We allocate two buffers on the stack to use for double buffering. const uint8_t samples_per_buffer = SAMPLES_PER_BUFFER; // For every word we record, we throw away 2 bytes of a phantom second channel. - const uint8_t words_per_sample = self->bytes_per_sample / 2; - const uint8_t words_per_buffer = samples_per_buffer * words_per_sample; + uint8_t words_per_sample = self->bytes_per_sample / 2; + uint32_t words_per_buffer = samples_per_buffer * words_per_sample; uint32_t first_buffer[words_per_buffer]; uint32_t second_buffer[words_per_buffer]; + turn_on_event_system(); + COMPILER_ALIGNED(16) DmacDescriptor second_descriptor; - setup_dma(self, output_buffer_length, &second_descriptor, words_per_buffer, - words_per_sample, first_buffer, second_buffer); + setup_dma(self, output_buffer_length, dma_descriptor(dma_channel), &second_descriptor, + words_per_buffer, words_per_sample, first_buffer, second_buffer); - start_dma(self); + dma_configure(dma_channel, I2S_DMAC_ID_RX_0, true); + init_event_channel_interrupt(event_channel, CORE_GCLK, EVSYS_ID_GEN_DMAC_CH_0 + dma_channel); + dma_enable_channel(dma_channel); // Record uint32_t buffers_processed = 0; @@ -311,24 +381,23 @@ uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* se uint32_t remaining_samples_needed = output_buffer_length; while (values_output < output_buffer_length) { + if (event_interrupt_overflow(event_channel)) { + // Looks like we aren't keeping up. We shouldn't skip a buffer so stop early. + break; + } // Wait for the next buffer to fill - uint32_t block_counter; - while ((block_counter = tc_get_count_value(MP_STATE_VM(audiodma_block_counter))) == buffers_processed) { + while (!event_interrupt_active(event_channel)) { #ifdef MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_LOOP #endif } - if (block_counter != (buffers_processed + 1)) { - // Looks like we aren't keeping up. We shouldn't skip a buffer. - break; - } // The mic is running all the time, so we don't need to wait the usual 10msec or 100msec // for it to start up. // Flip back and forth between processing the first and second buffers. uint32_t *buffer = first_buffer; - DmacDescriptor* descriptor = audio_dma.descriptor; + DmacDescriptor* descriptor = dma_descriptor(dma_channel); if (buffers_processed % 2 == 1) { buffer = second_buffer; descriptor = &second_descriptor; @@ -375,7 +444,8 @@ uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* se } } - stop_dma(self); + disable_event_channel(event_channel); + dma_disable_channel(dma_channel); return values_output; } diff --git a/ports/atmel-samd/common-hal/audiobusio/PDMIn.h b/ports/atmel-samd/common-hal/audiobusio/PDMIn.h index 50dc602623..1156c64210 100644 --- a/ports/atmel-samd/common-hal/audiobusio/PDMIn.h +++ b/ports/atmel-samd/common-hal/audiobusio/PDMIn.h @@ -36,11 +36,12 @@ typedef struct { mp_obj_base_t base; const mcu_pin_obj_t *clock_pin; const mcu_pin_obj_t *data_pin; - uint32_t frequency; + uint32_t sample_rate; uint8_t serializer; uint8_t clock_unit; uint8_t bytes_per_sample; uint8_t bit_depth; + uint8_t gclk; } audiobusio_pdmin_obj_t; void pdmin_reset(void); diff --git a/ports/atmel-samd/events.c b/ports/atmel-samd/events.c index 1854692ae6..05a2ae8a73 100644 --- a/ports/atmel-samd/events.c +++ b/ports/atmel-samd/events.c @@ -91,7 +91,7 @@ uint8_t find_async_event_channel(void) { #define EVSYS_SYNCH_NUM EVSYS_CHANNELS #endif uint8_t find_sync_event_channel(void) { - int8_t channel; + uint8_t channel; for (channel = 0; channel < EVSYS_SYNCH_NUM; channel++) { if (channel_free(channel)) { break; @@ -145,6 +145,8 @@ void init_event_channel_interrupt(uint8_t channel, uint8_t gclk, uint8_t generat EVSYS->Channel[channel].CHANNEL.reg = EVSYS_CHANNEL_EVGEN(generator) | EVSYS_CHANNEL_PATH_SYNCHRONOUS | EVSYS_CHANNEL_EDGSEL_RISING_EDGE; + EVSYS->Channel[channel].CHINTFLAG.reg = EVSYS_CHINTFLAG_EVD | EVSYS_CHINTFLAG_OVR; + EVSYS->Channel[channel].CHINTENSET.reg = EVSYS_CHINTENSET_EVD | EVSYS_CHINTENSET_OVR; #endif #ifdef SAMD21 EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(channel) | @@ -187,8 +189,25 @@ bool event_interrupt_active(uint8_t channel) { // 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; + EVSYS->Channel[channel].CHINTFLAG.reg = EVSYS_CHINTFLAG_EVD | EVSYS_CHINTFLAG_OVR; } #endif return active; } + +bool event_interrupt_overflow(uint8_t channel) { + bool overflow = false; + #ifdef SAMD21 + if (channel > 7) { + uint8_t value = 1 << (channel - 7); + overflow = (EVSYS->INTFLAG.reg & EVSYS_INTFLAG_OVRp8(value)) != 0; + } else { + uint8_t value = 1 << channel; + overflow = (EVSYS->INTFLAG.reg & EVSYS_INTFLAG_OVR(value)) != 0; + } + #endif + #ifdef SAMD51 + overflow = EVSYS->Channel[channel].CHINTFLAG.bit.OVR; + #endif + return overflow; +} diff --git a/ports/atmel-samd/events.h b/ports/atmel-samd/events.h index 69215c3e7d..64093c0ab3 100644 --- a/ports/atmel-samd/events.h +++ b/ports/atmel-samd/events.h @@ -42,5 +42,6 @@ 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); +bool event_interrupt_overflow(uint8_t channel); #endif // MICROPY_INCLUDED_ATMEL_SAMD_EVENTS_H diff --git a/ports/atmel-samd/i2s.c b/ports/atmel-samd/i2s.c new file mode 100644 index 0000000000..663ecdd1dc --- /dev/null +++ b/ports/atmel-samd/i2s.c @@ -0,0 +1,84 @@ +/* + * 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 "i2s.h" + +#include "clocks.h" + +#include "hpl/gclk/hpl_gclk_base.h" +#ifdef SAMD21 +#include "hpl/pm/hpl_pm_base.h" +#endif + +void turn_on_i2s(void) { + // 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 +} + +void i2s_set_enable(bool enable) { + while (I2S->SYNCBUSY.bit.ENABLE == 1) {} + I2S->CTRLA.bit.ENABLE = enable; + while (I2S->SYNCBUSY.bit.ENABLE == 1) {} +} + +void i2s_set_clock_unit_enable(uint8_t clock_unit, bool enable) { + while ((I2S->SYNCBUSY.vec.CKEN & (1 << clock_unit)) != 0) {} + I2S->CTRLA.vec.CKEN = 1 << clock_unit; + while ((I2S->SYNCBUSY.vec.CKEN & (1 << clock_unit)) != 0) {} +} + +void i2s_set_serializer_enable(uint8_t serializer, bool enable) { + #ifdef SAMD21 + while ((I2S->SYNCBUSY.vec.SEREN & (1 << serializer)) != 0) {} + if (enable) { + I2S->CTRLA.vec.SEREN = 1 << serializer; + } else { + I2S->CTRLA.vec.SEREN &= ~(1 << serializer); + } + while ((I2S->SYNCBUSY.vec.SEREN & (1 << serializer)) != 0) {} + #endif + #ifdef SAMD51 + if (serializer == 0) { + while (I2S->SYNCBUSY.bit.TXEN == 1) {} + I2S->CTRLA.bit.TXEN = enable; + while (I2S->SYNCBUSY.bit.TXEN == 1) {} + } else { + while (I2S->SYNCBUSY.bit.RXEN == 1) {} + I2S->CTRLA.bit.RXEN = enable; + while (I2S->SYNCBUSY.bit.RXEN == 1) {} + } + #endif +} diff --git a/ports/atmel-samd/i2s.h b/ports/atmel-samd/i2s.h new file mode 100644 index 0000000000..7df667b2ae --- /dev/null +++ b/ports/atmel-samd/i2s.h @@ -0,0 +1,41 @@ +/* + * 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_I2S_H +#define MICROPY_INCLUDED_ATMEL_SAMD_I2S_H + +#include +#include + +#include "include/sam.h" + +void turn_on_i2s(void); + +void i2s_set_enable(bool enable); +void i2s_set_clock_unit_enable(uint8_t clock, bool enable); +void i2s_set_serializer_enable(uint8_t serializer, bool enable); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_I2S_H diff --git a/shared-bindings/audiobusio/PDMIn.c b/shared-bindings/audiobusio/PDMIn.c index 6f0a130e83..3e0f3c1fcf 100644 --- a/shared-bindings/audiobusio/PDMIn.c +++ b/shared-bindings/audiobusio/PDMIn.c @@ -42,18 +42,18 @@ //| //| PDMIn can be used to record an input audio signal on a given set of pins. //| -//| .. class:: PDMIn(clock_pin, data_pin, \*, frequency=16000, bit_depth=8, mono=True, oversample=64, startup_delay=0.11) +//| .. class:: PDMIn(clock_pin, data_pin, \*, sample_rate=16000, bit_depth=8, mono=True, oversample=64, startup_delay=0.11) //| //| Create a PDMIn object associated with the given pins. This allows you to //| record audio signals from the given pins. Individual ports may put further -//| restrictions on the recording parameters. The overall frequency is -//| determined by `frequency` x ``oversample``, and the total must be 1MHz or -//| higher, so `frequency` must be a minimum of 16000. +//| restrictions on the recording parameters. The overall sample rate is +//| determined by `sample_rate` x ``oversample``, and the total must be 1MHz or +//| higher, so `sample_rate` must be a minimum of 16000. //| //| :param ~microcontroller.Pin clock_pin: The pin to output the clock to //| :param ~microcontroller.Pin data_pin: The pin to read the data from -//| :param int frequency: Target frequency of the resulting samples. Check `frequency` for actual value. -//| Minimum frequency is about 16000 Hz. +//| :param int sample_rate: Target sample_rate of the resulting samples. Check `sample_rate` for actual value. +//| Minimum sample_rate is about 16000 Hz. //| :param int bit_depth: Final number of bits per sample. Must be divisible by 8 //| :param bool mono: True when capturing a single channel of audio, captures two channels otherwise //| :param int oversample: Number of single bit samples to decimate into a final sample. Must be divisible by 8 @@ -69,7 +69,7 @@ //| //| # Prep a buffer to record into //| b = bytearray(200) -//| with audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, frequency=16000) as mic: +//| with audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000) as mic: //| mic.record(b, len(b)) //| //| Record 16-bit unsigned samples to buffer:: @@ -83,15 +83,15 @@ //| b = array.array("H") //| for i in range(200): //| b.append(0) -//| with audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, frequency=16000, bit_depth=16) as mic: +//| with audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16) as mic: //| mic.record(b, len(b)) //| STATIC mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) { - enum { ARG_frequency, ARG_bit_depth, ARG_mono, ARG_oversample, ARG_startup_delay }; + enum { ARG_sample_rate, ARG_bit_depth, ARG_mono, ARG_oversample, ARG_startup_delay }; mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args); static const mp_arg_t allowed_args[] = { - { MP_QSTR_frequency, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 16000} }, + { MP_QSTR_sample_rate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 16000} }, { MP_QSTR_bit_depth, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, { MP_QSTR_mono, MP_ARG_KW_ONLY | MP_ARG_BOOL,{.u_bool = true} }, { MP_QSTR_oversample, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, @@ -117,7 +117,7 @@ STATIC mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_ar audiobusio_pdmin_obj_t *self = m_new_obj(audiobusio_pdmin_obj_t); self->base.type = &audiobusio_pdmin_type; - uint32_t frequency = args[ARG_frequency].u_int; + uint32_t sample_rate = args[ARG_sample_rate].u_int; uint8_t bit_depth = args[ARG_bit_depth].u_int; if (bit_depth % 8 != 0) { mp_raise_ValueError("Bit depth must be multiple of 8."); @@ -135,7 +135,7 @@ STATIC mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_ar mp_raise_ValueError("Microphone startup delay must be in range 0.0 to 1.0"); } - common_hal_audiobusio_pdmin_construct(self, clock_pin, data_pin, frequency, + common_hal_audiobusio_pdmin_construct(self, clock_pin, data_pin, sample_rate, bit_depth, mono, oversample); // Wait for the microphone to start up. Some start in 10 msecs; some take as much as 100 msecs. @@ -215,21 +215,21 @@ STATIC mp_obj_t audiobusio_pdmin_obj_record(mp_obj_t self_obj, mp_obj_t destinat } MP_DEFINE_CONST_FUN_OBJ_3(audiobusio_pdmin_record_obj, audiobusio_pdmin_obj_record); -//| .. attribute:: frequency +//| .. attribute:: sample_rate //| -//| The actual frequency of the recording. This may not match the constructed -//| frequency due to internal clock limitations. +//| The actual sample_rate of the recording. This may not match the constructed +//| sample rate due to internal clock limitations. //| -STATIC mp_obj_t audiobusio_pdmin_obj_get_frequency(mp_obj_t self_in) { +STATIC mp_obj_t audiobusio_pdmin_obj_get_sample_rate(mp_obj_t self_in) { audiobusio_pdmin_obj_t *self = MP_OBJ_TO_PTR(self_in); raise_error_if_deinited(common_hal_audiobusio_pdmin_deinited(self)); - return MP_OBJ_NEW_SMALL_INT(common_hal_audiobusio_pdmin_get_frequency(self)); + return MP_OBJ_NEW_SMALL_INT(common_hal_audiobusio_pdmin_get_sample_rate(self)); } -MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_pdmin_get_frequency_obj, audiobusio_pdmin_obj_get_frequency); +MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_pdmin_get_sample_rate_obj, audiobusio_pdmin_obj_get_sample_rate); -const mp_obj_property_t audiobusio_pdmin_frequency_obj = { +const mp_obj_property_t audiobusio_pdmin_sample_rate_obj = { .base.type = &mp_type_property, - .proxy = {(mp_obj_t)&audiobusio_pdmin_get_frequency_obj, + .proxy = {(mp_obj_t)&audiobusio_pdmin_get_sample_rate_obj, (mp_obj_t)&mp_const_none_obj, (mp_obj_t)&mp_const_none_obj}, }; @@ -240,7 +240,7 @@ STATIC const mp_rom_map_elem_t audiobusio_pdmin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiobusio_pdmin___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_record), MP_ROM_PTR(&audiobusio_pdmin_record_obj) }, - { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&audiobusio_pdmin_frequency_obj) } + { MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&audiobusio_pdmin_sample_rate_obj) } }; STATIC MP_DEFINE_CONST_DICT(audiobusio_pdmin_locals_dict, audiobusio_pdmin_locals_dict_table); diff --git a/shared-bindings/audiobusio/PDMIn.h b/shared-bindings/audiobusio/PDMIn.h index a0702c9788..eaed59830a 100644 --- a/shared-bindings/audiobusio/PDMIn.h +++ b/shared-bindings/audiobusio/PDMIn.h @@ -35,13 +35,13 @@ extern const mp_obj_type_t audiobusio_pdmin_type; void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self, const mcu_pin_obj_t* clock_pin, const mcu_pin_obj_t* data_pin, - uint32_t frequency, uint8_t bit_depth, bool mono, uint8_t oversample); + uint32_t sample_rate, uint8_t bit_depth, bool mono, uint8_t oversample); void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t* self); bool common_hal_audiobusio_pdmin_deinited(audiobusio_pdmin_obj_t* self); uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* self, uint16_t* buffer, uint32_t length); uint8_t common_hal_audiobusio_pdmin_get_bit_depth(audiobusio_pdmin_obj_t* self); -uint32_t common_hal_audiobusio_pdmin_get_frequency(audiobusio_pdmin_obj_t* self); +uint32_t common_hal_audiobusio_pdmin_get_sample_rate(audiobusio_pdmin_obj_t* self); // TODO(tannewt): Add record to file #endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOBUSIO_AUDIOOUT_H diff --git a/shared-bindings/audiobusio/__init__.c b/shared-bindings/audiobusio/__init__.c index 528a8fb31e..f7e3a07668 100644 --- a/shared-bindings/audiobusio/__init__.c +++ b/shared-bindings/audiobusio/__init__.c @@ -62,7 +62,7 @@ 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_I2SOut), MP_ROM_PTR(&audiobusio_i2sout_type) }, - //{ MP_ROM_QSTR(MP_QSTR_PDMIn), MP_ROM_PTR(&audiobusio_pdmin_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);