/* * SPDX-License-Identifier: MIT * * Copyright (C) 2013-2024 OpenMV, LLC. * * 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. * * Audio Python module. */ #include #include "py/obj.h" #include "py/objarray.h" #include "py/nlr.h" #include "py/mphal.h" #include "py/binary.h" #include "systick.h" #include "runtime.h" #include "py_audio.h" #include "py_assert.h" #include "py_helper.h" #include "pdm2pcm_glo.h" #include "fb_alloc.h" #include "omv_boardconfig.h" #include "omv_common.h" #include "stm_dma.h" #if MICROPY_PY_AUDIO #if defined(OMV_SAI) static CRC_HandleTypeDef hcrc; static SAI_HandleTypeDef hsai; static DMA_HandleTypeDef hdma_sai_rx; static PDM_Filter_Config_t PDM_FilterConfig[OMV_AUDIO_MAX_CHANNELS]; static PDM_Filter_Handler_t PDM_FilterHandler[OMV_AUDIO_MAX_CHANNELS]; // NOTE: BDMA can only access D3 SRAM4 memory. #define PDM_BUFFER_SIZE (16384) uint8_t OMV_ATTR_SEC_ALIGN(PDM_BUFFER[PDM_BUFFER_SIZE], ".d3_dma_buffer", OMV_DMA_ALIGNMENT); #elif defined(OMV_DFSDM) static DFSDM_Channel_HandleTypeDef hdfsdm; // NOTE: Only 1 filter is supported right now. static DFSDM_Filter_HandleTypeDef hdfsdm_filter[OMV_AUDIO_MAX_CHANNELS]; static DMA_HandleTypeDef hdma_filter[OMV_AUDIO_MAX_CHANNELS]; // NOTE: placed in D2 memory. #define PDM_BUFFER_SIZE (512 * 2) int32_t OMV_ATTR_SEC_ALIGN(PDM_BUFFER[PDM_BUFFER_SIZE], ".d2_dma_buffer", OMV_DMA_ALIGNMENT); #define DFSDM_GAIN_FRAC_BITS (3) static int32_t dfsdm_gain = 1; #elif defined(OMV_MDF) static MDF_HandleTypeDef hmdf; static MDF_FilterConfigTypeDef hmdf_filter[OMV_AUDIO_MAX_CHANNELS]; // NOTE: Only 1 filter is supported right now. static DMA_QListTypeDef dma_queue; static DMA_NodeTypeDef OMV_ATTR_SEC_ALIGN(dma_nodes, ".dma_buffer", OMV_DMA_ALIGNMENT); static DMA_HandleTypeDef hdma_filter[OMV_AUDIO_MAX_CHANNELS]; #define PDM_BUFFER_SIZE (512 * 2) int32_t OMV_ATTR_SEC_ALIGN(PDM_BUFFER[PDM_BUFFER_SIZE], ".dma_buffer", OMV_DMA_ALIGNMENT); #else #error "No audio driver defined for this board" #endif static volatile uint32_t xfer_status = 0; static int g_channels = OMV_AUDIO_MAX_CHANNELS; static uint32_t g_pdm_buffer_size = 0; static mp_sched_node_t audio_task_sched_node; #define DMA_XFER_NONE (0x00U) #define DMA_XFER_HALF (0x01U) #define DMA_XFER_FULL (0x04U) #define RAISE_OS_EXCEPTION(msg) mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT(msg)) // Scheduler callback. static void audio_task_callback(mp_sched_node_t *node); #if defined(OMV_SAI) void OMV_SAI_DMA_IRQHandler(void) { HAL_DMA_IRQHandler(hsai.hdmarx); } #elif defined(OMV_DFSDM) void OMV_DFSDM_FLT0_IRQHandler(void) { HAL_DFSDM_IRQHandler(&hdfsdm_filter[0]); } #elif defined(OMV_MDF) void OMV_MDF_FLT0_IRQHandler(void) { HAL_MDF_IRQHandler(&hmdf); } #endif // defined(OMV_SAI) #if defined(OMV_SAI) void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai) #elif defined(OMV_DFSDM) void HAL_DFSDM_FilterRegConvHalfCpltCallback(DFSDM_Filter_HandleTypeDef *hdfsdm_filter) #elif defined(OMV_MDF) void HAL_MDF_AcqHalfCpltCallback(MDF_HandleTypeDef *hmdf) #endif { xfer_status |= DMA_XFER_HALF; if (MP_STATE_PORT(audio_callback) != mp_const_none) { mp_sched_schedule_node(&audio_task_sched_node, audio_task_callback); } } #if defined(OMV_SAI) void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) #elif defined(OMV_DFSDM) void HAL_DFSDM_FilterRegConvCpltCallback(DFSDM_Filter_HandleTypeDef *hdfsdm_filter) #elif defined(OMV_MDF) void HAL_MDF_AcqCpltCallback(MDF_HandleTypeDef *hmdf) #endif { xfer_status |= DMA_XFER_FULL; if (MP_STATE_PORT(audio_callback) != mp_const_none) { mp_sched_schedule_node(&audio_task_sched_node, audio_task_callback); } } #if defined(OMV_SAI) static uint32_t get_decimation_factor(uint32_t decimation) { switch (decimation) { case 16: return PDM_FILTER_DEC_FACTOR_16; case 24: return PDM_FILTER_DEC_FACTOR_24; case 32: return PDM_FILTER_DEC_FACTOR_32; case 48: return PDM_FILTER_DEC_FACTOR_48; case 64: return PDM_FILTER_DEC_FACTOR_64; case 80: return PDM_FILTER_DEC_FACTOR_80; case 128: return PDM_FILTER_DEC_FACTOR_128; default: return 0; } } #endif static mp_obj_t py_audio_init(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_channels, ARG_frequency, ARG_gain_db, ARG_highpass, ARG_samples }; static const mp_arg_t allowed_args[] = { { MP_QSTR_channels, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = OMV_AUDIO_MAX_CHANNELS } }, { MP_QSTR_frequency, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16000 } }, { MP_QSTR_gain_db, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 24 } }, { MP_QSTR_highpass, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_samples, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, }; // Parse args. mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Read Args. g_channels = args[ARG_channels].u_int; uint32_t frequency = args[ARG_frequency].u_int; int gain_db = args[ARG_gain_db].u_int; #if defined(OMV_SAI) float highpass = py_helper_arg_to_float(args[ARG_highpass].u_obj, 0.9883f); #endif // Sanity checks if (frequency < 16000 || frequency > 128000) { RAISE_OS_EXCEPTION("Invalid frequency!"); } if (g_channels != 1 && g_channels > OMV_AUDIO_MAX_CHANNELS) { RAISE_OS_EXCEPTION("Invalid number of channels!"); } // Default/max PDM buffer size; g_pdm_buffer_size = PDM_BUFFER_SIZE; #if defined(OMV_MDF) uint32_t samples_per_channel = PDM_BUFFER_SIZE / 2; // Half a transfer #elif defined(OMV_DFSDM) uint32_t samples_per_channel = PDM_BUFFER_SIZE / 2; // Half a transfer dfsdm_gain = __USAT(fast_roundf(expf((gain_db / 20.0f) * M_LN10) * (1 << DFSDM_GAIN_FRAC_BITS)), 15); #else uint32_t decimation_factor = OMV_SAI_FREQKHZ / (frequency / 1000); uint32_t decimation_factor_const = get_decimation_factor(decimation_factor); if (decimation_factor_const == 0) { RAISE_OS_EXCEPTION("This frequency is not supported!"); } uint32_t samples_per_channel = (PDM_BUFFER_SIZE * 8) / (decimation_factor * g_channels * 2); // Half a transfer #endif // defined(OMV_DFSDM) if (args[ARG_samples].u_int > 0) { if (args[ARG_samples].u_int % 16 != 0 || args[ARG_samples].u_int > samples_per_channel) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid number of samples." \ "The number of samples must be a multiple of 16," \ "and a maximum of %d"), samples_per_channel); } samples_per_channel = args[ARG_samples].u_int; // Recalculate the PDM buffer size for the requested samples. #if defined(OMV_DFSDM) || defined(OMV_MDF) g_pdm_buffer_size = samples_per_channel * 2; #else g_pdm_buffer_size = (samples_per_channel * decimation_factor * g_channels) / 4; #endif // defined(OMV_DFSDM) } #if defined(OMV_SAI) hsai.Instance = OMV_SAI; hsai.Init.Protocol = SAI_FREE_PROTOCOL; hsai.Init.AudioMode = SAI_MODEMASTER_RX; hsai.Init.DataSize = (g_channels == 1) ? SAI_DATASIZE_8 : SAI_DATASIZE_16; hsai.Init.FirstBit = SAI_FIRSTBIT_LSB; hsai.Init.ClockStrobing = SAI_CLOCKSTROBING_RISINGEDGE; hsai.Init.Synchro = SAI_ASYNCHRONOUS; hsai.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE; hsai.Init.NoDivider = SAI_MASTERDIVIDER_DISABLE; hsai.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_1QF; hsai.Init.SynchroExt = SAI_SYNCEXT_DISABLE; hsai.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_MCKDIV; hsai.Init.MonoStereoMode = (g_channels == 1) ? SAI_MONOMODE: SAI_STEREOMODE; hsai.Init.CompandingMode = SAI_NOCOMPANDING; hsai.Init.TriState = SAI_OUTPUT_RELEASED; // The master clock output (MCLK_x) is disabled and the SAI clock // is passed out to SCK_x bit clock. SCKx frequency = SAI_KER_CK / MCKDIV / 2 hsai.Init.Mckdiv = OMV_SAI_MCKDIV; //2.048MHz hsai.Init.MckOutput = SAI_MCK_OUTPUT_DISABLE; hsai.Init.MckOverSampling = SAI_MCK_OVERSAMPLING_DISABLE; // Enable and configure PDM mode. hsai.Init.PdmInit.Activation = ENABLE; hsai.Init.PdmInit.MicPairsNbr = 1; hsai.Init.PdmInit.ClockEnable = SAI_PDM_CLOCK1_ENABLE; hsai.FrameInit.FrameLength = 16; hsai.FrameInit.ActiveFrameLength = 1; hsai.FrameInit.FSDefinition = SAI_FS_STARTFRAME; hsai.FrameInit.FSPolarity = SAI_FS_ACTIVE_HIGH; hsai.FrameInit.FSOffset = SAI_FS_FIRSTBIT; hsai.SlotInit.FirstBitOffset = 0; hsai.SlotInit.SlotSize = SAI_SLOTSIZE_DATASIZE; hsai.SlotInit.SlotNumber = (g_channels == 1) ? 2 : 1; hsai.SlotInit.SlotActive = (g_channels == 1) ? (SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_1) : SAI_SLOTACTIVE_0; // Initialize the SAI HAL_SAI_DeInit(&hsai); if (HAL_SAI_Init(&hsai) != HAL_OK) { RAISE_OS_EXCEPTION("Failed to init SAI"); } // Enable the DMA clock OMV_SAI_DMA_CLK_ENABLE(); // Initialize the DMA stream uint32_t ssize = (g_channels == 1) ? 1 : 2; uint32_t dsize = (g_channels == 1) ? 1 : 2; if (stm_dma_init(&hdma_sai_rx, OMV_SAI_DMA_STREAM, OMV_SAI_DMA_REQUEST, DMA_PERIPH_TO_MEMORY, ssize, dsize, 0, &stm_dma_sai_init, true)) { RAISE_OS_EXCEPTION("SAI DMA init failed!"); } // Link DMA handle. __HAL_LINKDMA(&hsai, hdmarx, hdma_sai_rx); // Configure and enable SAI DMA IRQ Channel NVIC_SetPriority(OMV_SAI_DMA_IRQ, IRQ_PRI_DMA21); HAL_NVIC_EnableIRQ(OMV_SAI_DMA_IRQ); // Init CRC for the PDM library hcrc.Instance = CRC; hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE; hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE; hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_NONE; hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_DISABLE; hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES; if (HAL_CRC_Init(&hcrc) != HAL_OK) { RAISE_OS_EXCEPTION("Failed to initialize CRC!"); } __HAL_CRC_DR_RESET(&hcrc); // Configure PDM filters for (int i = 0; i < g_channels; i++) { PDM_FilterHandler[i].bit_order = PDM_FILTER_BIT_ORDER_MSB; PDM_FilterHandler[i].endianness = PDM_FILTER_ENDIANNESS_LE; PDM_FilterHandler[i].high_pass_tap = (uint32_t) (highpass * (float) 2147483647U); // coff * (2^31-1) PDM_FilterHandler[i].out_ptr_channels = g_channels; PDM_FilterHandler[i].in_ptr_channels = g_channels; PDM_Filter_Init(&PDM_FilterHandler[i]); PDM_FilterConfig[i].mic_gain = gain_db; PDM_FilterConfig[i].output_samples_number = samples_per_channel; PDM_FilterConfig[i].decimation_factor = decimation_factor_const; PDM_Filter_setConfig(&PDM_FilterHandler[i], &PDM_FilterConfig[i]); } #elif defined(OMV_DFSDM) hdfsdm.Instance = OMV_DFSDM; hdfsdm.Init.OutputClock.Activation = ENABLE; hdfsdm.Init.OutputClock.Selection = DFSDM_CHANNEL_OUTPUT_CLOCK_AUDIO; hdfsdm.Init.OutputClock.Divider = OMV_DFSDM_FREQMHZ / 2; /* Divider = Aclk / 2MHz*/ hdfsdm.Init.Input.Multiplexer = DFSDM_CHANNEL_EXTERNAL_INPUTS; hdfsdm.Init.Input.DataPacking = DFSDM_CHANNEL_STANDARD_MODE; hdfsdm.Init.Input.Pins = DFSDM_CHANNEL_SAME_CHANNEL_PINS; hdfsdm.Init.SerialInterface.Type = DFSDM_CHANNEL_SPI_RISING; hdfsdm.Init.SerialInterface.SpiClock = DFSDM_CHANNEL_SPI_CLOCK_INTERNAL; hdfsdm.Init.Awd.FilterOrder = DFSDM_CHANNEL_FASTSINC_ORDER; hdfsdm.Init.Awd.Oversampling = 125; /* 2MHz/125 = 16kHz */ hdfsdm.Init.Offset = 0; hdfsdm.Init.RightBitShift = 0x02; __HAL_DFSDM_CHANNEL_RESET_HANDLE_STATE(&hdfsdm); if (HAL_DFSDM_ChannelInit(&hdfsdm) != HAL_OK) { RAISE_OS_EXCEPTION("Failed to init DFSDM"); } hdfsdm_filter[0].Instance = OMV_DFSDM_FLT0; hdfsdm_filter[0].Init.RegularParam.Trigger = DFSDM_FILTER_SW_TRIGGER; hdfsdm_filter[0].Init.RegularParam.FastMode = ENABLE; hdfsdm_filter[0].Init.RegularParam.DmaMode = ENABLE; hdfsdm_filter[0].Init.InjectedParam.Trigger = DFSDM_FILTER_SINC3_ORDER; hdfsdm_filter[0].Init.InjectedParam.ScanMode = ENABLE; hdfsdm_filter[0].Init.InjectedParam.DmaMode = ENABLE; hdfsdm_filter[0].Init.InjectedParam.ExtTrigger = DFSDM_FILTER_EXT_TRIG_TIM1_TRGO; hdfsdm_filter[0].Init.InjectedParam.ExtTriggerEdge = DFSDM_FILTER_EXT_TRIG_RISING_EDGE; hdfsdm_filter[0].Init.FilterParam.SincOrder = DFSDM_FILTER_FASTSINC_ORDER; hdfsdm_filter[0].Init.FilterParam.Oversampling = 125; /* 2MHz/125 = 16kHz */ hdfsdm_filter[0].Init.FilterParam.IntOversampling = 1; __HAL_DFSDM_FILTER_RESET_HANDLE_STATE(&hdfsdm_filter[0]); if (HAL_DFSDM_FilterInit(&hdfsdm_filter[0]) != HAL_OK || HAL_DFSDM_FilterConfigRegChannel(&hdfsdm_filter[0], OMV_DFSDM_CHANNEL, DFSDM_CONTINUOUS_CONV_ON) != HAL_OK) { RAISE_OS_EXCEPTION("Failed to init DFSDM filter"); return 0; } // Enable the DMA clock OMV_DFSDM_DMA_CLK_ENABLE(); // Configure the DFSDM Filter 0 DMA/IRQ if (stm_dma_init(&hdma_filter[0], OMV_DFSDM_FLT0_DMA_STREAM, OMV_DFSDM_FLT0_DMA_REQUEST, DMA_PERIPH_TO_MEMORY, 4, 4, 0, &stm_dma_dfsdm_init, true)) { RAISE_OS_EXCEPTION("DFSDM DMA init failed!"); } // Link DMA handles. __HAL_LINKDMA(&hdfsdm_filter[0], hdmaInj, hdma_filter[0]); __HAL_LINKDMA(&hdfsdm_filter[0], hdmaReg, hdma_filter[0]); // Set DMA IRQ handle stm_dma_set_irq_descr(OMV_DFSDM_FLT0_DMA_STREAM, &hdma_filter[0]); // Configure and enable DFSDM Filter 0 DMA IRQ. NVIC_SetPriority(OMV_DFSDM_FLT0_DMA_IRQ, IRQ_PRI_DMA21); HAL_NVIC_EnableIRQ(OMV_DFSDM_FLT0_DMA_IRQ); NVIC_SetPriority(OMV_DFSDM_FLT0_IRQ, IRQ_PRI_DMA21); HAL_NVIC_EnableIRQ(OMV_DFSDM_FLT0_IRQ); #elif defined(OMV_MDF) hmdf.Instance = OMV_MDF; hmdf.Init.CommonParam.InterleavedFilters = 0; hmdf.Init.CommonParam.ProcClockDivider = OMV_MDF_PROC_CLKDIV; hmdf.Init.CommonParam.OutputClock.Activation = ENABLE; hmdf.Init.CommonParam.OutputClock.Pins = MDF_OUTPUT_CLOCK_0; hmdf.Init.CommonParam.OutputClock.Divider = OMV_MDF_CCKY_CLKDIV; hmdf.Init.CommonParam.OutputClock.Trigger.Activation = DISABLE; hmdf.Init.SerialInterface.Activation = ENABLE; hmdf.Init.SerialInterface.Mode = MDF_SITF_LF_MASTER_SPI_MODE; hmdf.Init.SerialInterface.ClockSource = MDF_SITF_CCK0_SOURCE; hmdf.Init.SerialInterface.Threshold = 31; hmdf.Init.FilterBistream = MDF_BITSTREAM0_FALLING; // Initialize MDF. if (HAL_MDF_Init(&hmdf) != HAL_OK) { RAISE_OS_EXCEPTION("MDF init failed!"); } // No init is called on this filter config. hmdf_filter[0].DataSource = MDF_DATA_SOURCE_BSMX; hmdf_filter[0].Delay = 0; hmdf_filter[0].CicMode = MDF_ONE_FILTER_SINC4; hmdf_filter[0].DecimationRatio = 32; hmdf_filter[0].Offset = 0; hmdf_filter[0].Gain = gain_db / 3; // gain in steps of 3db hmdf_filter[0].ReshapeFilter.Activation = ENABLE; hmdf_filter[0].ReshapeFilter.DecimationRatio = MDF_RSF_DECIMATION_RATIO_4; hmdf_filter[0].HighPassFilter.Activation = DISABLE; // Disabled for now. hmdf_filter[0].HighPassFilter.CutOffFrequency = MDF_HPF_CUTOFF_0_000625FPCM; hmdf_filter[0].Integrator.Activation = DISABLE; hmdf_filter[0].SoundActivity.Activation = DISABLE; hmdf_filter[0].AcquisitionMode = MDF_MODE_ASYNC_CONT; hmdf_filter[0].FifoThreshold = MDF_FIFO_THRESHOLD_NOT_EMPTY; hmdf_filter[0].DiscardSamples = 0; // Initialize DMA. if (stm_dma_init(&hdma_filter[0], OMV_MDF_FLT0_DMA_STREAM, OMV_MDF_FLT0_DMA_REQUEST, DMA_PERIPH_TO_MEMORY, 4, 4, OMV_MDF_DMA_XFER_PORTS, &stm_dma_mdf_init, true)) { RAISE_OS_EXCEPTION("MDF DMA init failed!"); } // Initialize DMA circular mode. if (stm_dma_ll_init(&hdma_filter[0], &dma_queue, &dma_nodes, 1, OMV_MDF_DMA_LIST_PORTS)) { RAISE_OS_EXCEPTION("MDF DMA init failed!"); } // Link DMA handle. __HAL_LINKDMA(&hmdf, hdma, hdma_filter[0]); // Set DMA IRQ handle stm_dma_set_irq_descr(OMV_MDF_FLT0_DMA_STREAM, &hdma_filter[0]); // Configure and enable MDF Filter 0 DMA IRQ. NVIC_SetPriority(OMV_MDF_FLT0_IRQ, IRQ_PRI_DMA21); HAL_NVIC_EnableIRQ(OMV_MDF_FLT0_IRQ); NVIC_SetPriority(OMV_MDF_FLT0_DMA_IRQ, IRQ_PRI_DMA21); HAL_NVIC_EnableIRQ(OMV_MDF_FLT0_DMA_IRQ); #endif // defined(OMV_SAI) // Allocate global PCM buffer. MP_STATE_PORT(audio_pcm_buffer) = m_new(int16_t, samples_per_channel * g_channels); MP_STATE_PORT(audio_pcm_array) = mp_obj_new_bytearray_by_ref(samples_per_channel * g_channels * sizeof(int16_t), MP_STATE_PORT(audio_pcm_buffer)); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_KW(py_audio_init_obj, 0, py_audio_init); void py_audio_deinit() { #if defined(OMV_SAI) // Stop SAI DMA. if (hdma_sai_rx.Instance != NULL) { HAL_SAI_DMAStop(&hsai); } // Disable IRQs HAL_NVIC_DisableIRQ(OMV_SAI_DMA_IRQ); if (hsai.Instance != NULL) { HAL_SAI_DeInit(&hsai); hsai.Instance = NULL; } if (hdma_sai_rx.Instance != NULL) { HAL_DMA_DeInit(&hdma_sai_rx); hdma_sai_rx.Instance = NULL; } #elif defined(OMV_MDF) if (hmdf.Instance != NULL) { HAL_MDF_AcqStop_DMA(&hmdf); HAL_MDF_DeInit(&hmdf); } // Disable IRQs HAL_NVIC_DisableIRQ(OMV_MDF_FLT0_IRQ); HAL_NVIC_DisableIRQ(OMV_MDF_FLT0_DMA_IRQ); #elif defined(OMV_DFSDM) if (hdma_filter[0].Instance != NULL) { HAL_DFSDM_FilterRegularStop_DMA(&hdfsdm_filter[0]); } // Disable IRQs HAL_NVIC_DisableIRQ(OMV_DFSDM_FLT0_DMA_IRQ); HAL_NVIC_DisableIRQ(OMV_DFSDM_FLT0_IRQ); if (hdfsdm.Instance != NULL) { HAL_DFSDM_ChannelDeInit(&hdfsdm); hdfsdm.Instance = NULL; } if (hdma_filter[0].Instance != NULL) { HAL_DMA_DeInit(&hdma_filter[0]); hdma_filter[0].Instance = NULL; } #endif g_channels = 0; MP_STATE_PORT(audio_pcm_buffer) = NULL; MP_STATE_PORT(audio_pcm_array) = mp_const_none; MP_STATE_PORT(audio_callback) = mp_const_none; } static void audio_task_callback(mp_sched_node_t *node) { int16_t *pcmbuf = (int16_t *) MP_STATE_PORT(audio_pcm_buffer); // Check for half transfer complete. if ((xfer_status & DMA_XFER_HALF)) { // Clear buffer state. xfer_status &= ~(DMA_XFER_HALF); #if defined(OMV_SAI) // Convert PDM samples to PCM. for (int i = 0; i < g_channels; i++) { PDM_Filter(&((uint8_t *) PDM_BUFFER)[i], &pcmbuf[i], &PDM_FilterHandler[i]); } #elif defined(OMV_MDF) for (int i = 0; i < g_pdm_buffer_size / 2; i++) { pcmbuf[i] = PDM_BUFFER[i] >> 16; } #elif defined(OMV_DFSDM) for (int i = 0; i < g_pdm_buffer_size / 2; i++) { pcmbuf[i] = __SSAT_ASR((PDM_BUFFER[i] >> 8) * dfsdm_gain, 16, DFSDM_GAIN_FRAC_BITS); } #endif } else if ((xfer_status & DMA_XFER_FULL)) { // Check for transfer complete. // Clear buffer state. xfer_status &= ~(DMA_XFER_FULL); #if defined(OMV_SAI) // Convert PDM samples to PCM. for (int i = 0; i < g_channels; i++) { PDM_Filter(&((uint8_t *) PDM_BUFFER)[g_pdm_buffer_size / 2 + i], &pcmbuf[i], &PDM_FilterHandler[i]); } #elif defined(OMV_MDF) for (int i = 0; i < g_pdm_buffer_size / 2; i++) { pcmbuf[i] = PDM_BUFFER[g_pdm_buffer_size / 2 + i] >> 16; } #elif defined(OMV_DFSDM) for (int i = 0; i < g_pdm_buffer_size / 2; i++) { pcmbuf[i] = __SSAT_ASR((PDM_BUFFER[g_pdm_buffer_size / 2 + i] >> 8) * dfsdm_gain, 16, DFSDM_GAIN_FRAC_BITS); } #endif } // Call user callback mp_call_function_1(MP_STATE_PORT(audio_callback), MP_STATE_PORT(audio_pcm_array)); } static mp_obj_t py_audio_start_streaming(mp_obj_t callback_obj) { if (!mp_obj_is_callable(callback_obj)) { RAISE_OS_EXCEPTION("Invalid callback object!"); } MP_STATE_PORT(audio_callback) = callback_obj; // Clear DMA buffer status xfer_status &= DMA_XFER_NONE; #if defined(OMV_SAI) // Start DMA transfer if (HAL_SAI_Receive_DMA(&hsai, (uint8_t *) PDM_BUFFER, g_pdm_buffer_size / g_channels) != HAL_OK) { MP_STATE_PORT(audio_callback) = mp_const_none; RAISE_OS_EXCEPTION("SAI DMA transfer failed!"); } #elif defined(OMV_MDF) MDF_DmaConfigTypeDef dma_config = { .Address = (uint32_t) PDM_BUFFER, .DataLength = sizeof(PDM_BUFFER[0]) * g_pdm_buffer_size, // in bytes .MsbOnly = DISABLE, }; if (HAL_MDF_AcqStart_DMA(&hmdf, &hmdf_filter[0], &dma_config) != HAL_OK) { RAISE_OS_EXCEPTION("MDF DMA transfer failed!"); } #elif defined(OMV_DFSDM) // Start DMA transfer if (HAL_DFSDM_FilterRegularStart_DMA(&hdfsdm_filter[0], PDM_BUFFER, g_pdm_buffer_size) != HAL_OK) { RAISE_OS_EXCEPTION("DFSDM DMA transfer failed!"); } #endif return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_audio_start_streaming_obj, py_audio_start_streaming); static mp_obj_t py_audio_stop_streaming() { #if defined(OMV_SAI) // Stop SAI DMA. if (hdma_sai_rx.Instance != NULL) { HAL_SAI_DMAStop(&hsai); } #elif defined(OMV_DFSDM) if (hdma_filter[0].Instance != NULL) { HAL_DFSDM_FilterRegularStop_DMA(&hdfsdm_filter[0]); } #endif MP_STATE_PORT(audio_callback) = mp_const_none; return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_0(py_audio_stop_streaming_obj, py_audio_stop_streaming); #if defined(OMV_SAI) static mp_obj_t py_audio_read_pdm(mp_obj_t buf_in) { mp_buffer_info_t pdmbuf; mp_get_buffer_raise(buf_in, &pdmbuf, MP_BUFFER_WRITE); size_t typesize = mp_binary_get_size('@', pdmbuf.typecode, NULL); uint32_t xfer_samples = 0; // Note: samples are copied as bytes for 1 and 2 channels. uint32_t n_samples = pdmbuf.len; if (typesize != g_channels) { // Make sure the buffer type matches the number of channels. RAISE_OS_EXCEPTION("Buffer data type does not match the number of channels!"); } // Clear DMA buffer status xfer_status &= DMA_XFER_NONE; // Start DMA transfer if (HAL_SAI_Receive_DMA(&hsai, (uint8_t *) PDM_BUFFER, g_pdm_buffer_size / g_channels) != HAL_OK) { RAISE_OS_EXCEPTION("SAI DMA transfer failed!"); } while (n_samples) { uint32_t start = HAL_GetTick(); // Wait for transfer complete. while ((xfer_status & DMA_XFER_FULL) == 0) { if ((HAL_GetTick() - start) >= 1000) { HAL_SAI_DMAStop(&hsai); RAISE_OS_EXCEPTION("SAI DMA transfer timeout!"); } } // Clear buffer state. xfer_status &= DMA_XFER_NONE; // Copy samples to pdm output buffer. // Note: samples are copied as bytes for 1 and 2 channels. uint32_t samples = OMV_MIN(n_samples, g_pdm_buffer_size); for (int i = 0; i < samples; i++, n_samples--, xfer_samples++) { ((uint8_t *) pdmbuf.buf)[xfer_samples] = ((uint8_t *) PDM_BUFFER)[i]; } if (xfer_status & DMA_XFER_FULL) { printf("Dropping samples!\n"); } } // Stop SAI DMA. HAL_SAI_DMAStop(&hsai); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_audio_read_pdm_obj, py_audio_read_pdm); #endif static const mp_rom_map_elem_t globals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audio) }, { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&py_audio_init_obj) }, { MP_ROM_QSTR(MP_QSTR_start_streaming), MP_ROM_PTR(&py_audio_start_streaming_obj)}, { MP_ROM_QSTR(MP_QSTR_stop_streaming), MP_ROM_PTR(&py_audio_stop_streaming_obj) }, #if defined(OMV_SAI) { MP_ROM_QSTR(MP_QSTR_read_pdm), MP_ROM_PTR(&py_audio_read_pdm_obj) }, #endif }; static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); const mp_obj_module_t audio_module = { .base = { &mp_type_module }, .globals = (mp_obj_t) &globals_dict, }; MP_REGISTER_ROOT_POINTER(mp_obj_t audio_callback); MP_REGISTER_ROOT_POINTER(mp_obj_t audio_pcm_array); MP_REGISTER_ROOT_POINTER(int16_t * audio_pcm_buffer); MP_REGISTER_MODULE(MP_QSTR_audio, audio_module); #endif //MICROPY_PY_AUDIO