43650b6896
Changes: * New faster filter loop, by @ladyada. New filter coefficients as well. * Turn on microphone clock when PDMIn object is created, and run it all the time, so the user code doesn't have to wait for microphone startup, which can be 10ms or even 100ms. * Wait for microphone startup when PDMIn is first created, based on new optional parameter microphone_startup in seconds (takes a float). * record() returns number of samples actually recorded, so you can see if it's not keeping up. * Fix buffer overflow errors when buffer size was not a multiple of 16 or something like that. * Tweak a few peripheral settings. * Minimum sampling frequency is now 16kHZ or so, because 8kHz runs microphone at only 0.5MHz, which is too slow for many mics. Note: I tried 128x oversampling instead of 64x, but the code cannot keep up at 24kHz or above sampling. 128x would reduce the high-frequency noise by 6db.
363 lines
14 KiB
C
363 lines
14 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "py/gc.h"
|
|
#include "py/mperrno.h"
|
|
#include "py/runtime.h"
|
|
#include "common-hal/analogio/AnalogOut.h"
|
|
#include "common-hal/audiobusio/PDMIn.h"
|
|
#include "shared-bindings/analogio/AnalogOut.h"
|
|
#include "shared-bindings/audiobusio/PDMIn.h"
|
|
#include "shared-bindings/microcontroller/Pin.h"
|
|
|
|
#include "asf/sam0/drivers/port/port.h"
|
|
#include "samd21_pins.h"
|
|
|
|
#include "shared_dma.h"
|
|
#include "tick.h"
|
|
|
|
#define OVERSAMPLING 64
|
|
#define SAMPLES_PER_BUFFER 32
|
|
|
|
// MEMS microphones must be clocked at at least 1MHz.
|
|
#define MIN_MIC_CLOCK 1000000
|
|
|
|
void pdmin_reset(void) {
|
|
while (I2S->SYNCBUSY.reg & I2S_SYNCBUSY_ENABLE) {}
|
|
I2S->INTENCLR.reg = I2S_INTENCLR_MASK;
|
|
I2S->INTFLAG.reg = I2S_INTFLAG_MASK;
|
|
I2S->CTRLA.reg &= ~I2S_SYNCBUSY_ENABLE;
|
|
while (I2S->SYNCBUSY.reg & I2S_SYNCBUSY_ENABLE) {}
|
|
I2S->CTRLA.reg = I2S_CTRLA_SWRST;
|
|
}
|
|
|
|
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) {
|
|
self->clock_pin = clock_pin; // PA10, PA20 -> SCK0, PB11 -> SCK1
|
|
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
|
|
} else {
|
|
mp_raise_ValueError("Invalid clock pin");
|
|
}
|
|
|
|
self->data_pin = data_pin; // PA07, PA19 -> SD0, PA08, PB16 -> SD1
|
|
|
|
if (data_pin == &pin_PA07 || data_pin == &pin_PA19) {
|
|
self->serializer = 0;
|
|
} else if (data_pin == &pin_PA08
|
|
#ifdef PB16
|
|
|| data_pin == &pin_PB16) {
|
|
#else
|
|
) {
|
|
#endif
|
|
self->serializer = 1;
|
|
} 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
|
|
|
|
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");
|
|
}
|
|
|
|
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);
|
|
|
|
// 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);
|
|
|
|
self->bytes_per_sample = oversample >> 3;
|
|
self->bit_depth = bit_depth;
|
|
}
|
|
|
|
bool common_hal_audiobusio_pdmin_deinited(audiobusio_pdmin_obj_t* self) {
|
|
return self->clock_pin == mp_const_none;
|
|
}
|
|
|
|
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);
|
|
reset_pin(self->clock_pin->pin);
|
|
reset_pin(self->data_pin->pin);
|
|
self->clock_pin = mp_const_none;
|
|
self->data_pin = mp_const_none;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
// 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;
|
|
if (length * words_per_sample > words_per_buffer) {
|
|
descriptor_config.next_descriptor_address = ((uint32_t)second_descriptor);
|
|
}
|
|
dma_descriptor_create(audio_dma.descriptor, &descriptor_config);
|
|
|
|
if (length * words_per_sample > words_per_buffer) {
|
|
block_transfer_count = words_per_buffer;
|
|
descriptor_config.next_descriptor_address = ((uint32_t)audio_dma.descriptor);
|
|
if (length * words_per_sample < 2 * words_per_buffer) {
|
|
block_transfer_count = 2 * words_per_buffer - length * words_per_sample;
|
|
descriptor_config.next_descriptor_address = 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);
|
|
}
|
|
|
|
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
|
|
//
|
|
// This filter is good enough to use for lower sample rates as
|
|
// well. It does not increase the noise enough to be a problem.
|
|
//
|
|
// In the long run we could use a fast filter like this to do the
|
|
// decimation and initial filtering in real time, filtering to a
|
|
// higher sample rate than specified. Then after the audio is
|
|
// recorded, a more expensive filter non-real-time filter could be
|
|
// used to down-sample and low-pass.
|
|
uint16_t sinc_filter [OVERSAMPLING] = {
|
|
0, 2, 9, 21, 39, 63, 94, 132,
|
|
179, 236, 302, 379, 467, 565, 674, 792,
|
|
920, 1055, 1196, 1341, 1487, 1633, 1776, 1913,
|
|
2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516,
|
|
2506, 2474, 2422, 2352, 2263, 2159, 2042, 1913,
|
|
1776, 1633, 1487, 1341, 1196, 1055, 920, 792,
|
|
674, 565, 467, 379, 302, 236, 179, 132,
|
|
94, 63, 39, 21, 9, 2, 0, 0
|
|
};
|
|
|
|
#define REPEAT_16_TIMES(X) X X X X X X X X X X X X X X X X
|
|
|
|
static uint16_t filter_sample(uint32_t pdm_samples[4]) {
|
|
uint16_t running_sum = 0;
|
|
const uint16_t *filter_ptr = sinc_filter;
|
|
for (uint8_t i = 0; i < OVERSAMPLING/16; i++) {
|
|
// The sample is 16-bits right channel in the upper two bytes and 16-bits left channel
|
|
// in the lower two bytes.
|
|
// We just ignore the upper bits
|
|
uint32_t pdm_sample = pdm_samples[i];
|
|
REPEAT_16_TIMES( {
|
|
if (pdm_sample & 0x8000) {
|
|
running_sum += *filter_ptr++;
|
|
}
|
|
pdm_sample <<= 1;
|
|
}
|
|
)
|
|
}
|
|
return running_sum;
|
|
}
|
|
|
|
// output_buffer may be a byte buffer or a halfword buffer.
|
|
// 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) {
|
|
// 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;
|
|
uint32_t first_buffer[words_per_buffer];
|
|
uint32_t second_buffer[words_per_buffer];
|
|
|
|
COMPILER_ALIGNED(16) DmacDescriptor second_descriptor;
|
|
|
|
setup_dma(self, output_buffer_length, &second_descriptor, words_per_buffer,
|
|
words_per_sample, first_buffer, second_buffer);
|
|
|
|
start_dma(self);
|
|
|
|
// Record
|
|
uint32_t buffers_processed = 0;
|
|
uint32_t values_output = 0;
|
|
|
|
uint32_t remaining_samples_needed = output_buffer_length;
|
|
while (values_output < output_buffer_length) {
|
|
// Wait for the next buffer to fill
|
|
while (tc_get_count_value(MP_STATE_VM(audiodma_block_counter)) == buffers_processed) {
|
|
#ifdef MICROPY_VM_HOOK_LOOP
|
|
MICROPY_VM_HOOK_LOOP
|
|
#endif
|
|
}
|
|
if (tc_get_count_value(MP_STATE_VM(audiodma_block_counter)) != (buffers_processed + 1)) {
|
|
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.
|
|
|
|
uint32_t *buffer = first_buffer;
|
|
DmacDescriptor* descriptor = audio_dma.descriptor;
|
|
if (buffers_processed % 2 == 1) {
|
|
buffer = second_buffer;
|
|
descriptor = &second_descriptor;
|
|
}
|
|
// Decimate and filter the last buffer
|
|
uint32_t samples_gathered = descriptor->BTCNT.reg / words_per_sample;
|
|
// Don't run off the end of output buffer. Process only as many as needed.
|
|
uint32_t samples_to_process = min(remaining_samples_needed, samples_gathered);
|
|
for (uint32_t i = 0; i < samples_to_process; i++) {
|
|
// Call filter_sample just one place so it can be inlined.
|
|
uint16_t value = filter_sample(buffer + i * words_per_sample);
|
|
if (self->bit_depth == 8) {
|
|
// Truncate to 8 bits.
|
|
((uint8_t*) output_buffer)[values_output] = value >> 8;
|
|
} else {
|
|
output_buffer[values_output] = value;
|
|
}
|
|
values_output++;
|
|
}
|
|
buffers_processed++;
|
|
|
|
// See if we need to transfer less than a full buffer for the remaining needed samples.
|
|
remaining_samples_needed = output_buffer_length - values_output;
|
|
if (remaining_samples_needed > 0 && remaining_samples_needed < samples_per_buffer) {
|
|
descriptor->BTCNT.reg = remaining_samples_needed;
|
|
descriptor->DSTADDR.reg = ((uint32_t) buffer) + remaining_samples_needed * words_per_sample;
|
|
descriptor->DESCADDR.reg = 0;
|
|
}
|
|
}
|
|
|
|
stop_dma(self);
|
|
|
|
return values_output;
|
|
}
|
|
|
|
void common_hal_audiobusio_pdmin_record_to_file(audiobusio_pdmin_obj_t* self, uint8_t* buffer, uint32_t length) {
|
|
|
|
}
|