Grabar audio con un microcontrolador siempre fue un dolor de cabeza: micrófonos analógicos con ruido, ADC saturados y librerías a medio terminar. Eso cambió. Desde CircuitPython 10.3.0-alpha.3 existe el módulo audioi2sin, que deja usar micrófonos digitales I2S como el ICS-43434 o el SPH0645LM4H directamente desde Python, sobre placas Raspberry Pi (RP2350) y Espressif.
En esta guía vas a montar tres proyectos con el mismo micrófono I2S: grabar audio a un archivo WAV en tarjeta microSD, hacer NeoPixels que reaccionan al sonido, y. como idea avanzada. separar frecuencias con FFT. Al terminar vas a entender por qué un micrófono I2S de 24 bits viaja en "ranuras" de 32 bits y cómo convertir eso a PCM de 16 bits.
Por qué I2S y no un micrófono analógico
Un micrófono MEMS I2S entrega la señal ya digitalizada por un bus serial de tres líneas (reloj de bits, selección de palabra y datos). Eso significa cero ruido analógico en el cable y nada de calibrar un ADC. La contraparte: el dato llega en un formato particular. Estos micrófonos son de 24 bits pero cada muestra "viaja" dentro de una ranura de 32 bits, así que en el código vas a ver desplazamientos de bits (>> 16 o >> 8) para quedarte con los bits altos, que ya son una representación PCM utilizable.


Proyecto 1: grabar 10 segundos a un WAV en microSD
Este ejemplo graba 10 segundos de audio a RAM y después los escribe a un archivo .wav en la microSD. Corre en una Metro RP2350 con PSRAM: esa RAM extra es la que permite bufferear los 10 segundos completos en memoria antes de volcarlos a la tarjeta.
Necesitas CircuitPython 10.3.0-alpha.3 o más nuevo (bájalo de la página de descargas de la Metro RP2350). El cableado entre la Metro y el breakout del micrófono es directo:
- 3V Metro → 3V breakout ICS-43434
- GND Metro → GND breakout
- D5 Metro → BCLK (reloj de bits)
- D6 Metro → LRCL (word select)
- D9 Metro → DOUT (salida de datos)
Copia el code.py a la unidad CIRCUITPY que aparece al conectar la placa, inserta una microSD formateada en FAT y reinicia con el botón reset. Al ejecutarse, el código te pide presionar Enter en el terminal serial para empezar a grabar:
# SPDX-FileCopyrightText: 2026 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""Basic wave file recording example for Metro RP2350 w/ PSRAM.
Records 10 seconds of audio to RAM then writes it to a .wav
file on the sd card.
Must use Metro RP2350 with PSRAM for 10 second recording.
Wiring:
bclk -> board.D5
word select -> board.D6
data -> board.D9
"""
import array
import struct
import time
import board
import audioi2sin
input("press enter to start recording")
# Recording config
SAMPLE_RATE = 16000
RECORD_SECONDS = 10
CHUNK_SAMPLES = 1024 # samples captured per record() call
OUTPUT_PATH = "/sd/recording.wav"
# Mount SD: Uncomment for devices without builtin SDCard auto-mounting.
# spi = busio.SPI(board.SD_SCK, board.SD_MOSI, board.SD_MISO)
# sdcard = sdcardio.SDCard(spi, cs=board.SD_CARD_DETECT, baudrate=24_000_000)
# vfs = storage.VfsFat(sdcard)
# storage.mount(vfs, "/sd")
# Mic
# 24-bit MEMS mics ride in 32-bit slots. Downconvert each slot to a
# signed 16-bit PCM sample before writing.
mic = audioi2sin.I2SIn(
bit_clock=board.D5,
word_select=board.D6,
data=board.D9,
sample_rate=SAMPLE_RATE,
bit_depth=32,
mono=True,
left_justified=False, # True for SPH0645LM4H
)
actual_rate = mic.sample_rate
print("Recording at", actual_rate, "Hz for", RECORD_SECONDS, "s ->", OUTPUT_PATH)
total_samples = actual_rate * RECORD_SECONDS
# Per-call scratch buffer for the raw 32-bit slots.
raw = array.array("i", [0] * CHUNK_SAMPLES)
# Whole recording buffered in RAM as signed int16 PCM. At 16 kHz mono this is
# ~2 bytes/sample (e.g. 2 s -> 64 KB). Increase RAM headroom or RECORD_SECONDS
# only as far as available memory allows.
pcm16 = array.array("h", [0] * total_samples)
def write_wav_header(file, sample_rate, num_samples, bits_per_sample=16, channels=1):
byte_rate = sample_rate * channels * bits_per_sample // 8
block_align = channels * bits_per_sample // 8
data_size = num_samples * block_align
file.write(b"RIFF")
file.write(struct.pack("<I", 36 + data_size))
file.write(b"WAVE")
file.write(b"fmt ")
file.write(
struct.pack(
"<IHHIIHH",
16, # fmt chunk size
1, # PCM
channels,
sample_rate,
byte_rate,
block_align,
bits_per_sample,
)
)
file.write(b"data")
file.write(struct.pack("<I", data_size))
written = 0
start = time.monotonic()
while written < total_samples:
# Don't overrun the RAM buffer on the final partial chunk.
request = min(CHUNK_SAMPLES, total_samples - written)
n = mic.record(raw, request)
# Convert 24-bit-in-32-bit slots to signed int16. Top 16 bits of each
# slot are the high bits of the signed 24-bit sample, which is already
# a serviceable 16-bit PCM representation.
for i in range(n):
pcm16[written + i] = raw[i] >> 16
written += n
elapsed = time.monotonic() - start
print("Captured", written, "samples in", round(elapsed, 2), "s; writing to SD...")
# Flush the whole capture to SD at once -
with open(OUTPUT_PATH, "wb") as f:
write_wav_header(f, actual_rate, written)
f.write(memoryview(pcm16)[:written])
print("Done. Wrote", written, "samples ->", OUTPUT_PATH)
while True:
pass
Después de grabar, reinicia la Metro con reset para que el computador refresque la lista de archivos de la microSD; ahí vas a encontrar recording.wav.
Proyecto 2: NeoPixels que reaccionan al sonido
Acá el micrófono I2S alimenta una barra de sonido tipo VU meter arcoíris: mientras más fuerte el sonido, más píxeles se encienden. El truco interesante del código es que calcula un piso de ruido y un peak que se adaptan solos a la sala (auto ganancia), así que no tienes que calibrar nada según el ambiente.

Cableado de la tira NeoPixel a la placa (pines de ejemplo para Sparkle Motion; ajusta según tu placa):
- 5V → 5V de la tira NeoPixel
- GND → GND de la tira
- D19 → DIN de la tira
Si tu tira no tiene 30 píxeles, cambia el valor de NUM_PIXELS en el código para que calce con tu largo:
# SPDX-FileCopyrightText: 2026 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""Audio reactive NeoPixels for SparkleMotion.
The louder the sound picked up by the microphone, the more NeoPixels will turn on.
Wiring:
LED strip -> board.D19
"""
import array
import math
import time
import board
from audioi2sin import I2SIn
import neopixel
NUM_PIXELS = 30
# uncomment the line matching your device type
PIXEL_PIN = board.D19 # Pin D19 on Sparkle Motion
# PIXEL_PIN = board.D22 # Pin D22 on Sparkle Motion Stick
# PIXEL_PIN = board.D32 # Pin D32 on Mini Sparkle Motion
SAMPLE_RATE = 16000
SAMPLES_PER_FRAME = 512 # ~32 ms windows
pixels = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, brightness=0.3, auto_write=False)
mic = I2SIn(
bit_clock=board.D26,
word_select=board.D33,
data=board.D25,
sample_rate=SAMPLE_RATE,
bit_depth=32,
mono=True,
left_justified=False,
)
buf = array.array("i", [0] * SAMPLES_PER_FRAME)
def to_signed24(u32):
return u32 >> 8
def wheel(pos):
pos = pos % 256
if pos < 85:
return (pos * 3, 255 - pos * 3, 0)
if pos < 170:
pos -= 85
return (255 - pos * 3, 0, pos * 3)
pos -= 170
return (0, pos * 3, 255 - pos * 3)
# Smoothed noise floor + peak so the effect adapts to the room.
noise_floor = 2000.0
peak = 20000.0
hue = 0
smoothed_level = 0.0
while True:
mic.record(buf, len(buf))
# Compute RMS of the window.
acc = 0
for raw in buf:
s = to_signed24(raw)
acc += s * s
rms = math.sqrt(acc / len(buf))
# Track a slow noise floor and a decaying peak for auto-gain.
noise_floor = 0.995 * noise_floor + 0.005 * rms
if rms > peak:
peak = rms
else:
peak *= 0.995
peak = max(peak, noise_floor + 1000)
level = (rms - noise_floor) / (peak - noise_floor)
if level < 0:
level = 0.0
elif level > 1:
level = 1.0
# Smooth the bar so it doesn't jitter on every frame.
smoothed_level = 0.6 * smoothed_level + 0.4 * level
lit = int(smoothed_level * NUM_PIXELS)
hue = (hue + 2) % 256
for i in range(NUM_PIXELS):
if i < lit:
r, g, b = wheel((hue + i * (256 // NUM_PIXELS)) % 256)
pixels[i] = (r, g, b)
else:
pixels[i] = (0, 0, 0)
pixels.show()
time.sleep(0.005)
Fíjate cómo el RMS de cada ventana de muestras se normaliza entre el piso de ruido y el peak, y cómo smoothed_level suaviza la barra para que no tiemble en cada frame. Ese es el tipo de detalle que separa un efecto que se ve profesional de uno que parpadea feo.
Variantes y mejoras
- Guardar en vez de mostrar: combina el proyecto 1 y el 2 para grabar clips solo cuando el volumen supera un umbral (un grabador activado por voz). Usa la lógica de
noise_floordel proyecto 2 como disparador. - Detección de aplausos (clap switch): en vez de mover LEDs, cuenta peaks de RMS en una ventana de tiempo y usa dos aplausos seguidos para prender o apagar un relé. Es el "hola mundo" de la domótica por sonido.
- Análisis de frecuencias con FFT: el ejemplo avanzado del proyecto original usa el módulo
ulabpara separar graves, medios y agudos en tres tiras distintas. Es la base para un visualizador de espectro o para detectar tonos específicos (por ejemplo, el pitido de un electrodoméstico).
Personalización para Chile
Este proyecto usa hardware bastante específico de Adafruit (Metro RP2350, breakout ICS-43434). En MechatronicStore encuentras los componentes de propósito general que sí son intercambiables:
- Placa con RP2040/RP2350 o ESP32 compatible con CircuitPython. cualquiera con soporte para
audioi2siny suficiente RAM sirve como cerebro; para grabaciones largas prioriza modelos con PSRAM. - Módulo micrófono I2S MEMS (INMP441 es el equivalente más común en el mercado local y usa el mismo bus I2S que el ICS-43434).
- Tira o anillo NeoPixel WS2812B para la parte de audio reactivo.
- Módulo lector microSD (SPI) si tu placa no trae ranura integrada.
- Cable USB-A a USB-C para conectar la placa al computador.
Nota honesta: el INMP441 es un micrófono I2S distinto al ICS-43434 del tutorial; el protocolo I2S es el mismo, pero revisa el parámetro left_justified en el constructor I2SIn, que cambia según el modelo de micrófono.
Recursos
- Tutorial original (inglés): I2S Microphones with CircuitPython
- Código de ejemplo: disponible como "Download Project Bundle" y en el GitHub de Adafruit Learning System
- Documentación: módulo
audioi2sinyulab(FFT) en la documentación de CircuitPython
Proyecto inspirado en la guía de Tim Cocks para Adafruit. Versión chilena con componentes en stock local en MechatronicStore.




