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");
}
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");
}
}
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 -> ----------------------------------
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)
----------------------------------