The I2S (Inter-IC Sound) driver provides a complete interface for audio and serial data streaming using industry-standard I2S protocol and common variants. The driver supports multiple hardware instances, flexible data formats, and both master and slave operation modes.
The I2S interface can operate in several data format modes including standard I2S (MSB-first with WS transition before data), Left/Right Justified formats, and TDM (Time Division Multiplexing) for multi-channel applications.
Features:
- Standard I2S, Left Justified, and Right Justified data formats
- TDM (Time Division Multiplexing) for multi-channel operation
- Master and Slave clock modes with independent bit and frame clock control
- Configurable word sizes: 8, 16, 24, and 32 bits
- Multi-channel support (mono, stereo, and multi-channel TDM)
- Automatic DMA integration for efficient asynchronous data transfer
- Asynchronous, callback-driven operation
- Event-driven callback system for TX complete, RX complete, and error events
- Configurable clock polarity and phase
- Note
- The I2S driver uses a callback-based DMA architecture:
- All data transfers are performed asynchronously using DMA
- Buffers are queued using i2s_write_async and i2s_read_async
- App-managed transfers use caller buffers directly, while driver-managed TX may copy or replicate data into an internal pool before submission
- Buffer ownership passes to the driver until completion
- Completion is signaled via registered callbacks
- This design minimizes CPU overhead and enables real-time streaming with deterministic latency
- Attention
- DMA Buffer Requirements:
- Buffers must be allocated in a DMA-accessible memory region (typically SRAM; not flash, not tightly-coupled cache-only RAM, not memory-mapped peripherals).
- Buffers must be naturally aligned for the target DMA hardware (commonly 4, 8, or 16 bytes; see hardware manual for details).
- Buffers must not be allocated on the stack. Use static, global, or heap-allocated memory.
- If the system uses data/instruction caches, the caller is responsible for:
- Cleaning (write-back) the cache for TX buffers before queuing (if not uncached memory).
- Invalidating the cache for RX buffers after completion (if not uncached memory). The driver does not perform cache maintenance unless explicitly documented.
- Buffers must remain valid and DMA-safe for the entire duration of the transfer.
- Failure to meet these requirements may result in data corruption, missed transfers, or hard faults.
- Warning
- The I2S hardware operates as a continuous streaming interface. Changing configuration while the interface is running may result in data corruption, audio glitches, or synchronization loss. Software should always stop the interface using i2s_trigger with I2S_TRIGGER_STOP and verify the state is READY before reconfiguring. When operating in slave mode, ensure the external clock source is stable before starting the interface.
Configuration Considerations
To initialize and operate the I2S driver:
- Call i2s_init to initialize the I2S instance hardware.
- DMA channel assignment is hardware-dependent and not part of the portable API. The driver will auto-allocate channels as needed, or consult platform-specific documentation for static assignment if required.
- Call i2s_configure to configure parameters for TX or RX direction (DMA auto-configured).
- Register buffer callback using i2s_register_buffer_callback to receive completed buffers for processing or reuse.
- Optionally register event callback using i2s_register_callback for error notification and state change events.
- Start the interface using i2s_trigger with I2S_TRIGGER_START command.
- Queue buffers for transfer using i2s_write_async and i2s_read_async. Buffers are returned via callbacks when transfer completes.
- Monitor state using i2s_get_state.
Stop the interface using i2s_trigger with I2S_TRIGGER_STOP before reconfiguration or shutdown.
- Call i2s_deinit to release all resources when done.
- Note
- Clocking Model (Hardware Considerations):
- BCLK (bit clock) and LRCLK (frame clock) are calculated internally by the driver based on sample_rate, word_size, and channels.
- MCLK (master clock) support is controlled via mclk_present, mclk_required, and mclk_ratio in the config struct. See MCLK note above.
- If clock_mode = MASTER:
- The driver attempts to derive BCLK and LRCLK from the main clock source using integer divisors (unless hardware supports fractional divisors or PLL).
- Most hardware supports only integer divisors; if the requested sample rate cannot be generated exactly, the driver will use the closest supported rate or return an error (see below).
- If the hardware supports fractional divisors or a fractional PLL, this is not currently configurable via the API; the driver will use hardware defaults or best match.
- MCLK is generated at mclk_ratio × sample_rate if supported; not all hardware supports all ratios (e.g., 256fs, 384fs, 512fs).
- If clock_mode = SLAVE:
- The driver expects external BCLK/LRCLK (and optionally MCLK) to be provided.
- Acceptable external clock frequency ranges and tolerances are hardware-dependent; consult the hardware manual for min/max rates and jitter tolerance.
- The driver does not validate external clock quality; it is the application's responsibility to ensure clocks are within spec.
- If the requested sample rate cannot be generated exactly (in master mode):
- The driver will select the closest supported rate if possible, or return I2S_ERROR_NOT_SUPPORTED.
- Applications should check the actual rate set via i2s_get_config after configuration.
- bit_clk_gated:
- When true, BCLK is only active during data transfer; when false, BCLK is continuous.
- Some codecs and I2S devices require continuous BCLK/LRCLK (bit_clk_gated = false) for proper operation; enabling gating may violate I2S spec for such devices.
- Only enable bit_clk_gated if you are certain your external device supports it (typically for power savings in embedded use).
- Advanced clocking features (manual divisor selection, fractional PLL, external clock muxing) are not exposed in this API. For special requirements, contact the driver maintainer.
- Always verify actual clock signals with an oscilloscope or logic analyzer when integrating with new hardware.
- Warning
- The I2S hardware operates as a continuous streaming interface. Changing configuration while the interface is running may result in data corruption, audio glitches, or synchronization loss. Software should always stop the interface using i2s_trigger with I2S_TRIGGER_STOP and verify the state is READY before reconfiguring. When operating in slave mode, ensure the external clock source is stable before starting the interface.
Callback Execution Context and Concurrency Rules
- Note
- Callback Execution Context:
- All I2S buffer and event callbacks (i2s_buffer_callback_t, i2s_event_callback_t) are invoked from interrupt context (ISR) unless otherwise noted for a specific platform.
- Callbacks are executed at the priority of the I2S peripheral's DMA or data-ready interrupt.
- Callbacks are not invoked from a dedicated driver thread or deferred work queue unless explicitly documented for a given port.
Reentrancy and API Calls from Callbacks:
- It is safe to call i2s_write_async and i2s_read_async from within a buffer callback to re-queue buffers (see usage examples).
- All other API functions (configuration, trigger, deinit, etc.) must NOT be called from within a callback.
- Callbacks must execute quickly and must not block, allocate memory, or perform lengthy processing.
- If additional processing is required, defer work to a lower-priority thread or task.
Locking and Concurrency:
- The driver guarantees that callbacks for a given instance/direction (TX or RX) are never invoked concurrently (no reentrancy for the same direction).
- However, TX and RX callbacks for the same instance may be invoked independently and may nest if both TX and RX interrupts occur simultaneously.
- Applications must ensure that any shared data accessed from both TX and RX callbacks is protected (e.g., with atomic operations or critical sections).
- Callbacks for different I2S instances may be invoked concurrently if multiple I2S peripherals generate interrupts at the same time.
Stack Usage and Priorities:
- Callbacks run on the interrupt stack of the I2S peripheral's DMA or data-ready interrupt.
- Stack size is hardware- and RTOS-dependent; callbacks must use minimal stack and avoid recursion or large local variables.
- The priority of the callback is determined by the I2S/DMA interrupt priority; avoid lengthy processing to prevent interrupt latency issues.
Summary Table:
| Context | Callback Type | API Calls Allowed | Concurrency/Nesting |
| ISR (default) | buffer/event | i2s_write/read_async | TX/RX may nest, not reentrant |
| Thread (rare) | (if ported) | See port documentation | Platform-specific |
For advanced use cases or porting to an RTOS with thread-based driver model, consult the platform-specific documentation or contact the driver maintainer.
For continuous streaming applications:
Multi-buffer Queue:
- Pre-queue multiple buffers (typically 2-4) using i2s_read_async
- As each buffer completes, the callback receives it for processing
- Application re-queues the buffer to maintain continuous reception
- This approach prevents gaps and RX overruns during callback processing
Audio Format Timing and Framing (Critical)
The I2S driver supports multiple serial audio formats (I2S, Left/Right Justified, TDM), but not all timing/framing parameters are currently configurable. The following details are critical for correct interoperability and must be verified against your hardware:
Standard I2S Format:
- Frame sync (WS/LRCLK) is low for left channel, high for right channel.
- WS transitions one bit clock before MSB of left channel.
- Data is valid on the rising edge of bit clock (default; see bit_clk_inv).
- For mono (channels=1): Most hardware still outputs 2 slots per frame; right slot is zeroed/muted. True mono (single slot per frame) is not supported unless hardware provides a mode for this.
Left Justified Format:
- WS transitions with MSB of left channel; data is left-aligned to WS edge.
- Otherwise, timing is as above.
Right Justified Format:
- WS transitions just before LSB of right channel; data is right-aligned to WS edge.
- Otherwise, timing is as above.
TDM (Time Division Multiplexing):
- Frame sync (FS) is typically a single bit clock wide (pulse) at start of frame, but some hardware uses 50% duty.
- Number of slots per frame is fixed by hardware or not currently configurable via this API.
- Only 'channels' field is configurable; it sets the number of active channels, not total slots.
- Slot width is assumed equal to word size; 24-bit samples in 32-bit slots is not directly supported unless hardware pads automatically.
- No support for slot mask, slot offset, or frame alignment in current config struct.
Slot Width vs Word Size:
- The config struct only allows setting word_size; slot width is assumed to match word size.
- If your hardware requires 24-bit samples in 32-bit slots, ensure your hardware pads automatically, or use 32-bit word size and pack data accordingly.
Bit Clock Edge (bit_clk_inv):
- By default, data is sampled on the rising edge of bit clock.
- Setting bit_clk_inv inverts the clock polarity, but does not change launch/sample edge semantics.
- If your hardware requires data to be launched/sampled on a specific edge, verify the actual behavior in the hardware manual.
Frame Sync Polarity and Pulse Width:
- Not currently configurable; assumed to match standard for each format (see above).
- For TDM, frame sync is assumed to be a single bit clock pulse at start of frame.
TDM Slot Mask, Offset, and Alignment:
- Not currently supported in config struct; all slots up to 'channels' are active, others are zeroed/muted.
- If your use case requires custom slot mapping, contact the driver maintainer.
Limitations and Recommendations:
- The current API does not expose all timing/framing options required for advanced TDM or non-standard I2S modes.
- Always verify the actual wire protocol with a logic analyzer when integrating with external codecs or DSPs.
- If you require additional configurability (slot width, slot mask, frame sync width, etc.), file a feature request.
Both directions operate independently with separate queues
- Configure TX and RX separately using I2S_DIR_TX and I2S_DIR_RX
- Queue TX buffers: i2s_write_async(tx_buf0), i2s_write_async(tx_buf1)
- Queue RX buffers: i2s_read_async(rx_buf0), i2s_read_async(rx_buf1)
- Start both: i2s_trigger(I2S_DIR_TX, I2S_TRIGGER_START); i2s_trigger(I2S_DIR_RX, I2S_TRIGGER_START)
- Callbacks deliver completed buffers for each direction independently
Contiguous Buffer Strategy:
- Application manages a large ring buffer (e.g., 16KB)
- Queue fixed-size chunks: i2s_read_async(&ringbuf[0], 2KB)
- On callback, queue next chunk: i2s_read_async(&ringbuf[2KB], 2KB)
- Application tracks read/write positions within the ring buffer
- More memory efficient but requires careful pointer management
- Note
- Multi-buffer approach is recommended for simplicity and safety
- Minimum 2 buffers required to prevent underrun/overrun
- Buffer size should be: (sample_rate × channels × word_size × duration) / 8
- Example: 48kHz, stereo, 16-bit, 10ms = 48000 × 2 × 2 × 0.01 = 1920 bytes
Usage Examples
Application-Managed Multi-buffer RX (Recommended)
#define NUM_RX_BUFFERS 3
#define BUFFER_SIZE 2048
uint8_t rx_buffers[NUM_RX_BUFFERS][BUFFER_SIZE];
void *buffer, size_t size, void *user_data)
{
process_audio(buffer, size);
}
}
void start_continuous_capture(void)
{
.channels = 2,
.sample_rate = 48000,
.clock_config = {
.bit_clk_gated = false,
.bit_clk_inv = false,
.frame_clk_inv = false
},
.mclk_present = true,
.mclk_required = true,
.mclk_ratio = 384,
.block_size = BUFFER_SIZE,
.num_blocks = 0,
.enable_dma = true
};
for (int i = 0; i < NUM_RX_BUFFERS; i++) {
}
}
i2s_instance_en
I2S instances for CM52.
Definition sl2610_cm52.h:523
i2s_direction_en
I2S transfer direction.
Definition i2s.h:527
@ I2S_TRIGGER_START
Definition i2s.h:555
@ I2S_WORD_SIZE_16BIT
Definition i2s.h:584
@ I2S_FMT_STANDARD_I2S
Definition i2s.h:490
@ I2S_MODE_MASTER
Definition i2s.h:516
@ I2S_DIR_RX
Definition i2s.h:529
i2s_status_en i2s_trigger(i2s_instance_en instance, i2s_direction_en direction, i2s_trigger_cmd_en cmd)
i2s_status_en i2s_configure(i2s_instance_en id, i2s_direction_en direction, const i2s_config_t *cfg)
Configure I2S (clock + bit clock + mode + format).
i2s_status_en i2s_register_buffer_callback(i2s_instance_en instance, i2s_buffer_cb_t callback, void *user_data)
i2s_status_en i2s_read_async(i2s_instance_en instance, void *buffer, size_t size)
INIT_CODE i2s_status_en i2s_init(i2s_instance_en instance)
I2S configuration structure.
Definition i2s.h:677
Contiguous Ring Buffer (Memory Efficient)
#define RING_BUFFER_SIZE 16384
#define CHUNK_SIZE 2048
uint8_t ring_buffer[RING_BUFFER_SIZE];
uint32_t write_offset = 0;
void *buffer, size_t size, void *user_data)
{
process_audio(buffer, size);
write_offset = (write_offset + CHUNK_SIZE) % RING_BUFFER_SIZE;
}
void setup_ring_buffer_rx(void)
{
.channels = 2,
.sample_rate = 48000,
.clock_config = { .bit_clk_gated = false, .bit_clk_inv = false, .frame_clk_inv = false },
.mclk_present = true,
.mclk_required = true,
.mclk_ratio = 384,
.block_size = CHUNK_SIZE,
.num_blocks = 0,
.enable_dma = true
};
for (uint32_t i = 0; i < 3; i++) {
i2s_read_async(I2S_INSTANCE_0, &ring_buffer[i * CHUNK_SIZE], CHUNK_SIZE);
}
write_offset = 3 * CHUNK_SIZE;
}
Full Duplex TX/RX
#define BUFFER_SIZE 2048
uint8_t tx_buf[3][BUFFER_SIZE];
uint8_t rx_buf[3][BUFFER_SIZE];
void *buffer, size_t size, void *user_data)
{
prepare_tx_data(buffer, size);
process_audio(buffer, size);
}
}
void setup_full_duplex(void)
{
.channels = 2,
.sample_rate = 48000,
.clock_config = { .bit_clk_gated = false, .bit_clk_inv = false, .frame_clk_inv = false },
.mclk_present = true,
.mclk_required = true,
.mclk_ratio = 256,
.block_size = BUFFER_SIZE,
.num_blocks = 0,
.enable_dma = true
};
for (int i = 0; i < 3; i++) {
prepare_tx_data(tx_buf[i], BUFFER_SIZE);
}
}
@ I2S_DIR_TX
Definition i2s.h:528
i2s_status_en i2s_write_async(i2s_instance_en instance, const void *buffer, size_t size)