Caso práctico: Semáforo de ruido con Raspberry Pi

Caso práctico: Semáforo de ruido con Raspberry Pi — hero

Nota educativa de seguridad: Este proyecto está diseñado con fines educativos para demostrar el procesamiento de audio I2S y el control PWM. Proporciona retroalimentación visual complementaria, pero no debe reemplazar el equipo de seguridad industrial certificado, el monitoreo de ruido compatible con OSHA o los protocolos de protección auditiva obligatorios en entornos peligrosos.

Objetivo y caso de uso

  • Qué construirás: Construirás un semáforo de ruido de taller automatizado y en tiempo real utilizando un micrófono digital I2S y un anillo LED RGB programable.
  • Por qué es importante / Casos de uso:
    • Espacios de creación compartidos (Makerspaces): Recuerda visualmente a los usuarios que usen protección auditiva cuando maquinaria pesada (como enrutadores CNC o sierras de mesa) esté en funcionamiento.
    • Gestión del aula: Proporciona retroalimentación objetiva y no verbal a los estudiantes cuando el volumen del trabajo en grupo supera los niveles aceptables.
    • Plantas de fábrica: Actúa como un monitor de ruido ambiental localizado y de bajo costo para registrar las condiciones ambientales y activar advertencias visuales localizadas.
    • Bibliotecas / Zonas de estudio: Puede ajustarse a umbrales altamente sensibles para mantener zonas de silencio estrictas sin intervención humana.
  • Resultado esperado:
    • Un servicio basado en Python que captura continuamente datos de audio de modulación por impulsos codificados (PCM) a través de la interfaz I2S.
    • Conversión matemática en tiempo real de fragmentos de audio sin procesar a la raíz cuadrada media (RMS) y decibelios relativos a la escala completa (dBFS).
    • Cambios de estado visuales inmediatos en el anillo NeoPixel: Verde (silencioso), Amarillo (advertencia/moderado) y Rojo (ruidoso).
    • Registro en consola de valores numéricos de dBFS para calibración y seguimiento ambiental.
  • Audiencia y nivel: Estudiantes de electrónica básica e ingeniería informática.
  • Arquitectura/flujo: INMP441 (I2S Audio) -> Raspberry Pi 4B (Python/RMS Math) -> WS2812B NeoPixel Ring (PWM).

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 15 apartados, 1 tablas y 2 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Python/py_compile, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este proyecto es un prototipo educativo de bajo voltaje, no un producto certificado. Antes de encender la configuración, verifique la distribución de pines (pinout) de su Raspberry Pi exacta, nunca conecte 5 V a pines GPIO de 3.3 V, desconecte la alimentación antes de cambiar el cableado y use interfaces o fuentes de alimentación externas adecuadas para sensores, relés, motores o cargas.

Requisitos previos

  • Hardware: Raspberry Pi 4 Model B (o Raspberry Pi 5).
  • Sistema Operativo: Raspberry Pi OS Bookworm 64-bit.
  • Software: Python 3.11.
  • Paquetes del sistema: python3-pip, python3-venv, portaudio19-dev y git.
  • Privilegios: El usuario debe ser miembro de los grupos audio, gpio y video para acceder al hardware I2S y a los periféricos PWM sin privilegios de root.

Materiales

  • Computación: Raspberry Pi 4 Model B (se recomiendan 4GB o 8GB de RAM).
  • Micrófono: Módulo de micrófono digital omnidireccional I2S INMP441.
  • Indicador visual: Anillo de estado NeoPixel WS2812B (variante de 12 o 16 LED).
  • Alimentación: Fuente de alimentación oficial USB-C de 15W (5V/3A) para Raspberry Pi.
  • Cableado: Cables puente (jumper) hembra-hembra (al menos 9 cables).

Configuración / Conexión

Conectar un micrófono I2S y un anillo NeoPixel simultáneamente requiere una cuidadosa selección de pines. El protocolo I2S utiliza pines de hardware específicos para su reloj en serie (BCLK), selección de palabra (LRCLK) y datos en serie (DIN). En la Raspberry Pi 4 Model B, el pin de reloj I2S predeterminado es el GPIO 18.

Debido a que los NeoPixels WS2812B requieren una sincronización estricta típicamente generada por el hardware PWM de la Pi, y dado que PWM0 también está asignado al GPIO 18 por defecto, tenemos un conflicto de hardware. Para resolver esto, enrutamos la línea de datos del NeoPixel al GPIO 12, lo que proporciona acceso al PWM0 sin interferir con el reloj I2S en el GPIO 18.

Tabla de asignación de pines

ComponentePin del componentePin de Raspberry Pi 4BN.º de pin físicoNota
INMP441VDDAlimentación 3.3VPin 1No use 5V para el INMP441.
INMP441GNDTierra (Ground)Pin 9Tierra común.
INMP441L/RTierra (Ground)Pin 14Conecta el micrófono al canal I2S izquierdo.
INMP441WSGPIO 19 (PCM_FS)Pin 35Word Select / Reloj Izquierda-Derecha.
INMP441SCKGPIO 18 (PCM_CLK)Pin 12Reloj en serie / Reloj de bits.
INMP441SDGPIO 20 (PCM_DIN)Pin 38Entrada de datos en serie a la Pi.
NeoPixelVCCAlimentación 5VPin 2Los NeoPixels requieren lógica/alimentación de 5V.
NeoPixelGNDTierra (Ground)Pin 6Tierra común.
NeoPixelDINGPIO 12 (PWM0)Pin 32Evita el conflicto con I2S en el GPIO 18.

Habilitar I2S en el sistema operativo

Por defecto, la interfaz I2S está deshabilitada en Raspberry Pi OS. Debe habilitarla editando el archivo de configuración de arranque.
Abra /boot/firmware/config.txt (o /boot/config.txt en versiones anteriores) y agregue la siguiente línea en la parte inferior:
dtparam=i2s=on
Además, para cargar el controlador de audio I2S genérico, asegúrese de que esta línea esté presente:
dtoverlay=googlevoicehat-soundcard
Después de modificar este archivo, la Raspberry Pi debe reiniciarse.

Método de validación

Para validar la precisión matemática del cálculo de dBFS y la capacidad de respuesta del hardware, coloque un medidor de SPL (Nivel de Presión Sonora) comercial calibrado junto al micrófono INMP441. Reproduzca un tono de prueba constante de 1 kHz desde un altavoz a un volumen fijo.
Evidencia esperada: El registro de la consola debería generar un valor de dBFS estable con una variación de +/- 1.0 dBFS. Ajuste los argumentos --threshold-red y --threshold-yellow para que coincidan con los requisitos de referencia de su entorno.

Código de implementación

La siguiente sección contiene dos scripts. El primero es un script de Bash para automatizar la instalación de dependencias y la configuración del sistema. El segundo es la aplicación principal en Python. La aplicación de Python incluye clases de adaptadores simulados (mock), lo que permite validar el código en una PC estándar (modo de prueba en seco) sin hardware GPIO o I2S físico conectado.

1. Script de configuración del entorno (setup_env.sh)

#!/bin/bash

echo "Updating package lists..."
sudo apt-get update

echo "Installing system dependencies..."
sudo apt-get install -y portaudio19-dev python3-pyaudio python3-pip python3-venv

echo "Creating Python virtual environment..."
python3 -m venv workshop_env
source workshop_env/bin/activate

echo "Installing Python libraries..."
pip install pyaudio rpi_ws281x adafruit-circuitpython-neopixel

echo "Adding user to required hardware groups..."
sudo usermod -a -G audio,gpio,video $USER

echo "Setup complete. Please reboot your Raspberry Pi to apply group changes."
echo "After reboot, activate the environment using: source workshop_env/bin/activate"

2. Aplicación principal (noise_traffic_light.py)

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

#!/usr/bin/env python3
"""
noise_traffic_light.py
Monitors ambient noise via an I2S microphone and controls a NeoPixel ring.
Includes a dry-run mode for educational validation on standard PCs.
"""

import sys
import math
import struct
import time
import argparse
import logging

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Hardware libraries (wrapped in try-except for dry-run compatibility)
try:
    import pyaudio
    HAS_PYAUDIO = True
except ImportError:
    HAS_PYAUDIO = False

try:
    import board
    import neopixel
    HAS_NEOPIXEL = True
except ImportError:
    HAS_NEOPIXEL = False


class MockAudioStream:
    """Generates synthetic PCM audio data to simulate a fluctuating noise environment."""
    def __init__(self, chunk_size, rate):
        self.chunk_size = chunk_size
        self.rate = rate
        self.start_time = time.time()

    def read(self, num_frames, exception_on_overflow=False):
        # Simulate elapsed time
        time.sleep(num_frames / self.rate)
        t = time.time() - self.start_time

        # Create a pulsing envelope: goes from 10% to 100% of max amplitude over 10 seconds
        envelope = 0.1 + 0.9 * ((math.sin(t * 2 * math.pi / 10) + 1) / 2)
        max_amp = 32767 * envelope

        data = bytearray()
        for i in range(num_frames):
            # Generate a 440Hz sine wave sample
            sample = int(max_amp * math.sin(2 * math.pi * 440 * (i / self.rate)))
            # Pack as 16-bit signed integer (little-endian)
            data.extend(struct.pack('<h', sample))
        return bytes(data)

    def close(self):
        pass


class MockStatusRing:
    """Simulates a NeoPixel ring by printing colors to the console."""
    def __init__(self, num_pixels):
        self.num_pixels = num_pixels
        self.current_color = None

    def fill(self, color):
        if color != self.current_color:
            self.current_color = color
            logging.info(f"[MOCK LED] Ring color changed to RGB: {color}")


class RealAudioStream:
    """Wrapper for the PyAudio I2S stream."""
    def __init__(self, chunk_size, rate):
        self.pa = pyaudio.PyAudio()
        self.stream = self.pa.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=rate,
            input=True,
            frames_per_buffer=chunk_size
        )

    def read(self, num_frames, exception_on_overflow=False):
        return self.stream.read(num_frames, exception_on_overflow=exception_on_overflow)

    def close(self):
        self.stream.stop_stream()
        self.stream.close()
        self.pa.terminate()
# ...

🔒 Este contenido es premium. Con la suscripción mensual (7 días gratis) podrás desbloquear el material didáctico completo y el pack PDF listo para imprimir.🔓 Desbloquéalo — 7 días gratis
#!/usr/bin/env python3
"""
noise_traffic_light.py
Monitors ambient noise via an I2S microphone and controls a NeoPixel ring.
Includes a dry-run mode for educational validation on standard PCs.
"""

import sys
import math
import struct
import time
import argparse
import logging

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Hardware libraries (wrapped in try-except for dry-run compatibility)
try:
    import pyaudio
    HAS_PYAUDIO = True
except ImportError:
    HAS_PYAUDIO = False

try:
    import board
    import neopixel
    HAS_NEOPIXEL = True
except ImportError:
    HAS_NEOPIXEL = False


class MockAudioStream:
    """Generates synthetic PCM audio data to simulate a fluctuating noise environment."""
    def __init__(self, chunk_size, rate):
        self.chunk_size = chunk_size
        self.rate = rate
        self.start_time = time.time()

    def read(self, num_frames, exception_on_overflow=False):
        # Simulate elapsed time
        time.sleep(num_frames / self.rate)
        t = time.time() - self.start_time

        # Create a pulsing envelope: goes from 10% to 100% of max amplitude over 10 seconds
        envelope = 0.1 + 0.9 * ((math.sin(t * 2 * math.pi / 10) + 1) / 2)
        max_amp = 32767 * envelope

        data = bytearray()
        for i in range(num_frames):
            # Generate a 440Hz sine wave sample
            sample = int(max_amp * math.sin(2 * math.pi * 440 * (i / self.rate)))
            # Pack as 16-bit signed integer (little-endian)
            data.extend(struct.pack('<h', sample))
        return bytes(data)

    def close(self):
        pass


class MockStatusRing:
    """Simulates a NeoPixel ring by printing colors to the console."""
    def __init__(self, num_pixels):
        self.num_pixels = num_pixels
        self.current_color = None

    def fill(self, color):
        if color != self.current_color:
            self.current_color = color
            logging.info(f"[MOCK LED] Ring color changed to RGB: {color}")


class RealAudioStream:
    """Wrapper for the PyAudio I2S stream."""
    def __init__(self, chunk_size, rate):
        self.pa = pyaudio.PyAudio()
        self.stream = self.pa.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=rate,
            input=True,
            frames_per_buffer=chunk_size
        )

    def read(self, num_frames, exception_on_overflow=False):
        return self.stream.read(num_frames, exception_on_overflow=exception_on_overflow)

    def close(self):
        self.stream.stop_stream()
        self.stream.close()
        self.pa.terminate()


class RealStatusRing:
    """Wrapper for the hardware NeoPixel ring."""
    def __init__(self, pin, num_pixels):
        # board.D12 corresponds to GPIO 12
        self.pixels = neopixel.NeoPixel(pin, num_pixels, brightness=0.5, auto_write=True)

    def fill(self, color):
        self.pixels.fill(color)


def calculate_dbfs(pcm_data):
    """
    Calculates the Decibels Relative to Full Scale (dBFS) of a PCM audio chunk.
    0 dBFS is the maximum possible digital value. Normal audio is negative.
    """
    # 16-bit audio = 2 bytes per frame
    count = len(pcm_data) // 2
    if count == 0:
        return -96.0

    # Unpack raw bytes into signed 16-bit integers
    shorts = struct.unpack(f'<{count}h', pcm_data)

    # Calculate Root Mean Square (RMS)
    sum_squares = sum(s * s for s in shorts)
    rms = math.sqrt(sum_squares / count)

    if rms == 0:
        return -96.0

    # Calculate dBFS (Max RMS for 16-bit signed is approx 32768)
    dbfs = 20 * math.log10(rms / 32768.0)
    return dbfs


def main():
    parser = argparse.ArgumentParser(description="Workshop Noise Traffic Light")
    parser.add_argument("--dry-run", action="store_true", help="Run with mock hardware interfaces")
    parser.add_argument("--threshold-yellow", type=float, default=-25.0, help="dBFS threshold for Yellow")
    parser.add_argument("--threshold-red", type=float, default=-15.0, help="dBFS threshold for Red")
    args = parser.parse_args()

    # Configuration constants
    CHUNK = 4096
    RATE = 44100
    NUM_PIXELS = 12

    # Initialize Hardware / Mocks
    if args.dry_run:
        logging.info("Starting in DRY-RUN mode. Using mock hardware.")
        audio = MockAudioStream(CHUNK, RATE)
        ring = MockStatusRing(NUM_PIXELS)
    else:
        if not HAS_PYAUDIO or not HAS_NEOPIXEL:
            logging.error("Hardware libraries missing. Run environment setup or use --dry-run.")
            sys.exit(1)
        logging.info("Starting in HARDWARE mode.")
        audio = RealAudioStream(CHUNK, RATE)
        ring = RealStatusRing(board.D12, NUM_PIXELS)

    # Traffic Light Colors (R, G, B)
    COLOR_GREEN = (0, 255, 0)
    COLOR_YELLOW = (255, 255, 0)
    COLOR_RED = (255, 0, 0)

    logging.info("Listening for ambient noise... Press Ctrl+C to stop.")

    try:
        while True:
            # 1. Capture audio chunk
            pcm_data = audio.read(CHUNK, exception_on_overflow=False)

            # 2. Process audio to dBFS
            dbfs = calculate_dbfs(pcm_data)

            # 3. Determine state and set visual indicator
            if dbfs >= args.threshold_red:
                state = "LOUD"
                ring.fill(COLOR_RED)
            elif dbfs >= args.threshold_yellow:
                state = "WARNING"
                ring.fill(COLOR_YELLOW)
            else:
                state = "QUIET"
                ring.fill(COLOR_GREEN)

            logging.info(f"Level: {dbfs:.2f} dBFS | State: {state}")

    except KeyboardInterrupt:
        logging.info("Stopping...")
    finally:
        audio.close()


if __name__ == "__main__":
    main()

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 propósito principal del proyecto descrito en el texto?




Pregunta 2: ¿Qué componentes principales se utilizan para construir el semáforo de ruido?




Pregunta 3: Según la nota de seguridad, ¿qué NO debe hacer este proyecto?




Pregunta 4: ¿Cuál es un caso de uso mencionado para los espacios de creación compartidos (Makerspaces)?




Pregunta 5: ¿Cómo ayuda este proyecto en la gestión del aula?




Pregunta 6: ¿Qué función cumple el proyecto en las plantas de fábrica?




Pregunta 7: ¿Qué tipo de retroalimentación proporciona el semáforo de ruido?




Pregunta 8: ¿Qué tecnología de procesamiento de audio se menciona en el texto?




Pregunta 9: ¿Qué tipo de control se demuestra junto con el procesamiento de audio?




Pregunta 10: ¿Qué tipo de maquinaria pesada se menciona como ejemplo en los Makerspaces?




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:

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

INMP441 (I2S Audio)

Raspberry Pi 4B (Python/RMS Math)

WS2812B NeoPixel Ring (PWM) . Nota educat…

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Scroll al inicio