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.
This commit is contained in:
Scott Shawcroft 2018-04-26 12:51:37 -07:00
parent dd0f8689a1
commit cfea51ec68
13 changed files with 370 additions and 167 deletions

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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

84
ports/atmel-samd/i2s.c Normal file
View File

@ -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
}

41
ports/atmel-samd/i2s.h Normal file
View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);