summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Shawcroft <scott.shawcroft@gmail.com>2018-04-26 12:51:37 -0700
committerScott Shawcroft <scott.shawcroft@gmail.com>2018-05-01 14:35:43 -0700
commitcfea51ec68504710ee683e77fada7adf4c3cffd4 (patch)
tree0109c13839c96ccd852e003a09fedd4c8aa48fcd
parentdd0f8689a135b33dbeedaefdfa149fd7ec3c94e8 (diff)
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.
-rw-r--r--ports/atmel-samd/Makefile14
-rw-r--r--ports/atmel-samd/audio_dma.c1
-rw-r--r--ports/atmel-samd/audio_dma.h2
-rw-r--r--ports/atmel-samd/common-hal/audiobusio/I2SOut.c22
-rw-r--r--ports/atmel-samd/common-hal/audiobusio/PDMIn.c304
-rw-r--r--ports/atmel-samd/common-hal/audiobusio/PDMIn.h3
-rw-r--r--ports/atmel-samd/events.c23
-rw-r--r--ports/atmel-samd/events.h1
-rw-r--r--ports/atmel-samd/i2s.c84
-rw-r--r--ports/atmel-samd/i2s.h41
-rw-r--r--shared-bindings/audiobusio/PDMIn.c42
-rw-r--r--shared-bindings/audiobusio/PDMIn.h4
-rw-r--r--shared-bindings/audiobusio/__init__.c2
13 files changed, 373 insertions, 170 deletions
diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile
index 6b661cd84..bb2d92b4a 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 7030009d9..227a2e188 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 deecd27f3..fd2e6a153 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 ec21636d0..65dae5dcb 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 e0ac3f9a3..7788335d1 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;
+ 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);
+
+ 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.clock.mck_src = I2S_MASTER_CLOCK_SOURCE_GCLK;
- config_clock_unit.clock.mck_out_enable = false;
+ 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.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");
- }
+ // Clock unit configuration
+
+ 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
- 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;
-
- config_clock_unit.frame.frame_sync.width = I2S_FRAME_SYNC_WIDTH_SLOT;
-
- 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);
-
- 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);
+ // 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);
- }
-
- switch_audiodma_trigger(I2S_DMAC_ID_RX_0 + self->serializer);
-}
+ second_descriptor->DSTADDR.reg = ((uint32_t) second_buffer + sizeof(uint32_t) * block_transfer_count);
-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);
+ 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;
+ }
}
// 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 50dc60262..1156c6421 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 1854692ae..05a2ae8a7 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 69215c3e7..64093c0ab 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 000000000..663ecdd1d
--- /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 000000000..7df667b2a
--- /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 <stdbool.h>
+#include <stdint.h>
+
+#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 6f0a130e8..3e0f3c1fc 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 a0702c978..eaed59830 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 528a8fb31..f7e3a0766 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);