diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 768daa8651..348ef33021 100755 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -3714,10 +3714,20 @@ msgid "offset out of bounds" msgstr "" #: ports/nrf/common-hal/audiobusio/PDMIn.c +#: ports/stm/common-hal/audiobusio/PDMIn.c msgid "only bit_depth=16 is supported" msgstr "" +#: ports/stm/common-hal/audiobusio/PDMIn.c +msgid "only mono is supported" +msgstr "" + +#: ports/stm/common-hal/audiobusio/PDMIn.c +msgid "only oversample=64 is supported" +msgstr "" + #: ports/nrf/common-hal/audiobusio/PDMIn.c +#: ports/stm/common-hal/audiobusio/PDMIn.c msgid "only sample_rate=16000 is supported" msgstr "" diff --git a/ports/stm/Makefile b/ports/stm/Makefile index afde51bdc0..cd37a9bda0 100755 --- a/ports/stm/Makefile +++ b/ports/stm/Makefile @@ -234,6 +234,17 @@ SRC_C += \ peripherals/stm32$(MCU_SERIES_LOWER)/$(MCU_VARIANT_LOWER)/periph.c \ packages/$(MCU_PACKAGE).c +ifneq ($(CIRCUITPY_AUDIOBUSIO_PDMIN),0) + SRC_C += \ + common-hal/audiobusio/MEMS_Audio.c \ + common-hal/audiobusio/MEMS_Audio_ll_stm32l4.c \ + common-hal/audiobusio/OpenPDMFilter.c + + SRC_STM32 += \ + $(HAL_DIR)/Src/stm32$(MCU_SERIES_LOWER)xx_hal_sai.c + +endif + ifneq ($(CIRCUITPY_USB),0) SRC_C += lib/tinyusb/src/portable/st/synopsys/dcd_synopsys.c endif diff --git a/ports/stm/boards/swan_r5/mpconfigboard.mk b/ports/stm/boards/swan_r5/mpconfigboard.mk index 2ebaa9c23f..9e5be99bfa 100644 --- a/ports/stm/boards/swan_r5/mpconfigboard.mk +++ b/ports/stm/boards/swan_r5/mpconfigboard.mk @@ -41,10 +41,9 @@ CIRCUITPY_PULSEIO = 1 CIRCUITPY_PWMIO = 1 CIRCUITPY_AUDIOPWMIO = 1 CIRCUITPY_CANIO = 0 -CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_I2CTARGET = 0 # Requires SPI, PulseIO (stub ok): -CIRCUITPY_DISPLAYIO = 0 +CIRCUITPY_DISPLAYIO = 1 # These modules are implemented in shared-module/ - they can be included in # any port once their prerequisites in common-hal are complete. @@ -71,5 +70,8 @@ CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_KEYPAD = 1 CIRCUITPY_RGBMATRIX = 0 CIRCUITPY_RTC = 1 - CIRCUITPY_BUILD_EXTENSIONS = bin,uf2 +CIRCUITPY_ENABLE_MPY_NATIVE = 1 +CIRCUITPY_AUDIOBUSIO = 1 +CIRCUITPY_AUDIOBUSIO_I2SOUT = 0 +CIRCUITPY_AUDIOBUSIO_PDMIN = 1 diff --git a/ports/stm/boards/swan_r5/pins.c b/ports/stm/boards/swan_r5/pins.c index cf97c3587a..dc273cb6d4 100644 --- a/ports/stm/boards/swan_r5/pins.c +++ b/ports/stm/boards/swan_r5/pins.c @@ -129,5 +129,8 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, + + { MP_ROM_QSTR(MP_QSTR_MICROPHONE_CLOCK), MP_ROM_PTR(&pin_PA03) }, + { MP_ROM_QSTR(MP_QSTR_MICROPHONE_DATA), MP_ROM_PTR(&pin_PC03) }, }; MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/stm/common-hal/audiobusio/I2SOut.c b/ports/stm/common-hal/audiobusio/I2SOut.c new file mode 100644 index 0000000000..b63c3e7b22 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/I2SOut.c @@ -0,0 +1 @@ +// Although IS2Out is not enabled on the STM32L4 family, this file is still required for the build to pass diff --git a/ports/stm/common-hal/audiobusio/I2SOut.h b/ports/stm/common-hal/audiobusio/I2SOut.h new file mode 100644 index 0000000000..b63c3e7b22 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/I2SOut.h @@ -0,0 +1 @@ +// Although IS2Out is not enabled on the STM32L4 family, this file is still required for the build to pass diff --git a/ports/stm/common-hal/audiobusio/MEMS_Audio.c b/ports/stm/common-hal/audiobusio/MEMS_Audio.c new file mode 100644 index 0000000000..4371b609c0 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/MEMS_Audio.c @@ -0,0 +1,72 @@ +#include +#include + +#include "MEMS_Audio.h" +#include "MEMS_Audio_ll.h" + +static void default_pcm_data_available(MemsAudio *audio, pcm_sample_t *pcmSamples, size_t pcmLength) { +} + + + +/** + * @brief Initializes the MemsAudio instance. Only one instance can be initialized and used at a given time. + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_init(MemsAudio *audio) { + if (!audio->pcm_data_available) { + audio->pcm_data_available = default_pcm_data_available; + } + return mems_audio_ll_init(audio); +} + +/** + * @brief Uninitializes the MemsAudio instance. + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_uninit(MemsAudio *audio) { + return mems_audio_ll_uninit(audio); +} + +/** + * @brief Asynchronously records audio. + * + * @param audio + * @param pdmBuffer + * @param pdmBufferLength + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_record(MemsAudio *audio) { + return mems_audio_ll_record(audio); +} + +/** + * @brief Pause recording audio. + */ +mems_audio_err_t mems_audio_pause(MemsAudio *audio) { + return mems_audio_ll_pause(audio); +} + +/** + * @brief Resume recording audio. + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_resume(MemsAudio *audio) { + return mems_audio_ll_resume(audio); +} + +/** + * @brief Stop recording audio and + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_stop(MemsAudio *audio) { + return mems_audio_ll_stop(audio); +} diff --git a/ports/stm/common-hal/audiobusio/MEMS_Audio.h b/ports/stm/common-hal/audiobusio/MEMS_Audio.h new file mode 100644 index 0000000000..a26b94fef3 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/MEMS_Audio.h @@ -0,0 +1,132 @@ +#ifndef _MEMS_AUDIO_H_ +#define _MEMS_AUDIO_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief How many milliseconds of audio can fit in the audio buffer(s). + * Interrupts for recieved data fire at half this duration / twice the frequency. + */ +#ifndef MEMS_AUDIO_MS_BUFFER +#define MEMS_AUDIO_MS_BUFFER (1) +#endif + + +/** + * @brief The number of bits per sample of the PCM output + */ +#define PCM_OUT_RESOLUTION 16 + +/** + * @brief The output frequency of PCM samples in Hz. + */ +#define PCM_OUT_SAMPLING_FREQUENCY 16000 + +/** + * @brief type for describing error conditions. + */ +typedef int32_t mems_audio_err_t; + +/** + * @brief The datatype that holds an output PCM sample. + */ +typedef int16_t pcm_sample_t; +_Static_assert(PCM_OUT_RESOLUTION==16, "Output PCM resolution must be 16-bits"); + + +typedef enum { + MEMS_AUDIO_OK = 0, + MEMS_AUDIO_ERROR_ALREADY_INITIALIZED = -1, + MEMS_AUDIO_ERROR_NOT_INITIALIZED = -2 +} mems_audio_err_enum_t; + +#define IS_MEMS_AUDIO_ERROR(e) (e) +#define CHECK_MEMS_AUDIO_ERROR(e) { if (IS_MEMS_AUDIO_ERROR(e)) return e; } +#define CHECK_MEMS_AUDIO_INITIALIZED(x) { if (!x) return MEMS_AUDIO_ERROR_NOT_INITIALIZED; } + +typedef struct MemsAudio_t MemsAudio; + +/** + * @brief Callback informing that PCM samples are available for processing. + */ +typedef void (*pcm_data_available_t)(MemsAudio* audio, pcm_sample_t* pcmSamples, size_t pcmLength); + +/** + * @brief MemsAudio manages the filter, buffers and callbacks used to capture PDM audio samples and convert to PCM. + * + */ +typedef struct MemsAudio_t { + + /** + * @brief The buffer to store PCM audio samples + */ + volatile pcm_sample_t* volatile pcmOutputBuffer; + + /** + * @brief The length of the PCM buffer. SHould be at least MEMS_AUDIO_PCM_BUFFER_LENGTH + */ + volatile size_t pcmOutputBufferLength; + + /** + * @brief Optional callback for when PCM data is available. + */ + pcm_data_available_t pcm_data_available; + + void* audioImpl; + void* userData; +} MemsAudio; + + +mems_audio_err_t mems_audio_init(MemsAudio* audio); + +/** + * @brief Uninitializes the MemsAudio instance. + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_uninit(MemsAudio* audio); + +/** + * @brief Asynchronously records audio. + * + * @param audio + * @param pdmBuffer + * @param pdmBufferLength + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_record(MemsAudio* audio); + +/** + * @brief Pause recording audio. + */ +mems_audio_err_t mems_audio_pause(MemsAudio* audio); + +/** + * @brief Resume recording audio. + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_resume(MemsAudio* audio); + +/** + * @brief Stop recording audio and + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_stop(MemsAudio* audio); + +#ifdef __cplusplus +} +#endif + + +#endif // _MEMS_AUDIO_H_ diff --git a/ports/stm/common-hal/audiobusio/MEMS_Audio_ll.h b/ports/stm/common-hal/audiobusio/MEMS_Audio_ll.h new file mode 100644 index 0000000000..6419b90959 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/MEMS_Audio_ll.h @@ -0,0 +1,50 @@ +#ifndef _MEMS_AUDIO_LL_H_ +#define _MEMS_AUDIO_LL_H_ + +#include "MEMS_Audio.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +mems_audio_err_t mems_audio_ll_init(MemsAudio *audio); +mems_audio_err_t mems_audio_ll_uninit(MemsAudio *audio); + +/** + * @brief Asynchronously records audio. + * + * @param audio + * @param pdmBuffer + * @param pdmBufferLength + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_ll_record(MemsAudio *audio); + +/** + * @brief Pause recording audio. + */ +mems_audio_err_t mems_audio_ll_pause(MemsAudio *audio); + +/** + * @brief Resume recording audio. + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_ll_resume(MemsAudio *audio); + +/** + * @brief Stop recording audio and + * + * @param audio + * @return mems_audio_err_t + */ +mems_audio_err_t mems_audio_ll_stop(MemsAudio *audio); + +#ifdef __cplusplus +} +#endif + + +#endif // _MEMS_AUDIO_LL_H_ diff --git a/ports/stm/common-hal/audiobusio/MEMS_Audio_ll_stm32l4.c b/ports/stm/common-hal/audiobusio/MEMS_Audio_ll_stm32l4.c new file mode 100644 index 0000000000..71f046f9b9 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/MEMS_Audio_ll_stm32l4.c @@ -0,0 +1,361 @@ +#include +#include "MEMS_Audio_ll_stm32l4.h" +#include "MEMS_Audio.h" + +/** + * @brief The implementation is a singleton. + * + */ +MemsAudio_STM32L4SAIPDM* volatile audioImpl; + +static mems_audio_err_t MX_DMA_Init(void); +static mems_audio_err_t MX_DMA_Uninit(void); +static mems_audio_err_t MX_SAI1_Init(void); + +#define CHECK_HAL_ERROR(x, e) \ + { \ + if ((x) != HAL_OK) \ + return e; \ + } + +/** + * @brief Checks the HAL return code and returns from a void function on error. The + * error is saved to lastError. + */ +#define CHECK_HAL_ERROR_VOID(x, e) \ + { \ + if ((x) != HAL_OK) { \ + audioImpl->lastError = e; \ + return; \ + } \ + } + +#define CHECK_MEMS_AUDIO_ERROR_LAST() \ + { \ + if (audioImpl->lastError != MEMS_AUDIO_OK) \ + return audioImpl->lastError; \ + } + +static bool default_pdm_data_available(MemsAudio_STM32L4SAIPDM* audio, pdm_sample_t* pdmSamples, size_t count) +{ + return true; +} + +int filter_pdm(MemsAudio_STM32L4SAIPDM* impl, pdm_sample_t* input, pcm_sample_t* output) +{ + if (impl->filter.Decimation==64) { + Open_PDM_Filter_64(input, output, 1, &impl->filter); + } + else { + Open_PDM_Filter_128(input, output, 1, &impl->filter); + } + return impl->filter.nSamples; +} + +static void mems_audio_init_filter(MemsAudio_STM32L4SAIPDM *impl) +{ + TPDMFilter_InitStruct* filter = &impl->filter; + filter->Fs = PCM_OUT_SAMPLING_FREQUENCY; + filter->nSamples = MEMS_AUDIO_PCM_BUFFER_LENGTH; + filter->LP_HZ = PCM_OUT_SAMPLING_FREQUENCY / 2; // The Nyquist frequency + filter->HP_HZ = 10; // high pass to remove DC offset + filter->In_MicChannels = 1; + filter->Out_MicChannels = 1; + filter->Decimation = PDM_IN_DECIMATION_FACTOR; + Open_PDM_Filter_Init(filter); +} +volatile unsigned ignore_dma_count; + +/** + * @brief Converts PDM samples + * + * @param pdmBuffer The buffer holding the PDM samples + * @param pdmBufferLength The number of samples available + */ +void pdm2pcm(uint8_t *pdmBuffer, size_t pdmBufferLength) +{ + MemsAudio_STM32L4SAIPDM *impl = audioImpl; + if (impl) + { + bool convert = impl->discard_dma || impl->pdm_data_available(impl, pdmBuffer, pdmBufferLength); + if (convert) + { + MemsAudio* audio = impl->audio; + filter_pdm(impl, pdmBuffer, (pcm_sample_t*)audio->pcmOutputBuffer); + if (!impl->discard_dma) + audio->pcm_data_available(audio, (pcm_sample_t*)audio->pcmOutputBuffer, impl->filter.nSamples); + else + impl->discard_dma--; + } + } +} + +/** + * @brief Initialize the PDM interface ready to begin capture. + * @retval + */ +mems_audio_err_t mems_audio_ll_init(MemsAudio *audio) +{ + mems_audio_init_filter(audioImpl); + if (!audioImpl->pdm_data_available) { + audioImpl->pdm_data_available = &default_pdm_data_available; + } + + CHECK_MEMS_AUDIO_ERROR(MX_DMA_Init()); + CHECK_MEMS_AUDIO_ERROR(MX_SAI1_Init()); + return MEMS_AUDIO_OK; +} + +mems_audio_err_t uninit(void) { + if (audioImpl) { + MemsAudio_STM32L4SAIPDM* impl = audioImpl; + audioImpl = NULL; + mems_audio_ll_stop(impl->audio); + CHECK_HAL_ERROR(HAL_SAI_DeInit(&impl->hSAI_BlockA1), MEMS_AUDIO_ERROR_SAI_DEINIT); + CHECK_MEMS_AUDIO_ERROR(MX_DMA_Uninit()); + } + return MEMS_AUDIO_OK; +} + +/** + * @brief Uninitialize low level PDM capture + */ +mems_audio_err_t mems_audio_ll_uninit(MemsAudio *audio) +{ + if (audioImpl->audio == audio) { + uninit(); + } + return MEMS_AUDIO_OK; +} + +mems_audio_err_t mems_audio_ll_record(MemsAudio *audio) +{ + audioImpl->discard_dma = (100/MEMS_AUDIO_MS_BUFFER)+1; + CHECK_HAL_ERROR(HAL_SAI_Receive_DMA(&audioImpl->hSAI_BlockA1, audioImpl->pdmBuffer, audioImpl->pdmBufferLength), + MEMS_AUDIO_ERROR_DMA_START); + return MEMS_AUDIO_OK; +} + +mems_audio_err_t mems_audio_ll_stop(MemsAudio *audio) +{ + CHECK_HAL_ERROR(HAL_SAI_DMAStop(&audioImpl->hSAI_BlockA1), MEMS_AUDIO_ERROR_DMA_STOP); + return MEMS_AUDIO_OK; +} + +mems_audio_err_t mems_audio_ll_pause(MemsAudio *audio) +{ + CHECK_HAL_ERROR(HAL_SAI_DMAPause(&audioImpl->hSAI_BlockA1), MEMS_AUDIO_ERROR_DMA_PAUSE); + return MEMS_AUDIO_OK; +} + +mems_audio_err_t mems_audio_ll_resume(MemsAudio *audio) +{ + CHECK_HAL_ERROR(HAL_SAI_DMAResume(&audioImpl->hSAI_BlockA1), MEMS_AUDIO_ERROR_DMA_RESUME); + return MEMS_AUDIO_OK; +} + +/** + * @brief SAI1 Initialization Function + * @param None + * @retval None + */ +static mems_audio_err_t MX_SAI1_Init(void) +{ + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + + SAI_HandleTypeDef hSAI_BlockA1 = {0}; + MemsAudio_STM32L4SAIPDM* impl = audioImpl; + CHECK_MEMS_AUDIO_INITIALIZED(impl); + hSAI_BlockA1.Instance = SAI1_Block_A; + hSAI_BlockA1.Init.Protocol = SAI_FREE_PROTOCOL; + hSAI_BlockA1.Init.AudioMode = SAI_MODEMASTER_RX; + /* The PDM interface provides 8 1-bit samples at a time */ + hSAI_BlockA1.Init.DataSize = SAI_DATASIZE_8; + hSAI_BlockA1.Init.FirstBit = SAI_FIRSTBIT_MSB; + + hSAI_BlockA1.Init.ClockStrobing = SAI_CLOCKSTROBING_FALLINGEDGE; + hSAI_BlockA1.Init.Synchro = SAI_ASYNCHRONOUS; /* asynchronous - not chained to other SAI blocks */ + hSAI_BlockA1.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE; /* Not driving the primary SAI clock */ + hSAI_BlockA1.Init.NoDivider = SAI_MASTERDIVIDER_DISABLE; + hSAI_BlockA1.Init.MckOverSampling = SAI_MCK_OVERSAMPLING_DISABLE; + hSAI_BlockA1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_FULL; + hSAI_BlockA1.Init.MonoStereoMode = SAI_MONOMODE; /* PDM is intrinsicly stereo, sampling on */ + hSAI_BlockA1.Init.CompandingMode = SAI_NOCOMPANDING; + hSAI_BlockA1.Init.PdmInit.Activation = ENABLE; /* Enable PDM interface in the SAI */ + hSAI_BlockA1.Init.PdmInit.MicPairsNbr = 1; /* 1 pair - 2 mics */ + hSAI_BlockA1.Init.PdmInit.ClockEnable = SAI_PDM_CLOCK1_ENABLE; + hSAI_BlockA1.FrameInit.FrameLength = 16; + hSAI_BlockA1.FrameInit.ActiveFrameLength = 1; + hSAI_BlockA1.FrameInit.FSDefinition = SAI_FS_STARTFRAME; /* FS is not really used */ + hSAI_BlockA1.FrameInit.FSPolarity = SAI_FS_ACTIVE_HIGH; + hSAI_BlockA1.FrameInit.FSOffset = SAI_FS_FIRSTBIT; + hSAI_BlockA1.SlotInit.FirstBitOffset = 0; + hSAI_BlockA1.SlotInit.SlotSize = SAI_SLOTSIZE_DATASIZE; + hSAI_BlockA1.SlotInit.SlotNumber = 2; + hSAI_BlockA1.SlotInit.SlotActive = 0x0001; + impl->hSAI_BlockA1 = hSAI_BlockA1; + CHECK_HAL_ERROR(HAL_SAI_Init(&impl->hSAI_BlockA1), MEMS_AUDIO_ERROR_SAI_INIT); + CHECK_MEMS_AUDIO_ERROR_LAST(); + return MEMS_AUDIO_OK; +} + +#define MEMS_AUDIO_DMA_IRQn DMA1_Channel6_IRQn +#define MEMS_AUDIO_DMA_CHANNEL DMA1_Channel6 +#define MEMS_AUDIO_DMA_PRIORITY 6 +#define DMA_HANDLER DMA1_Channel6_IRQHandler + +void HAL_SAI_MspInit(SAI_HandleTypeDef *hsai) +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + /* SAI1 */ + MemsAudio_STM32L4SAIPDM* impl = audioImpl; + if (hsai->Instance == SAI1_Block_A && impl) + { + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SAI1; + PeriphClkInit.Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLLSAI1; + PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_MSI; + PeriphClkInit.PLLSAI1.PLLSAI1M = MEMS_AUDIO_CLOCK_PLLM; + PeriphClkInit.PLLSAI1.PLLSAI1N = MEMS_AUDIO_CLOCK_PLLN; + PeriphClkInit.PLLSAI1.PLLSAI1P = MEMS_AUDIO_CLOCK_PLLP; + PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2; + PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2; + PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_SAI1CLK; + CHECK_HAL_ERROR_VOID(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit), MEMS_AUDIO_ERROR_SAI_CLOCK); + + if (impl->SAI1_client == 0) + { + __HAL_RCC_SAI1_CLK_ENABLE(); + } + impl->SAI1_client++; + + /**SAI1_A_Block_A GPIO Configuration + PC3 ------> SAI1_D1 + PA3 ------> SAI1_CK1 + */ + GPIO_InitStruct.Pin = GPIO_PIN_3; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Alternate = GPIO_AF3_SAI1; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_3; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Alternate = GPIO_AF3_SAI1; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + /* Peripheral DMA init*/ + DMA_HandleTypeDef hdma_sai1_a = {0}; + hdma_sai1_a.Instance = MEMS_AUDIO_DMA_CHANNEL; + hdma_sai1_a.Init.Request = DMA_REQUEST_SAI1_A; + hdma_sai1_a.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_sai1_a.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_sai1_a.Init.MemInc = DMA_MINC_ENABLE; + hdma_sai1_a.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_sai1_a.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + hdma_sai1_a.Init.Mode = DMA_CIRCULAR; + hdma_sai1_a.Init.Priority = DMA_PRIORITY_HIGH; + impl->hdma_sai1_a = hdma_sai1_a; + CHECK_HAL_ERROR_VOID(HAL_DMA_Init(&impl->hdma_sai1_a), MEMS_AUDIO_ERROR_SAI_DMA_INIT); + + /* Several peripheral DMA handle pointers point to the same DMA handle. + Be aware that there is only one channel to perform all the requested DMAs. */ + __HAL_LINKDMA(hsai, hdmarx, impl->hdma_sai1_a); + + __HAL_LINKDMA(hsai, hdmatx, impl->hdma_sai1_a); + } +} + +void HAL_SAI_MspDeInit(SAI_HandleTypeDef *hsai) +{ + /* SAI1 */ + MemsAudio_STM32L4SAIPDM* impl = audioImpl; + if (hsai->Instance == SAI1_Block_A && impl) + { + impl->SAI1_client--; + if (impl->SAI1_client == 0) + { + /* Peripheral clock disable */ + __HAL_RCC_SAI1_CLK_DISABLE(); + } + + /**SAI1_A_Block_A GPIO Configuration + PC3 ------> SAI1_D1 + PA3 ------> SAI1_CK1 + */ + HAL_GPIO_DeInit(GPIOC, GPIO_PIN_3); + + HAL_GPIO_DeInit(GPIOA, GPIO_PIN_3); + + /* SAI1 DMA Deinit */ + HAL_DMA_DeInit(hsai->hdmarx); + HAL_DMA_DeInit(hsai->hdmatx); + } +} + +/** + * @brief Initialize the DMA peripheral + * + */ +static mems_audio_err_t MX_DMA_Init(void) +{ + + /* DMA controller clock enable */ + __HAL_RCC_DMAMUX1_CLK_ENABLE(); + __HAL_RCC_DMA1_CLK_ENABLE(); + + /* DMA interrupt init */ + /* DMA1_Channel1_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(MEMS_AUDIO_DMA_IRQn, MEMS_AUDIO_DMA_PRIORITY, 0); + HAL_NVIC_EnableIRQ(MEMS_AUDIO_DMA_IRQn); + return MEMS_AUDIO_OK; +} + +static mems_audio_err_t MX_DMA_Uninit(void) +{ + HAL_NVIC_DisableIRQ(MEMS_AUDIO_DMA_IRQn); + return MEMS_AUDIO_OK; +} + +/** + * @brief Global handler for the DMA interrupt. Forwards to the HAL for further processing. + * + */ +void DMA_HANDLER(void) +{ + HAL_DMA_IRQHandler(&audioImpl->hdma_sai1_a); +} + +/** + * @brief Converts PDM samples in the upper half of the PDM buffer. + * + * @param hSai + */ +void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hSai) +{ + (void)hSai; + pdm2pcm(audioImpl->pdmBuffer, audioImpl->pdmBufferLength>>1); +} + +/** + * @brief Converts PDM samples in the upper half of the PDM buffer. + * + * @param hSai + */ +void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hSai) +{ + (void)hSai; + pdm2pcm(audioImpl->pdmBuffer+(audioImpl->pdmBufferLength>>1), audioImpl->pdmBufferLength>>1); +} + +mems_audio_err_t mems_audio_init_stm32l4_sai_pdm(MemsAudio* audio, MemsAudio_STM32L4SAIPDM* impl) +{ + uninit(); + audioImpl = impl; + impl->audio = audio; + return mems_audio_init(audio); +} diff --git a/ports/stm/common-hal/audiobusio/MEMS_Audio_ll_stm32l4.h b/ports/stm/common-hal/audiobusio/MEMS_Audio_ll_stm32l4.h new file mode 100644 index 0000000000..c76ccce5e1 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/MEMS_Audio_ll_stm32l4.h @@ -0,0 +1,184 @@ +#ifndef _MEMS_AUDIO_LL_STM32L4_H_ +#define _MEMS_AUDIO_LL_STM32L4_H_ + +#include +#include +#include +#include "OpenPDMFilter.h" +#include "MEMS_Audio.h" +#include "MEMS_Audio_ll.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The SAI PDM interface captures 8 bits from the PDM signal. + */ +typedef uint8_t pdm_sample_t; + +/** + * @brief The PDM sample frequency in kHz. (Bit samples per millisecond.) + */ +#define PDM_IN_FREQUENCY_KHZ 1024 + +#define PDM_IN_FREQUENCY (PDM_IN_FREQUENCY_KHZ * 1000) + +/** + * @brief The number of channels of audio captured + */ +#define PDM_IN_CHANNELS 1 + +/** + * @brief The decimation of the PDM bit stream to produce PCM samples at the desired output rate. + */ +#define PDM_IN_DECIMATION_FACTOR 64 + +/** + * @brief The number of pdm samples captured per millisecond from the PDM interface. + */ +#define MEMS_AUDIO_PDM_SAMPLES_PER_MS ((PDM_IN_FREQUENCY_KHZ / (sizeof(pdm_sample_t) * 8)) * PDM_IN_CHANNELS) + +/** + * @brief The size of the buffer used to hold PDM samples prior to conversion to PCM. + * Each half of the buffer generates an interrupt. + */ +#define MEMS_AUDIO_PDM_BUFFER_LENGTH (MEMS_AUDIO_PDM_SAMPLES_PER_MS * MEMS_AUDIO_MS_BUFFER * 2) + +/** + * @brief The length of the PCM buffer required to hold converted samples. + */ +#define MEMS_AUDIO_PCM_BUFFER_LENGTH (PCM_OUT_SAMPLING_FREQUENCY * MEMS_AUDIO_MS_BUFFER / 1000) + + +/** + * Presently the internal PDM parameters and output PCM parameters are fixed for the values given here. + */ + + +/** + * @brief 128 point decimation did not work with the OpenPDMFilter and just produced PCM output + * approaching a square wave. + */ +_Static_assert(PDM_IN_DECIMATION_FACTOR == 64 || PDM_IN_DECIMATION_FACTOR == 128, "A decomation factor of 64 or 128 is supported at present."); + + +/** + * @brief The PDM bitstream frequency divided by the decimation factor should be the same as the desired output PCM frequency. + */ +_Static_assert(PDM_IN_FREQUENCY / PDM_IN_DECIMATION_FACTOR == PCM_OUT_SAMPLING_FREQUENCY, "PDM output frequency should equal the input frequency divided by the decimation factor."); + +// +// SAI PDM interface clock configuration +// + +#define MEMS_AUDIO_MSI_FREQUENCY (48 * 1000 * 1000) +#define MEMS_AUDIO_CLOCK_PLLM (15) +#define MEMS_AUDIO_CLOCK_PLLN (16) +#define MEMS_AUDIO_CLOCK_PLLP (RCC_PLLP_DIV25) + +/** + * @brief The SAI PDM clock should be twice the desired PDM bitstream frequency + */ +_Static_assert((MEMS_AUDIO_MSI_FREQUENCY / MEMS_AUDIO_CLOCK_PLLM * MEMS_AUDIO_CLOCK_PLLN / MEMS_AUDIO_CLOCK_PLLP) == (PDM_IN_FREQUENCY_KHZ * 1000 * 2), "PDM clock should be twice the PDM sample frequency."); + +typedef struct MemsAudio_STM32L4SAIPDM_t MemsAudio_STM32L4SAIPDM; + +/** + * @brief Callback informing that PDM samples are available for processing. + * @param audio The MemsAudio instance + * @return `false` to skip conversion of PDM to PCM. `true` to convert the PDM samples to PCM. + */ +typedef bool (*pdm_data_available_t)(MemsAudio_STM32L4SAIPDM *audio, pdm_sample_t *pdmSamples, size_t pdmLength); + +/** + * @brief Implementation details for the STM32 SAI PDM implementation. + * + */ +/** + * @brief Audio capture from a MEMS microphone on the STM32L4 using the SAI PDM interface. + */ +typedef struct MemsAudio_STM32L4SAIPDM_t { + + MemsAudio *audio; + + /** + * @brief The last error that happened in a void function (e.g. HAL callback) + */ + mems_audio_err_t lastError; + + /** + * @brief The buffer to store PDM audio samples + */ + pdm_sample_t *pdmBuffer; + + /** + * @brief The length of the PDM buffer. Should be at least MEMS_AUDIO_PDM_BUFFER_LENGTH + */ + size_t pdmBufferLength; + + /** + * @brief Optional callback for when PDM data is available. + */ + pdm_data_available_t pdm_data_available; + + /** + * @brief A cound of the number of PDM clients in use. + */ + uint32_t SAI1_client; + + /** + * @brief The SAI peripheral handle being used for SAI A subclock 1. + */ + SAI_HandleTypeDef hSAI_BlockA1; + + /** + * @brief The DMA handle to transfer SAI data from the peripheral to memory. + */ + DMA_HandleTypeDef hdma_sai1_a; + + /** + * @brief An instance of the PDM filter that performs decimation, and high and low pass filtering. + * Unlike the DFSDM peripheral, the SAI PDM interface doesn't perform these operations in hardware. + */ + TPDMFilter_InitStruct filter; + + /** + * @brief The number of DMA transfers to ignore after starting recording. + */ + volatile uint16_t discard_dma; + +} MemsAudio_STM32L4SAIPDM; + +/** + * @brief Creates a MemsAudio instance that retrieves PDM samples from SAI A block 1 via the PDM interface, + * decimates and filters these in software to produce the PCM output stream. + * + * @param audio + * @param implementation + * @return meems_audio_error_t + */ +mems_audio_err_t mems_audio_init_stm32l4_sai_pdm(MemsAudio *audio, MemsAudio_STM32L4SAIPDM *implementation); + +/** + * @brief Implementation-specific error codes. + * + */ +typedef enum mems_audio_err_stm32l4_t { + MEMS_AUDIO_ERROR_SAI_DMA_INIT = 1, + MEMS_AUDIO_ERROR_SAI_CLOCK = 2, + MEMS_AUDIO_ERROR_SAI_INIT = 3, + MEMS_AUDIO_ERROR_SAI_DEINIT = 4, + MEMS_AUDIO_ERROR_DMA_START = 5, + MEMS_AUDIO_ERROR_DMA_STOP = 6, + MEMS_AUDIO_ERROR_DMA_PAUSE = 7, + MEMS_AUDIO_ERROR_DMA_RESUME = 8 +} mems_audio_err_stm32l4_t; + + +#ifdef __cplusplus +} +#endif + + +#endif // _MEMS_AUDIO_LL_STM32L4_H_ diff --git a/ports/stm/common-hal/audiobusio/OpenPDMFilter.c b/ports/stm/common-hal/audiobusio/OpenPDMFilter.c new file mode 100644 index 0000000000..c65353b146 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/OpenPDMFilter.c @@ -0,0 +1,302 @@ +/** + ******************************************************************************* + * @file OpenPDMFilter.c + * @author CL + * @version V1.0.0 + * @date 9-September-2015 + * @brief Open PDM audio software decoding Library. + * This Library is used to decode and reconstruct the audio signal + * produced by ST MEMS microphone (MP45Dxxx, MP34Dxxx). + ******************************************************************************* + * @attention + * + *

© COPYRIGHT 2018 STMicroelectronics

+ * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************* + */ + + +/* Includes ------------------------------------------------------------------*/ + +#include "OpenPDMFilter.h" + + + +/* Functions -----------------------------------------------------------------*/ + +#ifdef USE_LUT +int32_t filter_table_mono_64(lut_t lut, uint8_t *data, uint8_t sincn) { + return (int32_t) + lut[data[0]][0][sincn] + + lut[data[1]][1][sincn] + + lut[data[2]][2][sincn] + + lut[data[3]][3][sincn] + + lut[data[4]][4][sincn] + + lut[data[5]][5][sincn] + + lut[data[6]][6][sincn] + + lut[data[7]][7][sincn]; +} +int32_t filter_table_stereo_64(lut_t lut, uint8_t *data, uint8_t sincn) { + return (int32_t) + lut[data[0]][0][sincn] + + lut[data[2]][1][sincn] + + lut[data[4]][2][sincn] + + lut[data[6]][3][sincn] + + lut[data[8]][4][sincn] + + lut[data[10]][5][sincn] + + lut[data[12]][6][sincn] + + lut[data[14]][7][sincn]; +} +#if DECIMATION_MAX == 128 +int32_t filter_table_mono_128(lut_t lut, uint8_t *data, uint8_t sincn) { + return (int32_t) + lut[data[0]][0][sincn] + + lut[data[1]][1][sincn] + + lut[data[2]][2][sincn] + + lut[data[3]][3][sincn] + + lut[data[4]][4][sincn] + + lut[data[5]][5][sincn] + + lut[data[6]][6][sincn] + + lut[data[7]][7][sincn] + + lut[data[8]][8][sincn] + + lut[data[9]][9][sincn] + + lut[data[10]][10][sincn] + + lut[data[11]][11][sincn] + + lut[data[12]][12][sincn] + + lut[data[13]][13][sincn] + + lut[data[14]][14][sincn] + + lut[data[15]][15][sincn]; +} +int32_t filter_table_stereo_128(lut_t lut, uint8_t *data, uint8_t sincn) { + return (int32_t) + lut[data[0]][0][sincn] + + lut[data[2]][1][sincn] + + lut[data[4]][2][sincn] + + lut[data[6]][3][sincn] + + lut[data[8]][4][sincn] + + lut[data[10]][5][sincn] + + lut[data[12]][6][sincn] + + lut[data[14]][7][sincn] + + lut[data[16]][8][sincn] + + lut[data[18]][9][sincn] + + lut[data[20]][10][sincn] + + lut[data[22]][11][sincn] + + lut[data[24]][12][sincn] + + lut[data[26]][13][sincn] + + lut[data[28]][14][sincn] + + lut[data[30]][15][sincn]; +} +#endif +int32_t (*filter_tables_64[2])(lut_t lut, uint8_t *data, uint8_t sincn) = {filter_table_mono_64, filter_table_stereo_64}; +#if DECIMATION_MAX == 128 +int32_t (*filter_tables_128[2])(lut_t lut, uint8_t *data, uint8_t sincn) = {filter_table_mono_128, filter_table_stereo_128}; +#endif +#else +int32_t filter_table(uint8_t *data, uint8_t sincn, TPDMFilter_InitStruct *param) { + uint8_t c, i; + uint16_t data_index = 0; + uint32_t *coef_p = ¶m->coef[sincn][0]; + int32_t F = 0; + uint8_t decimation = param->Decimation; + uint8_t channels = param->In_MicChannels; + + for (i = 0; i < decimation; i += 8) { + c = data[data_index]; + F += ((c >> 7)) * coef_p[i ] + + ((c >> 6) & 0x01) * coef_p[i + 1] + + ((c >> 5) & 0x01) * coef_p[i + 2] + + ((c >> 4) & 0x01) * coef_p[i + 3] + + ((c >> 3) & 0x01) * coef_p[i + 4] + + ((c >> 2) & 0x01) * coef_p[i + 5] + + ((c >> 1) & 0x01) * coef_p[i + 6] + + ((c) & 0x01) * coef_p[i + 7]; + data_index += channels; + } + return F; +} +#endif + +void convolve(uint32_t Signal[] /* SignalLen */, unsigned short SignalLen, + uint32_t Kernel[] /* KernelLen */, unsigned short KernelLen, + uint32_t Result[] /* SignalLen + KernelLen - 1 */) { + uint16_t n; + + for (n = 0; n < SignalLen + KernelLen - 1; n++) + { + unsigned short kmin, kmax, k; + + Result[n] = 0; + + kmin = (n >= KernelLen - 1) ? n - (KernelLen - 1) : 0; + kmax = (n < SignalLen - 1) ? n : SignalLen - 1; + + for (k = kmin; k <= kmax; k++) { + Result[n] += Signal[k] * Kernel[n - k]; + } + } +} + +void Open_PDM_Filter_Init(TPDMFilter_InitStruct *Param) { + uint16_t i, j; + int64_t sum = 0; + + uint8_t decimation = Param->Decimation; + + for (i = 0; i < SINCN; i++) { + Param->Coef[i] = 0; + Param->bit[i] = 0; + } + for (i = 0; i < decimation; i++) { + Param->sinc1[i] = 1; + } + + Param->OldOut = Param->OldIn = Param->OldZ = 0; + Param->LP_ALFA = (Param->LP_HZ != 0 ? (uint16_t)((float)Param->LP_HZ * 256 / (Param->LP_HZ + Param->Fs / (2 * 3.14159))) : 0); + Param->HP_ALFA = (Param->HP_HZ != 0 ? (uint16_t)((float)Param->Fs * 256 / (2 * 3.14159 * Param->HP_HZ + Param->Fs)) : 0); + + Param->FilterLen = decimation * SINCN; + Param->sinc[0] = 0; + Param->sinc[decimation * SINCN - 1] = 0; + convolve(Param->sinc1, decimation, Param->sinc1, decimation, Param->sinc2); + convolve(Param->sinc2, (decimation << 1) - 1, Param->sinc1, decimation, &Param->sinc[1]); + for (j = 0; j < SINCN; j++) { + for (i = 0; i < decimation; i++) { + Param->coef[j][i] = Param->sinc[j * decimation + i]; + sum += Param->sinc[j * decimation + i]; + } + } + + Param->sub_const = sum >> 1; + uint32_t div_const = Param->sub_const * Param->MaxVolume / 32768 / FILTER_GAIN; + Param->div_const = (div_const == 0 ? 1 : div_const); + + #ifdef USE_LUT + /* Look-Up Table. */ + uint16_t c, d, s; + for (s = 0; s < SINCN; s++) + { + uint32_t *coef_p = &Param->coef[s][0]; + for (c = 0; c < 256; c++) { + for (d = 0; d < decimation / 8; d++) { + Param->lut[c][d][s] = ((c >> 7)) * coef_p[d * 8 ] + + ((c >> 6) & 0x01) * coef_p[d * 8 + 1] + + ((c >> 5) & 0x01) * coef_p[d * 8 + 2] + + ((c >> 4) & 0x01) * coef_p[d * 8 + 3] + + ((c >> 3) & 0x01) * coef_p[d * 8 + 4] + + ((c >> 2) & 0x01) * coef_p[d * 8 + 5] + + ((c >> 1) & 0x01) * coef_p[d * 8 + 6] + + ((c) & 0x01) * coef_p[d * 8 + 7]; + } + } + } + #endif +} + +int Open_PDM_Filter_64(uint8_t *data, int16_t *dataOut, uint16_t volume, TPDMFilter_InitStruct *Param) { + uint8_t i, data_out_index; + uint8_t channels = Param->In_MicChannels; + uint8_t data_inc = ((DECIMATION_MAX >> 4) * channels); + int64_t Z, Z0, Z1, Z2; + int64_t OldOut, OldIn, OldZ; + + OldOut = Param->OldOut; + OldIn = Param->OldIn; + OldZ = Param->OldZ; + + #ifdef USE_LUT + uint8_t j = channels - 1; + #endif + + for (i = 0, data_out_index = 0; i < Param->nSamples; i++, data_out_index += channels) { + #ifdef USE_LUT + Z0 = filter_tables_64[j](Param->lut, data, 0); + Z1 = filter_tables_64[j](Param->lut, data, 1); + Z2 = filter_tables_64[j](Param->lut, data, 2); + #else + Z0 = filter_table(data, 0, Param); + Z1 = filter_table(data, 1, Param); + Z2 = filter_table(data, 2, Param); + #endif + + Z = Param->Coef[1] + Z2 - Param->sub_const; + Param->Coef[1] = Param->Coef[0] + Z1; + Param->Coef[0] = Z0; + + OldOut = (Param->HP_ALFA * (OldOut + Z - OldIn)) >> 8; + OldIn = Z; + OldZ = ((256 - Param->LP_ALFA) * OldZ + Param->LP_ALFA * OldOut) >> 8; + + Z = OldZ * volume; + Z = RoundDiv(Z, (Param->div_const)); + Z = SaturaLH(Z, -32700, 32700); + + dataOut[data_out_index] = Z; + data += data_inc; + } + + Param->OldOut = OldOut; + Param->OldIn = OldIn; + Param->OldZ = OldZ; + return data_out_index; +} + +#if DECIMATION_MAX == 128 +int Open_PDM_Filter_128(uint8_t *data, int16_t *dataOut, uint16_t volume, TPDMFilter_InitStruct *Param) { + uint8_t i, data_out_index; + uint8_t channels = Param->In_MicChannels; + uint8_t data_inc = ((DECIMATION_MAX >> 3) * channels); + int64_t Z, Z0, Z1, Z2; + int64_t OldOut, OldIn, OldZ; + + OldOut = Param->OldOut; + OldIn = Param->OldIn; + OldZ = Param->OldZ; + + #ifdef USE_LUT + uint8_t j = channels - 1; + #endif + + for (i = 0, data_out_index = 0; i < Param->nSamples; i++, data_out_index += channels) { + #ifdef USE_LUT + Z0 = filter_tables_128[j](Param->lut, data, 0); + Z1 = filter_tables_128[j](Param->lut, data, 1); + Z2 = filter_tables_128[j](Param->lut, data, 2); + #else + Z0 = filter_table(data, 0, Param); + Z1 = filter_table(data, 1, Param); + Z2 = filter_table(data, 2, Param); + #endif + + Z = Param->Coef[1] + Z2 - Param->sub_const; + Param->Coef[1] = Param->Coef[0] + Z1; + Param->Coef[0] = Z0; + + OldOut = (Param->HP_ALFA * (OldOut + Z - OldIn)) >> 8; + OldIn = Z; + OldZ = ((256 - Param->LP_ALFA) * OldZ + Param->LP_ALFA * OldOut) >> 8; + + Z = OldZ * volume; + Z = RoundDiv(Z, (Param->div_const)); + Z = SaturaLH(Z, -32700, 32700); + + dataOut[data_out_index] = Z; + data += data_inc; + } + + Param->OldOut = OldOut; + Param->OldIn = OldIn; + Param->OldZ = OldZ; + return data_out_index; +} +#endif diff --git a/ports/stm/common-hal/audiobusio/OpenPDMFilter.h b/ports/stm/common-hal/audiobusio/OpenPDMFilter.h new file mode 100644 index 0000000000..a6e4a8c063 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/OpenPDMFilter.h @@ -0,0 +1,112 @@ +/** + ******************************************************************************* + * @file OpenPDMFilter.h + * @author CL + * @version V1.0.0 + * @date 9-September-2015 + * @brief Header file for Open PDM audio software decoding Library. + * This Library is used to decode and reconstruct the audio signal + * produced by ST MEMS microphone (MP45Dxxx, MP34Dxxx). + ******************************************************************************* + * @attention + * + *

© COPYRIGHT 2018 STMicroelectronics

+ * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************* + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ + +#ifndef __OPENPDMFILTER_H +#define __OPENPDMFILTER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ + +#include + +/* Definitions ---------------------------------------------------------------*/ + +/* + * Enable to use a Look-Up Table to improve performances while using more FLASH + * and RAM memory. + * Note: Without Look-Up Table up to stereo@16KHz configuration is supported. + */ +#define USE_LUT + +#define SINCN 3 +#define DECIMATION_MAX 128 // can be 128 but this didn't work for me +#define FILTER_GAIN 16 + +#define HTONS(A) ((((uint16_t)(A) & 0xff00) >> 8) | \ + (((uint16_t)(A) & 0x00ff) << 8)) + +#define RoundDiv(a, b) (((a) > 0) ? (((a) + (b) / 2) / (b)) : (((a) - (b) / 2) / (b))) + +#define SaturaLH(N, L, H) (((N) < (L)) ? (L) : (((N) > (H)) ? (H) : (N))) + +/* Types ---------------------------------------------------------------------*/ + +typedef int32_t lut_t[256][DECIMATION_MAX / 8][SINCN]; + + +typedef struct +{ + /* Public */ + uint16_t LP_HZ; + uint16_t HP_HZ; + uint16_t Fs; + unsigned int nSamples; + uint8_t In_MicChannels; + uint8_t Out_MicChannels; + uint8_t Decimation; + uint8_t MaxVolume; + /* Private */ + uint32_t Coef[SINCN]; + uint16_t FilterLen; + int64_t OldOut, OldIn, OldZ; + uint16_t LP_ALFA; + uint16_t HP_ALFA; + uint16_t bit[5]; + uint16_t byte; + uint32_t div_const; + int64_t sub_const; + uint32_t sinc[DECIMATION_MAX * SINCN]; + uint32_t sinc1[DECIMATION_MAX]; + uint32_t sinc2[DECIMATION_MAX * 2]; + uint32_t coef[SINCN][DECIMATION_MAX]; + #ifdef USE_LUT + lut_t lut; + #endif +} TPDMFilter_InitStruct; + +/* Exported functions ------------------------------------------------------- */ + +void Open_PDM_Filter_Init(TPDMFilter_InitStruct *init_struct); +int Open_PDM_Filter_64(uint8_t *data, int16_t *data_out, uint16_t mic_gain, TPDMFilter_InitStruct *init_struct); + +#if DECIMATION_MAX == 128 +int Open_PDM_Filter_128(uint8_t *data, int16_t *data_out, uint16_t mic_gain, TPDMFilter_InitStruct *init_struct); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __OPENPDMFILTER_H + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/ports/stm/common-hal/audiobusio/PDMIn.c b/ports/stm/common-hal/audiobusio/PDMIn.c new file mode 100644 index 0000000000..b1aa2047d8 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/PDMIn.c @@ -0,0 +1,199 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler 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 +#include "common-hal/audiobusio/PDMIn.h" +#include "shared-bindings/audiobusio/PDMIn.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "py/runtime.h" +#include "supervisor/memory.h" +#include "MEMS_Audio_ll_stm32l4.h" + + +MemsAudio memsAudio; +MemsAudio_STM32L4SAIPDM memsAudioImpl; +pdm_sample_t pdmBuffer[MEMS_AUDIO_PDM_BUFFER_LENGTH]; +audiobusio_pdmin_obj_t *instance; + +static bool pdm_data_available(MemsAudio_STM32L4SAIPDM *impl, uint8_t *pdmBuffer, size_t pdmBufferLength); + +// Caller validates that pins are free. +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 sample_rate, + uint8_t bit_depth, + bool mono, + uint8_t oversample) { + self->sample_rate = sample_rate; + self->mono = mono; + self->oversample = oversample; + self->recording_complete = true; + + + if (!mono) { + mp_raise_ValueError(translate("only mono is supported")); + } + if (sample_rate != 16000) { + mp_raise_ValueError(translate("only sample_rate=16000 is supported")); + } + if (bit_depth != 16) { + mp_raise_ValueError(translate("only bit_depth=16 is supported")); + } + if (oversample != 64) { + mp_raise_ValueError(translate("only oversample=64 is supported")); + } + + // wait for the previous instance to finish. + if (instance) { + common_hal_audiobusio_pdmin_deinit(instance); + } + instance = self; + + memset(&memsAudio, 0, sizeof(memsAudio)); + memset(&memsAudioImpl, 0, sizeof(memsAudioImpl)); + + common_hal_mcu_pin_claim(clock_pin); + self->clock_pin = clock_pin; + common_hal_mcu_pin_claim(data_pin); + self->data_pin = data_pin; + + self->audio = &memsAudio; + self->audio_impl = &memsAudioImpl; + self->audio_impl->pdmBuffer = pdmBuffer; + self->audio_impl->pdmBufferLength = sizeof(pdmBuffer) / sizeof(pdmBuffer[0]); + self->audio_impl->pdm_data_available = pdm_data_available; + + mems_audio_init_stm32l4_sai_pdm(self->audio, self->audio_impl); + mems_audio_record(self->audio); + mems_audio_pause(self->audio); +} + +bool common_hal_audiobusio_pdmin_deinited(audiobusio_pdmin_obj_t *self) { + return self->clock_pin == NULL; +} + +void wait_dma_complete(audiobusio_pdmin_obj_t *self) { + while (!self->recording_complete) { + MICROPY_VM_HOOK_LOOP; + } +} + +void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t *self) { + if (instance != self) { + return; + } + instance = NULL; + if (self->audio) { + wait_dma_complete(self); + mems_audio_stop(self->audio); + mems_audio_uninit(self->audio); + self->audio = NULL; + self->audio_impl = NULL; + } + + if (self->data_pin) { + common_hal_reset_pin(self->data_pin); + self->data_pin = NULL; + } + if (self->clock_pin) { + common_hal_reset_pin(self->clock_pin); + self->clock_pin = NULL; + } + + +} + +uint8_t common_hal_audiobusio_pdmin_get_bit_depth(audiobusio_pdmin_obj_t *self) { + return 16; +} + +uint32_t common_hal_audiobusio_pdmin_get_sample_rate(audiobusio_pdmin_obj_t *self) { + return 16000; +} + +static bool pdm_data_available(MemsAudio_STM32L4SAIPDM *impl, uint8_t *pdmBuffer, size_t pdmBufferLength) { + // update the filter with the correct number of samples + + audiobusio_pdmin_obj_t *pdmIn = (audiobusio_pdmin_obj_t *)(impl->audio->userData); + MemsAudio *audio = impl->audio; + + uint32_t pcmSamplesAvailable = pdmBufferLength * 8 / PDM_IN_DECIMATION_FACTOR; + if (pcmSamplesAvailable > audio->pcmOutputBufferLength) { + pcmSamplesAvailable = audio->pcmOutputBufferLength; + } + + // ensure the filter doesn't try to produce more samples than available + pdmIn->audio_impl->filter.nSamples = pcmSamplesAvailable; + + return pcmSamplesAvailable > 0; +} + +static void pcm_data_available(MemsAudio *audio, int16_t *pcmBuffer, size_t pcmBufferLength) { + // data is already in the output buffer + audiobusio_pdmin_obj_t *pdmIn = (audiobusio_pdmin_obj_t *)(audio->userData); + + // if DMA copies more data than will fit into the output buffer, crop the length to what will fit + if (audio->pcmOutputBufferLength < pcmBufferLength) { + pcmBufferLength = audio->pcmOutputBufferLength; + } + + audio->pcmOutputBuffer += pcmBufferLength; + audio->pcmOutputBufferLength -= pcmBufferLength; + if (audio->pcmOutputBufferLength == 0) { + pdmIn->recording_complete = true; + mems_audio_pause(audio); + } +} + +uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t *self, + uint16_t *output_buffer, uint32_t output_buffer_length) { + + MemsAudio *audio = self->audio; + + wait_dma_complete(self); + + audio->pcmOutputBuffer = (int16_t *)output_buffer; + audio->pcmOutputBufferLength = output_buffer_length; + audio->pcm_data_available = pcm_data_available; + audio->userData = self; /// reference back to the PDMIn instance + self->recording_complete = false; + + mems_audio_err_t err = mems_audio_resume(audio); + if (!IS_MEMS_AUDIO_ERROR(err)) { + wait_dma_complete(self); + } + + mems_audio_pause(audio); + int samples_output = (int)(output_buffer_length) - audio->pcmOutputBufferLength; + + // convert from signed to unsigned (min-point moves from 0 to 32k) + for (int i = 0; i < samples_output; i++) { + output_buffer[i] += 0x8000; + } + + return samples_output; +} diff --git a/ports/stm/common-hal/audiobusio/PDMIn.h b/ports/stm/common-hal/audiobusio/PDMIn.h new file mode 100644 index 0000000000..b6ef4ee8bf --- /dev/null +++ b/ports/stm/common-hal/audiobusio/PDMIn.h @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler 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_STM_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H +#define MICROPY_INCLUDED_STM_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H + +#include +#include "py/obj.h" +#include "peripherals/pins.h" +#include "supervisor/memory.h" + +typedef struct MemsAudio_t MemsAudio; +typedef struct MemsAudio_STM32L4SAIPDM_t MemsAudio_STM32L4SAIPDM; + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *clock_pin; + const mcu_pin_obj_t *data_pin; + uint32_t sample_rate; + uint8_t bit_depth; + bool mono; + uint8_t oversample; + supervisor_allocation *audio_allocation; + MemsAudio *audio; + MemsAudio_STM32L4SAIPDM *audio_impl; + /** + * @brief Flag to indicate from the ISR that recording is complete. + */ + volatile bool recording_complete; +} audiobusio_pdmin_obj_t; + + +#endif diff --git a/ports/stm/common-hal/audiobusio/__init__.c b/ports/stm/common-hal/audiobusio/__init__.c new file mode 100644 index 0000000000..e69de29bb2 diff --git a/supervisor/shared/memory.c b/supervisor/shared/memory.c index 06c568b42e..a5f3296a28 100644 --- a/supervisor/shared/memory.c +++ b/supervisor/shared/memory.c @@ -58,6 +58,9 @@ enum { #endif + CIRCUITPY_PORT_NUM_SUPERVISOR_ALLOCATIONS + #if CIRCUITPY_AUDIOBUSIO_PDMIN + + 1 + #endif , CIRCUITPY_SUPERVISOR_MOVABLE_ALLOC_COUNT =