diff options
| author | Dan Halbert <halbert@halwitz.org> | 2017-12-13 13:12:49 -0500 |
|---|---|---|
| committer | Scott Shawcroft <scott@tannewt.org> | 2017-12-13 10:12:49 -0800 |
| commit | 43650b689674a18b95d520921d10a91b67f64c84 (patch) | |
| tree | 092ac3a864b6ccdd08896865df528bc4c57aba47 /shared-bindings | |
| parent | 3c49f53eac8d39c0681f4b38d71d1b854b870fe0 (diff) | |
Fix PDMIn MEMS microphone support (#479)
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.
Diffstat (limited to 'shared-bindings')
| -rw-r--r-- | shared-bindings/audiobusio/PDMIn.c | 53 |
1 files changed, 37 insertions, 16 deletions
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; } |
