summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--atmel-samd/common-hal/audiobusio/PDMIn.c153
-rw-r--r--shared-bindings/audiobusio/PDMIn.c53
2 files changed, 129 insertions, 77 deletions
diff --git a/atmel-samd/common-hal/audiobusio/PDMIn.c b/atmel-samd/common-hal/audiobusio/PDMIn.c
index db671f213..8263ac7d2 100644
--- a/atmel-samd/common-hal/audiobusio/PDMIn.c
+++ b/atmel-samd/common-hal/audiobusio/PDMIn.c
@@ -26,6 +26,7 @@
#include <stdint.h>
#include <string.h>
+#include <math.h>
#include "py/gc.h"
#include "py/mperrno.h"
@@ -42,6 +43,12 @@
#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;
@@ -96,8 +103,8 @@ void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self,
mp_raise_RuntimeError("Unable to allocate audio DMA block counter.");
}
- if (!(bit_depth == 16 || bit_depth == 8) || !mono || oversample != 64) {
- mp_raise_NotImplementedError("Only 8 or 16 bit mono with 64 oversample is supported.");
+ 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.
@@ -112,12 +119,17 @@ void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self,
config_clock_unit.clock.mck_out_enable = false;
config_clock_unit.clock.sck_src = I2S_SERIAL_CLOCK_SOURCE_MCKDIV;
- config_clock_unit.clock.sck_div = 8000000 / frequency / oversample;
- self->frequency = 8000000 / config_clock_unit.clock.sck_div / oversample;
+ 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_1;
+ config_clock_unit.frame.data_delay = I2S_DATA_DELAY_0;
config_clock_unit.frame.frame_sync.width = I2S_FRAME_SYNC_WIDTH_SLOT;
@@ -141,6 +153,10 @@ void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self,
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;
}
@@ -154,6 +170,8 @@ void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t* 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);
@@ -213,74 +231,82 @@ static void setup_dma(audiobusio_pdmin_obj_t* self, uint32_t length,
void start_dma(audiobusio_pdmin_obj_t* self) {
dma_start_transfer_job(&audio_dma);
tc_start_counter(MP_STATE_VM(audiodma_block_counter));
- i2s_clock_unit_enable(&self->i2s_instance, self->clock_unit);
- i2s_serializer_enable(&self->i2s_instance, self->serializer);
I2S->DATA[1].reg = I2S->DATA[1].reg;
}
void stop_dma(audiobusio_pdmin_obj_t* self) {
- // Turn off the I2S clock and serializer. Peripheral is still enabled.
- i2s_serializer_disable(&self->i2s_instance, self->serializer);
- i2s_clock_unit_disable(&self->i2s_instance, self->clock_unit);
-
- // Shutdown the DMA
+ // Shutdown the DMA: serializer keeps running.
tc_stop_counter(MP_STATE_VM(audiodma_block_counter));
dma_abort_job(&audio_dma);
}
-static const uint16_t sinc_filter[64] = {
- 0, 1, 6, 16, 29, 49, 75, 108,
- 149, 200, 261, 334, 418, 514, 622, 742,
- 872, 1012, 1161, 1315, 1472, 1631, 1787, 1938,
- 2081, 2212, 2329, 2429, 2509, 2568, 2604, 2616,
- 2604, 2568, 2509, 2429, 2329, 2212, 2081, 1938,
- 1787, 1631, 1472, 1315, 1161, 1012, 872, 742,
- 622, 514, 418, 334, 261, 200, 149, 108,
- 75, 49, 29, 16, 6, 1, 0, 0
+// 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 sample = 0;
- for (uint8_t i = 0; i < 4; i++) {
- uint16_t pdm = pdm_samples[i] & 0xffff;
- for (uint8_t j = 0; j < 16; j++) {
- if ((pdm & 0x8000) != 0) {
- sample += sinc_filter[i * 16 + j];
+ 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;
}
- pdm <<= 1;
- }
+ )
}
- return sample;
+ 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 length) {
- // Write the wave file header.
-
- // We allocate two 256 byte buffers on the stack to use for double buffering.
- // Our oversample rate is 64 (bits) so each buffer produces 32 samples.
- // TODO(tannewt): Can the compiler optimize better if we fix the size of
- // these buffers?
- uint8_t samples_per_buffer = 32;
+ 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.
- uint8_t words_per_sample = self->bytes_per_sample / 2;
- uint8_t words_per_buffer = samples_per_buffer * words_per_sample;
+ 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, length, &second_descriptor, words_per_buffer,
+ 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 total_bytes = 0;
+ uint32_t values_output = 0;
- uint64_t start_ticks = ticks_ms;
- while (total_bytes < length) {
+ 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
@@ -290,40 +316,45 @@ uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* se
if (tc_get_count_value(MP_STATE_VM(audiodma_block_counter)) != (buffers_processed + 1)) {
break;
}
- // Throw away the first ~10ms of data because thats during mic start up.
- if (ticks_ms - start_ticks < 10) {
- buffers_processed++;
- continue;
- }
- uint32_t* buffer = first_buffer;
+
+ // 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
- int32_t samples_gathered = descriptor->BTCNT.reg / words_per_sample;
- for (uint16_t i = 0; i < samples_gathered; i++) {
+ 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) {
- ((uint8_t*) output_buffer)[total_bytes] = filter_sample(buffer + i * words_per_sample) >> 8;
- total_bytes += 1;
- } else if (self->bit_depth == 16) {
- output_buffer[total_bytes / 2] = filter_sample(buffer + i * words_per_sample);
- total_bytes += 2;
+ // Truncate to 8 bits.
+ ((uint8_t*) output_buffer)[values_output] = value >> 8;
+ } else {
+ output_buffer[values_output] = value;
}
+ values_output++;
}
buffers_processed++;
- if (length - total_bytes < samples_per_buffer) {
- descriptor->BTCNT.reg = (length - total_bytes) * words_per_sample;
- descriptor->DSTADDR.reg = ((uint32_t) buffer) + (length - total_bytes) * self->bytes_per_sample;
+ // 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 total_bytes;
+ return values_output;
}
void common_hal_audiobusio_pdmin_record_to_file(audiobusio_pdmin_obj_t* self, uint8_t* buffer, uint32_t length) {
diff --git a/shared-bindings/audiobusio/PDMIn.c b/shared-bindings/audiobusio/PDMIn.c
index e13e09d64..c71f44679 100644
--- a/shared-bindings/audiobusio/PDMIn.c
+++ b/shared-bindings/audiobusio/PDMIn.c
@@ -28,6 +28,7 @@
#include "lib/utils/context_manager_helpers.h"
#include "py/binary.h"
+#include "py/mphal.h"
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/microcontroller/Pin.h"
@@ -41,7 +42,7 @@
//|
//| PDMIn can be used to record an input audio signal on a given set of pins.
//|
-//| .. class:: PDMIn(clock_pin, data_pin, \*, frequency=8000, bit_depth=8, mono=True, oversample=64)
+//| .. class:: PDMIn(clock_pin, data_pin, \*, frequency=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
@@ -49,11 +50,16 @@
//|
//| :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 real value.
+//| :param int frequency: Target frequency of the resulting samples. Check `frequency` for actual value.
+//| Minimum frequency 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
+//| :param float startup_delay: seconds to wait after starting microphone clock
+//| to allow microphone to turn on. Most require only 0.01s; some require 0.1s. Longer is safer.
+//| Must be in range 0.0-1.0 seconds.
//|
+
//| Record 8-bit unsigned samples to buffer::
//|
//| import audiobusio
@@ -79,15 +85,19 @@
//| 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 };
+ enum { ARG_frequency, 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_INT, {.u_int = 8000} },
- { MP_QSTR_bit_depth, MP_ARG_INT, {.u_int = 8} },
- { MP_QSTR_mono, MP_ARG_BOOL,{.u_bool = true} },
- { MP_QSTR_oversample, MP_ARG_INT, {.u_int = 64} },
+ { MP_QSTR_frequency, 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} },
+ { MP_QSTR_startup_delay, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
};
+ // Default microphone startup delay is 110msecs. Have seen mics that need 100 msecs plus a bit.
+ static const float STARTUP_DELAY_DEFAULT = 0.110F;
+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 2, pos_args + 2, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@@ -116,8 +126,18 @@ STATIC mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_ar
}
bool mono = args[ARG_mono].u_bool;
+ float startup_delay = (args[ARG_startup_delay].u_obj == MP_OBJ_NULL)
+ ? STARTUP_DELAY_DEFAULT
+ : mp_obj_get_float(args[ARG_startup_delay].u_obj);
+ if (startup_delay < 0.0 || startup_delay > 1.0) {
+ 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,
- bit_depth, mono, oversample);
+ bit_depth, mono, oversample);
+
+ // Wait for the microphone to start up. Some start in 10 msecs; some take as much as 100 msecs.
+ mp_hal_delay_ms(startup_delay * 1000);
return MP_OBJ_FROM_PTR(self);
}
@@ -160,11 +180,14 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiobusio_pdmin___exit___obj, 4, 4,
//| audio at the given rate. For internal flash, writing all 1s to the file
//| before recording is recommended to speed up writes.
//|
+//| :return: The number of samples recorded. If this is less than `destination_length`,
+//| some samples were missed due to processing time.
+//|
STATIC mp_obj_t audiobusio_pdmin_obj_record(mp_obj_t self_obj, mp_obj_t destination, mp_obj_t destination_length) {
audiobusio_pdmin_obj_t *self = MP_OBJ_TO_PTR(self_obj);
raise_error_if_deinited(common_hal_audiobusio_pdmin_deinited(self));
- if (!MP_OBJ_IS_SMALL_INT(destination_length)) {
- mp_raise_TypeError("destination_length must be int");
+ if (!MP_OBJ_IS_SMALL_INT(destination_length) || MP_OBJ_SMALL_INT_VALUE(destination_length) < 0) {
+ mp_raise_TypeError("destination_length must be an int >= 0");
}
uint32_t length = MP_OBJ_SMALL_INT_VALUE(destination_length);
@@ -172,8 +195,8 @@ STATIC mp_obj_t audiobusio_pdmin_obj_record(mp_obj_t self_obj, mp_obj_t destinat
if (MP_OBJ_IS_TYPE(destination, &fatfs_type_fileio)) {
mp_raise_NotImplementedError("");
} else if (mp_get_buffer(destination, &bufinfo, MP_BUFFER_WRITE)) {
- if (bufinfo.len < length) {
- mp_raise_ValueError("Target buffer cannot hold destination_length bytes.");
+ if (bufinfo.len / mp_binary_get_size('@', bufinfo.typecode, NULL) < length) {
+ mp_raise_ValueError("Destination capacity is smaller than destination_length.");
}
uint8_t bit_depth = common_hal_audiobusio_pdmin_get_bit_depth(self);
if (bufinfo.typecode != 'H' && bit_depth == 16) {
@@ -181,12 +204,10 @@ STATIC mp_obj_t audiobusio_pdmin_obj_record(mp_obj_t self_obj, mp_obj_t destinat
} else if (bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE && bit_depth == 8) {
mp_raise_ValueError("destination buffer must be a bytearray or array of type 'B' for bit_depth = 8");
}
- length *= bit_depth / 8;
+ // length is the buffer length in slots, not bytes.
uint32_t length_written =
common_hal_audiobusio_pdmin_record_to_buffer(self, bufinfo.buf, length);
- if (length_written != length) {
- mp_printf(&mp_plat_print, "length mismatch %d %d\n", length_written, length);
- }
+ return MP_OBJ_NEW_SMALL_INT(length_written);
}
return mp_const_none;
}