openmv/ports/nrf/modules/py_audio.c
iabdalkader daf2bb30da misc: Restructure repo.
Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
2025-04-13 08:28:34 +02:00

216 lines
7.8 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Copyright (C) 2020-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 <stdio.h>
#include "py/obj.h"
#include "py/objarray.h"
#include "py/nlr.h"
#include "py/mphal.h"
#include "py/binary.h"
#include "runtime.h"
#include "py_audio.h"
#include "py_assert.h"
#include "py_helper.h"
#include "fb_alloc.h"
#include "omv_boardconfig.h"
#include "omv_common.h"
#include "nrfx_pdm.h"
#include "hal/nrf_gpio.h"
#if MICROPY_PY_AUDIO
#define PDM_DEFAULT_GAIN (20)
#define PDM_IRQ_PRIORITY (7)
#define PDM_BUFFER_SIZE (256)
#define NRF_PDM_FREQ_1280K (nrf_pdm_freq_t) (0x0A000000UL) // PDM_CLK= 1.280 MHz (32 MHz / 25) => Fs= 20000 Hz
#define NRF_PDM_FREQ_2000K (nrf_pdm_freq_t) (0x10000000UL) // PDM_CLK= 2.000 MHz (32 MHz / 16) => Fs= 31250 Hz
#define NRF_PDM_FREQ_2667K (nrf_pdm_freq_t) (0x15000000UL) // PDM_CLK= 2.667 MHz (32 MHz / 12) => Fs= 41667 Hz
#define NRF_PDM_FREQ_3200K (nrf_pdm_freq_t) (0x19000000UL) // PDM_CLK= 3.200 MHz (32 MHz / 10) => Fs= 50000 Hz
#define NRF_PDM_FREQ_4000K (nrf_pdm_freq_t) (0x20000000UL) // PDM_CLK= 4.000 MHz (32 MHz / 8) => Fs= 62500 Hz
#define RAISE_OS_EXCEPTION(msg) mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT(msg))
static mp_obj_array_t *g_pcmbuf = NULL;
static mp_obj_t g_audio_callback = mp_const_none;
static uint32_t g_channels = 1;
static uint32_t g_buf_index = 0;
static int16_t PDM_BUFFERS[2][PDM_BUFFER_SIZE];
static mp_sched_node_t audio_task_sched_node;
static void audio_task_callback(mp_sched_node_t *node);
static void nrfx_pdm_event_handler(nrfx_pdm_evt_t const *evt) {
if (evt->error == NRFX_PDM_NO_ERROR) {
if (evt->buffer_requested) {
if (evt->buffer_released == PDM_BUFFERS[0]) {
nrfx_pdm_buffer_set(PDM_BUFFERS[1], PDM_BUFFER_SIZE);
} else {
// NULL (first buffer request) or second buffer is full.
nrfx_pdm_buffer_set(PDM_BUFFERS[0], PDM_BUFFER_SIZE);
}
}
if (evt->buffer_released && g_audio_callback != mp_const_none) {
g_buf_index = (evt->buffer_released == PDM_BUFFERS[0]) ? 0 : 1;
mp_sched_schedule_node(&audio_task_sched_node, audio_task_callback);
}
} else {
// TODO
}
}
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 };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_channels, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 2 } },
{ 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 = PDM_DEFAULT_GAIN } },
};
// 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);
g_channels = args[ARG_channels].u_int;
uint32_t frequency = args[ARG_frequency].u_int;
int gain_db = args[ARG_gain_db].u_int;
nrfx_pdm_config_t nrfx_pdm_config = {
.pin_clk = OMV_PDM_CLK_PIN,
.pin_din = OMV_PDM_DIN_PIN,
.gain_l = gain_db,
.gain_r = gain_db,
.interrupt_priority = PDM_IRQ_PRIORITY,
};
// Enable high frequency oscillator if not already enabled
if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {
NRF_CLOCK->TASKS_HFCLKSTART = 1;
while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {
}
}
// configure the sample rate and channels
switch (frequency) {
case 16000:
NRF_PDM->RATIO = ((PDM_RATIO_RATIO_Ratio80 << PDM_RATIO_RATIO_Pos) & PDM_RATIO_RATIO_Msk);
nrfx_pdm_config.clock_freq = NRF_PDM_FREQ_1280K;
break;
case 41667:
nrfx_pdm_config.clock_freq = NRF_PDM_FREQ_2667K;
break;
default:
RAISE_OS_EXCEPTION("Invalid frequency!");
}
switch (g_channels) {
case 1:
nrfx_pdm_config.mode = NRF_PDM_MODE_MONO;
nrfx_pdm_config.edge = NRF_PDM_EDGE_LEFTFALLING;
break;
case 2:
#if 0 // Disable 2 channels for now.
nrfx_pdm_config.mode = NRF_PDM_MODE_STEREO;
nrfx_pdm_config.edge = NRF_PDM_EDGE_LEFTFALLING;
break;
#endif
default:
RAISE_OS_EXCEPTION("Invalid number of channels! Expected 1 or 2.");
}
// Power the mic on.
nrf_gpio_cfg_output(OMV_PDM_PWR_PIN);
nrf_gpio_pin_set(OMV_PDM_PWR_PIN);
// Allocate global PCM buffer.
g_pcmbuf = mp_obj_new_bytearray_by_ref(PDM_BUFFER_SIZE * sizeof(int16_t), m_new(int16_t, PDM_BUFFER_SIZE));
nrfx_pdm_init(&nrfx_pdm_config, nrfx_pdm_event_handler);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_KW(py_audio_init_obj, 0, py_audio_init);
void py_audio_deinit() {
// Disable PDM and IRQ
nrfx_pdm_uninit();
// Power the mic off
nrf_gpio_cfg_output(OMV_PDM_PWR_PIN);
nrf_gpio_pin_clear(OMV_PDM_PWR_PIN);
nrf_gpio_cfg_input(OMV_PDM_PWR_PIN, NRF_GPIO_PIN_PULLDOWN);
g_channels = 0;
g_pcmbuf = NULL;
g_buf_index = 0;
g_audio_callback = mp_const_none;
}
static void audio_task_callback(mp_sched_node_t *node) {
memcpy(g_pcmbuf->items, PDM_BUFFERS[g_buf_index], PDM_BUFFER_SIZE * sizeof(int16_t));
// Call user callback
mp_call_function_1(g_audio_callback, MP_OBJ_FROM_PTR(g_pcmbuf));
}
static mp_obj_t py_audio_start_streaming(mp_obj_t callback_obj) {
g_audio_callback = callback_obj;
if (!mp_obj_is_callable(g_audio_callback)) {
g_audio_callback = mp_const_none;
RAISE_OS_EXCEPTION("Invalid callback object!");
}
// Start PDM.
if (nrfx_pdm_start() != NRFX_SUCCESS) {
g_audio_callback = mp_const_none;
RAISE_OS_EXCEPTION("PDM start failed!");
}
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() {
// Stop PDM.
nrfx_pdm_stop();
g_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);
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) },
};
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_MODULE(MP_QSTR_audio, audio_module);
#endif //MICROPY_PY_AUDIO