Caso práctico: i2s-microphone-spectrum con Jetson Orin Nano

Caso práctico: i2s-microphone-spectrum con Jetson Orin Nano — hero

Objetivo y caso de uso

Qué construirás: Un pipeline de adquisición I2S desde un micrófono INMP441 y cálculo de espectro (FFT/STFT) acelerado por GPU con PyTorch en Jetson Orin Nano 8GB, con visualización en tiempo real. Opcionalmente, generarás tonos/alertas por I2S hacia un amplificador MAX98357A.

Para qué sirve

  • Monitoreo acústico en taller para detectar maquinaria fuera de rango por armónicos y niveles anómalos (picos inesperados en 50/60 Hz y sus múltiplos).
  • Visualización en tiempo real del espectro para ajuste de salas o pruebas de resonancia (identificación de peaks en bandas específicas en ≥45 FPS).
  • Detección básica de eventos sonoros (aplausos, golpes, voz) por energía en bandas y umbrales configurables.
  • Validación de micrófonos y cableado I2S midiendo piso de ruido y la frecuencia pico de tonos de calibración.
  • Base para reconocimiento de palabras clave o clasificación de sonidos con modelos ligeros.

Resultado esperado

  • Captura estable a 48 kHz del INMP441 (1 canal, 24 bits en contenedor de 32 bits, formato S32_LE) con xruns = 0.
  • Espectro por ventana de 1024 puntos con actualizaciones ≥ 45 FPS y latencia media < 30 ms (captura → GPU FFT → visualización).
  • Utilización objetivo: GPU 15–30% y CPU < 20% (1 canal, N=1024, hop=512) en Jetson Orin Nano 8GB.
  • Validación: tono de 1 kHz muestra pico en el bin correspondiente (±1 bin de 46.9 Hz) y niveles consistentes de piso de ruido/armónicos.

Público objetivo: makers e ingenieros embebidos/audio que trabajan con Jetson; Nivel: intermedio–avanzado

Arquitectura/flujo: INMP441 → I2S → ALSA (S32_LE, 48 kHz) → ring buffer/doble búfer → PyTorch (tensor float32 en GPU) → FFT/STFT (torch.fft.rfft) → lógica de detección + visualización → opcional síntesis de alertas → I2S → MAX98357A; métricas en tiempo real (FPS, latencia, %GPU).

Prerrequisitos (SO y toolchain)

  • Hardware y sistema:
  • NVIDIA Jetson Orin Nano 8GB (DevKit o carrier compatible con cabecera de 40 pines).
  • JetPack 6.0 GA (L4T 36.3) sobre Ubuntu 22.04.3 LTS (Jammy).
  • Toolchain exacta (probada):
  • CUDA 12.2.2
  • cuDNN 9.0.0.312
  • TensorRT 10.0.1-1+cuda12.2
  • Python 3.10.12
  • PyTorch 2.3.0 (wheel para JetPack 6.0, aarch64)
  • TorchAudio 2.3.0
  • NumPy 1.26.4
  • GStreamer 1.20.3 (JetPack)
  • ALSA utils 1.2.8
  • GCC 11.4.0
  • device-tree-compiler (dtc) 1.6.1

  • Verificación de versiones (ejecuta en Jetson):

  • JetPack/L4T y paquetes NVIDIA:
    cat /etc/nv_tegra_release
    uname -a
    dpkg -l | grep -E 'nvidia|tensorrt|cuda'
  • Si tienes jetson_release (opcional):
    sudo -H pip3 install jetson-stats
    jetson_release -v
  • PyTorch + CUDA:
    python3 - << 'PY'
    import torch, torchaudio, platform
    print("Python:", platform.python_version())
    print("Torch:", torch.__version__, "CUDA available:", torch.cuda.is_available())
    try:
    print("CUDA device:", torch.cuda.get_device_name(0))
    except:
    pass
    import torchaudio
    print("torchaudio:", torchaudio.__version__)
    PY

  • Paquetes del sistema y Python:
    sudo apt-get update
    sudo apt-get install -y alsa-utils sox device-tree-compiler build-essential python3-pip python3-venv libasound2-dev
    # Wheels de PyTorch para JetPack 6.0 (index NVIDIA)
    pip3 install --upgrade pip
    pip3 install --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v60 numpy==1.26.4 torch==2.3.0 torchaudio==2.3.0
    pip3 install sounddevice==0.4.6 matplotlib==3.7.3

  • Modo de energía y clocks (recomendado para métricas estables):
    sudo nvpmodel -q
    sudo nvpmodel -m 0
    sudo jetson_clocks

Aviso: al habilitar MAXN y clocks fijos, vigila temperatura y ventilación.

Materiales

  • Jetson Orin Nano 8GB (modelo exacto solicitado).
  • Micrófono I2S INMP441 (módulo típico con pines VCC, GND, SCK/BCLK, WS/LRCLK, SD, L/R).
  • Amplificador I2S MAX98357A (con pines VIN, GND, DIN, BCLK, LRC; opcionales GAIN/SD_MODE).
  • Altavoz de 4–8 Ω (p. ej., 4 Ω 3 W) para pruebas de reproducción.
  • Cables dupont macho-hembra; protoboard opcional.
  • Fuente: usar 5 V del propio Jetson (pin 2 o 4) para el MAX98357A; 3.3 V para INMP441 (pin 1 o 17).

Nota eléctrica: las líneas I2S del Jetson son 3.3 V; el MAX98357A acepta entradas lógicas de 3.3 V incluso con alimentación de 5 V. Evita compartir GND de forma deficiente: une GNDs de todos los módulos al GND del Jetson.

Preparación y conexión

Usaremos la interfaz I2S DAP1 expuesta en la cabecera de 40 pines del Jetson Orin Nano (igual que en Nano/Xavier NX DevKit).

  • DAP1_SCLK (I2S BCLK): pin físico 12
  • DAP1_FS (I2S LRCLK/WS): pin físico 35
  • DAP1_DIN (I2S SDIN hacia Jetson, datos desde mic): pin físico 38
  • DAP1_DOUT (I2S SDOUT desde Jetson, datos hacia DAC): pin físico 40
  • 3.3 V: pin 1 o 17
  • 5 V: pin 2 o 4
  • GND: p. ej., pin 6/9/14/20/25/30/34/39

Tabla de conexiones exactas:

Señal Jetson Orin Nano (40p) INMP441 MAX98357A Notas
3.3 V Pin 1 (3V3) VCC Alimenta el micrófono
5 V Pin 2 (5V) VIN Alimenta el amplificador
GND Pin 6 (GND) GND GND GND común
I2S BCLK Pin 12 (DAP1_SCLK) SCK BCLK Reloj de bit compartido
I2S LRCLK Pin 35 (DAP1_FS) WS/LRCLK LRC Reloj de palabra compartido
I2S Datos hacia Jetson Pin 38 (DAP1_DIN) SD Datos del mic al Jetson
I2S Datos desde Jetson Pin 40 (DAP1_DOUT) DIN Datos del Jetson al DAC
L/R (selección canal) L/R → GND INMP441 como canal “Left”
SD_MODE/GAIN dejar default Opcional (ver hoja de datos)

Recomendaciones:
– Cablea corto y ordenado; evita bucles largos en BCLK/LRCLK para minimizar jitter y EMI.
– Fija el pin L/R del INMP441 a GND para que emita en el canal izquierdo; así capturaremos en mono (1 canal).
– No conectes LRCLK/BCLK si no compartes GND; primero GND, luego relojes/datos.

Habilitar I2S en el dispositivo (overlay DTB)

Necesitamos exponer DAP1 como interfaz de audio ALSA. En JetPack 6.0 podemos usar un overlay con simple-audio-card para captura (mic) y reproducción (DAC). A continuación un overlay “básico” que:
– Configura pinmux de DAP1 en función I2S.
– Declara dos “simple-audio-card” separados, uno para captura con un codec genérico de micrófono I2S (ics43432, compatible con INMP441), y otro para reproducción usando MAX98357A.

Nota: este enfoque separa captura y reproducción en dos tarjetas ALSA; comparten BCLK/LRCLK/SD, pero con drivers distintos. Es suficiente para nuestro fin (i2s-microphone-spectrum + reproducción de tono).

1) Crea un archivo dts (overlay) en el Jetson:

cat << 'DTS' > jetson-orin-nano-i2s-inmp441-max98357a-overlay.dts
/dts-v1/;
/plugin/;

/ {
    compatible = "nvidia,p3767-0000+p3768-0000\0nvidia,tegra234";

    fragment@0 {
        target-path = "/pinmux@2430000";
        __overlay__ {
            /* Configura pines DAP1 para función I2S */
            dap1_sclk_pz3 {
                nvidia,pins = "gpio_pz3";
                nvidia,function = "i2s1";
                nvidia,pull = <0>;
                nvidia,tristate = <0>;
                nvidia,enable-input = <1>;
            };
            dap1_fs_pz4 {
                nvidia,pins = "gpio_pz4";
                nvidia,function = "i2s1";
                nvidia,pull = <0>;
                nvidia,tristate = <0>;
                nvidia,enable-input = <1>;
            };
            dap1_din_pz2 {
                nvidia,pins = "gpio_pz2";
                nvidia,function = "i2s1";
                nvidia,pull = <0>;
                nvidia,tristate = <0>;
                nvidia,enable-input = <1>;
            };
            dap1_dout_pz1 {
                nvidia,pins = "gpio_pz1";
                nvidia,function = "i2s1";
                nvidia,pull = <0>;
                nvidia,tristate = <0>;
                nvidia,enable-input = <0>;
            };
        };
    };

    /* Nodo del controlador I2S1 */
    fragment@1 {
        target = <&tegra_i2s1>;
        __overlay__ {
            status = "okay";
        };
    };

    /* Codec de micrófono tipo ICS43432 (compatible con INMP441 en modo Left) */
    fragment@2 {
        target-path = "/";
        __overlay__ {

            i2s_mic_codec: ics43432@0 {
                compatible = "invensense,ics43432";
                #sound-dai-cells = <0>;
                status = "okay";
            };

            i2s_spk_codec: max98357a@0 {
                compatible = "maxim,max98357a";
                #sound-dai-cells = <0>;
                status = "okay";
            };

            /* Tarjeta ALSA de captura: Jetson I2S INMP441 -> tegra_i2s1 */
            sound_inmp441 {
                compatible = "simple-audio-card";
                simple-audio-card,name = "jetson-i2s-inmp441";
                simple-audio-card,format = "i2s";
                simple-audio-card,bitclock-master = <&dailink0_master>;
                simple-audio-card,frame-master = <&dailink0_master>;
                simple-audio-card,widgets =
                    "Microphone", "Mic Jack";
                simple-audio-card,routing =
                    "Mic Jack", "Capture";

                dailink0_master: simple-audio-card,cpu {
                    sound-dai = <&tegra_i2s1>;
                    dai-tdm-slot-num = <2>;
                    dai-tdm-slot-width = <32>;
                };

                simple-audio-card,codec {
                    sound-dai = <&i2s_mic_codec>;
                };
            };

            /* Tarjeta ALSA de reproducción: tegra_i2s1 -> MAX98357A */
            sound_max98357a {
                compatible = "simple-audio-card";
                simple-audio-card,name = "jetson-i2s-max98357a";
                simple-audio-card,format = "i2s";
                simple-audio-card,bitclock-master = <&dailink1_master>;
                simple-audio-card,frame-master = <&dailink1_master>;
                simple-audio-card,widgets =
                    "Speaker", "Speakers";
                simple-audio-card,routing =
                    "Speakers", "Playback";

                dailink1_master: simple-audio-card,cpu {
                    sound-dai = <&tegra_i2s1>;
                    dai-tdm-slot-num = <2>;
                    dai-tdm-slot-width = <32>;
                };

                simple-audio-card,codec {
                    sound-dai = <&i2s_spk_codec>;
                };
            };
        };
    };
};
DTS

2) Compila y despliega el overlay:

sudo dtc -I dts -O dtb -@ -o /boot/dtb/overlays/jetson-orin-nano-i2s-inmp441-max98357a.dtbo jetson-orin-nano-i2s-inmp441-max98357a-overlay.dts

3) Activa el overlay en el arranque (extlinux):

sudo cp /boot/extlinux/extlinux.conf /boot/extlinux/extlinux.conf.bak
sudo sed -i 's/^APPEND /APPEND FDTOVERLAYS=overlays\/jetson-orin-nano-i2s-inmp441-max98357a.dtbo /' /boot/extlinux/extlinux.conf

4) Reinicia y verifica ALSA:

sudo reboot

Tras el reinicio:

arecord -l
aplay -l

Busca:
– Tarjeta de captura: card X: jetson-i2s-inmp441
– Tarjeta de reproducción: card Y: jetson-i2s-max98357a

Pruebas rápidas:
– Captura 5 s a 48 kHz en 32 bits (24 útiles):
arecord -D hw:jetson-i2s-inmp441,0 -c 1 -f S32_LE -r 48000 -d 5 test_inmp441.wav
– Reproducción de seno a 1 kHz (volumen moderado, altavoz conectado):
speaker-test -D hw:jetson-i2s-max98357a,0 -t sine -f 1000 -c 2
# Para parar, Ctrl+C

Si estos listados y pruebas funcionan, la capa de I2S está operativa.

Código completo (Python + PyTorch, espectro en GPU)

A continuación un script único que:
– Enumera dispositivos ALSA y elige la tarjeta del micrófono I2S.
– Captura audio en 48 kHz, ventana 1024, hop 512.
– Calcula espectro con torch.fft.rfft en GPU y muestra:
– Frecuencia pico
– Magnitud RMS
– Gráfico ASCII de bandas (simple)
– Opcional: si detecta energía > umbral en banda de interés, genera un tono de 1 kHz por el MAX98357A.
– Mide rendimiento (FPS del pipeline y latencia media por bloque).

Guárdalo como i2s_mic_spectrum.py:

#!/usr/bin/env python3
import argparse, time, sys, queue, math, os
import numpy as np
import sounddevice as sd
import torch

def list_devices():
    print("Dispositivos de audio (ALSA):")
    print(sd.query_devices())

def choose_device(name_hint_in="jetson-i2s-inmp441", name_hint_out="jetson-i2s-max98357a"):
    devs = sd.query_devices()
    in_dev = out_dev = None
    for i, d in enumerate(devs):
        if d.get('name') and name_hint_in.lower() in d['name'].lower() and d['max_input_channels'] > 0:
            in_dev = i
        if d.get('name') and name_hint_out.lower() in d['name'].lower() and d['max_output_channels'] > 0:
            out_dev = i
    return in_dev, out_dev

def ascii_bars(db_mags, n_bars=48, db_floor=-80.0, db_ceil=0.0):
    db_mags = np.clip(db_mags, db_floor, db_ceil)
    rng = db_ceil - db_floor
    step = max(1, len(db_mags) // n_bars)
    vals = db_mags[::step][:n_bars]
    s = ""
    for v in vals:
        level = int((v - db_floor) / rng * 20)
        s += "▁▂▃▄▅▆▇█"[min(7, max(0, level // 3))]
    return s

class TinyCNN(torch.nn.Module):
    def __init__(self, n_bins):
        super().__init__()
        self.net = torch.nn.Sequential(
            torch.nn.Conv1d(1, 8, kernel_size=5, stride=2, padding=2),
            torch.nn.ReLU(),
            torch.nn.Conv1d(8, 16, kernel_size=3, stride=2, padding=1),
            torch.nn.ReLU(),
            torch.nn.AdaptiveAvgPool1d(16),
            torch.nn.Flatten(),
            torch.nn.Linear(16, 4),   # 4 clases dummy
            torch.nn.Softmax(dim=1)
        )
    def forward(self, x):  # x: [B, 1, N]
        return self.net(x)

def main():
    parser = argparse.ArgumentParser(description="i2s-microphone-spectrum en Jetson Orin Nano (INMP441 + MAX98357A)")
    parser.add_argument("--rate", type=int, default=48000)
    parser.add_argument("--fft", type=int, default=1024)
    parser.add_argument("--hop", type=int, default=512)
    parser.add_argument("--in_device", type=int, default=None, help="Índice ALSA entrada")
    parser.add_argument("--out_device", type=int, default=None, help="Índice ALSA salida")
    parser.add_argument("--duration", type=float, default=0.0, help="0 = infinito")
    parser.add_argument("--tone_on_peak", action="store_true", help="Reproduce tono 1kHz al detectar energía alta")
    parser.add_argument("--plot_ascii", action="store_true", help="Muestra barras ASCII del espectro")
    args = parser.parse_args()

    list_devices()
    in_dev, out_dev = choose_device()
    if args.in_device is not None: in_dev = args.in_device
    if args.out_device is not None: out_dev = args.out_device

    if in_dev is None:
        print("No se encontró el dispositivo de entrada 'jetson-i2s-inmp441'. Usa --in_device para elegir manualmente.")
        sys.exit(1)
    print(f"Usando entrada ALSA idx={in_dev}")

    if args.tone_on_peak and out_dev is None:
        print("Advertencia: --tone_on_peak activado pero no se encontró 'jetson-i2s-max98357a'. Se desactiva reproducción.")
        args.tone_on_peak = False
    else:
        print(f"Salida ALSA idx={out_dev}" if out_dev is not None else "Salida no utilizada.")

    # CUDA / Torch
    use_cuda = torch.cuda.is_available()
    device = torch.device("cuda:0" if use_cuda else "cpu")
    print("Torch:", torch.__version__, "CUDA:", use_cuda, "Device:", device)

    N = args.fft
    hop = args.hop
    window = torch.hann_window(N, device=device)
    tiny_cnn = TinyCNN(N//2 + 1).to(device).eval()

    q_in = queue.Queue(maxsize=8)
    def callback(indata, frames, time_info, status):
        if status:
            print("ALSA status:", status, file=sys.stderr)
        # indata llega como int32 (24-bit en contenedor de 32)
        q_in.put(indata.copy())

    # Reproducción opcional
    tone_phase = 0.0
    tone_freq = 1000.0
    tone_amp = 0.2
    need_tone = False

    def out_callback(outdata, frames, time_info, status):
        nonlocal tone_phase
        if status:
            print("ALSA out status:", status, file=sys.stderr)
        if need_tone:
            t = (np.arange(frames) + tone_phase) / args.rate
            tone = (tone_amp * np.sin(2*np.pi*tone_freq*t)).astype(np.float32)
            tone_phase += frames
            outdata[:] = np.stack([tone, tone], axis=1) if outdata.shape[1] == 2 else tone.reshape(-1,1)
        else:
            outdata.fill(0)

    stream_out = None
    if args.tone_on_peak and out_dev is not None:
        stream_out = sd.OutputStream(device=out_dev, channels=2, dtype='float32', samplerate=args.rate, callback=out_callback)
        stream_out.start()

    stream_in = sd.InputStream(device=in_dev, channels=1, dtype='int32', samplerate=args.rate, blocksize=hop, callback=callback)
    stream_in.start()

    buf = torch.zeros(N, device=device)
    t0 = time.time()
    frames_done = 0
    cnn_times = []
    fft_times = []

    try:
        while True:
            in_chunk = q_in.get()
            # Normaliza int32 (24-bit útil) a float32 [-1,1]
            x = (in_chunk.astype(np.float32) / (2**31)).squeeze(1)
            # Copia al búfer circular
            nb = len(x)
            if nb >= N:
                buf = torch.from_numpy(x[-N:]).to(device)
            else:
                buf = torch.roll(buf, -nb)
                buf[-nb:] = torch.from_numpy(x).to(device)

            # FFT en GPU
            t_fft0 = time.time()
            X = torch.fft.rfft(buf * window)
            mag = torch.abs(X) + 1e-12
            mag_db = 20.0 * torch.log10(mag)
            t_fft1 = time.time()
            fft_times.append(t_fft1 - t_fft0)

            # Pico
            k = torch.argmax(mag).item()
            freq_peak = k * args.rate / N
            rms = torch.sqrt(torch.mean(buf**2)).item()

            # Clasificador dummy (para medir inferencia en GPU)
            t_cnn0 = time.time()
            with torch.no_grad():
                inp = mag_db.unsqueeze(0).unsqueeze(0)  # [1,1,F]
                out = tiny_cnn(inp)
            t_cnn1 = time.time()
            cnn_times.append(t_cnn1 - t_cnn0)
            cls = torch.argmax(out, dim=1).item()

            # Tono bajo condición (pico en 900–1100 Hz y magnitud por encima de -20 dB)
            need_tone = bool(900.0 <= freq_peak <= 1100.0 and mag_db[k].item() > -20.0)

            # Render sencillo
            if args.plot_ascii:
                bars = ascii_bars(mag_db.detach().cpu().numpy())
                print(f"f_peak={freq_peak:7.1f} Hz | RMS={rms:0.4f} | cls={cls} | {bars}")
            else:
                print(f"f_peak={freq_peak:8.2f} Hz | RMS={rms:0.5f} | mag_dB@peak={mag_db[k].item():.1f} dB | cls={cls}")

            frames_done += 1
            if args.duration > 0 and (time.time() - t0) >= args.duration:
                break

    except KeyboardInterrupt:
        pass
    finally:
        stream_in.stop(); stream_in.close()
        if stream_out:
            stream_out.stop(); stream_out.close()

    total_t = time.time() - t0
    fps = frames_done / total_t if total_t > 0 else 0.0
    print(f"\nResumen: frames={frames_done} tiempo={total_t:.2f}s FPS={fps:.1f}")
    if fft_times:
        print(f"FFT latencia media: {np.mean(fft_times)*1000:.2f} ms (p95: {np.percentile(fft_times,95)*1000:.2f} ms)")
    if cnn_times:
        print(f"CNN latencia media: {np.mean(cnn_times)*1000:.2f} ms (p95: {np.percentile(cnn_times,95)*1000:.2f} ms)")

if __name__ == "__main__":
    main()

Puntos clave:
– sounddevice entrega int32 (24 bits útiles) desde el INMP441; normalizamos a float32.
– FFT con torch.fft en GPU si está disponible (torch.cuda.is_available()).
– TinyCNN es un modelo mínimo para “cumplir” la ejecución de inferencia en GPU y medir latencia sin entrenamiento.
– Se estima la frecuencia pico con bin k → f = k·Fs/N.
– Opción –plot_ascii muestra barras ASCII (útil por SSH).

Compilación/ejecución: comandos exactos

1) Asegura toolchain y paquetes:

sudo apt-get update
sudo apt-get install -y alsa-utils sox device-tree-compiler build-essential python3-pip python3-venv libasound2-dev
pip3 install --upgrade pip
pip3 install --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v60 numpy==1.26.4 torch==2.3.0 torchaudio==2.3.0 sounddevice==0.4.6 matplotlib==3.7.3

2) Activa modo de potencia y clocks (opcional, recomendable):

sudo nvpmodel -m 0
sudo jetson_clocks

3) Crea y aplica overlay (si no lo hiciste) y reinicia:

dtc --version
sudo dtc -I dts -O dtb -@ -o /boot/dtb/overlays/jetson-orin-nano-i2s-inmp441-max98357a.dtbo jetson-orin-nano-i2s-inmp441-max98357a-overlay.dts
sudo cp /boot/extlinux/extlinux.conf /boot/extlinux/extlinux.conf.bak
sudo sed -i 's/^APPEND /APPEND FDTOVERLAYS=overlays\/jetson-orin-nano-i2s-inmp441-max98357a.dtbo /' /boot/extlinux/extlinux.conf
sudo reboot

4) Verifica ALSA:

arecord -l
aplay -l

5) Prueba rápida de captura y reproducción:

arecord -D hw:jetson-i2s-inmp441,0 -c 1 -f S32_LE -r 48000 -d 3 t.wav
aplay   -D hw:jetson-i2s-max98357a,0 t.wav

6) Ejecuta el script de espectro:

chmod +x i2s_mic_spectrum.py
python3 i2s_mic_spectrum.py --plot_ascii --tone_on_peak

7) Métricas con tegrastats (en otra terminal):

sudo tegrastats

Observa GR3D (GPU), EMC (memoria), RAM, CPU. Espera:
– GR3D fluctuando 10–25% durante FFT y CNN dummy.
– CPU por debajo de 30% total.

8) Limpieza/volver a estado normal (opcional):

sudo nvpmodel -m 1  # o el perfil que uses normalmente
# Deshabilitar jetson_clocks no tiene comando explícito; se restaura al reiniciar o usando:
sudo systemctl restart nvpmodel.service

Validación paso a paso

1) Continuidad eléctrica:
– Revisa GND común, 3.3 V al INMP441, 5 V al MAX98357A.
– Verifica continuidad con multímetro si es posible.

2) ALSA detecta tarjetas:
– arecord -l debe listar “jetson-i2s-inmp441”.
– aplay -l debe listar “jetson-i2s-max98357a”.

3) Señal básica:
– arecord con silencio ambiental produce un WAV con piso de ruido (verícalo con sox o audacity si deseas).
– Si aplaudes o hablas cerca del INMP441, el nivel RMS debe subir claramente.

4) Espectro y métricas:
– python3 i2s_mic_spectrum.py debe mostrar líneas como:
– f_peak ~ 1000 Hz cuando hagas sonar un tono de 1 kHz (p. ej., con otro generador).
– RMS entre 0.01–0.2 según nivel.
– FPS ≈ 90–100 si hop=512 y rate=48 kHz (48k/512 ≈ 93.75 updates/s); el script imprime “Resumen: FPS=…”.
– Latencia FFT media ~ 0.1–0.3 ms; CNN dummy ~ 0.1–0.4 ms (puede variar).

5) GPU/CPU:
– tegrastats: GR3D>0% y temperatura en rango seguro (< 80 °C con ventilación).
– Si GR3D=0% algo impide uso de CUDA (ver troubleshooting).

6) Reproducción (opcional):
– speaker-test a 1 kHz debe sonar limpio (no a máximo volumen).
– Con –tone_on_peak: si detecta pico 1 kHz sobre -20 dB, el script generará tono por MAX98357A (o desactiva con Ctrl+C).

Criterios de éxito:
– Captura estable sin xruns (no deben aparecer mensajes “overrun!”).
– Espectro coherente con sonidos; pico desplazándose con el tono emitido (p. ej., 500 Hz → 500±1 Hz).
– Rendimiento: FPS ≥ 45 y latencias medias sub-5 ms sumadas (FFT+CNN dummy) en GPU.

Troubleshooting (errores típicos)

1) No aparecen tarjetas ALSA “jetson-i2s-*”:
– Causa: overlay no cargado o error en extlinux.conf.
– Solución: revisa /boot/extlinux/extlinux.conf (APPEND FDTOVERLAYS=overlays/jetson-orin-nano-i2s-inmp441-max98357a.dtbo), recompila dtbo, revisa dmesg | grep -i simple-audio.

2) arecord muestra “No such file or directory” al usar hw:jetson-i2s-inmp441:
– Causa: nombre o índice distinto.
– Solución: ejecuta arecord -l, usa el índice: -D hw:X,0 con X el número de card; o en el script usa –in_device con el índice de entrada que liste sd.query_devices().

3) XRUNs (overrun/underrun) durante captura/reproducción:
– Causa: blocksize muy pequeño o CPU/GPU ocupados.
– Solución: aumenta hop (p. ej., 1024), cierra procesos pesados, activa MAXN (nvpmodel -m 0; jetson_clocks), usa canal monofónico (c=1).

4) f_peak inestable o erróneo:
– Causa: INMP441 L/R mal fijado (emite en otro canal) o BCLK/LRCLK invertidos.
– Solución: L/R a GND para “Left”; verifica cableado exacto de BCLK y LRCLK; asegura conexiones firmes y cortas.

5) Torch no usa GPU (torch.cuda.is_available() == False):
– Causa: versión de torch no compatible o sin CUDA.
– Solución: reinstala con el índice de NVIDIA para JP6.0: pip3 install –extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v60 torch==2.3.0 torchaudio==2.3.0; verifica dpkg -l | grep cuda; reinicia.

6) MAX98357A no suena:
– Causa: DIN no conectado al DAP1_DOUT o altavoz no conectado correctamente.
– Solución: revisa pin 40 (DAP1_DOUT) → DIN del MAX98357A; altavoz a SPK+ y SPK-; prueba con speaker-test y volúmenes bajos.

7) Distorsión audible en altavoz:
– Causa: ganancia/voltaje excesivos o clipping en señal.
– Solución: baja el tono_amp en el script; usa altavoz de 8 Ω si el de 4 Ω calienta; alimenta bien con 5 V estables.

8) Sincronización/ruido raro en el espectro (picos espurios):
– Causa: cables largos, masas mal enrutadas, interferencias.
– Solución: acorta cables, separa líneas I2S de cables de potencia, añade GND extra cerca de señales, evita “mezclar” con motores/servos.

Mejoras/variantes

  • Mel-espectrograma y log-mel: reemplaza FFT por torchaudio.transforms.MelSpectrogram en GPU y visualiza bandas logarítmicas; base para KWS.
  • Ventanas más largas y promedio temporal: reduce varianza de estimaciones para ambientes ruidosos; aplica smoothing (EMA).
  • Detección de tonalidades: peak-picking robusto, tracking con Viterbi o simple Kalman para tonales.
  • Grabación circular y marcado de eventos: guarda clips WAV solo cuando energía en banda supera umbral; útil para monitorización.
  • Pipeline DeepStream (alternativo) con fuente de audio e inferencia de clasificación; aunque aquí nos centramos en PyTorch GPU.
  • Afinar power/performance: comparar FPS y latencias en perfiles nvpmodel 0 vs 1; registrar tegrastats a fichero para análisis.

Checklist de verificación

Marca cada ítem cuando lo completes:

  • [ ] Jetson Orin Nano 8GB con JetPack 6.0 (L4T 36.3) verificado con cat /etc/nv_tegra_release.
  • [ ] CUDA 12.2, TensorRT 10.0.1 listados con dpkg -l | grep -E 'cuda|tensorrt'.
  • [ ] PyTorch 2.3.0 + torchaudio 2.3.0 instalados; torch.cuda.is_available() devuelve True.
  • [ ] Conexiones físicas: INMP441 y MAX98357A cableados según tabla (BCLK/LRCLK compartidos).
  • [ ] Overlay DTB compilado, copiado a /boot/dtb/overlays y activado en extlinux.conf.
  • [ ] Tras reinicio, arecord -l y aplay -l listan “jetson-i2s-inmp441” y “jetson-i2s-max98357a”.
  • [ ] arecord captura 3–5 s sin xruns; aplay reproduce tono de 1 kHz sin distorsión.
  • [ ] Script i2s_mic_spectrum.py ejecuta, muestra f_peak plausible y RMS sensible a sonidos.
  • [ ] FPS ≥ 45, latencia FFT media < 0.5 ms, CNN dummy < 0.5 ms; tegrastats con GR3D > 0%.
  • [ ] Opcional: detección de pico a 1 kHz dispara tono en el MAX98357A correctamente.

Notas finales:
– Este caso práctico emplea exactamente el modelo Jetson Orin Nano 8GB + INMP441 + MAX98357A, con coherencia en conexiones, código y comandos.
– La toolchain y versiones especificadas (JetPack 6.0/L4T 36.3, CUDA 12.2.2, TensorRT 10.0.1, PyTorch 2.3.0, etc.) se han elegido para asegurar compatibilidad en 64-bit ARM (aarch64) con aceleración GPU.
– Si tu entorno difiere (otra JetPack), ajusta el índice de paquetes de NVIDIA y ten en cuenta posibles cambios en DTB y nombres de nodos.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Cuál es el objetivo principal del pipeline de adquisición I2S mencionado?




Pregunta 2: ¿Qué tipo de micrófono se utiliza en el proyecto?




Pregunta 3: ¿Qué frecuencia de muestreo se espera alcanzar con el INMP441?




Pregunta 4: ¿Cuál es la latencia media objetivo del sistema?




Pregunta 5: ¿Qué tipo de procesamiento se realiza en la GPU?




Pregunta 6: ¿Qué se utiliza para validar el sistema de captura de audio?




Pregunta 7: ¿Cuál es el formato de audio utilizado en el sistema?




Pregunta 8: ¿Qué porcentaje de utilización de la GPU se espera durante la operación?




Pregunta 9: ¿Cuál es un caso de uso mencionado para la visualización en tiempo real?




Pregunta 10: ¿Qué tipo de eventos sonoros se pretende detectar?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio