Inter-IC Sound (I²S) Usage Scenarios#

The WiSeConnect software development kit (SDK) for SiWx917 devices provides flexible APIs for Inter-IC Sound (I²S) audio streaming. You can:

  • Use blocking APIs for simple, short transfers

  • Use non-blocking direct memory access (DMA) for continuous streaming

  • Run ultra-low-power (ULP) I²S in low-power states to extend battery life

This section presents common usage patterns with concise workflows and example code.

Typical Use Case 1: Blocking (Non-DMA)#

Use blocking mode when:

  • Transfers are short, such as tones, control frames, or small buffers

  • The CPU can wait for completion

  • Latency and throughput requirements are moderate

Workflow#

  1. Initialize the I²S instance.

  2. Configure sample rate, bit depth, and channels.

  3. (Optional) Configure FIFO thresholds.

  4. Call blocking transmit or receive functions.

  5. Check return codes and validate data.

  6. Deinitialize the I²S instance when finished.

I²S Primary (Controller) — Blocking Example#

Step 1: Configure and initialize#

#include "sl_si91x_i2s.h"
#include "sl_i2s_instances.h"
#include "rsi_debug.h"

#define I2S_INSTANCE_USED  SL_I2S_ULP
#define I2S_BUFFER_SIZE    1024

static uint32_t write_buf[I2S_BUFFER_SIZE];
static uint32_t read_buf[I2S_BUFFER_SIZE];
static sl_i2s_config_t i2s_cfg;

// Select ULP or HP config generated by UC
i2s_cfg = sl_i2s_ulp_config;

sl_status_t st = sl_si91x_i2s_init(I2S_INSTANCE_USED, &i2s_cfg);
if (st != SL_STATUS_OK) {
  DEBUGOUT("I²S init failed: %u\n", st);
}

Step 2: Audio Transfer (Blocking)#

// Fill DOUT pattern (left|right packed)
for (uint32_t i = 0; i < I2S_BUFFER_SIZE; i++) {
  write_buf[i] = (i + 1u) << 16 | (i + 1u);
}

st = sl_si91x_i2s_transmit_data_blocking(I2S_INSTANCE_USED, write_buf, I2S_BUFFER_SIZE);
if (st != SL_STATUS_OK) {
  DEBUGOUT("I²S blocking transmit failed: %u\n", st);
}

st = sl_si91x_i2s_receive_data_blocking(I2S_INSTANCE_USED, read_buf, I2S_BUFFER_SIZE);
if (st != SL_STATUS_OK) {
  DEBUGOUT("I²S blocking receive failed: %u\n", st);
}

Step 3: Validate and Clean Up#

bool match = true;
for (uint32_t i = 0; i < I2S_BUFFER_SIZE; i++) {
  if (write_buf[i] != read_buf[i]) { match = false; break; }
}
DEBUGOUT("Buffer compare: %s\n", match ? "PASS" : "FAIL");

sl_si91x_i2s_deinit(I2S_INSTANCE_USED);

Tips:

  • Keep buffers small in blocking mode to avoid audible glitches.

  • Verify DIN/DOUT activity and BCLK/WS alignment with a logic analyzer.

I²S Secondary (Target) — Blocking Sketch#

sl_i2s_config_t sec_cfg = sl_i2s_ulp_config;
sec_cfg.mode = SL_I2S_SLAVE;

sl_status_t st = sl_si91x_i2s_init(I2S_INSTANCE_USED, &sec_cfg);
st = sl_si91x_i2s_receive_data_blocking(I2S_INSTANCE_USED, read_buf, I2S_BUFFER_SIZE);
st = sl_si91x_i2s_transmit_data_blocking(I2S_INSTANCE_USED, write_buf, I2S_BUFFER_SIZE);
sl_si91x_i2s_deinit(I2S_INSTANCE_USED);

Typical Use Case 2: Non-Blocking (DMA)#

Use non-blocking DMA when:

  • Continuous, high-quality audio streaming is required

  • The CPU must perform other tasks concurrently

  • Latency and robustness are important

Workflow#

  1. Configure DMA channels (TX and RX).

  2. Register a callback for transfer completion and error handling.

  3. Use non-blocking I²S transmit and receive APIs.

  4. Handle events in the callback, such as:

    • Buffer completion

    • Underrun or overrun conditions

    • Error status notifications

Example — UDMA-Based I²S Transfer#

#include "sl_si91x_i2s.h"
#include "sl_i2s_instances.h"
#include "rsi_debug.h"

#define I2S_INSTANCE_USED  SL_I2S_ULP
#define I2S_BUFFER_SIZE    1024

static uint32_t dma_tx_buf[I2S_BUFFER_SIZE];
static uint32_t dma_rx_buf[I2S_BUFFER_SIZE];
static sl_i2s_dma_config_t dma_cfg;
static sl_i2s_config_t i2s_cfg;

static void i2s_cb(sl_i2s_callback_t event, void *arg) {
  switch (event) {
    case SL_I2S_SEND_COMPLETE:    DEBUGOUT("DMA transmit complete\n"); break;
    case SL_I2S_RECEIVE_COMPLETE: DEBUGOUT("DMA receive complete\n");  break;
    case SL_I2S_TX_UNDERFLOW:     DEBUGOUT("I²S DOUT underrun\n");     break;
    case SL_I2S_RX_OVERFLOW:      DEBUGOUT("I²S DIN overflow\n");      break;
    case SL_I2S_FRAME_ERROR:      DEBUGOUT("I²S frame error\n");       break;
    default: break;
  }
}

// DMA channels
dma_cfg.dma_tx_channel = SL_ULP_I2S_DMA_TX_CHANNEL;
dma_cfg.dma_rx_channel = SL_ULP_I2S_DMA_RX_CHANNEL;

i2s_cfg = sl_i2s_ulp_config;
i2s_cfg.callback_event = i2s_cb;

sl_status_t st = sl_si91x_i2s_init(I2S_INSTANCE_USED, &i2s_cfg);

// Prepare DOUT buffer
for (uint32_t i = 0; i < I2S_BUFFER_SIZE; i++) {
  dma_tx_buf[i] = (i + 1u) << 16 | (i + 1u);
}

// Start non-blocking transfers
st = sl_si91x_i2s_transmit_data_non_blocking(I2S_INSTANCE_USED, dma_tx_buf, I2S_BUFFER_SIZE, &dma_cfg);
st = sl_si91x_i2s_receive_data_non_blocking(I2S_INSTANCE_USED, dma_rx_buf, I2S_BUFFER_SIZE, &dma_cfg);

// ... app continues work; callback signals completion ...

Tips

  • Size buffers to avoid underrun/overrun.

  • Adjust DMA and FIFO thresholds.

  • Prioritize I²S/DMA interrupts appropriately in your RTOS.

Secondary (Target) — Non-Blocking Sketch

sl_i2s_config_t sec_cfg = sl_i2s_ulp_config;
sec_cfg.mode = SL_I2S_SLAVE;
sec_cfg.callback_event = i2s_cb;

sl_status_t st = sl_si91x_i2s_init(I2S_INSTANCE_USED, &sec_cfg);
st = sl_si91x_i2s_receive_data_non_blocking(I2S_INSTANCE_USED, dma_rx_buf, I2S_BUFFER_SIZE, &dma_cfg);
st = sl_si91x_i2s_transmit_data_non_blocking(I2S_INSTANCE_USED, dma_tx_buf, I2S_BUFFER_SIZE, &dma_cfg);

Typical Use Case 3: Ultra-Low-Power (ULP) I²S Operation#

Use ULP I²S for always-on or battery-powered audio applications where the system spends significant time in the PS2 low-power state.

Workflow#

  1. Configure the power manager.

    • Enable PS2 low-power mode.

    • Ensure required RAM retention settings are active.

  2. Enable ULP I²S and ULP GPIO pins in your project configuration.

  3. Initialize I²S and verify audio streaming while in an active state (PS4 or PS3).

  4. Transition to PS2:

    • Keep ULP I²S running during the low-power state.

    • Ensure peripheral clocks and GPIO routing match ULP requirements.

  5. Handle wake-up operations.

    • Reinitialize any necessary modules.

    • Resume audio transfers seamlessly based on application needs.

ULP sketch

// Prepare for PS2
sl_si91x_i2s_deinit(I2S_INSTANCE_USED);
sl_si91x_power_manager_add_ps_requirement(SL_SI91X_POWER_MANAGER_PS2);
// ... enter PS2 ...

// After wake
sl_si91x_i2s_init(I2S_INSTANCE_USED, &i2s_cfg);
sl_si91x_i2s_configure_power_mode(I2S_INSTANCE_USED, SL_I2S_LOW_POWER_MODE);
// Resume streaming on DIN/DOUT

Tips

  • Place DMA buffers in ULP memory for retention.

  • Use ULP GPIO for I²S pins to keep BCLK, WS, DIN, and DOUT alive in PS2.

  • Re-register callbacks and restore thresholds after wake.

Typical Application Areas#

  • Battery-powered audio end nodes

  • Always-on voice or key-phrase detection

  • Wearables and IoT audio sensors with duty cycling

  • Long-life, low-maintenance deployments

Related Example Projects#

  • SL Si91x – I²S Primary

  • SL Si91x – I²S Secondary

  • SL Si91x – I²S Loopback

  • SL Si91x – ULP I²S Primary

See WiSeConnect SDK I²S Peripheral Examples:
https://docs.silabs.com/wiseconnect/latest/wiseconnect-examples/#peripheral-examples