I am currently developing a stereo I²S audio capture system using the MAX32666 microcontroller and two DMM-4026-B-I2S-EB-R MEMS microphones. The setup uses a shared data line (I2S_DATA_IN) with common BCLK and LRCLK, configured for a 64-bit frame (2 × 32-bit slots) at 48 kHz LRCLK and 3.072 MHz BCLK. While valid audio samples are successfully captured from one microphone channel(CH0 - Left Channel), the second channel (CH1 - Right Channel) continues to output invalid or zero data despite enabling both RX channels (CH0 and CH1) and verifying hardware connections. Several configuration attempts—including interleaved (TDM) mode, slot mapping adjustments, and clock tuning—have not resolved the issue.
I would greatly appreciate it if you could review the source code included below and confirm whether any register configurations or initialization steps may be missing or incorrect. Additionally, please share any available example code or application notes demonstrating stereo I²S read/write operations on the MAX32666. If there are specific considerations or internal settings for dual-microphone operation over a shared data line, your recommended approach would be highly valuable. As this functionality is critical to our ongoing audio development, I kindly request your feedback and solution at the earliest convenience.
/**
* @file main.c
* @brief I2S Stereo Microphone Capture + UART 16-bit PCM Sender @115200 baud
*
* This program captures 24-bit stereo audio samples from two I²S digital MEMS microphones
* connected to the MAX32666 (or similar MAXIM MCU). It then converts the 24-bit samples
* into 16-bit signed PCM format, downsamples them to ~8 kHz (for simplicity and bandwidth) || right now no downsampling is not applied,
* and transmits interleaved stereo samples over UART for logging or audio recording on PC.
*
* ----------------
* Audio Path:
* I²S Mics (L & R) → I²S Peripheral → Interrupt Handler → PCM Buffers
* → Interleaved Stereo PCM → UART → PC
*
* ----------------
* Author: Surajkumar Sharma
* Date: Nov 2025
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "mxc_device.h"
#include "mxc_delay.h"
#include "mxc_pins.h"
#include "board.h"
#include "gpio.h"
#include "drv_uart.h"
#include "nvic_table.h"
#include "icc.h"
#include "gcr_regs.h"
#include "audio_regs.h"
/* -------------------------------------------------------------------------
* CONFIGURATION PARAMETERS
* ------------------------------------------------------------------------- */
// Sampling and bit rate configuration
#define SAMPLE_RATE 48000U // Original sampling rate of I²S microphones (48 kHz)
#define NUM_SLOTS 2 // Stereo → 2 channels
#define BITS_PER_SLOT 32 // Each I²S slot = 32 bits
#define AUDIO_CLOCK_IN 96000000U // 96 MHz input clock to audio peripheral
#define AUDIO_CLOCK_OUT (NUM_SLOTS * BITS_PER_SLOT * SAMPLE_RATE)
// GPIO pin definitions for I²S interface
#define BCLK_PIN MXC_GPIO_PIN_27
#define DIN_PIN MXC_GPIO_PIN_26
#define LRCLK_PIN MXC_GPIO_PIN_24
// I²S pin configuration (alternate function mapping)
const mxc_gpio_cfg_t gpio_cfg_i2s = {
MXC_GPIO0,
BCLK_PIN | DIN_PIN | LRCLK_PIN,
MXC_GPIO_FUNC_ALT1,
MXC_GPIO_PAD_PULL_UP
};
/* -------------------------------------------------------------------------
* AUDIO BUFFER DEFINITIONS
* ------------------------------------------------------------------------- */
#define AUDIO_BUFFER_SIZE (8000 * 10) // Samples per channel (≈10 seconds of audio)
#define SAMPLE_SKIP 1 // Downsampling factor → 48kHz / 6 = 8kHz effective
// Buffers for Left and Right channels
static volatile int16_t audio_buffer_L[AUDIO_BUFFER_SIZE];
static volatile int16_t audio_buffer_R[AUDIO_BUFFER_SIZE];
// State variables
static volatile uint32_t sample_index = 0; // Current index in buffer
static volatile uint8_t skip_count = 0; // Downsampling counter
static volatile uint8_t buffer_full = 0; // Flag when buffer is filled
/* -------------------------------------------------------------------------
* HELPER FUNCTIONS
* ------------------------------------------------------------------------- */
/**
* @brief Compute greatest common divisor (used for clock ratio setup)
*/
static uint32_t gcd(uint32_t a, uint32_t b)
{
return b ? gcd(b, a % b) : a;
}
/* -------------------------------------------------------------------------
* I2S AUDIO INTERRUPT HANDLER
* ------------------------------------------------------------------------- */
/**
* @brief Handles incoming I²S samples from both stereo channels.
*
* The handler performs the following steps:
* 1. Reads raw 24-bit PCM samples from left and right channels.
* 2. Extracts and sign-extends 24-bit samples to 32-bit integers.
* 3. Converts 24-bit samples to 16-bit for UART transmission.
* 4. Downsamples data by a factor of SAMPLE_SKIP.
* 5. Stores samples into audio buffers until full.
*/
void AUDIO_IRQHandler(void)
{
// Step 1: Read raw 24-bit PCM samples (packed in 32-bit words)
uint32_t raw_L = MXC_AUDIO->rx_pcm_ch0_addr;
uint32_t raw_R = MXC_AUDIO->rx_pcm_ch1_addr;
// Step 2: Extract 24-bit MSB-aligned samples
int32_t s24_L = (int32_t)((raw_L >> 8) & 0x00FFFFFFU);
int32_t s24_R = (int32_t)((raw_R >> 8) & 0x00FFFFFFU);
// Step 3: Sign-extend 24-bit to 32-bit integer
if (s24_L & 0x00800000U) s24_L |= 0xFF000000U;
if (s24_R & 0x00800000U) s24_R |= 0xFF000000U;
// Step 4: Convert to 16-bit signed PCM by discarding LSBs
int16_t s16_L = (int16_t)(s24_L >> 8);
int16_t s16_R = (int16_t)(s24_R >> 8);
// Step 5: Downsample to reduce UART bandwidth || right now SAMPLE_SKIP = 1 (no downsampling)
if (++skip_count >= SAMPLE_SKIP)
{
skip_count = 0;
if (!buffer_full)
{
// Store samples into buffers
audio_buffer_L[sample_index] = s16_L;
audio_buffer_R[sample_index] = s16_R;
sample_index++;
// If buffer filled, mark full
if (sample_index >= AUDIO_BUFFER_SIZE)
{
buffer_full = 1;
sample_index = 0;
}
}
}
// Clear interrupt flags
MXC_AUDIO->int_pcm_rx_clr = 0xFFFFFFFF;
}
/* -------------------------------------------------------------------------
* I2S MICROPHONE INITIALIZATION
* ------------------------------------------------------------------------- */
/**
* @brief Initializes the I²S hardware for stereo microphone capture.
*
* Steps:
* 1. Enable required peripheral clocks.
* 2. Configure GPIO pins for I²S signals.
* 3. Set up clock ratio (M/N) for target audio rate.
* 4. Configure PCM format and slot mapping.
* 5. Enable RX channels and global audio engine.
* 6. Enable interrupt for RX data availability.
*/
void i2s_mic_init(void)
{
uint16_t m, n;
MXC_ICC_Enable(); // Enable instruction cache
MXC_GCR->perckcn0 &= ~MXC_F_GCR_PERCKCN0_GPIO0D; // Enable GPIO0 clock
MXC_GCR->perckcn1 &= ~MXC_F_GCR_PERCKCN1_AUDIOD; // Enable Audio clock
MXC_GPIO_Config(&gpio_cfg_i2s); // Configure I²S pins
// Compute M/N divider for audio clock generation
m = AUDIO_CLOCK_IN / gcd(AUDIO_CLOCK_IN, AUDIO_CLOCK_OUT);
n = (AUDIO_CLOCK_OUT * (uint64_t)m) / AUDIO_CLOCK_IN;
MXC_AUDIO->m_val = m;
MXC_AUDIO->n_val = n;
// Configure I²S/PCM clock and format
MXC_AUDIO->modulator_controls = MXC_AUDIO_MOD_CNTL_PCM_SEL; // PCM mode
MXC_AUDIO->pcm_clock_set_up = MXC_AUDIO_CLK_CFG_BSEL_64; // 64-bit frame (2x32)
MXC_AUDIO->pcm_config = MXC_AUDIO_PCM_CFG_CHANSZ_32 | MXC_AUDIO_PCM_CFG_FRMT_I2S | MXC_AUDIO_PCM_CFG_CHANSEL; // 32-bit, I²S format
// Sample rate configuration
MXC_AUDIO->pcm_rx_sample_rates = MXC_AUDIO_PCM_RX_SR_DATAPORT_48000 |
MXC_AUDIO_PCM_RX_SR_INTERFACE_48000;
// Enable RX channels (Left + Right)
MXC_AUDIO->pcm_rx_enables_byte_0 = MXC_AUDIO_PCM_RX_EN_0_CH0 | MXC_AUDIO_PCM_RX_EN_0_CH1;
// Select master clocking
MXC_AUDIO->pcm_clock_dividers_msb = MXC_AUDIO_CLK_DIV_MSB_BLCK_MASTER_SEL |
MXC_AUDIO_CLK_DIV_MSB_BLCK_SEL_MASTER;
// Map data ports to PCM slots
MXC_AUDIO->dataport_2_slot_mapping_channel_0 = MXC_AUDIO_PCM_DP2_SLOT_MAP_CH0_EN;
MXC_AUDIO->dataport_2_slot_mapping_channel_1 = MXC_AUDIO_PCM_DP2_SLOT_MAP_CH1_EN;
// Enable global PCM and interrupt
MXC_AUDIO->global_enable = MXC_AUDIO_PCM_GLOBAL_EN;
MXC_AUDIO->int_en = MXC_AUDIO_PCM_RX_AF_EN;
// Attach ISR and enable NVIC interrupt
MXC_NVIC_SetVector(AUDIO_IRQn, AUDIO_IRQHandler);
NVIC_EnableIRQ(AUDIO_IRQn);
}
/* -------------------------------------------------------------------------
* MAIN LOOP
* ------------------------------------------------------------------------- */
/**
* @brief Main entry point.
*
* Initializes UART and I²S, then continuously captures and sends audio frames.
*
* Flow:
* 1. Initialize UART for transmission at 115200 baud.
* 2. Initialize I²S microphone interface.
* 3. Wait for buffer to fill with samples.
* 4. Once full, interleave stereo data (L0R0, L1R1...) and send over UART.
* 5. Reset buffer and repeat.
*/
int main(void)
{
uart_init(); // Initialize UART driver
i2s_mic_init(); // Initialize I²S interface
MXC_Delay(200000); // Small delay before starting capture
while (1)
{
// When buffer is full → send data
if (buffer_full)
{
// Interleave L & R channels: L0R0 L1R1 L2R2 ...
for (uint32_t i = 0; i < AUDIO_BUFFER_SIZE; i++)
{
int16_t sample_pair[2] = { audio_buffer_L[i], audio_buffer_R[i] };
uart_write((uint8_t *)sample_pair, sizeof(sample_pair));
}
// Reset buffer flag to capture next frame
buffer_full = 0;
}
}
}