fix(audio): resolve ES7210 microphone popping sound by correcting OSR and clock division configuration

This commit is contained in:
Adolfo Reyna
2026-06-18 22:38:47 -04:00
parent 9eca3549c3
commit d778391c5c
7 changed files with 212 additions and 77 deletions
+9
View File
@@ -14,3 +14,12 @@ __pycache__/
# Log files # Log files
desktop_client/*.log desktop_client/*.log
# Helper scripts and datasheets
deploy_main_serial.py
dtr_reset.py
hard_reset.py
pcm2wav.py
plot_audio.py
*.pdf
+7
View File
@@ -137,3 +137,10 @@ Here is a summary of the MCP tools exposed by the bridge:
| `write_file` | `path`, `content` | Writes text file to board flash. | | `write_file` | `path`, `content` | Writes text file to board flash. |
| `read_file` | `path` | Reads text file from board flash. | | `read_file` | `path` | Reads text file from board flash. |
| `execute_python` | `code` | Executes arbitrary Python code dynamically. | | `execute_python` | `code` | Executes arbitrary Python code dynamically. |
---
## 5. Hardware & Troubleshooting Findings
For detailed technical findings regarding the board's hardware (e.g. I2S bit-depth configuration, I2C bus lockup recovery, pinout configuration, and ES7210 microphone clock/OSR registers to resolve popping sound issues), refer to [hardware_findings.md](hardware_findings.md).
+39 -34
View File
@@ -2,9 +2,8 @@ import time
import machine import machine
from machine import Pin, I2S from machine import Pin, I2S
import board_config import board_config
import audio_util
def play_raw_pcm(filename, channels=2, rate=16000, bits=16, volume=90): def play_ram_pcm(audio_chunks, channels=2, rate=16000, bits=16, volume=90):
from audio_util import ES8311 from audio_util import ES8311
# 1. Start MCLK PWM using board config parameters # 1. Start MCLK PWM using board config parameters
@@ -38,16 +37,11 @@ def play_raw_pcm(filename, channels=2, rate=16000, bits=16, volume=90):
amp_pin = Pin(board_config.audio_amp_pin, Pin.OUT, value=on_val) amp_pin = Pin(board_config.audio_amp_pin, Pin.OUT, value=on_val)
try: try:
print(f"Streaming raw audio to speaker from '{filename}'...") print(f"Streaming raw audio to speaker from RAM ({len(audio_chunks)} chunks)...")
with open(filename, 'rb') as f: for chunk in audio_chunks:
buf = bytearray(2048) i2s.write(chunk)
while True:
bytes_read = f.readinto(buf)
if bytes_read == 0:
break
i2s.write(buf[:bytes_read])
except Exception as e: except Exception as e:
print("Error during raw playback:", e) print("Error during RAM playback:", e)
finally: finally:
time.sleep_ms(100) # Let buffer play out time.sleep_ms(100) # Let buffer play out
amp_pin.value(off_val) # Disable amp amp_pin.value(off_val) # Disable amp
@@ -58,10 +52,10 @@ def play_raw_pcm(filename, channels=2, rate=16000, bits=16, volume=90):
def main(): def main():
display = board_config.display_instance display = board_config.display_instance
print("=== Dynamic Audio Loopback Utility Script ===") print("=== Dynamic RAM-based Audio Loopback Script ===")
print(f"Board Detected: {board_config.BOARD_TYPE}") print(f"Board Detected: {board_config.BOARD_TYPE}")
# Initialize buttons using polling Pins instead of BoardButtons class (to avoid edge-triggered interrupt storms) # Initialize buttons using polling Pins instead of BoardButtons class
key_pin = Pin(18, Pin.IN, Pin.PULL_UP) key_pin = Pin(18, Pin.IN, Pin.PULL_UP)
boot_pin = Pin(0, Pin.IN, Pin.PULL_UP) boot_pin = Pin(0, Pin.IN, Pin.PULL_UP)
@@ -71,8 +65,6 @@ def main():
return board_config.touch.is_touched() return board_config.touch.is_touched()
return key_pin.value() == 0 return key_pin.value() == 0
filename = "local_audio_test.pcm"
while True: while True:
if display: if display:
display.clear(0) display.clear(0)
@@ -81,13 +73,13 @@ def main():
display.line(10, 20, 390, 20, 1) display.line(10, 20, 390, 20, 1)
display.text("1. Press KEY button to record 10s", 15, 60, 1) display.text("1. Press KEY button to record 10s", 15, 60, 1)
display.text("2. Playback will start automatically", 15, 80, 1) display.text("2. Playback will start automatically", 15, 80, 1)
display.text("Ready...", 15, 120, 1) display.text("Ready (RAM-based)...", 15, 120, 1)
else: else:
display.text("Touch Audio Loopback Test", 10, 10, 1) display.text("Touch Audio Loopback Test", 10, 10, 1)
display.line(10, 20, 310, 20, 1) display.line(10, 20, 310, 20, 1)
display.text("1. Press screen/KEY to record 10s", 10, 50, 1) display.text("1. Press screen/KEY to record 10s", 10, 50, 1)
display.text("2. Playback starts automatically", 10, 70, 1) display.text("2. Playback starts automatically", 10, 70, 1)
display.text("Ready...", 10, 100, 1) display.text("Ready (RAM-based)...", 10, 100, 1)
display.show() display.show()
print("Ready: Press and hold key/screen to record...") print("Ready: Press and hold key/screen to record...")
@@ -129,8 +121,6 @@ def main():
display.text("Speak now!", 10, 80, 1) display.text("Speak now!", 10, 80, 1)
display.show() display.show()
# We record as long as the button is pressed (or up to 10 seconds max)
# 1. Start MCLK PWM using board config parameters # 1. Start MCLK PWM using board config parameters
mclk_pwm = None mclk_pwm = None
if board_config.audio_mclk_pin is not None: if board_config.audio_mclk_pin is not None:
@@ -139,26 +129,28 @@ def main():
mclk_pwm.freq(board_config.audio_mclk_freq) mclk_pwm.freq(board_config.audio_mclk_freq)
mclk_pwm.duty_u16(32768) mclk_pwm.duty_u16(32768)
# 2. Configure I2S RX (Stereo 16kHz) # 2. Configure I2S RX (Stereo 16kHz) - ibuf set to 16000 for safety
i2s_rx = I2S(1, i2s_rx = I2S(1,
sck=Pin(board_config.audio_i2s_sck), sck=Pin(board_config.audio_i2s_sck),
ws=Pin(board_config.audio_i2s_ws), ws=Pin(board_config.audio_i2s_ws),
sd=Pin(board_config.audio_i2s_rx_sd), sd=Pin(board_config.audio_i2s_rx_sd),
mode=I2S.RX, mode=I2S.RX,
ibuf=8000, ibuf=16000,
rate=16000, rate=16000,
bits=16, bits=16,
format=I2S.STEREO) format=I2S.STEREO)
# 3. Wake up and configure the microphone chip (ES7210 vs ES8311) # 3. Wake up and configure the microphone chip (ES7210 vs ES8311)
init_ok = False
if board_config.audio_mic_codec == "ES7210": if board_config.audio_mic_codec == "ES7210":
from audio_util import ES7210 from audio_util import ES7210
mic_adc = ES7210(board_config.i2c_bus) mic_adc = ES7210(board_config.i2c_bus)
mic_adc.init(sample_rate=16000, bit_width=16) init_ok = mic_adc.init(sample_rate=16000, bit_width=16)
else: else:
from audio_util import ES8311 from audio_util import ES8311
mic_adc = ES8311(board_config.i2c_bus) mic_adc = ES8311(board_config.i2c_bus)
if mic_adc.init(sample_rate=16000): if mic_adc.init(sample_rate=16000):
init_ok = True
mic_adc.set_volume(80) mic_adc.set_volume(80)
try: try:
mic_adc._write(0x14, 0x1A) # Enable analog mic input & PGA mic_adc._write(0x14, 0x1A) # Enable analog mic input & PGA
@@ -167,18 +159,31 @@ def main():
except: except:
pass pass
# Record loop if not init_ok:
buffer = bytearray(1024) print("Microphone codec initialization failed! Aborting recording.")
i2s_rx.deinit()
if mclk_pwm:
mclk_pwm.deinit()
if display:
display.clear(0)
display.text("Codec Init Failed!", 15, 80, 1)
display.show()
time.sleep(3)
continue
# Record loop to RAM - using ticks_ms for safe timing
buffer = bytearray(2048)
audio_chunks = []
total_bytes = 0 total_bytes = 0
start_rec_time = time.time() start_rec_time = time.ticks_ms()
max_duration_ms = 10000 # 10 seconds
try: try:
with open(filename, 'wb') as f: while time.ticks_diff(time.ticks_ms(), start_rec_time) < max_duration_ms:
while (time.time() - start_rec_time) < 10: bytes_read = i2s_rx.readinto(buffer)
bytes_read = i2s_rx.readinto(buffer) if bytes_read > 0:
if bytes_read > 0: audio_chunks.append(bytes(buffer[:bytes_read]))
f.write(buffer[:bytes_read]) total_bytes += bytes_read
total_bytes += bytes_read
except Exception as e: except Exception as e:
print("Recording failed:", e) print("Recording failed:", e)
finally: finally:
@@ -186,12 +191,12 @@ def main():
if mclk_pwm: if mclk_pwm:
mclk_pwm.deinit() mclk_pwm.deinit()
print(f"Recorded {total_bytes} bytes to '{filename}'.") print(f"Recorded {total_bytes} bytes in RAM ({len(audio_chunks)} chunks).")
# Wait for release of key/trigger to debounce # Wait for release of key/trigger to debounce
time.sleep_ms(200) time.sleep_ms(200)
# 4. Playback # 4. Playback from RAM
if display: if display:
display.clear(0) display.clear(0)
if board_config.BOARD_TYPE == 'WAVESHARE_RLCD': if board_config.BOARD_TYPE == 'WAVESHARE_RLCD':
@@ -206,7 +211,7 @@ def main():
display.text(f"Bytes: {total_bytes}", 10, 80, 1) display.text(f"Bytes: {total_bytes}", 10, 80, 1)
display.show() display.show()
play_raw_pcm(filename, channels=2, rate=16000, bits=16, volume=95) play_ram_pcm(audio_chunks, channels=2, rate=16000, bits=16, volume=95)
if display: if display:
display.clear(0) display.clear(0)
+32
View File
@@ -0,0 +1,32 @@
# Waveshare ESP32-S3-RLCD-4.2 & ES7210 Microphone Diagnostics
This document outlines the root causes of the audio recording failures and hardware crashes we encountered with the Waveshare ESP32-S3 board and its onboard ES7210 microphone array, along with their solutions.
## 1. I2S Bit-Depth Mismatch (The "Static Noise" Issue)
**Problem:** The audio captured by the board was entirely flat or unrecognizable static noise. The audio signal sent to the Whisper pipeline had extremely low RMS levels (25-120), leading to transcription timeouts.
**Root Cause:** The ES7210 microphone ADC was configured via its internal registers to stream **24-bit** audio (Register `0x11` was set to `0x00`). However, the ESP32's I2S hardware peripheral was configured to receive **16-bit** audio. The ESP32 sliced the 24-bit audio frames into misaligned 16-bit chunks, completely destroying the waveform.
**Solution:** Modified `audio_util.py` to write `0x60` to Register `0x11`. This locks the ES7210 into native 16-bit Standard I2S output, perfectly aligning it with the ESP32's buffer.
## 2. I2C Bus Deadlocks (The "Bootloop / Hang" Issue)
**Problem:** The board would frequently hang during the boot sequence or when attempting to re-initialize the audio components. This occurred primarily after soft-reboots or abrupt script terminations.
**Root Cause:** The ES7210 chip does not gracefully release the I2C SDA (data) line if communication is interrupted midway. When the ESP32 soft-reboots, the SDA line remains held low by the ES7210, which permanently hangs the ESP32's internal I2C driver on the next boot attempt.
**Solution:** Added a manual 9-clock I2C hardware recovery sequence to `board_config.py`. Before the `SoftI2C` interface is initialized, the ESP32 manually toggles the SCL pin 9 times as an output to force the ES7210 to release the SDA line, followed by generating a standard I2C STOP condition.
## 3. Incorrect I2C Pin Assignments (The "ENODEV" Issue)
**Problem:** The audio configuration would occasionally fail with `OSError: [Errno 19] ENODEV`, indicating the I2C bus could not find the microphone at address `0x40`.
**Root Cause:** The dynamic board-detection logic in `board_config.py` was originally configured to scan for I2C devices on pins 15 and 16 (the default for the Hosyond board). The Waveshare RLCD board uses pins 13 and 14 for the audio I2C bus.
**Solution:** Hardcoded the correct I2C pins (SDA=13, SCL=14) for the Waveshare board profile and ensured the I2C scan and initialization processes execute on the correct pins.
## 4. Invalid OSR & Clock Division (The "Popping Sound" Issue)
**Problem:** Even when I2S and I2C connected successfully, the recorded audio consisted only of loud, constant popping and clipping at max/min bounds (amplitude 32768), with no recognizable voice signal.
**Root Cause:** The ES7210 microphone ADC was initialized with an incorrect clock and oversampling configuration:
1. Register `0x07` was written with `0x40` to select an oversampling ratio (OSR) of 64. However, the register allocation for `ADC_OSR` is only 6 bits (`bits 5:0`), meaning `0x40` overflowed and set the OSR value to `0`, causing the internal modulator state machines to malfunction.
2. Register `0x02` was configured as a flat division of 12 (`0x0C`), which failed to route and clock the delta-sigma modulators properly.
**Solution:** We analyzed the official C++ implementation of the ES7210 driver in the ESPHome repository (`es7210.cpp` and `es7210_const.h`). By cross-referencing its clock coefficient lookup table for a 12.288MHz Master Clock and 16kHz sample rate, we retrieved the correct register values. We modified `audio_util.py` to match this C++ clock configuration:
* Set OSR configuration register `0x07` to `0x20` (OSR = 32).
* Set main clock control register `0x02` to `0xC3` (enables clock doubler, sets multiply by 2 via bits 7:6 = `11`, and sets division to 3 via bits 4:0 = `0x03`).
* Configured LRCK divider registers `0x04`/`0x05` to `0x03` and `0x00` (division factor of 768).
* Gated unused clocks by writing `0x34` to Register `0x01` (keeps only active ADC12 channels and master MCLK active).
* Corrected the power sequence by initially clearing all MIC bias and PGA settings (`0xFF` to `0x4B`/`0x4C`) before enabling MIC1 and MIC2.
+88 -37
View File
@@ -13,6 +13,12 @@ class ES7210:
def __init__(self, i2c): def __init__(self, i2c):
self.i2c = i2c self.i2c = i2c
def _write(self, reg, val):
self.i2c.writeto_mem(self.ADDR, reg, bytes([val]))
def _read(self, reg):
return self.i2c.readfrom_mem(self.ADDR, reg, 1)[0]
def init(self, sample_rate=16000, bit_width=16): def init(self, sample_rate=16000, bit_width=16):
"""Initializes the ES7210 registers for dual-microphone recording. """Initializes the ES7210 registers for dual-microphone recording.
@@ -23,45 +29,85 @@ class ES7210:
Returns: Returns:
bool: True if initialization was successful, False otherwise. bool: True if initialization was successful, False otherwise.
""" """
print("Initializing ES7210 Microphone ADC...") print("Initializing ES7210 Microphone ADC (ESPHome sequence)...")
try: try:
# 1. Reset the chip # 1. Software reset
self._write(0x00, 0xFF) # Write all 1s to reset register self._write(0x00, 0xFF)
time.sleep_ms(10) time.sleep_ms(20)
self._write(0x00, 0x00) # Release reset self._write(0x00, 0x32)
time.sleep_ms(20)
self._write(0x01, 0x3F) # Clock off during config
# 2. Power management and system configuration # 2. Timing control
self._write(0x01, 0x00) # Enable analog power, reference voltage self._write(0x09, 0x30)
self._write(0x11, 0x60) # Enable master clock PLL self._write(0x0A, 0x30)
# 3. Configure Clock Dividers # 3. High-pass filter
if sample_rate == 16000: self._write(0x23, 0x2A)
self._write(0x02, 0x0C) # BCLK divider self._write(0x22, 0x0A)
self._write(0x03, 0x10) # LRCK divider self._write(0x20, 0x0A)
else: # 44100 / 48000 defaults self._write(0x21, 0x2A)
self._write(0x02, 0x04)
self._write(0x03, 0x08)
# 4. Input Configuration (Enable Mics 1 and 2, power down Mics 3 and 4) # 4. Mode config: clear bit 0 of Reg 0x08
self._write(0x47, 0x00) # Enable MIC1 / MIC2 analog front-ends val08 = self._read(0x08)
self._write(0x48, 0xFF) # Power down MIC3 / MIC4 path self._write(0x08, val08 & ~0x01)
self._write(0x49, 0x0A) # Power up PGA (Programmable Gain Amplifier) 1 and 2
self._write(0x4A, 0x00) # Power down PGA 3 and 4
# 5. Microphone Gain Settings (+24dB standard) # 5. Configure analog power
# Gain range: 0x00 (0dB) to 0x0F (+45dB) in 3dB steps. 0x08 = +24dB. self._write(0x40, 0xC3)
self._write(0x43, 0x08) # Set MIC1 Gain (+24dB)
self._write(0x44, 0x08) # Set MIC2 Gain (+24dB)
# 6. Set Digital Interface Format (I2S standard format) # 6. Mic bias voltage
# Bit width: 0x00 = 24-bit, 0x01 = 16-bit, 0x02 = 8-bit, 0x03 = 32-bit self._write(0x41, 0x70)
fmt = 0x01 if bit_width == 16 else 0x00 self._write(0x42, 0x70)
self._write(0x13, fmt) # Set serial output interface format
self._write(0x14, 0x18) # Enable frame clock / bit clock output
# 7. Unmute ADCs and enable output # 7. Configure I2S format (16-bit, standard I2S, TDM disabled)
self._write(0x12, 0x00) # Enable ADC digital filters (unmute) self._write(0x11, 0x60)
self._write(0x15, 0x30) # Enable output data pin (SDOUT) active self._write(0x12, 0x00)
# 8. Configure sample rate (16kHz with 12.288MHz MCLK)
# adc_div = 0x03, dll = 0x01, doubler = 0x01, osr = 0x20, lrck_h = 0x03, lrck_l = 0x00
reg02_val = 0x03 | (1 << 6) | (1 << 7) # 0xC3
self._write(0x02, reg02_val)
self._write(0x07, 0x20)
self._write(0x04, 0x03)
self._write(0x05, 0x00)
# 9. Clear select bits for MIC gain registers
for i in range(4):
val_gain = self._read(0x43 + i)
self._write(0x43 + i, val_gain & ~0x10)
# 10. Power down all MIC bias & PGA initially
self._write(0x4B, 0xFF)
self._write(0x4C, 0xFF)
# 11. Configure MIC1 and MIC2 (gain = 30dB -> 0x0A, enable SELMIC)
gain_reg_val = 0x0A
# Enable ADC12 clocks
val01 = self._read(0x01)
self._write(0x01, val01 & ~0x0B)
# Power on MIC1/2 bias, ADC, PGA
self._write(0x4B, 0x00)
# Select MIC1 and gain
val43 = self._read(0x43)
self._write(0x43, (val43 & ~0x0F) | 0x10 | gain_reg_val)
# Select MIC2 and gain
val44 = self._read(0x44)
self._write(0x44, (val44 & ~0x0F) | 0x10 | gain_reg_val)
# 12. Power on mics low power registers
self._write(0x47, 0x08)
self._write(0x48, 0x08)
self._write(0x49, 0x08)
self._write(0x4A, 0x08)
# 13. Power down DLL
self._write(0x06, 0x04)
# 14. Enable device state machine
self._write(0x00, 0x71)
time.sleep_ms(20)
self._write(0x00, 0x41)
time.sleep_ms(100)
print("ES7210 initialization complete.") print("ES7210 initialization complete.")
return True return True
@@ -69,9 +115,6 @@ class ES7210:
print(f"Failed to initialize ES7210: {e}") print(f"Failed to initialize ES7210: {e}")
return False return False
def _write(self, reg, val):
self.i2c.writeto_mem(self.ADDR, reg, bytes([val]))
def record_audio(duration_seconds=10, filename='recording.pcm'): def record_audio(duration_seconds=10, filename='recording.pcm'):
"""Records raw stereo PCM data from the dual microphones to a file. """Records raw stereo PCM data from the dual microphones to a file.
@@ -120,6 +163,7 @@ def record_audio(duration_seconds=10, filename='recording.pcm'):
# Create reading buffer (reads 100ms chunks: 16000 samples/sec * 2 channels * 2 bytes/sample * 0.1s = 6400 bytes) # Create reading buffer (reads 100ms chunks: 16000 samples/sec * 2 channels * 2 bytes/sample * 0.1s = 6400 bytes)
buffer = bytearray(6400) buffer = bytearray(6400)
mono_buf = bytearray(3200) # Half size for mono extraction
start_time = time.time() start_time = time.time()
total_bytes = 0 total_bytes = 0
@@ -130,8 +174,15 @@ def record_audio(duration_seconds=10, filename='recording.pcm'):
# Read raw stereo PCM data from I2S # Read raw stereo PCM data from I2S
bytes_read = i2s.readinto(buffer) bytes_read = i2s.readinto(buffer)
if bytes_read > 0: if bytes_read > 0:
f.write(buffer[:bytes_read]) # Stereo-to-mono: extract left channel (every other 16-bit sample)
total_bytes += bytes_read mono_len = bytes_read // 2
j = 0
for i in range(0, bytes_read, 4):
mono_buf[j] = buffer[i]
mono_buf[j + 1] = buffer[i + 1]
j += 2
f.write(mono_buf[:mono_len])
total_bytes += mono_len
print(f"Recording saved successfully to '{filename}' ({total_bytes} bytes).") print(f"Recording saved successfully to '{filename}' ({total_bytes} bytes).")
return True return True
+23
View File
@@ -34,6 +34,26 @@ audio_amp_pin = None
audio_amp_active_level = 0 # 0 = Active Low, 1 = Active High audio_amp_active_level = 0 # 0 = Active Low, 1 = Active High
audio_mic_codec = "ES8311" # "ES7210" or "ES8311" audio_mic_codec = "ES8311" # "ES7210" or "ES8311"
def _i2c_recovery(sda_pin, scl_pin):
import time
scl = Pin(scl_pin, Pin.OUT)
sda = Pin(sda_pin, Pin.OUT)
scl.value(1)
sda.value(1)
time.sleep_ms(1)
for _ in range(9):
scl.value(0)
time.sleep_ms(1)
scl.value(1)
time.sleep_ms(1)
scl.value(0)
sda.value(0)
time.sleep_ms(1)
scl.value(1)
time.sleep_ms(1)
sda.value(1)
time.sleep_ms(1)
def detect_board(): def detect_board():
global BOARD_TYPE, DISPLAY_TYPE, DISPLAY_WIDTH, DISPLAY_HEIGHT global BOARD_TYPE, DISPLAY_TYPE, DISPLAY_WIDTH, DISPLAY_HEIGHT
global spi_bus, i2c_bus, display_instance, touch global spi_bus, i2c_bus, display_instance, touch
@@ -60,6 +80,7 @@ def detect_board():
# Setup Touch: FT6336U on I2C(1) # Setup Touch: FT6336U on I2C(1)
try: try:
_i2c_recovery(2, 3)
touch_i2c = I2C(1, sda=Pin(2), scl=Pin(3), freq=400000) touch_i2c = I2C(1, sda=Pin(2), scl=Pin(3), freq=400000)
from ft6336u import FT6336U from ft6336u import FT6336U
touch = FT6336U(touch_i2c, rst_pin=28, int_pin=25) touch = FT6336U(touch_i2c, rst_pin=28, int_pin=25)
@@ -73,6 +94,7 @@ def detect_board():
# Try scanning SDA=16, SCL=15 (Hosyond pins) # Try scanning SDA=16, SCL=15 (Hosyond pins)
try: try:
_i2c_recovery(16, 15)
test_i2c = SoftI2C(sda=Pin(16), scl=Pin(15)) test_i2c = SoftI2C(sda=Pin(16), scl=Pin(15))
devices = test_i2c.scan() devices = test_i2c.scan()
if 0x38 in devices: if 0x38 in devices:
@@ -121,6 +143,7 @@ def detect_board():
# 3. Try scanning SDA=13, SCL=14 (Waveshare RLCD pins) # 3. Try scanning SDA=13, SCL=14 (Waveshare RLCD pins)
try: try:
_i2c_recovery(13, 14)
test_i2c = SoftI2C(sda=Pin(13), scl=Pin(14)) test_i2c = SoftI2C(sda=Pin(13), scl=Pin(14))
devices = test_i2c.scan() devices = test_i2c.scan()
if 0x70 in devices or 0x51 in devices: if 0x70 in devices or 0x51 in devices:
+14 -6
View File
@@ -366,19 +366,20 @@ def main():
except Exception as e: except Exception as e:
print("Failed to set mic gain:", e) print("Failed to set mic gain:", e)
# 2. Configure I2S RX for recording (Mono 16kHz) # 2. Configure I2S RX for recording (Stereo 16kHz — ES7210 outputs stereo)
i2s_rx = I2S(1, i2s_rx = I2S(1,
sck=Pin(board_config.audio_i2s_sck), sck=Pin(board_config.audio_i2s_sck),
ws=Pin(board_config.audio_i2s_ws), ws=Pin(board_config.audio_i2s_ws),
sd=Pin(board_config.audio_i2s_rx_sd), sd=Pin(board_config.audio_i2s_rx_sd),
mode=I2S.RX, mode=I2S.RX,
ibuf=8000, ibuf=16000,
rate=16000, rate=16000,
bits=16, bits=16,
format=I2S.MONO) format=I2S.STEREO)
total_data_bytes = 0 total_data_bytes = 0
buffer = bytearray(1024) buffer = bytearray(2048)
mono_buf = bytearray(1024) # Half size for mono extraction
rec_start_time = time.ticks_ms() rec_start_time = time.ticks_ms()
max_rec_duration_ms = 10000 # 10 seconds max duration max_rec_duration_ms = 10000 # 10 seconds max duration
@@ -395,8 +396,15 @@ def main():
# Read I2S chunk # Read I2S chunk
bytes_read = i2s_rx.readinto(buffer) bytes_read = i2s_rx.readinto(buffer)
if bytes_read > 0: if bytes_read > 0:
f.write(buffer[:bytes_read]) # Stereo-to-mono: extract left channel (every other 16-bit sample)
total_data_bytes += bytes_read mono_len = bytes_read // 2
j = 0
for i in range(0, bytes_read, 4):
mono_buf[j] = buffer[i]
mono_buf[j + 1] = buffer[i + 1]
j += 2
f.write(mono_buf[:mono_len])
total_data_bytes += mono_len
except Exception as e: except Exception as e:
print("Error recording:", e) print("Error recording:", e)
finally: finally: