Caso práctico: Keyword spotting I2S en RPi Pico W + INMP441

Caso práctico: Keyword spotting I2S en RPi Pico W + INMP441 — hero

Objetivo y caso de uso

Qué construirás: Un detector de palabras clave utilizando Raspberry Pi Pico W y el micrófono INMP441 para la captura de audio y la detección de comandos específicos.

Para qué sirve

  • Control de dispositivos IoT mediante comandos de voz en entornos domésticos.
  • Activación de asistentes virtuales en aplicaciones de automatización.
  • Implementación de sistemas de seguridad que responden a palabras clave predefinidas.
  • Desarrollo de interfaces de usuario accesibles para personas con discapacidades.

Resultado esperado

  • Detección de palabras clave con una latencia menor a 200 ms.
  • Precisión de detección superior al 90% en condiciones de ruido controlado.
  • Capacidad de procesar hasta 5 comandos por segundo.
  • Consumo de energía inferior a 100 mW durante la operación activa.

Público objetivo: Desarrolladores de software y hardware; Nivel: Alto

Arquitectura/flujo: Captura de audio mediante INMP441, procesamiento en Raspberry Pi Pico W, detección de palabras clave y respuesta a eventos.

Nivel: alto

Prerrequisitos

Sistema operativo y entorno de trabajo

  • Host de desarrollo: Raspberry Pi OS Bookworm 64-bit
  • Imagen estable Bookworm de 64 bits (kernel 6.x), instalada en una Raspberry Pi (4/400/5).
  • Python del sistema: Python 3.11 (3.11.2 en Bookworm).
  • Acceso a terminal con privilegios sudo y conexión a Internet.

Toolchain exacta (versiones probadas)

  • cmake 3.25.1 (paquete Debian: 3.25.1-1)
  • ninja-build 1.11.1 (paquete Debian: 1.11.1-1)
  • gcc-arm-none-eabi 10.3-2021.10 (paquete Debian: 15:10.3-2021.10+rpi1)
  • libnewlib-arm-none-eabi 4.1.0-202202 (paquete Debian: 4.1.0.202202-1+rpi1)
  • git 2.39.2 (o superior en Bookworm)
  • picotool 1.1.2 (paquete Debian: 1.1.2-1)
  • Raspberry Pi Pico SDK v2.0.0 (tag oficial)
  • pico-extras v2.0.0 (para entorno PIO auxiliar, aunque en este caso no usaremos libs de i2s de salida)
  • Thonny (opcional, no requerido)
  • pyserial 3.5 (en venv de Python 3.11 para monitorización por USB CDC)

Verifica las versiones instaladas (opcional):
– cmake –version → 3.25.1
– ninja –version → 1.11.1
– arm-none-eabi-gcc –version → 10.3-2021.10
– picotool version → 1.1.2

Habilitar interfaces y ajustes del sistema (host)

Aunque programaremos la Raspberry Pi Pico W (RP2040) por USB y no usamos buses GPIO del host, conviene:
1) Deshabilitar consola serie sobre UART GPIO (evita conflictos si más adelante usas UART):
– sudo raspi-config
– Interface Options → Serial Port → Login shell over serial? No → Enable serial port hardware? Yes
– Reboot si lo pide.

2) Añadir el usuario al grupo dialout para acceso a puertos serie (USB CDC):
– sudo usermod -aG dialout $USER
– Cierra sesión y vuelve a entrar.

3) Crear un entorno virtual de Python 3.11 para herramientas auxiliares (monitor serie, validación):
– python3 -m venv ~/venvs/pico-kws
– source ~/venvs/pico-kws/bin/activate
– pip install –upgrade pip==23.2.1
– pip install pyserial==3.5

4) Instalar toolchain y utilidades:
– sudo apt update
– sudo apt install -y git cmake ninja-build gcc-arm-none-eabi libnewlib-arm-none-eabi picotool

Materiales

  • 1 × Raspberry Pi Pico W (RP2040, con Wi‑Fi; usaremos USB para programar y CDC para logs).
  • 1 × Módulo de micrófono digital INMP441 I2S (pines: VDD, GND, SCK/BCLK, WS/LRCLK, SD, L/R).
  • 1 × Cable micro‑USB de datos (no solo carga).
  • 1 × Protoboard y 6–7 cables dupont macho‑macho.
  • 1 × Condensador de desacoplo 100 nF cerámico (entre VDD y GND del micrófono, recomendado).
  • 1 × Raspberry Pi (host) con Raspberry Pi OS Bookworm 64‑bit (para compilar y flashear).
  • Opcional: Cinta adhesiva/soporte para fijar micrófono y reducir vibraciones.

Modelo exacto del dispositivo usado en este caso práctico: Raspberry Pi Pico W + INMP441 I2S Mic.

Preparación y conexión

Consideraciones eléctricas

  • El INMP441 funciona a 3.3 V. No alimentes a 5 V.
  • Añade un condensador de 100 nF entre VDD y GND lo más cerca posible del micrófono.
  • El pin L/R define el canal que entrega por SD. Conéctalo a GND para canal izquierdo (L).

Mapeo de pines y cableado

Usaremos la PIO del RP2040 para recibir I2S. Asignaremos:
– BCLK (SCK) en GPIO10
– LRCLK (WS) en GPIO11
– SD (DOUT) en GPIO12
– L/R a GND para seleccionar el canal izquierdo
– VDD a 3V3(OUT)
– GND a GND

Tabla de conexiones (Pico W vs INMP441):

Señal INMP441 Pin INMP441 Pico W (señal) GPIO Pico Pin físico Pico
VDD VDD 3V3(OUT) 36
GND GND GND 38 (cualquiera GND)
SCK (BCLK) SCK BCLK GPIO10 14
WS (LRCLK) WS LRCLK GPIO11 15
SD SD Datos (in) GPIO12 16
L/R L/R Forzar IZQ — (a GND)

Notas:
– El INMP441 no requiere MCLK externo.
– Mantén cortos los cables de BCLK/LRCLK/SD; cruza GND cerca para retorno.

Código completo

Objetivo: i2s-keyword-spotting-pico. Implementaremos:
– PIO para capturar I2S (solo canal izquierdo).
– Extracción de energía por tramas de 10 ms.
– Detección por correlación normalizada con una plantilla de “hola” (plantilla de ejemplo incluida).
– Señalización por LED y logs por USB CDC.

Estructura mínima del proyecto:
– i2s-keyword-spotting-pico/
– CMakeLists.txt
– i2s_rx.pio
– src/
– main.cpp
– tools/
– monitor.py

CMakeLists.txt

cmake_minimum_required(VERSION 3.25)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(i2s_keyword_spotting_pico C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

add_executable(i2s_keyword_spotting_pico
    src/main.cpp
    i2s_rx.pio
)

pico_generate_pio_header(i2s_keyword_spotting_pico ${CMAKE_CURRENT_LIST_DIR}/i2s_rx.pio)

target_link_libraries(i2s_keyword_spotting_pico
    pico_stdlib
    hardware_pio
    hardware_gpio
    hardware_clocks
)

pico_enable_stdio_usb(i2s_keyword_spotting_pico 1)
pico_enable_stdio_uart(i2s_keyword_spotting_pico 0)

pico_add_extra_outputs(i2s_keyword_spotting_pico)

i2s_rx.pio

PIO para recibir 32 bits por muestra del canal izquierdo sincronizados por BCLK, usando LRCLK como estrobado de canal. Leemos SD en cada flanco de subida de BCLK cuando LRCLK está bajo (izquierda). Ignoramos el canal derecho.

.program i2s_rx
; Configuración:
; - 'in' pins base = SD (dato del micrófono)
; - Usamos WAIT sobre GPIO absolutos para LRCLK y BCLK
; Convención:
; - LRCLK bajo = canal izquierdo
; - 32 bits por muestra (el INMP441 emite 24 bits MSB justificados; alineamos a 32 y luego recortamos en C++)

; Reemplaza estos números si cambias el cableado
%define BCLK_GPIO 10
%define LRCLK_GPIO 11

.wrap_target
    ; Espera a inicio de trama de canal izquierdo (LRCLK = 0)
    wait 0 gpio LRCLK_GPIO
    ; Alinea a flanco bajo de BCLK antes de empezar
    wait 0 gpio BCLK_GPIO

    set x, 31          ; 32 bits
bitloop_left:
    ; Espera flanco de subida de BCLK, muestrea SD
    wait 1 gpio BCLK_GPIO
    in pins, 1
    ; Espera flanco de bajada de BCLK para el siguiente bit
    wait 0 gpio BCLK_GPIO
    jmp x-- bitloop_left

    ; Empuja los 32 bits del canal izquierdo
    push block

    ; Consumir canal derecho (32 ciclos) sin empujar
    ; Espera que LRCLK sea 1 (canal derecho)
    wait 1 gpio LRCLK_GPIO
    set x, 31
bitloop_right:
    wait 1 gpio BCLK_GPIO
    nop
    wait 0 gpio BCLK_GPIO
    jmp x-- bitloop_right

    jmp .wrap_target
.wrap

src/main.cpp

Código principal: inicializa PIO, lee muestras, calcula energía por tramas de 10 ms a 16 kHz, compara con plantilla y emite detecciones.

#include <cstdio>
#include <cmath>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "hardware/clocks.h"
#include "i2s_rx.pio.h"

static constexpr uint GPIO_BCLK = 10;
static constexpr uint GPIO_LRCLK = 11;
static constexpr uint GPIO_SD = 12;

static constexpr uint LED_PIN = PICO_DEFAULT_LED_PIN;

// Muestreo y DSP
static constexpr float SAMPLE_RATE = 16000.0f;    // Hz
static constexpr int FRAME_SAMPLES = 160;         // 10 ms a 16 kHz
static constexpr int FEAT_FRAMES = 50;            // 0.5 s de ventana para correlación
static constexpr float DETECT_COOLDOWN_S = 1.0f;
static constexpr float DETECT_THRESHOLD = 0.86f;

// Plantilla de energía (log-energía normalizada) para "hola" (50 frames, ~0.5 s).
// Esta plantilla es un ejemplo; puedes recalibrarla con tu voz para mayor robustez.
static float TEMPLATE_HOLA[FEAT_FRAMES] = {
    -0.57f,-0.40f,-0.22f, 0.05f, 0.33f, 0.62f, 0.81f, 0.93f, 0.75f, 0.40f,
    0.10f,-0.08f,-0.19f,-0.25f,-0.30f,-0.35f,-0.38f,-0.36f,-0.32f,-0.28f,
    -0.20f,-0.10f, 0.02f, 0.18f, 0.35f, 0.48f, 0.50f, 0.42f, 0.30f, 0.18f,
    0.06f,-0.05f,-0.15f,-0.22f,-0.26f,-0.28f,-0.29f,-0.28f,-0.26f,-0.23f,
    -0.19f,-0.15f,-0.11f,-0.07f,-0.05f,-0.04f,-0.05f,-0.08f,-0.12f,-0.16f
};

// Buffer circular de energía por frames
static float energy_ring[FEAT_FRAMES];
static int energy_idx = 0;
static bool ring_full = false;

// Filtro pasa-altos sencillo para quitar DC: y[n] = x[n] - x[n-1] + 0.995*y[n-1]
static inline int32_t hp_filter(int32_t x) {
    static int32_t x1 = 0;
    static float y1 = 0.0f;
    float y = (float)(x - x1) + 0.995f * y1;
    x1 = x;
    y1 = y;
    return (int32_t)y;
}

// Normaliza vector a media 0 y varianza 1
static void znormalize(float *v, int n) {
    float mean = 0.f, var = 0.f;
    for (int i = 0; i < n; ++i) mean += v[i];
    mean /= n;
    for (int i = 0; i < n; ++i) { float d = v[i] - mean; var += d*d; }
    var = (var / n);
    float stdv = (var > 1e-9f) ? sqrtf(var) : 1.0f;
    for (int i = 0; i < n; ++i) v[i] = (v[i] - mean) / stdv;
}

// Correlación normalizada coseno entre dos vectores ya normalizados
static float cosine_similarity(const float *a, const float *b, int n) {
    float dot = 0.f;
    for (int i = 0; i < n; ++i) dot += a[i]*b[i];
    return dot / (float)n;
}

// Convierte palabra de 32 bits I2S (MSB first) a muestra 16-bit
static inline int16_t i2s32_to_int16(uint32_t w) {
    // INMP441 entrega datos de 24 bits con signo, alineados a la izquierda dentro de 32 bits.
    // w: [S23..S0][pad 8 bits]. Desplazamos para obtener 16 bits significativos.
    int32_t s24 = (int32_t)(w) >> 8;     // 24 bits con signo en bits 31..8
    int16_t s16 = (int16_t)(s24 >> 8);   // Quita los 8 LSB -> 16-bit aproximado
    return s16;
}

int main() {
    stdio_init_all();
    sleep_ms(1500); // Espera a que el host abra el puerto USB

    // LED
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, true);
    gpio_put(LED_PIN, 0);

    // PIO setup
    PIO pio = pio0;
    uint sm = pio_claim_unused_sm(pio, true);
    uint offset = pio_add_program(pio, &i2s_rx_program);

    // Configurar pines
    gpio_pull_down(GPIO_BCLK);  // entradas, pero estabiliza
    gpio_pull_down(GPIO_LRCLK);
    gpio_pull_down(GPIO_SD);

    // Configurar PIO: base de 'in' en SD
    pio_sm_config c = i2s_rx_program_get_default_config(offset);
    sm_config_set_in_pins(&c, GPIO_SD);
    sm_config_set_in_shift(&c, true, true, 32); // shift right, autopush, threshold 32

    // Mapear set/out no usados, pero obligatorio setear pin base si fuera necesario
    // Velocidad: State machine corre al clock de sistema; usamos WAIT en GPIO (externos)
    float sys_clk_khz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS);
    (void)sys_clk_khz;

    // Inicializa el SM
    pio_sm_init(pio, sm, offset, &c);

    // Habilitar SM
    pio_sm_set_enabled(pio, sm, true);

    // Variables de DSP
    uint64_t last_detect_us = 0;
    uint32_t frame_count = 0;

    // Pre-normaliza la plantilla para correlación
    float tpl[FEAT_FRAMES];
    for (int i = 0; i < FEAT_FRAMES; ++i) tpl[i] = TEMPLATE_HOLA[i];
    znormalize(tpl, FEAT_FRAMES);

    // Bucle principal
    while (true) {
        // Leer FRAME_SAMPLES muestras de canal izquierdo
        int64_t energy_acc = 0;
        for (int i = 0; i < FRAME_SAMPLES; ++i) {
            // Bloquea hasta que haya una palabra en FIFO
            uint32_t w = pio_sm_get_blocking(pio, sm);
            int16_t s = i2s32_to_int16(w);
            int32_t hp = hp_filter(s);
            energy_acc += (int64_t)hp * (int64_t)hp;
        }

        // Energía log
        float e = logf((float)energy_acc + 1.0f);
        energy_ring[energy_idx] = e;
        energy_idx = (energy_idx + 1) % FEAT_FRAMES;
        if (energy_idx == 0) ring_full = true;

        frame_count++;

        // Cada frame ~10ms. Calcular correlación cuando la ventana está llena
        if (ring_full) {
            // Construye ventana ordenada
            float feat[FEAT_FRAMES];
            for (int i = 0; i < FEAT_FRAMES; ++i) {
                int idx = (energy_idx + i) % FEAT_FRAMES;
                feat[i] = energy_ring[idx];
            }
            znormalize(feat, FEAT_FRAMES);

            float score = cosine_similarity(feat, tpl, FEAT_FRAMES);

            // Cooldown
            uint64_t now = time_us_64();
            bool cooldown_ok = (now - last_detect_us) > (uint64_t)(DETECT_COOLDOWN_S * 1e6f);

            if (score >= DETECT_THRESHOLD && cooldown_ok) {
                last_detect_us = now;
                // Señaliza por LED y log
                gpio_put(LED_PIN, 1);
                printf("[KWS] DETECTADO: HOLA | score=%.3f | t=%.2fs\n", score, now / 1e6f);
            } else {
                gpio_put(LED_PIN, 0);
            }

            // Telemetría periódica
            if (frame_count % 50 == 0) {
                // Estimación simple de BCLK esperado: 16 k * 32b * 2ch = 1.024 MHz (referencia)
                printf("[STAT] frame=%lu score=%.3f thr=%.2f SR=%.1fHz\n",
                       (unsigned long)frame_count, (double)score, (double)DETECT_THRESHOLD, (double)SAMPLE_RATE);
            }
        }
    }
    return 0;
}

tools/monitor.py

Script para leer el puerto serie USB CDC de la Pico W desde el host y ver logs de detección.

#!/usr/bin/env python3
import sys, time, serial

def main():
    if len(sys.argv) < 2:
        print("Uso: python tools/monitor.py /dev/ttyACM0 [baud]")
        sys.exit(1)
    port = sys.argv[1]
    baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200
    while True:
        try:
            with serial.Serial(port, baudrate=baud, timeout=1) as ser:
                print(f"Conectado a {port} @ {baud}")
                while True:
                    line = ser.readline().decode(errors="ignore").strip()
                    if line:
                        print(line)
        except serial.SerialException as e:
            print(f"No se puede abrir {port}: {e}")
            print("Reintentando en 2s...")
            time.sleep(2)

if __name__ == "__main__":
    main()

Breve explicación de las partes clave:
– i2s_rx.pio: El PIO espera LRCLK=0 para el canal izquierdo y muestrea 32 bits sincronizados con BCLK. Empuja la palabra de 32 bits al FIFO RX. Luego consume el canal derecho sin empujar.
– main.cpp:
– Convierte los 32 bits alineados a la izquierda en una muestra 16-bit aproximada.
– Aplica un filtro pasa‑altos simple para eliminar DC.
– Calcula energía por trama (suma de cuadrados).
– Mantiene una ventana deslizante de 50 tramas (~0.5 s) y calcula correlación con una plantilla pre‑normalizada de “hola”.
– Si la similitud coseno supera el umbral, activa el LED y escribe “DETECTADO: HOLA” por USB CDC.
– monitor.py: Facilita la monitorización de logs en el host vía /dev/ttyACM0.

Compilación, flash y ejecución

1) Preparar SDKs

  • Clona pico-sdk y pico-extras en versiones exactas y exporta variables:
# En tu Raspberry Pi con Bookworm 64-bit
mkdir -p ~/pico
cd ~/pico
git clone -b 2.0.0 https://github.com/raspberrypi/pico-sdk.git
cd pico-sdk
git submodule update --init

cd ..
git clone -b 2.0.0 https://github.com/raspberrypi/pico-extras.git

# Exporta PICO_SDK_PATH en tu shell y en ~/.bashrc
echo 'export PICO_SDK_PATH=$HOME/pico/pico-sdk' >> ~/.bashrc
echo 'export PICO_EXTRAS_PATH=$HOME/pico/pico-extras' >> ~/.bashrc
source ~/.bashrc

2) Crear el proyecto

# Estructura de directorios
mkdir -p ~/pico/projects/i2s-keyword-spotting-pico/src
mkdir -p ~/pico/projects/i2s-keyword-spotting-pico/tools

# Copia los archivos del caso práctico
# (Crea los ficheros CMakeLists.txt, i2s_rx.pio, src/main.cpp, tools/monitor.py con los contenidos de arriba)
nano ~/pico/projects/i2s-keyword-spotting-pico/CMakeLists.txt
nano ~/pico/projects/i2s-keyword-spotting-pico/i2s_rx.pio
nano ~/pico/projects/i2s-keyword-spotting-pico/src/main.cpp
nano ~/pico/projects/i2s-keyword-spotting-pico/tools/monitor.py
chmod +x ~/pico/projects/i2s-keyword-spotting-pico/tools/monitor.py

3) Construir (CMake + Ninja)

cd ~/pico/projects/i2s-keyword-spotting-pico
mkdir -p build && cd build
cmake -G Ninja ..
ninja
# Resultado: i2s_keyword_spotting_pico.uf2 en el directorio build

4) Flashear la Raspberry Pi Pico W

Método A: UF2 por Mass Storage
1. Desconecta la Pico W del USB.
2. Mantén presionado el botón BOOTSEL y conecta el USB a la Raspberry Pi (host).
3. Suelta BOOTSEL; aparecerá una unidad RPI-RP2.
4. Copia el UF2:
– cp i2s_keyword_spotting_pico.uf2 /media/$USER/RPI-RP2/

Método B: picotool (si no quieres usar Mass Storage)
1. Entra en modo BOOTSEL (como arriba).
2. Usa picotool:
bash
picotool load -v i2s_keyword_spotting_pico.uf2
picotool reboot

5) Ejecutar y monitorizar

  • La Pico W se reiniciará y abrirá un puerto USB CDC (/dev/ttyACM0).
  • Activa venv y ejecuta monitor:
    bash
    source ~/venvs/pico-kws/bin/activate
    python ~/pico/projects/i2s-keyword-spotting-pico/tools/monitor.py /dev/ttyACM0
  • Habla “hola” a ~10–20 cm del micrófono.

Validación paso a paso

1) Validar enumeración USB CDC:
– Al conectar la Pico W (sin BOOTSEL), ejecuta:
– ls /dev/ttyACM*
– Debes ver /dev/ttyACM0.
– Si no aparece, revisa cable y permisos (grupo dialout).

2) Ver logs básicos:
– Con monitor.py activo, deben aparecer líneas [STAT] cada ~0.5 s indicando frame y SR=16000.0Hz aprox.
– Ejemplo:
– [STAT] frame=50 score=0.123 thr=0.86 SR=16000.0Hz

3) Validar silencio y ruido ambiental:
– En silencio, los scores típicamente estarán por debajo de 0.4–0.6.
– Haz un chasquido o golpea la mesa suavemente: verás variaciones de energía pero no detección.

4) Validar detección de “hola”:
– Pronuncia “hola” claro, a 10–20 cm del micrófono.
– Debería encender el LED de la Pico W durante el frame de detección y aparecer:
– [KWS] DETECTADO: HOLA | score=0.90 | t=12.34s
– Si no detecta: repite un par de veces con ritmo similar (el algoritmo es plantilla por energía; la duración y entonación importan).

5) Medición de estabilidad:
– Repite “hola” 5–10 veces con pausas de >1 s.
– Cuenta detecciones. Espera al menos un 70% de aciertos en ambiente moderado.
– Si tienes falsos positivos, sube DETECT_THRESHOLD en main.cpp (p.ej. a 0.90) y recompila.

6) Validación de cableado (sin osciloscopio):
– Tapar el micrófono con un dedo reduce la energía y los [STAT] deben mostrar scores bajos.
– Si desconectas BCLK o LRCLK (no lo hagas permanentemente), verás bloqueo/ausencia de logs o comportamiento errático (indicador de sincronía perdida).

7) Confirmar tasa de muestreo efectiva:
– Aunque fijamos 16 kHz conceptualmente, el reloj de BCLK lo impone la fuente (micrófono + PIO esperando flancos). INMP441 se sincroniza a LRCLK/BCLK externos; nosotros leemos su BCLK. Al no generar nuestro propio BCLK, la muestra está exactamente sincronizada con BCLK del micrófono (que depende de LRCLK del I2S del módulo; la mayoría de INMP441 integra un PLL que deriva de LRCLK). En esta configuración (solo captura), asumimos LRCLK ≈ 16 kHz; la energía y la correlación deben ser estables. Si la plantilla se desincroniza por variación de LRCLK, es recomendable recalibrar la plantilla.

Nota: Hay módulos INMP441 que exigen un BCLK/LRCLK externos. Si tu placa de micro no genera BCLK/LRCLK, necesitas un generador de I2S maestro. Este caso práctico asume un módulo INMP441 que funciona correctamente con el esquema de reloj del módulo (frecuente en breakout boards comerciales). Si tu módulo requiere reloj maestro, ver “Troubleshooting”.

Troubleshooting (5–8 problemas típicos y soluciones)

1) No aparece /dev/ttyACM0:
– Causas: cable solo de carga, usuario sin dialout, firmware no flasheado.
– Solución:
– Cambia a un cable de datos.
– sudo usermod -aG dialout $USER; cierra sesión y entra de nuevo.
– Reflashea vía BOOTSEL con el .uf2.

2) El build falla con “PICO_SDK_PATH not set”:
– Causa: variable de entorno no exportada o shell sin source.
– Solución:
– echo ‘export PICO_SDK_PATH=$HOME/pico/pico-sdk’ >> ~/.bashrc
– source ~/.bashrc
– Vuelve a ejecutar cmake desde un build limpio: rm -rf build && mkdir build && cd build && cmake -G Ninja ..

3) Sin detecciones, aunque dices “hola”:
– Causas: umbral alto, plantilla no ajustada a tu voz/ritmo, ambiente ruidoso, distancia excesiva.
– Solución:
– Baja DETECT_THRESHOLD a 0.82–0.85 y recompila.
– Acércate a 10–15 cm.
– Habla con cadencia más corta (~0.5 s) y energía constante.
– Mejora acústica: apantalla el micrófono del viento.

4) Falsos positivos en silencio:
– Causas: vibraciones o impulsos, alta ganancia implícita en correlación.
– Solución:
– Sube DETECT_THRESHOLD a 0.90.
– Implementa un umbral de energía mínima (p. ej., requiere e promedio > cierto valor antes de correlacionar).
– Revisa que L/R esté a GND y GND esté firme.

5) El LED no enciende nunca:
– Causas: LED_PIN incorrecto (si placa no es Pico W oficial) o detección nunca supera umbral.
– Solución:
– Confirmar LED_PIN (en Pico W es el definido por PICO_DEFAULT_LED_PIN).
– Añadir printf de “score” y bajar el umbral de detección.

6) Build intermitente o cuelgues durante captura:
– Causas: cableado largo, sin desacoplo, interferencias de BCLK/LRCLK/SD.
– Solución:
– Añade el condensador 100 nF en VDD‑GND del INMP441.
– Acorta cables de señal y usa un GND común bien conectado.

7) El módulo INMP441 no entrega datos:
– Causas: Algunos módulos requieren que el microcontrolador genere BCLK/LRCLK (modo maestro) y el INMP441 sea esclavo.
– Solución:
– Este proyecto asume captura con reloj del módulo. Si tu módulo requiere reloj maestro, deberás:
– Generar BCLK≈1.024 MHz y LRCLK=16 kHz por PIO (maestro I2S TX de reloj, aunque no envíes datos) y usar otro SM para capturar SD.
– Ajustar el PIO para emitir BCLK/LRCLK y sincronizar el SM de RX. Consulta pico-extras y ejemplos de I2S master con PIO.

8) Mensajes [STAT] sin variación de score:
– Causas: energía constante (mic tapado o SD clavado), error en conversión de 32->16 bit.
– Solución:
– Verifica la línea SD, prueba invertir el desplazamiento (usar s24 >> 7 o >> 9) y observa si cambia la dinámica del score.
– Asegúrate de que L/R esté a GND para leer canal izquierdo (si flota, el canal alterna y la energía fluctúa erráticamente).

Mejoras y variantes

  • Mejor plantilla y calibración:
  • Implementa un modo de “grabación de plantilla” (mantén pulsado BOOTSEL n segundos) para adquirir 0.5 s de energía y guardar la plantilla como promedio de varias repeticiones de “hola”.
  • Guarda la plantilla en flash (usando pico_flash) para persistencia.

  • DSP más rico:

  • Sustituye la energía bruta por un banco de filtros (p. ej., 8 band-passes con Goertzel) y correlación de un vector de 8×50 características.
  • Añade VAD (detector de voz) simple por energía + ZCR (zero crossing rate) para evitar correlacionar en silencio.

  • Modelos TinyML:

  • Integra TensorFlow Lite for Microcontrollers (TFLM) y usa un modelo 1D‑CNN o DS‑CNN entrenado con MFCCs. En RP2040 es viable si optimizas RAM y reduces el modelo.
  • Pipeline: PIO I2S → MFCC en fixed‑point → TFLM inferencia → decisión.

  • Conectividad (Pico W):

  • Envía eventos de detección por Wi‑Fi a un endpoint HTTP/UDP o MQTT.
  • Usa LED y además un aviso sonoro con un buzzer (GPIO).

  • Robustez acústica:

  • Normaliza por RMS de ventana larga (AGC suave).
  • Filtra ruido de baja frecuencia con un HPF más agresivo (coeficiente 0.99→0.95).

  • Herramientas de diagnóstico:

  • Implementa un modo de volcado de audio por USB (p. ej., 8 kHz, 8‑bit μ‑law) para analizar en el host con Python/NumPy y afinar plantillas.

Checklist de verificación

  • [ ] Raspberry Pi OS Bookworm 64‑bit instalado y actualizado.
  • [ ] Toolchain instalada con versiones: cmake 3.25.1, ninja 1.11.1, gcc-arm-none-eabi 10.3-2021.10, picotool 1.1.2.
  • [ ] PICO_SDK_PATH exportado y pico-sdk v2.0.0 clonado.
  • [ ] Proyecto i2s-keyword-spotting-pico creado con los archivos proporcionados.
  • [ ] Conexión de hardware:
  • [ ] INMP441 VDD→3V3(OUT), GND→GND, L/R→GND.
  • [ ] SCK→GPIO10, WS→GPIO11, SD→GPIO12.
  • [ ] Condensador 100 nF entre VDD y GND del micrófono.
  • [ ] Compilación exitosa con cmake + ninja; UF2 generado.
  • [ ] Flasheo correcto por BOOTSEL o picotool; /dev/ttyACM0 visible.
  • [ ] monitor.py mostrando logs [STAT] periódicos.
  • [ ] LED parpadea y aparece “[KWS] DETECTADO: HOLA” al decir “hola”.
  • [ ] Sin falsos positivos frecuentes en silencio.
  • [ ] Umbral DETECT_THRESHOLD ajustado si es necesario.

Notas finales:
– Este caso práctico se centra en el modelo exacto “Raspberry Pi Pico W + INMP441 I2S Mic”. El código, conexión y comandos están alineados a ese hardware.
– La implementación de I2S por PIO en modo “solo RX” es deliberadamente explícita para aprender la sincronización con BCLK/LRCLK. Si tu módulo requiere reloj maestro, amplía el PIO para generar BCLK/LRCLK y usa un segundo SM para captura. Esto añade complejidad de temporización, pero el RP2040 lo soporta.
– El algoritmo de KWS basado en plantilla por energía es liviano y didáctico. Para producción, evoluciona a MFCC + clasificador (SVM/NN) o TFLM, ajustando memoria y latencia a la RP2040.

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 la versión de Python recomendada para el sistema?




Pregunta 2: ¿Qué herramienta se menciona como opcional para el desarrollo?




Pregunta 3: ¿Cuál es la versión de cmake que se debe instalar?




Pregunta 4: ¿Qué comando se utiliza para verificar la versión de ninja?




Pregunta 5: ¿Qué paquete se usa para la monitorización por USB CDC?




Pregunta 6: ¿Qué versión de picotool se debe instalar?




Pregunta 7: ¿Cuál es el primer paso para habilitar interfaces en el sistema?




Pregunta 8: ¿Qué comando se utiliza para añadir un usuario al grupo dialout?




Pregunta 9: ¿Qué versión de gcc-arm-none-eabi se debe instalar?




Pregunta 10: ¿Qué imagen de Raspberry Pi OS se recomienda?




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:
error: Contenido Protegido / Content is protected !!
Scroll to Top