diff --git a/cores/arduino/IRQManager.cpp b/cores/arduino/IRQManager.cpp index 4d9e6e1e8..185d1bd23 100644 --- a/cores/arduino/IRQManager.cpp +++ b/cores/arduino/IRQManager.cpp @@ -23,6 +23,7 @@ #define ADC_PRIORITY 12 #define CAN_PRIORITY 12 #define CANFD_PRIORITY 12 +#define I2S_PRIORITY 12 #define FIRST_INT_SLOT_FREE 0 IRQManager::IRQManager() : last_interrupt_index{0} { @@ -930,6 +931,41 @@ bool IRQManager::addPeripheral(Peripheral_t p, void *cfg) { } } #endif + +#if I2S_HOWMANY > 0 + /* ********************************************************************** + I2S + ********************************************************************** */ + else if(p == IRQ_I2S && cfg != NULL) { + i2s_cfg_t *i2s_cfg = (i2s_cfg_t *)cfg; + + if(i2s_cfg->txi_irq == FSP_INVALID_VECTOR) { + i2s_cfg->txi_irq = (IRQn_Type)last_interrupt_index; + i2s_cfg->txi_ipl = I2S_PRIORITY; + *(irq_ptr + last_interrupt_index) = (uint32_t)ssi_txi_isr; + R_ICU->IELSR[last_interrupt_index] = BSP_PRV_IELS_ENUM(EVENT_SSI0_TXI); + R_FSP_IsrContextSet(i2s_cfg->txi_irq, (void*)i2s_cfg->p_context); + last_interrupt_index++; + } + if(i2s_cfg->rxi_irq == FSP_INVALID_VECTOR) { + i2s_cfg->rxi_irq = (IRQn_Type)last_interrupt_index; + i2s_cfg->rxi_ipl = I2S_PRIORITY; + *(irq_ptr + last_interrupt_index) = (uint32_t)ssi_rxi_isr; + R_ICU->IELSR[last_interrupt_index] = BSP_PRV_IELS_ENUM(EVENT_SSI0_RXI); + R_FSP_IsrContextSet(i2s_cfg->rxi_irq, (void*)i2s_cfg->p_context); + last_interrupt_index++; + } + if(i2s_cfg->int_irq == FSP_INVALID_VECTOR) { + i2s_cfg->int_irq = (IRQn_Type)last_interrupt_index; + i2s_cfg->idle_err_ipl = I2S_PRIORITY; + *(irq_ptr + last_interrupt_index) = (uint32_t)ssi_int_isr; + R_ICU->IELSR[last_interrupt_index] = BSP_PRV_IELS_ENUM(EVENT_SSI0_INT); + R_FSP_IsrContextSet(i2s_cfg->int_irq, (void*)i2s_cfg->p_context); + last_interrupt_index++; + } + } +#endif + else { rv = false; } diff --git a/cores/arduino/IRQManager.h b/cores/arduino/IRQManager.h index 2f9e093cc..efa4284d1 100644 --- a/cores/arduino/IRQManager.h +++ b/cores/arduino/IRQManager.h @@ -18,6 +18,15 @@ #include "r_external_irq_api.h" #endif +#if I2S_HOWMANY > 0 +#include "r_ssi.h" +extern "C" { +void ssi_txi_isr(void); +void ssi_rxi_isr(void); +void ssi_int_isr(void); +} +#endif + #include "r_timer_api.h" #ifdef ELC_EVENT_DMAC0_INT @@ -43,7 +52,8 @@ typedef enum { IRQ_CAN, IRQ_ETHERNET, IRQ_CANFD, - IRQ_SDCARD + IRQ_SDCARD, + IRQ_I2S } Peripheral_t; #if SDCARD_HOWMANY > 0 diff --git a/libraries/I2S/I2S.cpp b/libraries/I2S/I2S.cpp new file mode 100644 index 000000000..b17232a28 --- /dev/null +++ b/libraries/I2S/I2S.cpp @@ -0,0 +1,184 @@ +/* + This file is part of the Arduino_AdvancedAnalog library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "I2S.h" + +I2SClass I2S; + +extern "C" i2s_cfg_t g_i2s0_cfg; +extern "C" ssi_instance_ctrl_t g_i2s0_ctrl; +extern "C" void i2s_callback(i2s_callback_args_t *p_args); + +int I2SClass::start_transfer() { + if (!rx_buf && (i2s_mode & I2S_MODE_IN)) { + rx_buf = rx_pool->alloc(DMA_BUFFER_WRITE); + } + + if (!tx_buf && (i2s_mode & I2S_MODE_OUT)) { + tx_buf = tx_pool->alloc(DMA_BUFFER_READ); + } + + // Start I2S DMA. + if (i2s_mode == I2S_MODE_IN) { + if (R_SSI_Read(&g_i2s0_ctrl, (void*) rx_buf->data(), rx_buf->bytes()) != FSP_SUCCESS) { + return 0; + } + } else if (i2s_mode == I2S_MODE_OUT) { + if (R_SSI_Write(&g_i2s0_ctrl, (void*) tx_buf->data(), tx_buf->bytes()) != FSP_SUCCESS) { + return 0; + } + } else { + if (R_SSI_WriteRead(&g_i2s0_ctrl, (void*) tx_buf->data(), + (void*) rx_buf->data(), tx_buf->bytes()) != FSP_SUCCESS) { + return 0; + } + } + return 1; +} + +int I2SClass::begin(uint32_t i2s_mode, uint32_t sample_rate, size_t n_samples, size_t n_buffers) { + this->i2s_mode = i2s_mode; + + IRQManager::getInstance().addPeripheral(IRQ_I2S, &g_i2s0_cfg); + if (R_SSI_Open(&g_i2s0_ctrl, &g_i2s0_cfg) != FSP_SUCCESS) { + return false; + } + + if (R_SSI_CallbackSet(&g_i2s0_ctrl, i2s_callback, this, NULL) != FSP_SUCCESS) { + return false; + } + + // Internal AUDIO_CLK from GPT channel. + // Need to find the timer connected to GPT_TIMER 2A for internal clock + auto pwm = new PwmOut(64); + pwm->begin(50, 25, true); + + // Configure I/Os. + pinPeripheral(BSP_IO_PORT_01_PIN_12, (uint32_t) (IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_SSI)); + pinPeripheral(BSP_IO_PORT_01_PIN_13, (uint32_t) (IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_SSI)); + pinPeripheral(BSP_IO_PORT_01_PIN_14, (uint32_t) (IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_SSI)); + pinPeripheral(BSP_IO_PORT_01_PIN_15, (uint32_t) (IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_SSI)); + + if (i2s_mode & I2S_MODE_IN) { + // Allocate RX buffer pool. + if (!(rx_pool = new DMAPool(n_samples, 2, n_buffers))) { + return 0; + } + } + + if (i2s_mode & I2S_MODE_OUT) { + // Allocate TX buffer pool. + if (!(tx_pool = new DMAPool(n_samples, 2, n_buffers))) { + return 0; + } + } + + if (i2s_mode == I2S_MODE_IN) { + return start_transfer(); + } + + if (i2s_mode == I2S_MODE_INOUT) { + // The transmit pool has to be primed with a few buffers first, + // before the transfer can be started in full-duplex mode. + for (int i=0; i<3; i++) { + SampleBuffer outbuf = dequeue(); + memset(outbuf.data(), 0, outbuf.bytes()); + write(outbuf); + } + } + + return 1; +} + +bool I2SClass::available() { + if (rx_pool && i2s_mode == I2S_MODE_IN) { + return rx_pool->readable(); + } else if (tx_pool && i2s_mode == I2S_MODE_OUT) { + return tx_pool->writable(); + } else if (tx_pool && rx_pool) { + return rx_pool->readable() && tx_pool->writable(); + } + return false; +} + +SampleBuffer I2SClass::read() { + while (!rx_pool->readable()) { + __WFI(); + } + return *rx_pool->alloc(DMA_BUFFER_READ); +} + +SampleBuffer I2SClass::dequeue() { + while (!tx_pool->writable()) { + __WFI(); + } + return *tx_pool->alloc(DMA_BUFFER_WRITE); +} + +void I2SClass::write(SampleBuffer buf) { + static uint32_t buf_count = 0; + if (tx_pool == nullptr) { + return; + } + + buf.flush(); + buf.release(); + + if (tx_buf == nullptr && (++buf_count % 3) == 0) { + start_transfer(); + } +} + +extern "C" void i2s_callback(i2s_callback_args_t *p_args) { + if (p_args == NULL) { + return; + } + + i2s_event_t i2s_event = p_args->event; + I2SClass *i2s = (I2SClass *) p_args->p_context; + + if (i2s_event == I2S_EVENT_TX_EMPTY) { + // Release the current buffer and get the next one. + if (i2s->tx_pool->readable()) { + i2s->tx_buf->release(); + i2s->tx_buf = i2s->tx_pool->alloc(DMA_BUFFER_READ); + } else { + // TODO stop/restart + } + R_SSI_Write(&g_i2s0_ctrl, (void*) i2s->tx_buf->data(), i2s->tx_buf->bytes()); + } + + if (i2s_event == I2S_EVENT_RX_FULL) { + // Update the buffer's timestamp. + //i2s->rx_buf->timestamp(us_ticker_read()); + if (i2s->rx_pool->writable()) { + // Move current DMA buffer to ready queue. + i2s->rx_buf->release(); + // Allocate a new free buffer. + i2s->rx_buf = i2s->rx_pool->alloc(DMA_BUFFER_WRITE); + // Currently, all multi-channel buffers are interleaved. + if (i2s->rx_buf->channels() > 1) { + i2s->rx_buf->set_flags(DMA_BUFFER_INTRLVD); + } + } else { + i2s->rx_buf->set_flags(DMA_BUFFER_DISCONT); + } + R_SSI_Read(&g_i2s0_ctrl, (void*) i2s->rx_buf->data(), i2s->rx_buf->bytes()); + } +} diff --git a/libraries/I2S/I2S.h b/libraries/I2S/I2S.h new file mode 100644 index 000000000..b30293566 --- /dev/null +++ b/libraries/I2S/I2S.h @@ -0,0 +1,58 @@ +/* + This file is part of the Arduino_AdvancedAnalog library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef __I2S_H__ +#define __I2S_H__ + +#include "r_i2s_api.h" +#include "r_ssi.h" +#include "pwm.h" +#include "api/DMAPool.h" + +typedef uint16_t Sample; +typedef DMABuffer &SampleBuffer; + +enum { + I2S_MODE_IN = (1U << 0U), + I2S_MODE_OUT = (1U << 1U), + I2S_MODE_INOUT = (I2S_MODE_IN | I2S_MODE_OUT), +}; + +class I2SClass { + private: + uint32_t i2s_mode; + int start_transfer(); + + public: + DMABuffer *tx_buf; + DMABuffer *rx_buf; + DMAPool *tx_pool; + DMAPool *rx_pool; + + I2SClass(): tx_buf(nullptr), rx_buf(nullptr), tx_pool(nullptr), rx_pool(nullptr) { + } + bool available(); + SampleBuffer read(); + SampleBuffer dequeue(); + void write(SampleBuffer buf); + int begin(uint32_t i2s_mode, uint32_t sample_rate, size_t n_samples, size_t n_buffers); + int stop(); +}; + +extern I2SClass I2S; +#endif // __I2S_H__ diff --git a/libraries/I2S/config.c b/libraries/I2S/config.c new file mode 100644 index 000000000..4d7a6b31f --- /dev/null +++ b/libraries/I2S/config.c @@ -0,0 +1,69 @@ +#include "r_i2s_api.h" +#include "r_ssi.h" +#include "r_dtc.h" +dtc_instance_ctrl_t g_transfer0_ctrl; + +transfer_info_t g_transfer0_info = { + .transfer_settings_word_b.dest_addr_mode = TRANSFER_ADDR_MODE_FIXED, + .transfer_settings_word_b.repeat_area = TRANSFER_REPEAT_AREA_SOURCE, + .transfer_settings_word_b.irq = TRANSFER_IRQ_END, + .transfer_settings_word_b.chain_mode = TRANSFER_CHAIN_MODE_DISABLED, + .transfer_settings_word_b.src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED, + .transfer_settings_word_b.size = TRANSFER_SIZE_4_BYTE, + .transfer_settings_word_b.mode = TRANSFER_MODE_BLOCK, + .p_dest = (void*) NULL, + .p_src = (void const*) NULL, + .num_blocks = 0, + .length = 0, +}; + +const dtc_extended_cfg_t g_transfer0_cfg_extend ={ + .activation_source = VECTOR_NUMBER_SSI0_TXI, +}; + +const transfer_cfg_t g_transfer0_cfg = { + .p_info = &g_transfer0_info, + .p_extend = &g_transfer0_cfg_extend +}; + +/* Instance structure to use this module. */ +const transfer_instance_t g_transfer0 = { + .p_ctrl = &g_transfer0_ctrl, + .p_cfg = &g_transfer0_cfg, + .p_api = &g_transfer_on_dtc +}; +ssi_instance_ctrl_t g_i2s0_ctrl; + +/** SSI instance configuration */ +const ssi_extended_cfg_t g_i2s0_cfg_extend = { + .audio_clock = SSI_AUDIO_CLOCK_INTERNAL, + .bit_clock_div = SSI_CLOCK_DIV_1 +}; + +/** I2S interface configuration */ +const i2s_cfg_t g_i2s0_cfg = { + .channel = 0, + .pcm_width = I2S_PCM_WIDTH_16_BITS, + //.pcm_width = I2S_PCM_WIDTH_32_BITS, + .operating_mode = I2S_MODE_MASTER, + .word_length = I2S_WORD_LENGTH_32_BITS, + .ws_continue = I2S_WS_CONTINUE_ON, + .p_callback = NULL, + .p_context = NULL, + .p_extend = &g_i2s0_cfg_extend, + .txi_irq = FSP_INVALID_VECTOR, + .rxi_irq = FSP_INVALID_VECTOR, + .int_irq = FSP_INVALID_VECTOR, + .txi_ipl = (2), + .rxi_ipl = (2), + .idle_err_ipl = (2), + .p_transfer_tx = NULL, // MAY be null + .p_transfer_rx = NULL, +}; + +/* Instance structure to use this module. */ +const i2s_instance_t g_i2s0 = { + .p_ctrl = &g_i2s0_ctrl, + .p_cfg = &g_i2s0_cfg, + .p_api = &g_i2s_on_ssi +}; diff --git a/libraries/I2S/examples/RecordAndStream/RecordAndStream.ino b/libraries/I2S/examples/RecordAndStream/RecordAndStream.ino new file mode 100644 index 000000000..8ed3b2a72 --- /dev/null +++ b/libraries/I2S/examples/RecordAndStream/RecordAndStream.ino @@ -0,0 +1,26 @@ +#include + +void setup() { + Serial.begin(9600); + while (!Serial) { + + } + + // Resolution, sample rate, number of samples per channel, queue depth. + if (!I2S.begin(I2S_MODE_INOUT, 32000, 256, 8)) { + Serial.println("Failed to start I2S"); + while (1); + } +} + +void loop() { + if (I2S.available()) { + SampleBuffer rxbuf = I2S.read(); + SampleBuffer txbuf = I2S.dequeue(); + for (size_t i=0; i