¿Cuánto ruido hay realmente en tu calle a las 11 de la noche? Casi nadie lo sabe, y esa es exactamente la razón por la que los reclamos por ruido son tan difíciles de ganar: se discuten con adjetivos ("es insoportable") en vez de con números. Un sonómetro certificado cuesta cientos de miles de pesos y solo mide el instante en que estás parado ahí con él.

Este proyecto resuelve el otro lado del problema: un medidor que se queda enchufado, mide sin parar, guarda el promedio de cada bloque de 15 minutos, cuenta cuántas veces se pasó del umbral y te manda un aviso al celular cuando eso ocurre. Todo con una placa XIAO ESP32-S3, un micrófono MAX4466 y una pantalla OLED que cabe en la palma de la mano.

Qué vas a aprender

  • Cómo un micrófono electret se convierte en una lectura en decibeles, paso por paso, incluyendo la fórmula logarítmica y por qué necesita calibración.
  • Cómo cablear el XIAO ESP32-S3 con el MAX4466 y una OLED SSD1306 por I2C sin pelear por los pines.
  • Cómo funciona la lógica de peak hold, que es lo que hace que un aplauso o un portazo no se te escapen entre muestra y muestra.
  • Cómo dejar los datos en un dashboard en la nube y disparar una alerta a WhatsApp cuando el ruido cruza el umbral.
  • Qué dicen los umbrales de la normativa chilena y cómo ajustar el firmware para que midan lo que a ti te importa.

Concepto: cómo un micrófono de bajo costo termina dando decibeles

Antes de conectar nada conviene entender qué está pasando, porque de ahí sale la única parte del proyecto que la gente hace mal: la calibración.

El MAX4466 no entrega decibeles. Entrega un voltaje que oscila alrededor de la mitad de la alimentación siguiendo la onda de presión sonora que capta la cápsula electret. Lo que hace el firmware es abrir una ventana de 50 ms, leer el ADC lo más rápido que puede dentro de esa ventana, y quedarse con el valor máximo y el mínimo. La resta de ambos es la amplitud pico a pico de la señal en cuentas del conversor:

Esa amplitud se pasa a volts con la regla de tres del ADC (12 bits, 4095 cuentas, referencia de 3,3 V) y recién ahí entra la fórmula logarítmica:

db = (41.52 * log10(voltage)) + 64.02

Vale la pena desarmar esos dos números, porque el tutorial original los da sin explicación y son la clave de todo:

  • El log10 está porque el decibel es una escala logarítmica por definición: el oído humano responde a razones de presión, no a diferencias. Duplicar la presión sonora suma alrededor de 6 dB, no el doble de dB.
  • El 41.52 es la pendiente, y es un factor empírico: cuánto sube la lectura en dB por cada década de voltaje, considerando la ganancia del amplificador.
  • El 64.02 es el offset, y es literalmente el número que estás ajustando cuando giras el trimmer del módulo. Es el que ancla la escala a la realidad.

Dos cosas que conviene tener claras desde el principio, y que este proyecto no puede saltarse:

  1. Esto mide amplitud pico a pico, no RMS ponderado en A. Un sonómetro de verdad calcula el valor cuadrático medio y le aplica una curva de ponderación A que imita la sensibilidad del oído humano por frecuencia. Acá no hay nada de eso. Los números sirven perfecto para comparar contra sí mismos ("hoy hubo 40% más eventos que ayer") y son inútiles como prueba legal.
  2. El ADC del ESP32-S3 no es lineal en los extremos. Por eso el setup() llama a analogSetAttenuation(ADC_11db): sin esa línea el conversor satura muy abajo y todo suena "fuerte". Aun con la atenuación al máximo, las lecturas cerca de 0 V y cerca de 3,3 V se comprimen. Como acá interesa la diferencia pico a pico y no el valor absoluto, el efecto es tolerable, pero explica por qué dos módulos MAX4466 distintos pueden necesitar trimmers en posiciones muy diferentes.

La tabla de umbrales, y qué pasa en Chile

El firmware clasifica cada lectura en tres zonas:

Rango Estado que muestra la OLED
30 a 60 dB SAFE
60 a 85 dB UNSAFE. ruido elevado
Sobre 85 dB UNSAFE. riesgo auditivo

Ese corte en 60 dB viene de las recomendaciones de la OMS y es un buen punto de partida, pero si tu objetivo es documentar ruido molesto en Chile te conviene saber que la normativa nacional (el DS 38 del Ministerio del Medio Ambiente, que regula ruidos molestos generados por fuentes fijas) trabaja con límites que dependen de la zona y del horario: en zonas habitacionales los límites nocturnos bajan de forma importante respecto de los diurnos, y se miden en la ventana de la persona afectada, no en la fuente.

Traducido a este proyecto: el 60 dB del código es una constante, y deberías cambiarla. La línea es una sola:

#define SAFE_LIMIT 60.0 // dB - adjust based on your environment

Si vas a monitorear un dormitorio de noche, bájala. Si vas a monitorear un taller, súbela. Y ojo con la constante de al lado, HYSTERESIS_LIMIT, que es la que evita que te llegue una avalancha de mensajes: el sistema solo vuelve a "armar" la alerta cuando el nivel cae por debajo de ese valor. Mantén siempre unos 5 dB de separación entre ambas o vas a recibir un mensaje por cada camión que pase.

Hardware: lista de materiales y conexionado

La lista es corta a propósito. Este es un proyecto de cuatro piezas:

Componente Especificación Cantidad
Microcontrolador XIAO ESP32-S3 1
Pantalla OLED 0.96" SSD1306 (I2C) 1
Micrófono amplificado MAX4466 con ganancia ajustable 1
Antena Antena tipo varilla 2.4 GHz (conector U.FL) 1

A eso súmale una protoboard y jumpers para la etapa de pruebas.

La arquitectura completa se ve así: el MAX4466 hace de sensor analógico, el XIAO ESP32-S3 se encarga del muestreo, del peak hold, de la lógica de umbral y de la red, la OLED muestra el estado local, y la nube recibe la telemetría por MQTT y dispara el mensaje de WhatsApp.

Diagrama de bloques del sistema: micrófono MAX4466, XIAO ESP32-S3, pantalla OLED y flujo de datos hacia la nube

Una nota sobre el XIAO ESP32-S3 que vale oro: la antena externa no es opcional. La placa trae un conector U.FL y viene con una antena en la caja precisamente porque su antena embebida es marginal. Si la dejas suelta, el síntoma no va a ser "no hay WiFi", va a ser un dispositivo que conecta, funciona un rato y se reinicia solo. Conéctala antes de energizar y ahórrate horas de depuración.

El cableado, pin por pin

Módulo MAX4466:

  • VCC → 3.3 V del XIAO
  • GND → GND del XIAO
  • OUT → GPIO 1 del XIAO

Pantalla OLED SSD1306:

  • VCC → 3.3 V del XIAO
  • GND → GND del XIAO
  • SDA → GPIO 5 del XIAO
  • SCL → GPIO 6 del XIAO

Diagrama de conexiones del monitor de ruido: MAX4466 y pantalla OLED cableados al XIAO ESP32-S3

Dos detalles que no aparecen marcados en el diagrama y que causan la mayoría de las fallas de montaje:

  • Alimenta el MAX4466 con 3,3 V, no con 5 V. El GPIO del ESP32-S3 no tolera 5 V, y la salida del micrófono se mueve alrededor de la mitad de su alimentación. Si lo alimentas con 5 V, la señal se centra en 2,5 V y los picos superan el rango del ADC.
  • Tierra común obligatoria. Si el micrófono y la OLED no comparten el GND del XIAO, el ADC lee basura y la pantalla directamente no arranca. Es la causa número uno de "conecté todo y no prende".

Calibración del MAX4466: el paso que no puedes saltar

El MAX4466 trae un trimmer diminuto en la cara inferior. Sin ajustarlo, tus lecturas van a estar desplazadas de forma constante hacia arriba o hacia abajo, y el umbral de 60 dB no va a significar nada.

El procedimiento es simple: carga el firmware, deja el equipo en una pieza en silencio, y gira el trimmer hasta que la OLED muestre entre 30 y 40 dB. En sentido antihorario sube la ganancia, en sentido horario la baja.

Si por más que gires el trimmer una pieza en silencio te sigue marcando cerca de 80 dB, no insistas: el módulo está malo. Es una falla conocida de las unidades de peor calidad, y la única solución es reemplazarlo y repetir el ajuste.

Configurar la nube y la alerta a WhatsApp

Esta parte es de navegación por el panel de CircuitDigest Cloud. Son seis pasos y ninguno tiene truco.

1. Crear la cuenta. Regístrate o inicia sesión con tu correo y contraseña.

Pantalla de registro e inicio de sesión de CircuitDigest Cloud

2. Agregar el dispositivo. Entra a Dashboard, ve a la opción Device en el panel lateral, pulsa Add New Device y ponle el nombre que quieras (en el original lo llamaron "Noise Pollution Monitoring"). Confirma con el botón azul Add Device.

Formulario para agregar un dispositivo nuevo en el panel de CircuitDigest Cloud

3. Crear las variables. En Variables, selecciona el dispositivo que acabas de crear y pulsa Add Variable. La primera es el nivel de ruido instantáneo: nómbrala noise, asígnale la clave analog-input-1, dirección bidireccional y unidad dB.

Creación de una variable de nivel de ruido en el panel de CircuitDigest Cloud

4. Repetir para las otras cuatro. El firmware publica cinco métricas y cada una necesita su clave. El reparto es fijo y tiene que calzar exactamente con los #define del código:

Clave Qué transporta Unidad
analog-input-1 Nivel actual (valor pico del último segundo) dB
analog-input-2 Promedio de los últimos 15 minutos dB
analog-input-3 Mínimo de la ventana de 15 minutos dB
analog-input-4 Máximo de la ventana de 15 minutos dB
analog-input-5 Cantidad de eventos sobre el umbral conteo

Listado de las cinco variables configuradas en la nube para el monitor de ruido

5. Armar el dashboard. Vuelve a Dashboard, elige tu dispositivo y pulsa Add WidgetValue Display Widget. Asócialo a analog-input-1 con unidad dB. Repite para las otras cuatro variables.

Agregando widgets de valor al dashboard del monitor de ruido

6. Vincular WhatsApp. En la pestaña Home, baja hasta WhatsApp notification, entra a la opción de vincular número, ingresa el tuyo y valida el código que te llega por SMS. Recuerda escribir el número en formato internacional (para Chile, con el prefijo +56).

Pantalla de integración de la API de WhatsApp en CircuitDigest Cloud

El firmware por dentro

El código se compila en el IDE de Arduino con el soporte de placas ESP32 instalado. Estas son las piezas que importan.

Pines y configuración de pantalla

C++
// ---------- Mic Config ----------
#define AUDIO_PIN 1
#define SAMPLE_WINDOW 50
#define ADC_RESOLUTION 4095.0
#define V_REF 3.3
// ---------- OLED Config ----------
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SDA_PIN 5
#define SCL_PIN 6
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

El micrófono queda en GPIO 1 con una ventana de muestreo de 50 ms, resolución de 12 bits y referencia de 3,3 V. La OLED usa GPIO 5 para SDA y GPIO 6 para SCL.

Credenciales y claves de telemetría

C++
#define WIFI_SSID      "Your-Wi-Fi-Name"
#define WIFI_PASS      "Your-Wi-Fi-Password"
#define DEVICE_ID      "Your-Device-ID"
#define CONNECTION_KEY "Your-Connection-Key"
#define API_KEY        "Your-API-Key"
#define PHONE_NUMBER   "Your-Phone-Number"
#define KEY_SOUND_NOW  "analog-input-1"
#define KEY_AVG_15MIN  "analog-input-2"
#define KEY_MIN        "analog-input-3"
#define KEY_MAX        "analog-input-4"
#define KEY_EVENTS     "analog-input-5"
const char* host = "www.circuitdigest.cloud";

Acá van tus credenciales de WiFi, los identificadores que te entrega la nube (device ID, connection key y API key) y tu número de teléfono para las alertas. Las cinco claves tienen que coincidir letra por letra con las variables que creaste en el panel.

La lectura en decibeles

C++
float readDBLevel() {
 unsigned long startMillis = millis();
 unsigned int signalMax = 0;
 unsigned int signalMin = 4095;
while (millis() - startMillis < SAMPLE_WINDOW) {
   unsigned int sample = analogRead(AUDIO_PIN);
   if (sample < 4095) {
     if (sample > signalMax) signalMax = sample;
     if (sample < signalMin) signalMin = sample;
   }
 }
unsigned int peakToPeak = signalMax - signalMin;
 float voltage = (peakToPeak * V_REF) / ADC_RESOLUTION;
 if (voltage < 0.01) voltage = 0.01;
float db = (41.52 * log10(voltage)) + 64.02;
 if (db < 30.0) db = 30.0;
return db;
}

Esta es la función que discutimos al principio. Fíjate en las dos protecciones: el if (voltage < 0.01) evita que el logaritmo de un número muy chico se dispare a menos infinito, y el if (db < 30.0) recorta el piso, porque por debajo de eso la medición ya no es señal sino ruido del propio conversor.

El dibujo en pantalla

C++
void updateOLED(bool safe, bool cloudConnected) {
 display.clearDisplay();
 display.setTextSize(1);
 display.setTextColor(SSD1306_WHITE);
// Row 1: Safe/Unsafe + Cloud status
 display.setCursor(0, 0);
 display.print(safe ? "SAFE" : "UNSAFE");
 display.print("  Cloud:");
 display.println(cloudConnected ? "OK" : "OFF");
// Row 2: Live dB value
 display.setCursor(0, 10);
 display.print("Noise: ");
 display.print(displayDB, 1);
 display.println(" dB");
// Bar graph
 int barWidth = map(constrain((int)displayDB, 30, 100), 30, 100, 0, 118);
 display.drawRect(0, 20, 120, 6, SSD1306_WHITE);
 display.fillRect(2, 21, barWidth, 4, SSD1306_WHITE);
// Row 3: 15-min average + trend
 display.setCursor(0, 30);
 display.print("Avg15m: ");
 display.print(last15MinAvg, 1);
 if (last15MinAvg > previous15MinAvg + 0.5) {
   display.print(" UP");
 } else if (last15MinAvg < previous15MinAvg - 0.5) {
   display.print(" DN");
 } else {
   display.print(" --");
 }
// Row 4: Min / Max
 display.setCursor(0, 40);
 display.print("Min:");
 display.print(windowMin, 0);
 display.print(" Max:");
 display.print(windowMax, 0);
// Row 5: Loud event count
 display.setCursor(0, 50);
 display.print("Events: ");
 display.print(loudEventCount);
// Row 6: Uptime
 display.setCursor(0, 58);
 display.print("Up: ");
 display.print(getUptimeString());
display.display();
}

Seis filas de información en 128×64 píxeles. El map() con constrain() es el que convierte los decibeles en el ancho de la barra: comprime el rango 30 a 100 dB a los 118 píxeles disponibles.

Peak hold: la parte inteligente

C++
if (!cloudConnected) {
   Serial.println("Wi-Fi Disconnected! Restarting system...");
   delay(2000);
   ESP.restart();
 }
// Sample mic (blocks ~50ms)
 instantDB = readDBLevel();
// Track loudest reading since last OLED refresh
 if (instantDB > peakDBSinceLastDisplay) {
   peakDBSinceLastDisplay = instantDB;
 }
// Track loudest reading since last CLOUD publish (this is what fixes the snap issue)
 if (instantDB > cloudPeakDB) {
   cloudPeakDB = instantDB;
 }
// Accumulate for 15-min average
 dbSum += instantDB;
 sampleCount++;
// Track min/max for this window
 if (instantDB < windowMin) windowMin = instantDB;
 if (instantDB > windowMax) windowMax = instantDB;

Acá está la idea que separa este proyecto de un medidor ingenuo. La pantalla se refresca cada 500 ms y la nube recibe datos cada 1000 ms, pero el micrófono se muestrea cada 50 ms. Si simplemente publicaras "la última lectura", el 95% de las muestras se perdería y un aplauso de 200 ms tendría una posibilidad entre veinte de aparecer.

Con el peak hold, cada ciclo compara la muestra nueva contra el máximo acumulado de la ventana y se queda con el mayor. Al publicar, se envía ese máximo y el acumulador se pone en cero. Resultado: ningún transitorio corto se pierde. Es el mismo principio del modo "peak hold" de los sonómetros comerciales.

El reinicio automático al perder WiFi merece un comentario aparte: es una decisión de diseño agresiva pero correcta para un equipo desatendido. Un ESP.restart() limpio es más confiable que intentar reconectar desde un estado de red corrupto.

La ventana de 15 minutos y la publicación

C++
// ---- Every 15 minutes, finalise average, reset window stats ----
 if (now - periodStartMillis >= AVG_WINDOW_MS) {
   if (sampleCount > 0) {
     previous15MinAvg = last15MinAvg;
     last15MinAvg = dbSum / sampleCount;
     Serial.print("15-min average: ");
Serial.println(last15MinAvg, 1);
   }
dbSum = 0; sampleCount = 0;
   windowMin = 999.0;
   windowMax = 0.0;
   loudEventCount = 0;
   periodStartMillis = now;
 }
// ---- Publish to CircuitDigest Cloud every 1 second using PEAK value ----
 if (now - lastCloudPublish >= CLOUD_PUBLISH_MS) {
   lastCloudPublish = now;
float reportedMin = (windowMin > 900.0) ? instantDB : windowMin;
CDcloud.publish({
     {KEY_SOUND_NOW, cloudPeakDB},
     {KEY_AVG_15MIN, last15MinAvg},
     {KEY_MIN,       reportedMin},
     {KEY_MAX,       windowMax},
     {KEY_EVENTS,    (float)loudEventCount}
   });
   Serial.print("Cloud publish | Peak dB: ");
   Serial.print(cloudPeakDB, 1);
   Serial.print(" | 15min Avg: ");
   Serial.print(last15MinAvg, 1);
   Serial.print(" | Min: ");
   Serial.print(reportedMin, 1);
   Serial.print(" | Max: ");
   Serial.print(windowMax, 1);
   Serial.print(" | Events: ");
   Serial.println(loudEventCount);
cloudPeakDB = 0.0;  // reset tracker for next 1-second window
 }

El truco del reportedMin es un detalle fino: windowMin se inicializa en 999.0 como centinela, así que durante los primeros instantes de cada ventana todavía no hay un mínimo válido. En vez de mandar un 999 al dashboard, se manda la lectura instantánea.

Nota de diseño: todo el bucle es no bloqueante salvo los 50 ms del muestreo. No hay un solo delay() en el loop(), y por eso el equipo puede sostener tres ritmos distintos a la vez (muestreo, pantalla, nube) sin atrasarse.

El envío del mensaje

C++
void sendWhatsAppAlert(float currentDB) {
 WiFiClientSecure client;
 client.setInsecure();
 client.setTimeout(5000); // Increased from 1000ms - TLS handshake often needs more time
 Serial.println("Attempting WhatsApp alert connection...");
 if (!client.connect(host, 443)) {
   Serial.println("WhatsApp API Connection FAILED - could not reach host");
   return;
 }
 Serial.println("Connected to CircuitDigest Cloud host. Sending request...");
 String payload =
   "{\"phone_number\":\"" + String(PHONE_NUMBER) + "\","
   "\"template_id\":\"threshold_violation_alert\","
   "\"variables\":{"
   "\"device_name\":\"XIAO ESP32-S3\","
   "\"parameter\":\"Sound Level\","
   "\"measured_value\":\"" + String(currentDB, 1) + " dB\","
   "\"limit\":\"" + String(SAFE_LIMIT, 1) + " dB\","
   "\"location\":\"Monitoring Room\"}}";
 client.println("POST /api/v1/whatsapp/send HTTP/1.1");
 client.println("Host: www.circuitdigest.cloud ");
 client.println("X-API-Key: " + String(API_KEY));
 client.println("Content-Type: application/json");
 client.println("Connection: close");
 client.print("Content-Length: ");
 client.println(payload.length());
 client.println();
 client.print(payload);
 // ---- Wait for and read the actual server response ----
 unsigned long responseStart = millis();
 while (client.connected() && !client.available()) {
   if (millis() - responseStart > 5000) {
     Serial.println("WhatsApp API response TIMEOUT - no reply from server");
     client.stop();
     return;
   }
   delay(10);
 }
 Serial.println("---- Server Response ----");
 String responseBody = "";
 while (client.connected() || client.available()) {
   if (client.available()) {
     String line = client.readStringUntil('\n');
     Serial.println(line);
     responseBody += line;
   }
 }
 Serial.println("--------------------------");
 // Quick success check based on HTTP status line
 if (responseBody.indexOf("200") > 0 || responseBody.indexOf("201") > 0) {
   Serial.println("WhatsApp Alert appears to have SENT successfully.");
 } else {
   Serial.println("WhatsApp Alert may have FAILED - check response above for error details.");
 }
 client.stop();
}

Presta atención al client.setInsecure(): significa que la conexión es TLS pero sin validar el certificado del servidor. Para un proyecto casero es aceptable; si esto va a un lugar donde importa, reemplázalo cargando el certificado raíz con client.setCACert().

El timeout de 5 segundos tampoco es arbitrario. El handshake TLS en un ESP32 toma bastante más que una petición HTTP plana, y con el segundo por defecto la alerta fallaba en silencio. Por eso el código también lee e imprime la respuesta del servidor: sin eso, un error de API es indistinguible de un mensaje entregado.

El arranque

C++
void setup() {
 Serial.begin(115200);
 delay(1000);
 pinMode(AUDIO_PIN, INPUT);
 analogSetAttenuation(ADC_11db);
 Wire.begin(SDA_PIN, SCL_PIN);
 if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
   Serial.println("SSD1306 allocation failed");
   while (1) delay(10);
 }
 display.clearDisplay();
 display.setTextColor(SSD1306_WHITE);
 display.setTextSize(1);
 display.setCursor(0, 0);
 display.println("Sound Pollution Monitor");
 display.println("Initializing...");
 display.display();
 Serial.println("Connecting to Wi-Fi & CircuitDigest Cloud...");
 if (!CDcloud.begin(WIFI_SSID, WIFI_PASS, DEVICE_ID, CONNECTION_KEY, API_KEY)) {
   Serial.println("CDcloud begin() failed — check credentials. Rebooting...");
   delay(2000);
   ESP.restart();
 } else {
   Serial.println("CDcloud initialized successfully.");
 }
 periodStartMillis = millis();
 bootMillis = millis();
 delay(500);
 Serial.println("System Ready.");
}

El 0x3C es la dirección I2C de la OLED. Si tu módulo no responde, prueba con 0x3D: los clones vienen con cualquiera de las dos según el fabricante, y a veces con un puente soldable en la parte trasera para elegir.

Qué se ve en la pantalla

Con todo funcionando, la OLED reparte seis filas de información:

  1. Estado de seguridad (SAFE / UNSAFE) y estado de conexión a la nube (OK / OFF).
  2. Nivel actual en decibeles, con una barra que representa la intensidad.
  3. Promedio de los últimos 15 minutos y un indicador de tendencia: UP si va subiendo, DN si va bajando, -- si se mantiene.
  4. Mínimo y máximo de la ventana actual.
  5. Cantidad de eventos que cruzaron el umbral en esa ventana.
  6. Tiempo encendido desde el último arranque.

Salida en la pantalla OLED del monitor de ruido, con lectura en dB, estado y tendencia de 15 minutos

Esa fila 3 es más útil de lo que parece. El valor instantáneo salta demasiado como para sacar conclusiones; la tendencia de un bloque a otro es lo que de verdad te dice si el barrio se está poniendo más ruidoso.

Problemas frecuentes y cómo resolverlos

Síntoma Causa probable y solución
La pieza en silencio marca ~80 dB aunque gires el trimmer El módulo MAX4466 está fallado. Reemplázalo y recalibra desde cero.
La OLED no enciende Revisa SDA en GPIO 5 y SCL en GPIO 6. Si el cableado está bien, prueba la dirección I2C 0x3D en vez de 0x3C.
El equipo no conecta a WiFi o se reinicia solo La antena externa 2.4 GHz no está puesta o quedó mal calzada en el conector U.FL.
Nada enciende después de conectar todo Falta tierra común: el GND del MAX4466 y el de la OLED tienen que ir al GND del XIAO.
Las lecturas están siempre altas o siempre bajas Falta calibración. Gira el trimmer en sentido antihorario para subir ganancia, horario para bajarla, hasta que el silencio marque 30 a 40 dB.
Llegan demasiados mensajes de WhatsApp Sube la separación entre SAFE_LIMIT y HYSTERESIS_LIMIT. Con menos de 5 dB de diferencia, cualquier fluctuación rearma la alerta.

Variantes y mejoras

Tres extensiones concretas que el tutorial original no cubre:

1. Cambiar WhatsApp por Telegram y salir de la nube propietaria. La API de bots de Telegram es gratuita, no necesita plantillas aprobadas ni vinculación por SMS, y se resuelve con una sola petición HTTPS a api.telegram.org usando la librería UniversalTelegramBot. Reemplazas la función sendWhatsAppAlert() completa por una llamada a bot.sendMessage() y el resto del firmware queda intacto. La ventaja real: puedes mandar el mensaje a un grupo, que es exactamente lo que quieres si el monitoreo es de una junta de vecinos y no de una persona sola.

2. Registro local en microSD para cuando se cae internet. Tal como está, si el WiFi se corta el equipo se reinicia y pierde la ventana en curso. Agregando un módulo microSD por SPI puedes escribir una línea CSV por cada ventana de 15 minutos (timestamp, promedio, mínimo, máximo, eventos) antes de publicar a la nube. Con eso tienes el respaldo completo del período, y además puedes abrir el archivo en una planilla y graficar el perfil de ruido de una semana entera. Súmale un módulo RTC DS3231 para que las marcas de tiempo sobrevivan a los cortes de luz.

3. Estación ambiental combinada. El XIAO ESP32-S3 tiene ADC y bus I2C de sobra. Colgando un BME280 en el mismo bus I2C de la OLED (comparte SDA y SCL, solo cambia la dirección) agregas temperatura, humedad y presión sin un solo cable extra hacia el microcontrolador. En un dashboard combinado se ven correlaciones interesantes: los picos de ruido de tránsito siguen patrones horarios muy marcados que se leen mejor junto a la temperatura del día.

Personalización para Chile

Todo lo que necesitas para este montaje se consigue en Chile, sin importaciones ni esperas de tres semanas. En el catálogo de MechatronicStore busca estos componentes:

  • XIAO ESP32-S3. es la placa del proyecto. Compacta, con WiFi y conector para antena externa. Si no la encuentras disponible, cualquier placa ESP32 DevKit funciona: tiene el mismo ADC de 12 bits y el mismo core. Solo tienes que ajustar los números de GPIO en los #define (el ESP32 clásico usa otros pines para I2C, típicamente GPIO 21 y 22) y recordar que ahí el micrófono debe ir a un pin del ADC1, porque el ADC2 queda inutilizable cuando el WiFi está activo. Ese detalle no aplica al S3, pero arruina muchos proyectos portados.
  • Micrófono MAX4466 con ganancia ajustable. el módulo con el trimmer. Es el componente crítico: no lo reemplaces por un sensor de sonido tipo KY-038, que entrega una salida digital de umbral y no sirve para medir decibeles.
  • Pantalla OLED 0.96" SSD1306 I2C. la de 128×64 píxeles, de cuatro pines (VCC, GND, SDA, SCL). Ojo de no comprar la variante SPI de siete pines, que no calza con este código.
  • Antena varilla 2.4 GHz con conector U.FL. normalmente viene incluida con la XIAO, pero si compras la placa suelta necesitas conseguirla aparte. No es opcional.
  • Protoboard 400 puntos y jumpers macho hembra 20 cm para el prototipo.

Sobre las equivalencias: si el tutorial original menciona marcas o distribuidores extranjeros, el componente genérico del catálogo local cumple exactamente la misma función. El MAX4466 es un integrado de Maxim y el módulo que se vende acá lleva el mismo chip que el de las tiendas internacionales.

Un cálculo de referencia: el proyecto completo, con placa, micrófono, pantalla, antena y prototipado, se mueve en el orden de los $35.000 a $40.000 CLP. Compáralo con un sonómetro comercial y el argumento se defiende solo. con la salvedad que ya dijimos: esto sirve para monitorear tendencias, no para peritajes.

Nota: los precios y la disponibilidad exacta se confirman en la ficha de cada producto en la tienda, que es donde están siempre actualizados.

Recursos

Versión chilena, basada en el proyecto de Circuit Digest, con componentes en stock local en MechatronicStore.