openmv/src/omv/ports/stm32/modules/py_audio.c
2024-07-03 14:42:18 +02:00

570 lines
21 KiB
C

/*
* This file is part of the OpenMV project.
*
* Copyright (c) 2013-2021 Ibrahim Abdelkader <iabdalkader@openmv.io>
* Copyright (c) 2013-2021 Kwabena W. Agyeman <kwagyeman@openmv.io>
*
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* Audio Python module.
*/
#include <stdio.h>
#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 "dma_utils.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_SECTION(OMV_ATTR_ALIGNED(PDM_BUFFER[PDM_BUFFER_SIZE], 32), ".d3_dma_buffer");
#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_SECTION(OMV_ATTR_ALIGNED(PDM_BUFFER[PDM_BUFFER_SIZE], 32), ".d2_dma_buffer");
#define SaturaLH(N, L, H) (((N) < (L))?(L):(((N) > (H))?(H):(N)))
#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() {
HAL_DFSDM_IRQHandler(&hdfsdm_filter[0]);
}
#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)
#endif
{
xfer_status |= DMA_XFER_HALF;
uint32_t pdm_buffer_size_bytes = (sizeof(PDM_BUFFER[0]) * g_pdm_buffer_size);
SCB_InvalidateDCache_by_Addr((uint32_t *) (&PDM_BUFFER[0]), pdm_buffer_size_bytes);
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)
#endif
{
xfer_status |= DMA_XFER_FULL;
uint32_t pdm_buffer_size_bytes = (sizeof(PDM_BUFFER[0]) * g_pdm_buffer_size);
SCB_InvalidateDCache_by_Addr((uint32_t *) (&PDM_BUFFER[g_pdm_buffer_size]), pdm_buffer_size_bytes / 2);
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;
#if defined(OMV_SAI)
int gain_db = args[ARG_gain_db].u_int;
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_DFSDM)
uint32_t samples_per_channel = PDM_BUFFER_SIZE / 2; // Half a transfer
#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)
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();
// Configure the SAI DMA
hdma_sai_rx.Instance = OMV_SAI_DMA_STREAM;
hdma_sai_rx.Init.Request = OMV_SAI_DMA_REQUEST;
hdma_sai_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_sai_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_sai_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_sai_rx.Init.PeriphDataAlignment = (g_channels == 1) ? DMA_PDATAALIGN_BYTE : DMA_PDATAALIGN_HALFWORD;
hdma_sai_rx.Init.MemDataAlignment = (g_channels == 1) ? DMA_MDATAALIGN_BYTE : DMA_MDATAALIGN_HALFWORD;
hdma_sai_rx.Init.Mode = DMA_CIRCULAR;
hdma_sai_rx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_sai_rx.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
hdma_sai_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_sai_rx.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_sai_rx.Init.PeriphBurst = DMA_MBURST_SINGLE;
__HAL_LINKDMA(&hsai, hdmarx, hdma_sai_rx);
// Initialize the DMA stream
HAL_DMA_DeInit(&hdma_sai_rx);
if (HAL_DMA_Init(&hdma_sai_rx) != HAL_OK) {
RAISE_OS_EXCEPTION("SAI DMA init failed!");
}
// 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 * 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
hdma_filter[0].Instance = OMV_DFSDM_FLT0_DMA_STREAM;
hdma_filter[0].Init.Request = OMV_DFSDM_FLT0_DMA_REQUEST;
hdma_filter[0].Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_filter[0].Init.PeriphInc = DMA_PINC_DISABLE;
hdma_filter[0].Init.MemInc = DMA_MINC_ENABLE;
hdma_filter[0].Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_filter[0].Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_filter[0].Init.Mode = DMA_CIRCULAR;
hdma_filter[0].Init.Priority = DMA_PRIORITY_HIGH;
__HAL_LINKDMA(&hdfsdm_filter[0], hdmaInj, hdma_filter[0]);
__HAL_LINKDMA(&hdfsdm_filter[0], hdmaReg, hdma_filter[0]);
// Set DMA IRQ handle
dma_utils_set_irq_descr(OMV_DFSDM_FLT0_DMA_STREAM, &hdma_filter[0]);
// Initialize the DMA stream
HAL_DMA_DeInit(&hdma_filter[0]);
if (HAL_DMA_Init(&hdma_filter[0]) != HAL_OK) {
RAISE_OS_EXCEPTION("SAI DFSDM init failed!");
}
// 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);
#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_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_DFSDM)
for (int i = 0; i < g_pdm_buffer_size / 2; i++) {
pcmbuf[i] = SaturaLH((PDM_BUFFER[i] >> 8), -32768, 32767);
}
#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_DFSDM)
for (int i = 0; i < g_pdm_buffer_size / 2; i++) {
pcmbuf[i] = SaturaLH((PDM_BUFFER[g_pdm_buffer_size / 2 + i] >> 8), -32768, 32767);
}
#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_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