Page 1 of 1

I2S Config in Rust with INMP441 mic Help

Posted: Tue Jan 20, 2026 11:52 pm
by fervenceslau
I have been trying to set up a quick demo to grab audio data from INMP441 mic using Rust and esp-hal v1.0, but no matter what I do I cannot get it to work the same way I could with a sample project in Arduino. I even dumped the I2S registers after configuring I2S and noticed there are a lot of changes that I can't seem to set through Rust's API, with the sample rate being the most critical change.

Has anyone solved this without manually setting the registers?

Thanks.

Here is the original Arduino Code:

Code: Select all

#include <driver/i2s.h>
#include "soc/i2s_reg.h"
#include "soc/i2s_struct.h"

#define I2S_WS 4
#define I2S_SD 5
#define I2S_SCK 2
#define I2S_PORT I2S_NUM_0

#define SAMPLE_RATE 16000

void setup() {
  Serial.begin(921600);
  Serial.println("Setup I2S ...");
  Serial.println("===");

  delay(1000);
  i2s_install();
  i2s_setpin();
  i2s_start(I2S_PORT);
  delay(500);

//   dump_all_i2s_registers();
//   while(1);
}

void loop() {
  int32_t sample = 0;
  size_t bytes_read = 0;

  // Modern replacement for i2s_pop_sample
  esp_err_t result = i2s_read(I2S_PORT, &sample, sizeof(sample), &bytes_read, portMAX_DELAY);

  if (result == ESP_OK && bytes_read > 0) {
    int16_t out_sample = (int16_t)(sample >> 14); 

    // Send the 2 raw bytes (Binary) instead of text
    Serial.write((uint8_t*)&out_sample, 2);
  }
}

void i2s_install() {
  const i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,                  // Set to 8000 for your specific project
    .bits_per_sample = I2S_BITS_PER_SAMPLE_24BIT, // INMP441 requires 32-bit slots
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    // Modern communication format syntax
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = 0,
    .dma_buf_count = 8,
    .dma_buf_len = 64,
    .use_apll = false
  };

  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
}

void i2s_setpin() {
  const i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = -1,
    .data_in_num = I2S_SD
  };

  i2s_set_pin(I2S_PORT, &pin_config);
}



void dump_all_i2s_registers() {
    Serial.println("\n--- TRM VERIFIED REGISTER DUMP ---");
    
    // Config Registers
    Serial.printf("I2S_CONF_REG:              0x%08X (0x3FF4F008)\n", *(volatile uint32_t*)(0x3FF4F008));
    Serial.printf("I2S_CONF1_REG:             0x%08X (0x3FF4F0A0)\n", *(volatile uint32_t*)(0x3FF4F0A0));
    Serial.printf("I2S_CONF2_REG:             0x%08X (0x3FF4F0A8)\n", *(volatile uint32_t*)(0x3FF4F0A8));
    Serial.printf("I2S_TIMING_REG:            0x%08X (0x3FF4F01C)\n", *(volatile uint32_t*)(0x3FF4F01C));
    Serial.printf("I2S_FIFO_CONF_REG:         0x%08X (0x3FF4F020)\n", *(volatile uint32_t*)(0x3FF4F020));
    Serial.printf("I2S_CONF_SINGLE_DATA_REG:  0x%08X (0x3FF4F028)\n", *(volatile uint32_t*)(0x3FF4F028));
    Serial.printf("I2S_CONF_CHAN_REG:         0x%08X (0x3FF4F02C)\n", *(volatile uint32_t*)(0x3FF4F02C));
    Serial.printf("I2S_LC_HUNG_CONF_REG:      0x%08X (0x3FF4F074)\n", *(volatile uint32_t*)(0x3FF4F074));
    Serial.printf("I2S_CLKM_CONF_REG:         0x%08X (0x3FF4F0AC)\n", *(volatile uint32_t*)(0x3FF4F0AC));
    Serial.printf("I2S_SAMPLE_RATE_CONF_REG:  0x%08X (0x3FF4F0B0)\n", *(volatile uint32_t*)(0x3FF4F0B0));
    Serial.printf("I2S_PD_CONF_REG:           0x%08X (0x3FF4F0A4)\n", *(volatile uint32_t*)(0x3FF4F0A4));
    Serial.printf("I2S_STATE0_REG:            0x%08X (0x3FF4F0BC)\n", *(volatile uint32_t*)(0x3FF4F0BC));
    
    Serial.printf("I2S_LC_CONF_REG:           0x%08X (0x3FF4F060)\n", *(volatile uint32_t*)(0x3FF4F060));
    Serial.printf("I2S_RXEOF_NUM_REG:         0x%08X (0x3FF4F024)\n", *(volatile uint32_t*)(0x3FF4F024));
    Serial.printf("I2S_IN_LINK_REG:           0x%08X (0x3FF4F034)\n", *(volatile uint32_t*)(0x3FF4F034));
    
    Serial.printf("I2S_INT_ENA_REG:           0x%08X (0x3FF4F014)\n", *(volatile uint32_t*)(0x3FF4F014));
    Serial.println("----------------------------------\n");
}
Here is my attempt to get it to work in Rust:

Code: Select all

#![no_std]
#![no_main]
#![deny(
    clippy::mem_forget,
    reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
    holding buffers for the duration of a data transfer."
)]
#![deny(clippy::large_stack_frames)]

// use defmt::info;
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer, Instant};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::Channel;
// use {esp_backtrace as _, esp_println as _};
use esp_backtrace as _;

use esp_hal::{
    clock::CpuClock,
    dma::{CHUNK_SIZE, DmaRxBuf, DmaTxBuf, DmaError},
    dma_buffers, 
    i2s::master::{Channels, Config, DataFormat, I2s, Polarity, WsWidth},
    time::Rate,
    timer::timg::TimerGroup,
    uart::{Uart, Config as UartConfig},
    Async
};

// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();

#[allow(
    clippy::large_stack_frames,
    reason = "it's not unusual to allocate larger buffers etc. in main"
)]

// Define a Channel that can hold 4 chunks of audio data.
// This acts as a 'shock absorber' for your processing delays.
static AUDIO_BUFFER_SIZE: usize = 4 * CHUNK_SIZE;
static AUDIO_CHANNEL: Channel<CriticalSectionRawMutex, [u8; AUDIO_BUFFER_SIZE], 1> = Channel::new();

static SAMPLE_RATE: u32 = 16_000;

static UART_RATE:   u32 = 921_600;

#[esp_rtos::main]
async fn main(spawner: Spawner) -> ! {

    // Initialize peripherals
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::_160MHz);
    let peripherals = esp_hal::init(config);

    // Start embassy time keeper for async tasks
    let timg0 = TimerGroup::new(peripherals.TIMG0);
    esp_rtos::start(timg0.timer0);
    // defmt::info!("Embassy initialized!");

    // Spawn the processing task (The Consumer)
    // defmt::info!("Spawning processing task...");
   
    // Setup the UART (usually UART0 for the USB port)
    let uart_config = UartConfig::default().with_baudrate(UART_RATE); 
    let uart0 = Uart::new(peripherals.UART0, uart_config)
        .unwrap()
        .into_async();

    // Pass 'uart0' into the task here
    spawner.spawn(processing_task(uart0)).unwrap();

   // create DMA buffers
    let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(AUDIO_BUFFER_SIZE, 0);
    
    // Create I2S peripheral from dma channel
    let dma_channel = peripherals.DMA_I2S0;

    // Standard I2S Configuration for 1.0.0
    let i2s = I2s::new(
        peripherals.I2S0,
        dma_channel,
        Config::new_tdm_philips()
            .with_sample_rate(Rate::from_hz(SAMPLE_RATE))
            .with_data_format(DataFormat::Data32Channel32)
            .with_channels(Channels::LEFT)              // Match FIFO_MOD in I2S_FIFO_CONF_REG
            .with_ws_width(WsWidth::HalfFrame)          // Ensure WS is 50% duty cycle
            .with_ws_polarity(Polarity::ActiveHigh)     // Standard I2S WS behavior,
            .with_msb_shift(true)                       // changes bits 10, 11 of I2S_CONF_REG
    )
    .unwrap()
    .with_mclk(peripherals.GPIO0)
    .into_async();

    // Build I2S RX driver
    let i2s_rx = i2s
        .i2s_rx
        .with_bclk(peripherals.GPIO2)
        .with_ws(peripherals.GPIO4)
        .with_din(peripherals.GPIO5)
        .build(rx_descriptors);

    // 4. Start the Audio Firehose (The Producer)
    // defmt::info!("Spawning producer task...");
    let mut transaction = i2s_rx.read_dma_circular_async(rx_buffer).unwrap();
    let mut temp_buf = [0u8; AUDIO_BUFFER_SIZE];

    dump_all_i2s_registers();

    esp_println::println!("===\n");

    // defmt::info!("Audio Stream Started...");
    loop {
        // High priority: Grab data and move it to the channel immediately
        match transaction.pop(&mut temp_buf).await {
            Ok(_) => {
                // Non-blocking send: if the channel is full, it will wait here 
                // but the DMA hardware still has its own 16k buffer to fill.
                AUDIO_CHANNEL.send(temp_buf).await; 
            }
            Err(e) => {
                // defmt::error!("DMA Error in Producer: {:?}", e);
                // Recovery: Since v1.0.0 lacks a stop() method easily,
                // real apps often use a watchdog reset here.
            }
        }
    }
}

#[embassy_executor::task]
async fn processing_task(mut uart: Uart<'static, Async>) {
// Inside your consumer/processing task
    loop {
        let raw_bytes = AUDIO_CHANNEL.receive().await;       
        uart.write_async(&raw_bytes).await.unwrap();
    }
}

pub fn dump_all_i2s_registers() {
    unsafe {
        esp_println::println!("\n--- RUST TRM VERIFIED REGISTER DUMP ---");

        let regs = [
            ("I2S_CONF_REG", 0x3FF4F008),
            ("I2S_CONF1_REG", 0x3FF4F0A0),
            ("I2S_CONF2_REG", 0x3FF4F0A8),
            ("I2S_TIMING_REG", 0x3FF4F01C),
            ("I2S_FIFO_CONF_REG", 0x3FF4F020),
            ("I2S_CONF_SINGLE_DATA_REG", 0x3FF4F028),
            ("I2S_CONF_CHAN_REG", 0x3FF4F02C),
            ("I2S_LC_HUNG_CONF_REG", 0x3FF4F074),
            ("I2S_CLKM_CONF_REG", 0x3FF4F0AC),
            ("I2S_SAMPLE_RATE_CONF_REG", 0x3FF4F0B0),
            ("I2S_PD_CONF_REG", 0x3FF4F0A4),
            ("I2S_STATE0_REG", 0x3FF4F0BC),
            ("I2S_LC_CONF_REG", 0x3FF4F060),
            ("I2S_RXEOF_NUM_REG", 0x3FF4F024),
            ("I2S_IN_LINK_REG", 0x3FF4F034),
            ("I2S_INT_ENA_REG", 0x3FF4F014),
        ];

        for (name, addr) in regs {
            let val = (addr as *const u32).read_volatile();
            esp_println::println!("{:<25} 0x{:08X} ({:#X})", name, val, addr);
        }

        esp_println::println!("----------------------------------\n");
    }
}
Arduino dump

Code: Select all

13:06:16.236 -> --- TRM VERIFIED REGISTER DUMP ---
13:06:16.236 -> I2S_CONF_REG:              0x00020820 (0x3FF4F008)	
13:06:16.236 -> I2S_CONF1_REG:             0x00000089 (0x3FF4F0A0)
13:06:16.236 -> I2S_CONF2_REG:             0x00000000 (0x3FF4F0A8)
13:06:16.236 -> I2S_TIMING_REG:            0x00000000 (0x3FF4F01C)
13:06:16.236 -> I2S_FIFO_CONF_REG:         0x00131820 (0x3FF4F020)
13:06:16.236 -> I2S_CONF_SINGLE_DATA_REG:  0x00000000 (0x3FF4F028)
13:06:16.236 -> I2S_CONF_CHAN_REG:         0x00000010 (0x3FF4F02C)
13:06:16.236 -> I2S_LC_HUNG_CONF_REG:      0x00000810 (0x3FF4F074)
13:06:16.236 -> I2S_CLKM_CONF_REG:         0x00140127 (0x3FF4F0AC)
13:06:16.236 -> I2S_SAMPLE_RATE_CONF_REG:  0x00610146 (0x3FF4F0B0)
13:06:16.236 -> I2S_PD_CONF_REG:           0x0000000A (0x3FF4F0A4)
13:06:16.236 -> I2S_STATE0_REG:            0x00000001 (0x3FF4F0BC)
13:06:16.236 -> I2S_LC_CONF_REG:           0x00000100 (0x3FF4F060)
13:06:16.236 -> I2S_RXEOF_NUM_REG:         0x00000040 (0x3FF4F024)
13:06:16.236 -> I2S_IN_LINK_REG:           0x000B8CE0 (0x3FF4F034)
13:06:16.236 -> I2S_INT_ENA_REG:           0x00000200 (0x3FF4F014)
13:06:16.236 -> ----------------------------------
Rust dump

Code: Select all

--- RUST TRM VERIFIED REGISTER DUMP ---
I2S_CONF_REG              0x00030F20 (0x3FF4F008)
I2S_CONF1_REG             0x00000089 (0x3FF4F0A0)
I2S_CONF2_REG             0x00000000 (0x3FF4F0A8)
I2S_TIMING_REG            0x00000000 (0x3FF4F01C)
I2S_FIFO_CONF_REG         0x001A5820 (0x3FF4F020)
I2S_CONF_SINGLE_DATA_REG  0x00000000 (0x3FF4F028)
I2S_CONF_CHAN_REG         0x00000000 (0x3FF4F02C)
I2S_LC_HUNG_CONF_REG      0x00000810 (0x3FF4F074)
I2S_CLKM_CONF_REG         0x00140127 (0x3FF4F0AC)
I2S_SAMPLE_RATE_CONF_REG  0x00820104 (0x3FF4F0B0)
I2S_PD_CONF_REG           0x0000000A (0x3FF4F0A4)
I2S_STATE0_REG            0x00000001 (0x3FF4F0BC)
I2S_LC_CONF_REG           0x00000500 (0x3FF4F060)
I2S_RXEOF_NUM_REG         0x00000FFC (0x3FF4F024)
I2S_IN_LINK_REG           0x000BD748 (0x3FF4F034)
I2S_INT_ENA_REG           0x00000000 (0x3FF4F014)
----------------------------------