diff --git a/docs/psoc6/quickref.rst b/docs/psoc6/quickref.rst index 42a08530cbf52..e3e411734d575 100644 --- a/docs/psoc6/quickref.rst +++ b/docs/psoc6/quickref.rst @@ -579,8 +579,8 @@ Constructor audio_in = I2S(0, sck="P5_4", ws="P5_5", sd="P5_6", mode=I2S.RX, bits=16, format=I2S.STEREO, rate=22050, ibuf=20000) # create I2S object num_read = audio_in.readinto(buf)# fill buffer with audio samples from I2S device -PDM - PCM ------------- +PDM - PCM bus +-------------- PDM/PCM is a asynchronous operation used to connect digital audio devices. At the physical level, a bus consists of 2 lines: CLK, DATA. @@ -600,7 +600,7 @@ PDM objects can be created and initialized using:: sample_rate=8000, decimation_rate=64, bits=PDM_PCM.BITS_16, - format=PDM_PCM.STEREO, + format=PDM_PCM.MONO_LEFT, left_gain=0, right_gain=0 ) @@ -611,7 +611,7 @@ PDM objects can be created and initialized using:: Constructor ------------ +^^^^^^^^^^^^ .. class:: PDM_PCM(id, *, clk, data, sample_rate, decimation_rate, bits, format, left_gain, right_gain) @@ -631,21 +631,37 @@ Constructor - ``right_gain`` is PGA in 0.5 dB increment Methods -------- +^^^^^^^^ + +.. method:: PDM_PCM.init() -.. method:: PDM_PCM.init(clk, ...) + Starts the PDM_PCM hardware block and conversion operation. - See constructor for argument descriptions +.. note:: + Once the block is started, about 35-45 samples are internally discarded to set the protocol. The actual data should be recorded after a sec to avoid any losses. .. method:: PDM_PCM.deinit() - Deinitialize PDM_PCM object + Stops the PDM_PCM hardware block deinitializes PDM_PCM object .. method:: PDM_PCM.readinto(buf) + Read audio samples into the buffer specified by ``buf``. ``buf`` must support the buffer protocol, such as bytearray or array. + For Stereo format, left channel sample precedes right channel sample. For Mono-left format, + the left channel sample data is used and for Mono-right format, right channel data is used. Ensure that ``buf`` size should be multiple of sample size. + Sample size can be calculated as (PCM_bits/8) * (format_size); where format_size is 2(stereo mode) and 1(mono mode). + Returns number of bytes read + .. method:: PDM_PCM.irq(handler) -.. method:: PDM_PCM.gain(gain_left, gain_right) + Set the callback.``handler`` is called when ``buf`` becomes full (``readinto`` method). + Setting a callback changes the ``readinto`` method to non-blocking operation. + ``handler`` is called in the context of the MicroPython scheduler. + +.. method:: PDM_PCM.gain(left_gain, right_gain) + + Set the gain for single or both microphones. When either of the gain value is not passed, + previously set value or default value of 0dB is set. Constants --------- diff --git a/ports/psoc6/machine_pdm_pcm.c b/ports/psoc6/machine_pdm_pcm.c index 614d68dd62c86..cfb3608a2979e 100644 --- a/ports/psoc6/machine_pdm_pcm.c +++ b/ports/psoc6/machine_pdm_pcm.c @@ -27,80 +27,140 @@ #include #include "py/runtime.h" +#include "py/stream.h" #include "py/mphal.h" #include "machine_pin_phy.h" #include "modmachine.h" #include "mplogger.h" - #if MICROPY_PY_MACHINE_PDM_PCM +#include "machine_pdm_pcm.h" + +cyhal_clock_t pdm_pcm_audio_clock; + + +#if MICROPY_PY_MACHINE_PDM_PCM_RING_BUF + -#define pdm_pcm_assert_raise_val(msg, ret) if (ret != CY_RSLT_SUCCESS) { \ - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT(msg), ret); \ +void ringbuf_init(ring_buf_t *rbuf, size_t size) { + rbuf->buffer = m_new(uint8_t, size); + memset(rbuf->buffer, 0, size); + rbuf->size = size; + rbuf->head = 0; + rbuf->tail = 0; } -#define DEFAULT_LEFT_GAIN 0 -#define DEFAULT_RIGHT_GAIN 0 -#define MICROPY_HW_MAX_PDM_PCM 1 +bool ringbuf_push(ring_buf_t *rbuf, uint8_t data) { + size_t next_tail = (rbuf->tail + 1) % rbuf->size; + if (next_tail != rbuf->head) { + rbuf->buffer[rbuf->tail] = data; + rbuf->tail = next_tail; + return true; + } + // full + return false; +} -#define AUDIO_SYS_CLOCK_24_576_000_HZ 24576000u /* (Ideally 24.576 MHz) For sample rates: 8 KHz / 16 KHz / 48 KHz */ -#define AUDIO_SYS_CLOCK_22_579_000_HZ 22579000u /* (Ideally 22.579 MHz) For sample rates: 22.05 KHz / 44.1 KHz */ +bool ringbuf_pop(ring_buf_t *rbuf, uint8_t *data) { + if (rbuf->head == rbuf->tail) { + // empty + return false; + } -cyhal_clock_t pdm_pcm_audio_clock; + *data = rbuf->buffer[rbuf->head]; + rbuf->head = (rbuf->head + 1) % rbuf->size; + return true; +} -// Constructor args -enum { - ARG_clk, - ARG_data, - ARG_sample_rate, - ARG_decimation_rate, - ARG_bits, - ARG_format, - ARG_left_gain, - ARG_right_gain, -}; +size_t ringbuf_available_data(ring_buf_t *rbuf) { + return (rbuf->tail - rbuf->head + rbuf->size) % rbuf->size; +} + +size_t ringbuf_available_space(ring_buf_t *rbuf) { + return rbuf->size - ringbuf_available_data(rbuf) - 1; +} + +static uint32_t fill_appbuf_from_ringbuf(machine_pdm_pcm_obj_t *self, mp_buffer_info_t *appbuf) { + uint32_t num_bytes_copied_to_appbuf = 0; + uint8_t *app_p = (uint8_t *)appbuf->buf; + uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1); + uint32_t num_bytes_needed_from_ringbuf = appbuf->len * (PDM_PCM_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes); + uint8_t discard_byte; + while (num_bytes_needed_from_ringbuf) { + + uint8_t f_index = get_frame_mapping_index(self->bits, self->format); + + for (uint8_t i = 0; i < PDM_PCM_RX_FRAME_SIZE_IN_BYTES; i++) { + int8_t r_to_a_mapping = pdm_pcm_frame_map[f_index][i]; + if (r_to_a_mapping != -1) { + if (self->io_mode == BLOCKING) { + while (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) { + ; + } + num_bytes_copied_to_appbuf++; + } else { + return 0; // should never get here (non-blocking mode does not use this function) + } + } else { // r_a_mapping == -1 + // discard unused byte from ring buffer + if (self->io_mode == BLOCKING) { + // poll the ringbuf until a sample becomes available + while (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) { + ; + } + } else { + return 0; // should never get here (non-blocking mode does not use this function) + } + } + num_bytes_needed_from_ringbuf--; + } + app_p += appbuf_sample_size_in_bytes; + } + return num_bytes_copied_to_appbuf; +} + +// Copy from ringbuf to appbuf as soon as ASYNC_TRANSFER_COMPLETE is triggered +static void fill_appbuf_from_ringbuf_non_blocking(machine_pdm_pcm_obj_t *self) { + uint32_t num_bytes_copied_to_appbuf = 0; + uint8_t *app_p = &(((uint8_t *)self->non_blocking_descriptor.appbuf.buf)[self->non_blocking_descriptor.index]); + + uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1); + uint32_t num_bytes_remaining_to_copy_to_appbuf = self->non_blocking_descriptor.appbuf.len - self->non_blocking_descriptor.index; + uint32_t num_bytes_remaining_to_copy_from_ring_buffer = num_bytes_remaining_to_copy_to_appbuf * + (PDM_PCM_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes); + uint32_t num_bytes_needed_from_ringbuf = MIN(SIZEOF_NON_BLOCKING_COPY_IN_BYTES, num_bytes_remaining_to_copy_from_ring_buffer); + uint8_t discard_byte; + if (ringbuf_available_data(&self->ring_buffer) >= num_bytes_needed_from_ringbuf) { + while (num_bytes_needed_from_ringbuf) { + + uint8_t f_index = get_frame_mapping_index(self->bits, self->format); + + for (uint8_t i = 0; i < PDM_PCM_RX_FRAME_SIZE_IN_BYTES; i++) { + int8_t r_to_a_mapping = pdm_pcm_frame_map[f_index][i]; + if (r_to_a_mapping != -1) { + ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping); + num_bytes_copied_to_appbuf++; + } else { // r_a_mapping == -1 + // discard unused byte from ring buffer + ringbuf_pop(&self->ring_buffer, &discard_byte); + } + num_bytes_needed_from_ringbuf--; + } + app_p += appbuf_sample_size_in_bytes; + } + self->non_blocking_descriptor.index += num_bytes_copied_to_appbuf; + + if (self->non_blocking_descriptor.index >= self->non_blocking_descriptor.appbuf.len) { + self->non_blocking_descriptor.copy_in_progress = false; + mp_sched_schedule(self->callback_for_non_blocking, MP_OBJ_FROM_PTR(self)); + } + } +} +#endif // MICROPY_PY_MACHINE_PDM_PCM_RING_BUF +/*========================================================================================================================*/ +// PDM_PCM higher level MPY functions (extmod/machine_pdm_pcm.c) -typedef enum { - BITS_16 = 16, - BITS_18 = 18, - BITS_20 = 20, - BITS_24 = 24 -} pdm_pcm_word_length_t; - -// To be compatible with extmod -typedef enum { - MONO_LEFT = CYHAL_PDM_PCM_MODE_LEFT, - MONO_RIGHT = CYHAL_PDM_PCM_MODE_RIGHT, - STEREO = CYHAL_PDM_PCM_MODE_STEREO -} format_t; - -typedef enum { - BLOCKING, - NON_BLOCKING, -} io_mode_t; - - -typedef struct _machine_pdm_pcm_obj_t { - mp_obj_base_t base; - uint8_t pdm_pcm_id; // Private variable in this port. ID not associated to any port pin pdm-pcm group. - cyhal_pdm_pcm_t pdm_pcm_obj; - uint32_t clk; - uint32_t data; - io_mode_t io_mode; - format_t format; - uint8_t bits; - uint32_t sample_rate; - uint8_t decimation_rate; - int16_t left_gain; - int16_t right_gain; -} machine_pdm_pcm_obj_t; - -static void mp_machine_pdm_pcm_init_helper(machine_pdm_pcm_obj_t *self, mp_arg_val_t *args); -static machine_pdm_pcm_obj_t *mp_machine_pdm_pcm_make_new_instance(mp_int_t pdm_pcm_id); -static void mp_machine_pdm_pcm_deinit(machine_pdm_pcm_obj_t *self); - - -// To be preesent in extmod/pdm_pcm MP_NOINLINE static void machine_pdm_pcm_init_helper(machine_pdm_pcm_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mplogger_print("machine_pdm_pcm_init_helper \r\n"); static const mp_arg_t allowed_args[] = { { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_data, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, @@ -114,7 +174,6 @@ MP_NOINLINE static void machine_pdm_pcm_init_helper(machine_pdm_pcm_obj_t *self, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_machine_pdm_pcm_init_helper(self, args); } @@ -140,41 +199,162 @@ static void machine_pdm_pcm_print(const mp_print_t *print, mp_obj_t self_in, mp_ ); } +// PDM_PCM(...) Constructor +static mp_obj_t machine_pdm_pcm_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { + mplogger_print("machine_pdm_pcm_make_new \r\n"); + mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); + mp_int_t pdm_pcm_id = mp_obj_get_int(args[0]); + + machine_pdm_pcm_obj_t *self = mp_machine_pdm_pcm_make_new_instance(pdm_pcm_id); + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); + machine_pdm_pcm_init_helper(self, n_pos_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + // PDM_PCM.init(...) -static mp_obj_t machine_pdm_pcm_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - machine_pdm_pcm_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - mp_machine_pdm_pcm_deinit(self); - machine_pdm_pcm_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); +static mp_obj_t machine_pdm_pcm_init(mp_obj_t self_in) { + mplogger_print("machine_pdm_pcm_init \r\n"); + machine_pdm_pcm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_machine_pdm_pcm_init(self); return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_KW(machine_pdm_pcm_init_obj, 1, machine_pdm_pcm_init); +static MP_DEFINE_CONST_FUN_OBJ_1(machine_pdm_pcm_init_obj, machine_pdm_pcm_init); // PDM_PCM.deinit() static mp_obj_t machine_pdm_pcm_deinit(mp_obj_t self_in) { + mplogger_print("machine_pdm_pcm_deinit \r\n"); machine_pdm_pcm_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_machine_pdm_pcm_deinit(self); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(machine_pdm_pcm_deinit_obj, machine_pdm_pcm_deinit); -// PDM_PCM() Constructor -static mp_obj_t machine_pdm_pcm_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { - mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); - mp_int_t pdm_pcm_id = mp_obj_get_int(args[0]); +// PDM_PCM.set_gain() +static mp_obj_t machine_pdm_pcm_set_gain(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mplogger_print("machine_pdm_pcm_set_gain \r\n"); - machine_pdm_pcm_obj_t *self = mp_machine_pdm_pcm_make_new_instance(pdm_pcm_id); + enum { ARG_left_gain, ARG_right_gain}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_left_gain, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_LEFT_GAIN} }, + { MP_QSTR_right_gain, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_RIGHT_GAIN} } + }; - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); - machine_pdm_pcm_init_helper(self, n_pos_args - 1, args + 1, &kw_args); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + machine_pdm_pcm_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - return MP_OBJ_FROM_PTR(self); + int16_t left_gain = args[ARG_left_gain].u_int; + int16_t right_gain = args[ARG_right_gain].u_int; + + int16_t prev_left_gain = self->left_gain; + int16_t prev_right_gain = self->right_gain; + + self->left_gain = (left_gain == prev_left_gain) ? prev_left_gain : left_gain; + self->right_gain = (right_gain == prev_right_gain) ? prev_right_gain : right_gain; + + mp_machine_pdm_pcm_set_gain(self, left_gain, right_gain); + return mp_const_none; } +static MP_DEFINE_CONST_FUN_OBJ_KW(machine_pdm_pcm_set_gain_obj, 1, machine_pdm_pcm_set_gain); + +// PDM_PCM.irq(handler) +static mp_obj_t machine_pdm_pcm_irq(mp_obj_t self_in, mp_obj_t handler) { + machine_pdm_pcm_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid callback")); + } + + if (handler != mp_const_none) { + self->io_mode = NON_BLOCKING; + } else { + self->io_mode = BLOCKING; + } + + self->callback_for_non_blocking = handler; + + mp_machine_pdm_pcm_irq_update(self); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(machine_pdm_pcm_irq_obj, machine_pdm_pcm_irq); + +// ======================================================================================= +// Port Private functions for DMA support (ports/psoc6) + +static inline void _dma_buff_init(machine_pdm_pcm_obj_t *self) { + memset(self->dma_active_buffer, 1, SIZEOF_DMA_BUFFER * sizeof(self->dma_active_buffer[0])); + memset(self->dma_processing_buffer, 1, SIZEOF_DMA_BUFFER * sizeof(self->dma_processing_buffer[0])); + + self->dma_active_buf_p = self->dma_active_buffer; + self->dma_processing_buf_p = self->dma_processing_buffer; +} + +static inline void _dma_swap_active_dmabuf(machine_pdm_pcm_obj_t *self) { + uint32_t *temp = self->dma_active_buf_p; + self->dma_active_buf_p = self->dma_processing_buf_p; + self->dma_processing_buf_p = temp; +} + +static void _dma_copy_from_dmabuf_to_ringbuf(machine_pdm_pcm_obj_t *self) { + uint8_t dma_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1); + uint8_t *dma_buff_p = (uint8_t *)self->dma_processing_buf_p; + uint32_t num_bytes_needed_from_ringbuf = SIZEOF_HALF_DMA_BUFFER_IN_BYTES * (PDM_PCM_RX_FRAME_SIZE_IN_BYTES / dma_sample_size_in_bytes); + + // when space exists, copy samples into ring buffer + if (ringbuf_available_space(&self->ring_buffer) >= num_bytes_needed_from_ringbuf) { + uint8_t f_index = get_frame_mapping_index(self->bits, self->format); + uint32_t i = 0; + while (i < SIZEOF_HALF_DMA_BUFFER_IN_BYTES) { + for (uint8_t j = 0; j < PDM_PCM_RX_FRAME_SIZE_IN_BYTES; j++) { + int8_t r_to_a_mapping = pdm_pcm_frame_map[f_index][j]; + if (r_to_a_mapping != -1) { + ringbuf_push(&self->ring_buffer, dma_buff_p[i]); + i++; + } else { // r_a_mapping == -1 + ringbuf_push(&self->ring_buffer, -1); + } + } + } + } +} + // ======================================================================================= -// Private functions +// PDM_PCM low-level abstracted functions and helper function (ports/psoc6) +uint8_t pdm_pcm_get_word_byte_size(machine_pdm_pcm_obj_t *self) { + uint8_t res_bits = self->bits; + if (res_bits == 16) { + return 2; + } else { + return 4; + } +} + +// Init hardware block +static void pdm_pcm_init(machine_pdm_pcm_obj_t *self, cyhal_clock_t *clock) { + mplogger_print("pdm_pcm_init \r\n"); + cyhal_pdm_pcm_cfg_t config = + { + .sample_rate = self->sample_rate, + .decimation_rate = self->decimation_rate, + .mode = self->format, + .word_length = self->bits, /* bits */ + .left_gain = self->left_gain, /* dB */ + .right_gain = self->right_gain, /* dB */ + }; + + cy_rslt_t result = cyhal_pdm_pcm_init(&self->pdm_pcm_obj, self->data, self->clk, &pdm_pcm_audio_clock, &config); + assert_pin_phy_used(result); + pdm_pcm_assert_raise_val("PDM_PCM initialisation failed with return code %lx !", result); +} + +// Initialize audio clock void pdm_pcm_audio_clock_init(uint32_t audio_clock_freq_hz) { + mplogger_print("pdm_pcm_audio_clock_init \r\n"); cyhal_clock_t pll_clock; cy_rslt_t result; @@ -213,31 +393,76 @@ void pdm_pcm_audio_clock_init(uint32_t audio_clock_freq_hz) { } cyhal_clock_free(&pll_clock); - cyhal_system_delay_ms(1); } -// PDM_PCM hardware block init -static void pdm_pcm_init(machine_pdm_pcm_obj_t *self, cyhal_clock_t *clock) { - cyhal_pdm_pcm_cfg_t config = - { - .sample_rate = self->sample_rate, - .decimation_rate = self->decimation_rate, - .mode = self->format, - .word_length = self->bits, /* bits */ - .left_gain = self->left_gain, /* dB */ - .right_gain = self->right_gain, /* dB */ - }; +// Set PDM_PCM async mode to DMA +static void pdm_pcm_set_async_mode_dma(machine_pdm_pcm_obj_t *self) { + mplogger_print("pdm_pcm_set_async_mode_dma \r\n"); + cy_rslt_t result = cyhal_pdm_pcm_set_async_mode(&self->pdm_pcm_obj, CYHAL_ASYNC_DMA, CYHAL_DMA_PRIORITY_DEFAULT); + pdm_pcm_assert_raise_val("PDM_PCM set DMA mode failed with return code %lx !", result); +} - cy_rslt_t result = cyhal_pdm_pcm_init(&self->pdm_pcm_obj, self->data, self->clk, &pdm_pcm_audio_clock, &config); - assert_pin_phy_used(result); - pdm_pcm_assert_raise_val("PDM_PCM initialisation failed with return code %lx !", result); +// Move data from PDM_PCM hardware buf to provisioned DMA buffer +static void pdm_pcm_read_rxbuf(machine_pdm_pcm_obj_t *self) { + uint16_t dma_half_buff_word_size = SIZEOF_HALF_DMA_BUFFER_IN_BYTES / pdm_pcm_get_word_byte_size(self); + while (cyhal_pdm_pcm_is_pending(&self->pdm_pcm_obj)) { + ; + } + cy_rslt_t result = cyhal_pdm_pcm_read_async(&self->pdm_pcm_obj, self->dma_active_buf_p, dma_half_buff_word_size); + pdm_pcm_assert_raise_val("PDM_PCM DMA read failed with return code %lx !", result); +} + +// First read and start the hw block +static void pdm_pcm_rx_init(machine_pdm_pcm_obj_t *self) { + pdm_pcm_read_rxbuf(self); + cy_rslt_t result = cyhal_pdm_pcm_start(&self->pdm_pcm_obj); + pdm_pcm_assert_raise_val("PDM_PCM rx start failed with return code %lx !", result); +} + +// IRQ Handler +static void pdm_pcm_irq_handler(void *arg, cyhal_pdm_pcm_event_t event) { + machine_pdm_pcm_obj_t *self = (machine_pdm_pcm_obj_t *)arg; + if (0u != (event & CYHAL_PDM_PCM_ASYNC_COMPLETE)) { + _dma_swap_active_dmabuf(self); + pdm_pcm_read_rxbuf(self); + _dma_copy_from_dmabuf_to_ringbuf(self); + + if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) { + fill_appbuf_from_ringbuf_non_blocking(self); + } + } +} + +// Configure PDM_PCM IRQ +static void pdm_pcm_irq_configure(machine_pdm_pcm_obj_t *self) { + mplogger_print("pdm_pcm_irq_configure \r\n"); + cyhal_pdm_pcm_register_callback(&self->pdm_pcm_obj, &pdm_pcm_irq_handler, self); + cyhal_pdm_pcm_enable_event(&self->pdm_pcm_obj, CYHAL_PDM_PCM_ASYNC_COMPLETE, CYHAL_ISR_PRIORITY_DEFAULT, true); +} + +int8_t get_frame_mapping_index(int8_t bits, format_t format) { + if ((format == MONO_LEFT) | (format == MONO_RIGHT)) { + if (bits == 16) { + return 0; + } else { // >16 bits + return 1; + } + } else { // STEREO + if (bits == 16) { + return 2; + } else { // >16 bits + return 3; + } + } } // ======================================================================================= -// MPY bindings in ports/psoc6 +// MPY bindings for PDM_PCM (ports/psoc6) +// constructor() static machine_pdm_pcm_obj_t *mp_machine_pdm_pcm_make_new_instance(mp_int_t pdm_pcm_id) { + mplogger_print("mp_machine_pdm_pcm_make_new_instance \r\n"); (void)pdm_pcm_id; machine_pdm_pcm_obj_t *self = NULL; for (uint8_t i = 0; i < MICROPY_HW_MAX_PDM_PCM; i++) { @@ -248,16 +473,15 @@ static machine_pdm_pcm_obj_t *mp_machine_pdm_pcm_make_new_instance(mp_int_t pdm_ break; } } - if (self == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("all available pdm pcm instances are allocated")); } - return self; } +// init.helper() static void mp_machine_pdm_pcm_init_helper(machine_pdm_pcm_obj_t *self, mp_arg_val_t *args) { - + mplogger_print("mp_machine_pdm_pcm_init_helper \r\n"); // Assign pins self->clk = pin_addr_by_name(args[ARG_clk].u_obj); self->data = pin_addr_by_name(args[ARG_data].u_obj); @@ -286,7 +510,8 @@ static void mp_machine_pdm_pcm_init_helper(machine_pdm_pcm_obj_t *self, mp_arg_v // Set sampling and decimation rates (as given by user) self->sample_rate = args[ARG_sample_rate].u_int; self->decimation_rate = args[ARG_decimation_rate].u_int; - // Set clock values #ToDo: To be verified for each possible values + + // Set clock values uint32_t audio_clock_freq_hz; uint32_t rate = args[ARG_sample_rate].u_int; if (rate == 8000 || @@ -300,19 +525,114 @@ static void mp_machine_pdm_pcm_init_helper(machine_pdm_pcm_obj_t *self, mp_arg_v mp_raise_ValueError(MP_ERROR_TEXT("rate not supported")); } + int32_t ring_buffer_len = 20000; // 2048 + if (ring_buffer_len < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid ibuf")); + } + self->ibuf = ring_buffer_len; + self->callback_for_non_blocking = MP_OBJ_NULL; + self->io_mode = BLOCKING; + + ringbuf_init(&self->ring_buffer, ring_buffer_len); pdm_pcm_audio_clock_init(audio_clock_freq_hz); pdm_pcm_init(self, &pdm_pcm_audio_clock); + pdm_pcm_irq_configure(self); + pdm_pcm_set_async_mode_dma(self); + _dma_buff_init(self); +} + +// init() +static void mp_machine_pdm_pcm_init(machine_pdm_pcm_obj_t *self) { + mplogger_print("mp_machine_pdm_pcm_init \r\n"); + pdm_pcm_rx_init(self); + cyhal_gpio_init(CYBSP_USER_LED, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, true); + cyhal_gpio_write(CYBSP_USER_LED, 1); } +// deinit() static void mp_machine_pdm_pcm_deinit(machine_pdm_pcm_obj_t *self) { + mplogger_print("mp_machine_pdm_pcm_deinit \r\n"); + cyhal_pdm_pcm_stop(&self->pdm_pcm_obj); cyhal_pdm_pcm_free(&self->pdm_pcm_obj); MP_STATE_PORT(machine_pdm_pcm_obj[self->pdm_pcm_id]) = NULL; } +// set_gain() +static void mp_machine_pdm_pcm_set_gain(machine_pdm_pcm_obj_t *self, int16_t left_gain, int16_t right_gain) { + mplogger_print("mp_machine_pdm_pcm_set_gain \r\n"); + mp_printf(&mp_plat_print, "machine.PDM_PCM: Setting left mic gain to %d and right mic gain to %d\n", self->left_gain, self->right_gain); + cy_rslt_t result = cyhal_pdm_pcm_set_gain(&self->pdm_pcm_obj, self->left_gain, self->right_gain); + pdm_pcm_assert_raise_val("PDM_PCM set gain failed with return code %lx !", result); +} + +// irq update +static void mp_machine_pdm_pcm_irq_update(machine_pdm_pcm_obj_t *self) { + (void)self; +} + +// ======================================================================================= +// Implementation for stream protocol (ports/psoc6) +void read_appbuf_value(uint8_t value, mp_buffer_info_t appbuf) { + if (value == 1) { + mp_printf(&mp_plat_print, "\nAppbuf Before : "); + } else { + mp_printf(&mp_plat_print, "\nAppbuf After : "); + } + for (uint32_t i = 0; i < appbuf.len; i++) { + mp_printf(&mp_plat_print, "0x%x ", ((uint8_t *)appbuf.buf)[i]); + } +} + +static mp_uint_t machine_pdm_pcm_stream_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + machine_pdm_pcm_obj_t *self = MP_OBJ_TO_PTR(self_in); + + uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1); + if (size % appbuf_sample_size_in_bytes != 0) { // size should be multiple of sample size + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } + if (size == 0) { + return 0; + } + + if (self->io_mode == BLOCKING) { + mp_buffer_info_t appbuf; + appbuf.buf = (void *)buf_in; + appbuf.len = size; + #if MICROPY_PY_MACHINE_PDM_PCM_RING_BUF + uint32_t num_bytes_read = fill_appbuf_from_ringbuf(self, &appbuf); + #else + uint32_t num_bytes_read = fill_appbuf_from_dma(self, &appbuf); + #endif + return num_bytes_read; + } else { + #if MICROPY_PY_MACHINE_PDM_PCM_RING_BUF + self->non_blocking_descriptor.appbuf.buf = (void *)buf_in; + self->non_blocking_descriptor.appbuf.len = size; + self->non_blocking_descriptor.index = 0; + self->non_blocking_descriptor.copy_in_progress = true; + #else + mp_raise_msg(&mp_type_Exception, MP_ERROR_TEXT("Implementation only through ringbuffer is supported.")); + #endif + + return size; + return 0; + } +} + +static const mp_stream_p_t pdm_pcm_stream_p = { + .read = machine_pdm_pcm_stream_read, + .is_text = false, +}; + static const mp_rom_map_elem_t machine_pdm_pcm_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pdm_pcm_init_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_pdm_pcm_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_gain), MP_ROM_PTR(&machine_pdm_pcm_set_gain_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pdm_pcm_irq_obj) }, + #if MICROPY_PY_MACHINE_PDM_PCM_FINALISER { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_pdm_pcm_deinit_obj) }, #endif @@ -331,16 +651,15 @@ static const mp_rom_map_elem_t machine_pdm_pcm_locals_dict_table[] = { }; MP_DEFINE_CONST_DICT(machine_pdm_pcm_locals_dict, machine_pdm_pcm_locals_dict_table); - MP_REGISTER_ROOT_POINTER(struct _machine_pdm_pcm_obj_t *machine_pdm_pcm_obj[MICROPY_HW_MAX_PDM_PCM]); - MP_DEFINE_CONST_OBJ_TYPE( machine_pdm_pcm_type, MP_QSTR_PDM_PCM, - MP_TYPE_FLAG_NONE, + MP_TYPE_FLAG_ITER_IS_STREAM, make_new, machine_pdm_pcm_make_new, print, machine_pdm_pcm_print, + protocol, &pdm_pcm_stream_p, locals_dict, &machine_pdm_pcm_locals_dict ); diff --git a/ports/psoc6/machine_pdm_pcm.h b/ports/psoc6/machine_pdm_pcm.h new file mode 100644 index 0000000000000..60858d19ff005 --- /dev/null +++ b/ports/psoc6/machine_pdm_pcm.h @@ -0,0 +1,103 @@ +#if MICROPY_PY_MACHINE_PDM_PCM + +#include "ringbuf.h" + +#define MICROPY_HW_MAX_PDM_PCM 1 +#define DEFAULT_LEFT_GAIN 0 +#define DEFAULT_RIGHT_GAIN 0 + +#define AUDIO_SYS_CLOCK_24_576_000_HZ 24576000u /* (Ideally 24.576 MHz) For sample rates: 8 KHz / 16 KHz / 48 KHz */ +#define AUDIO_SYS_CLOCK_22_579_000_HZ 22579000u /* (Ideally 22.579 MHz) For sample rates: 22.05 KHz / 44.1 KHz */ + +#define SIZEOF_DMA_BUFFER (128) +#define SIZEOF_HALF_DMA_BUFFER (SIZEOF_DMA_BUFFER / 2) +#define SIZEOF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER * sizeof(uint32_t)) +#define SIZEOF_HALF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER_IN_BYTES / 2) +#define PDM_PCM_RX_FRAME_SIZE_IN_BYTES (8) + +#define NON_BLOCKING_RATE_MULTIPLIER (4) +#define SIZEOF_NON_BLOCKING_COPY_IN_BYTES (SIZEOF_HALF_DMA_BUFFER * NON_BLOCKING_RATE_MULTIPLIER) + + +#define pdm_pcm_assert_raise_val(msg, ret) if (ret != CY_RSLT_SUCCESS) { \ + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT(msg), ret); \ +} + +// Constructor args +enum { + ARG_clk, + ARG_data, + ARG_sample_rate, + ARG_decimation_rate, + ARG_bits, + ARG_format, + ARG_left_gain, + ARG_right_gain, +}; + +typedef enum { + BITS_16 = 16, + BITS_18 = 18, + BITS_20 = 20, + BITS_24 = 24 +} pdm_pcm_word_length_t; + +typedef enum { + MONO_LEFT = CYHAL_PDM_PCM_MODE_LEFT, + MONO_RIGHT = CYHAL_PDM_PCM_MODE_RIGHT, + STEREO = CYHAL_PDM_PCM_MODE_STEREO +} format_t; + +typedef enum { + BLOCKING, + NON_BLOCKING, +} io_mode_t; + +typedef struct _non_blocking_descriptor_t { + mp_buffer_info_t appbuf; + uint32_t index; + bool copy_in_progress; +} non_blocking_descriptor_t; + +typedef struct _machine_pdm_pcm_obj_t { + mp_obj_base_t base; + uint8_t pdm_pcm_id; // Private variable in this port. ID not associated to any port pin pdm-pcm group. + cyhal_pdm_pcm_t pdm_pcm_obj; + uint32_t clk; + uint32_t data; + io_mode_t io_mode; + format_t format; + uint8_t bits; + uint32_t sample_rate; + uint8_t decimation_rate; + int16_t left_gain; + int16_t right_gain; + int32_t ibuf; // Private variable + mp_obj_t callback_for_non_blocking; + uint32_t dma_active_buffer[SIZEOF_DMA_BUFFER]; + uint32_t dma_processing_buffer[SIZEOF_DMA_BUFFER]; + uint32_t *dma_active_buf_p; + uint32_t *dma_processing_buf_p; + ring_buf_t ring_buffer; + non_blocking_descriptor_t non_blocking_descriptor; // For non-blocking mode +} machine_pdm_pcm_obj_t; + +static void mp_machine_pdm_pcm_init_helper(machine_pdm_pcm_obj_t *self, mp_arg_val_t *args); +static machine_pdm_pcm_obj_t *mp_machine_pdm_pcm_make_new_instance(mp_int_t pdm_pcm_id); +static void mp_machine_pdm_pcm_init(machine_pdm_pcm_obj_t *self); +static void mp_machine_pdm_pcm_deinit(machine_pdm_pcm_obj_t *self); +static void mp_machine_pdm_pcm_set_gain(machine_pdm_pcm_obj_t *self, int16_t left_gain, int16_t right_gain); +static void mp_machine_pdm_pcm_irq_update(machine_pdm_pcm_obj_t *self); + +int8_t get_frame_mapping_index(int8_t bits, format_t format); + +// Should change to MONO_LEFT and MONO_RIGHT when right mic works +static const int8_t pdm_pcm_frame_map[4][PDM_PCM_RX_FRAME_SIZE_IN_BYTES] = { + { 0, 1, -1, -1, -1, -1, -1, -1 }, // Mono, 16-bits + { 0, 1, 2, -1, -1, -1, -1, -1 }, // Mono, >16-bits + { 0, 1, -1, -1, 2, 3, -1, -1 }, // Stereo, 16-bits + { 0, 1, 2, -1, 3, 4, 5, -1 }, // Stereo, >16-bits +}; + + +#endif // MICROPY_PY_MACHINE_PDM_PCM diff --git a/ports/psoc6/mpconfigport.h b/ports/psoc6/mpconfigport.h index 9a17d5cc1a8ac..4f23022a35877 100644 --- a/ports/psoc6/mpconfigport.h +++ b/ports/psoc6/mpconfigport.h @@ -151,6 +151,7 @@ #define MICROPY_PY_MACHINE_I2S_INCLUDEFILE "ports/psoc6/machine_i2s.c" #define MICROPY_PY_MACHINE_PDM_PCM (1) +#define MICROPY_PY_MACHINE_PDM_PCM_RING_BUF (1) // VFS #define MICROPY_VFS (1) #define MICROPY_READER_VFS (1) diff --git a/tests/ports/psoc6/board_ext_hw/multi/pdm_pcm.py b/tests/ports/psoc6/board_ext_hw/multi/pdm_pcm.py index 7201348a3112d..1ac0b6b46d5c0 100644 --- a/tests/ports/psoc6/board_ext_hw/multi/pdm_pcm.py +++ b/tests/ports/psoc6/board_ext_hw/multi/pdm_pcm.py @@ -13,6 +13,9 @@ clk_pin = "P10_4" data_pin = "P10_5" +rx_buf = bytearray([0] * 4096) + +print("PDM_PCM Tests") pdm_pcm = PDM_PCM( 0, sck=clk_pin, @@ -25,4 +28,11 @@ right_gain=0, ) +pdm_pcm.init() + +# Read PCM data +num_read = pdm_pcm.readinto(rx_buf) +print("num bytes read: ", num_read) +print("Read buff values: ", rx_buf) + pdm_pcm.deinit()