01 · Punto de partida
Qué vamos a construir
En los temas 9 a 12 estudiamos los sensores por separado. Una aplicación real los combina, y entonces aparecen preguntas que ningún sensor responde: ¿qué hago si uno falla?, ¿cómo evito que un pico estropee el promedio?, ¿cuándo mido, y cuándo recojo el resultado?, ¿qué guardo?, ¿cuándo aviso? El taller responde a todas con una estación de monitoreo para un ambiente cerrado (un depósito, un archivo, una sala de equipos) donde la condensación sobre una tubería o una pared fría es el riesgo a vigilar.
Los requisitos son:
- Medir temperatura y humedad del aire con un SHT31 (I2C).
- Medir la temperatura de una superficie fría (una tubería o pared) con un DS18B20 (1-Wire).
- Calcular el punto de rocío del aire y avisar cuando la superficie se acerque a él, con una alarma que no tiemble (histéresis).
- Validar y filtrar las lecturas: descartar valores imposibles, picos y los valores de arranque.
- Llevar mínimo, máximo y promedio y guardar un registro de las últimas 24 horas.
- No bloquear el programa en ningún momento, y reaccionar de forma segura ante la falla de un sensor.
02 · Paso 1
Arquitectura y tiempos
Antes de escribir código se decide quién hace qué y cada cuánto. Separar responsabilidades en capas facilita probar cada una y cambiar un sensor sin tocar el resto:
| Capa | Responsabilidad | Se apoya en |
|---|---|---|
| Adquisición | Iniciar y recoger la conversión de cada sensor, sin bloquear | Tema 12: DS18B20 y SHT31 |
| Validación y filtro | Rechazar lecturas inválidas, suavizar las buenas, contar fallas | Temas 2 y 5 |
| Cálculo derivado | Punto de rocío y margen de la superficie | Tema 12 |
| Decisión | Alarma con histéresis; estado «sin datos» ante fallas | Temas 7 y 8 |
| Registro | Estadísticas y buffer circular; salida por serie | Este tema |
Los tiempos se eligen con la dinámica de lo que se mide. La temperatura de una sala cambia en minutos, no en segundos, y la constante de tiempo de un sensor de humedad ronda los 8 s o más (tema 12):
| Tarea | Período o duración | Justificación |
|---|---|---|
| Ciclo de medición | 10 minutos (5 s en las pruebas) | Muy superior a la constante de tiempo del ambiente; baja el consumo y el desgaste |
| Conversión del DS18B20 (12 bits) | 750 ms | Tiempo máximo según la hoja de datos |
| Conversión del SHT31 (alta repetibilidad) | 15 ms | Tiempo máximo según la hoja de datos |
| Registro histórico | 1 muestra por ciclo; 144 muestras = 24 h | Se descarta la más antigua al llenarse (buffer circular) |
Como la conversión más lenta tarda 750 ms, el ciclo tiene dos fases: iniciar ambas conversiones y, 750 ms después, recoger los resultados. En el medio el procesador queda libre para atender el puerto serie, un botón o una pantalla.
03 · Paso 2
Cableado y montaje
| Sensor | Señal | Pin | Observaciones |
|---|---|---|---|
| SHT31 | SDA / SCL | A4 / A5 | Dirección 0x44; pull-ups de I2C (la mayoría de las placas los traen); cable corto |
| SHT31 | Alimentación | 5 V y GND | Capacitor de 100 nF junto al sensor |
| DS18B20 | Datos | D2 | Pull-up de 4,7 kΩ a Vcc entre datos y alimentación; sonda impermeable en la superficie |
| DS18B20 | Alimentación | 5 V y GND | Alimentación normal (no parásita) para simplificar |
El montaje físico pesa tanto como el cableado, y es donde más se equivocan los principios:
- SHT31 en el aire, no en la placa. Lejos del regulador, del microcontrolador y de cualquier fuente de calor; en una plaqueta aparte o en el extremo de un cable corto. Un sensor 3 °C más caliente que el aire mide unos 10 puntos de humedad de menos (tema 12).
- DS18B20 pegado a la superficie con pasta térmica o cinta de aluminio y aislado del aire por una cubierta de espuma, para que mida la temperatura de la superficie y no la del ambiente.
- La carcasa ventilada en el caso del sensor de humedad y con membrana si hay polvo, pero sin cerrar herméticamente.
04 · Paso 3
Validar y filtrar cada canal
Cada magnitud (temperatura del aire, humedad, temperatura de la superficie) pasa por la misma capa de validación. Se la escribe una vez, como una estructura con su estado y una función, igual que el detector de eventos del tema 8. Reúne tres ideas del curso:
- Rechazar lo imposible: valores fuera del rango físico del sensor,
NAN(el sensor no respondió) y los valores de arranque conocidos (85,0 °C del DS18B20 en el primer ciclo, −127 °C de sensor desconectado). - Limitar el salto: una temperatura de ambiente no cambia 5 °C en 10 minutos; si lo hace, es un pico. Se compara con la última lectura aceptada, no con la última recibida; de lo contrario, un pico contagiaría a la lectura siguiente.
- Aceptar un cambio real después de varios rechazos seguidos: si la lectura «saltó» y el nuevo valor se sostiene en tres ciclos consecutivos, no era un pico sino un cambio verdadero y se acepta. Evita que el sistema quede enganchado a un valor viejo.
Lo que supera la validación se suaviza con un filtro exponencial (tema 5) y se cuenta cuántas fallas seguidas lleva el canal. Ese contador es la entrada de la política de fallas.
const float ALFA = 0.3; // filtro exponencial: más pesado = más lento
const uint8_t FALLAS_PARA_ALARMA = 3; // fallas seguidas que declaran «sensor en falla»
struct Canal {
float filtrado; // valor suavizado
float ultimaValida; // última lectura aceptada
bool inicializado; // ¿hay ya una lectura aceptada?
uint8_t fallas; // rechazos consecutivos
};
bool aceptar(Canal &c, float x, float minimo, float maximo, float saltoMax) {
bool ok = !isnan(x) && x >= minimo && x <= maximo;
if (ok && c.inicializado && fabs(x - c.ultimaValida) > saltoMax
&& c.fallas < FALLAS_PARA_ALARMA) {
ok = false; // salto sospechoso: se rechaza (hasta 3 veces)
}
if (!ok) {
if (c.fallas < 255) c.fallas++;
return false;
}
c.fallas = 0;
c.ultimaValida = x;
c.filtrado = c.inicializado ? c.filtrado + ALFA * (x - c.filtrado) : x;
c.inicializado = true;
return true;
}
bool enFalla(const Canal &c) {
return !c.inicializado || c.fallas >= FALLAS_PARA_ALARMA;
}
Los límites de cada canal se pasan como argumentos: temperatura del aire de −40 a 85 °C con salto de 5 °C; humedad de 0 a 100 % con salto de 25 puntos; temperatura de la superficie de −55 a 125 °C con salto de 5 °C. La función no sabe qué mide: es el mismo código para los tres canales.
05 · Paso 4
El ciclo de medición sin bloqueo
El ciclo tiene dos fases y se controla comparando millis() con marcas, como los detectores de eventos. No hay delay: mientras los sensores convierten, el loop sigue girando y atendiendo lo demás.
const uint32_t T_CICLO = 600000UL; // 10 minutos (5000 para las pruebas)
const uint16_t T_CONVERSION = 750; // el sensor más lento: DS18B20 a 12 bits
uint32_t tInicio = 0;
bool convirtiendo = false;
bool primerCiclo = true;
void iniciarMedicion() {
ds18b20.requestTemperatures(); // DS18B20 empieza a convertir
sht31Comenzar(); // envía el comando 0x2400 al SHT31
}
void completarMedicion() {
float t = NAN, h = NAN;
sht31Leer(t, h); // deja NAN si no respondió o el CRC falló (tema 12)
float ts = ds18b20.getTempCByIndex(0);
if (ts == DEVICE_DISCONNECTED_C || (primerCiclo && ts == 85.0)) ts = NAN;
primerCiclo = false;
aceptar(canalT, t, -40, 85, 5);
aceptar(canalH, h, 0, 100, 25);
aceptar(canalS, ts, -55, 125, 5);
evaluar(); // punto de rocío, alarma y registro
}
void cicloMedicion(uint32_t ahora) {
if (!convirtiendo && ahora - tInicio >= T_CICLO) {
iniciarMedicion();
tInicio = ahora;
convirtiendo = true;
} else if (convirtiendo && ahora - tInicio >= T_CONVERSION) {
completarMedicion();
convirtiendo = false;
}
}
Dos detalles. El primer ciclo se dispara de inmediato si en setup() se inicializa tInicio = millis() - T_CICLO; así la estación no queda 10 minutos sin datos tras un reinicio. Y la condición del valor de arranque (primerCiclo && ts == 85.0) es una defensa barata contra la trampa del tema 12: en el primer ciclo, un 85,0 exacto se descarta, mientras que en los siguientes es una lectura legítima (un ambiente puede llegar realmente a 85 °C).
06 · Paso 5
Punto de rocío y alarma con histéresis
Con la temperatura y la humedad filtradas del aire se calcula el punto de rocío con la fórmula de Magnus (tema 12). Con él se define el margen de la superficie: cuántos grados por encima del punto de rocío está. Cuanto menor, más cerca de condensar.
Una alarma con un único umbral de 1 °C temblaría: el ruido de las lecturas haría que el margen cruzara el umbral una y otra vez. Se aplica la histéresis del tema 7: la alarma se activa cuando el margen baja de 1,0 °C y se desactiva recién cuando supera 2,0 °C. Una tercera condición completa la política: si falta información (algún canal en falla), la estación no puede garantizar que no haya riesgo y lo declara con un estado propio, «sin datos», que se trata como alarma.
enum Estado { OK, RIESGO, SIN_DATOS };
Estado estado = OK;
float puntoDeRocio(float t, float h) {
const float A = 17.62, B = 243.12;
float g = log(h / 100.0) + A * t / (B + t);
return B * g / (A - g);
}
void evaluar() {
if (enFalla(canalT) || enFalla(canalH) || enFalla(canalS)) {
estado = SIN_DATOS; // sin información no se puede asegurar nada
return;
}
float rocio = puntoDeRocio(canalT.filtrado, canalH.filtrado);
float margen = canalS.filtrado - rocio;
if (estado != RIESGO && margen <= 1.0) estado = RIESGO; // se activa en 1,0 °C
else if (estado == RIESGO && margen >= 2.0) estado = OK; // se libera en 2,0 °C
else if (estado == SIN_DATOS) estado = (margen <= 1.0) ? RIESGO : OK;
registrar(canalT.filtrado, canalH.filtrado, canalS.filtrado);
actualizarEstadisticas(canalT.filtrado);
}
Observá la estructura de decisión: tres estados, no dos. Un sistema que solo distingue «alarma» y «normal» convierte el silencio de un sensor roto en «normal». Incluir SIN_DATOS es lo que lo vuelve seguro ante fallas (tema 6: la falla debe llevar al estado seguro). La ecuación de Magnus no vale con humedades cercanas a cero (el logaritmo diverge); la validación del canal (RH mayor que 0 por construcción del sensor) lo evita en la práctica.
07 · Paso 6
Registro de datos
Registrar es decidir qué se guarda, dónde y por cuánto tiempo. Cada medio tiene su límite:
| Medio | Capacidad | Persistencia | Cuidado |
|---|---|---|---|
| SRAM (buffer circular) | Unos cientos de bytes | Se pierde al apagar | Escribir es gratis; el espacio es el límite |
| EEPROM interna | 1 KB en el ATmega328P | Sobrevive al apagado | Unos 100 000 ciclos de escritura por celda: no escribir cada ciclo en la misma dirección |
| Tarjeta SD | Gigabytes | Persistente y transportable | Hardware adicional; consumo y escritura en bloques |
| Puerto serie hacia una computadora | Ilimitada | La guarda el receptor | Requiere un equipo conectado |
Para este taller usamos un buffer circular en SRAM de 144 registros (24 horas a 10 minutos) y la salida por serie en formato CSV. Cada registro guarda tres magnitudes en enteros para ahorrar memoria y evitar coma flotante: temperatura del aire y de la superficie en centésimas de grado, y humedad en centésimas de por ciento. Son 6 bytes por registro y 864 bytes en total, el 42 % de los 2 KB del ATmega328P: es considerable, y una de las razones para pasar a un registro horario o a una SD en un equipo real.
struct Registro {
int16_t tAire; // centésimas de °C
uint16_t humedad; // centésimas de %
int16_t tSuperficie; // centésimas de °C
};
const uint8_t CAPACIDAD = 144; // 24 h a 10 minutos
Registro historial[CAPACIDAD];
uint8_t cabeza = 0; // dónde se escribe el próximo
uint8_t cantidad = 0;
void registrar(float t, float h, float ts) {
historial[cabeza] = { (int16_t)lroundf(t * 100), (uint16_t)lroundf(h * 100), (int16_t)lroundf(ts * 100) };
cabeza = (cabeza + 1) % CAPACIDAD; // al llegar al final vuelve a empezar: sobrescribe el más antiguo
if (cantidad < CAPACIDAD) cantidad++;
}
// Estadísticas con acumuladores: no hace falta recorrer el historial
float tMin = 1000, tMax = -1000, tSuma = 0;
uint16_t tCuenta = 0;
void actualizarEstadisticas(float t) {
if (t < tMin) tMin = t;
if (t > tMax) tMax = t;
tSuma += t;
tCuenta++;
}
float tMedia() { return tCuenta ? tSuma / tCuenta : NAN; }
Los acumuladores de mínimo, máximo y suma permiten calcular las estadísticas sin recorrer el historial y sin guardar todas las muestras. Los de este ejemplo cubren «desde el arranque»; para estadísticas por día se los reinicia a una hora fija.
El buffer circular es la estructura natural de un registro acotado: el índice cabeza avanza y vuelve a cero al llegar al final con un módulo. Para volcarlo en orden cronológico se recorre desde la posición más antigua: cabeza si está lleno, o 0 si todavía no se llenó.
08 · Paso 7
El programa completo
Con todas las capas, el programa principal es muy corto. Declara los objetos, inicializa los sensores y, en cada vuelta, avanza el ciclo y atiende los comandos del usuario. Una cabecera de informe por serie muestra el estado actual y el volcado del historial:
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <math.h>
OneWire bus(2);
DallasTemperature ds18b20(&bus);
Canal canalT, canalH, canalS; // los tres canales usan el mismo código de validación
// ... (aceptar, enFalla, puntoDeRocio, evaluar, registrar,
// sht31Comenzar, sht31Leer, cicloMedicion: ver los pasos anteriores)
void imprimirInforme() {
Serial.print(F("T aire: ")); Serial.print(canalT.filtrado, 2);
Serial.print(F(" C HR: ")); Serial.print(canalH.filtrado, 1);
Serial.print(F(" % Rocio: ")); Serial.print(puntoDeRocio(canalT.filtrado, canalH.filtrado), 1);
Serial.print(F(" C T sup: ")); Serial.print(canalS.filtrado, 2);
Serial.print(F(" C Estado: "));
Serial.println(estado == OK ? F("OK") : estado == RIESGO ? F("RIESGO") : F("SIN DATOS"));
}
void volcarCSV() {
Serial.println(F("indice,t_aire,humedad,t_superficie"));
uint8_t inicio = (cantidad < CAPACIDAD) ? 0 : cabeza; // el más antiguo primero
for (uint8_t i = 0; i < cantidad; i++) {
const Registro &r = historial[(inicio + i) % CAPACIDAD];
Serial.print(i); Serial.print(',');
Serial.print(r.tAire / 100.0, 2); Serial.print(',');
Serial.print(r.humedad / 100.0, 2); Serial.print(',');
Serial.println(r.tSuperficie / 100.0, 2);
}
}
void atenderSerie() {
while (Serial.available()) {
char c = Serial.read();
if (c == 'i') imprimirInforme();
if (c == 'v') volcarCSV();
}
}
void setup() {
Serial.begin(115200);
Wire.begin();
ds18b20.begin();
ds18b20.setResolution(12);
ds18b20.setWaitForConversion(false); // la conversión no bloquea
tInicio = millis() - T_CICLO; // la primera medición es inmediata
}
void loop() {
uint32_t ahora = millis();
cicloMedicion(ahora);
atenderSerie();
}
La separación en capas se nota: loop() tiene dos líneas, y todo lo demás está en funciones que se prueban por separado. El mismo esquema (ciclo de adquisición, validación por canal, cálculo derivado, decisión, registro) se repite en cualquier sistema de monitoreo, cambiando solo los sensores y las fórmulas.
09 · Paso 8
Verificar
Una estación se prueba como una función: con entradas conocidas y salidas esperadas. Las pruebas de ambiente real se complementan con inyección de fallas: se provoca cada falla a propósito y se comprueba la reacción. Un cable desconectado es la forma más simple y más reveladora.
| Prueba | Cómo se hace | Resultado esperado |
|---|---|---|
| Valores en reposo | Comparar con un instrumento de referencia (un higrómetro de calidad) | Temperatura dentro de ±0,5 °C; humedad dentro de ±3 %RH |
| Punto de rocío | Calcular a mano con T y HR medidas | Coincide con el informe en 0,1 °C |
| Arranque | Reiniciar la placa | El primer ciclo ocurre de inmediato; un 85,0 del DS18B20 no se acepta |
| Pico espurio | Soplar un aliento cálido sobre el SHT31 apenas un instante, o inyectar un valor en el programa | Rechazado por el salto; el filtrado casi no se mueve |
| Cambio real | Llevar el sensor a otro ambiente y dejarlo | Tras 3 rechazos seguidos se acepta y el filtro converge |
| Sensor desconectado | Desenchufar el DS18B20 y el SHT31, uno por vez | Tras 3 ciclos: SIN_DATOS y alarma; vuelve a la normalidad al reconectar |
| Alarma con histéresis | Enfriar la superficie hasta que el margen baje de 1 °C y dejar entibiar | Se activa en 1,0 °C y se libera en 2,0 °C, sin parpadeos intermedios |
| Buffer lleno | Acelerar el ciclo (5 s) y dejar correr más de 144 muestras | El más antiguo se sobrescribe; el volcado sale en orden cronológico |
Desborde de millis() | Simular con una marca cercana a 2³² | Las restas sin signo mantienen los tiempos correctos |
Si las lecturas no coinciden, el método es el de siempre: aislar la capa. Primero el valor crudo de cada sensor por serie; luego el resultado de la validación (¿se aceptó?, ¿cuántas fallas lleva?); después el cálculo del punto de rocío; por último la decisión.
10 · Paso 9
Fallas y robustez
- Un sensor muere. Tres rechazos seguidos lo declaran en falla; la estación pasa a
SIN_DATOSy avisa. Importante: no sigue usando un valor viejo indefinidamente. - Un sensor «se pega». Un valor que nunca cambia en horas es sospechoso. Se puede agregar un contador de lecturas idénticas consecutivas (las mediciones con ruido nunca son idénticas).
- Cambio real confundido con pico. Una puerta que se abre puede mover la temperatura varios grados en minutos. La regla de «aceptar tras tres rechazos» lo resuelve a costa de un retardo de tres ciclos.
- El bus I2C queda trabado. Un sensor que mantiene SDA en bajo bloquea la comunicación. Un temporizador de vigilancia (watchdog) o un reinicio del bus se encargan de recuperarlo.
- Falta de energía. Al volver la tensión, el programa arranca de cero: el historial en SRAM se pierde. Si importa, se vuelcan registros a la EEPROM o a una SD cada cierto tiempo.
- Deriva del sensor de humedad. Con los meses, la humedad se desvía. Un procedimiento de verificación periódica (con sales saturadas o un instrumento patrón) es parte del mantenimiento.
- Interpretación de la alarma. Una alarma sin acción es ruido. Define qué se hace (ventilar, calentar la tubería, avisar a una persona) y registra cuándo ocurrió.
Una pregunta de diseño que conviene hacerse antes de dar por terminada la estación: ¿cómo me entero de que la estación misma dejó de funcionar? Una estación apagada es la peor, porque no alarma. La solución habitual es un «latido» (heartbeat) que se informa regularmente; si deja de llegar, alguien lo nota.
11 · Analogía con programación
Simular un día de la estación
La lógica de validación, filtrado, punto de rocío y alarma se puede probar sin hardware. Simulamos 24 horas con una muestra cada 10 minutos: el aire sigue un ciclo diario de temperatura con un punto de rocío constante de 12 °C (de modo que la humedad relativa sube cuando enfría y baja cuando calienta), una superficie con su propia oscilación, ruido en los sensores, un pico espurio en la muestra 40 y una falla del SHT31 en la muestra 90. Comparamos una alarma de un solo umbral con la que usa histéresis.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
import math
A, B = 17.62, 243.12
def p_sat(t):
return 6.112 * math.exp(A * t / (B + t))
def punto_rocio(t, h):
g = math.log(h / 100) + A * t / (B + t)
return B * g / (A - g)
def azar(semilla):
estado = semilla
while True:
estado = (1103515245 * estado + 12345) % 2**31
yield estado / 2**31
def normal(generador):
return sum(next(generador) for _ in range(12)) - 6
# ---- Un día de datos: una muestra cada 10 minutos ----
N = 144
generador = azar(11)
muestras = []
for i in range(N):
fase = 2 * math.pi * i / N
t_aire = 21 + 4 * math.sin(fase - 2.2) # ciclo diario
humedad = 100 * p_sat(12.0) / p_sat(t_aire) # el punto de rocío del aire es 12 °C
t_sup = 14.0 + 1.5 * math.sin(fase - 0.8) # superficie fría
muestras.append([t_aire + 0.1 * normal(generador),
humedad + 0.5 * normal(generador),
t_sup + 0.15 * normal(generador)])
muestras[40][0] += 15 # pico espurio en la temperatura
muestras[90][0] = muestras[90][1] = None # el SHT31 no responde
# ---- Validación y filtro ----
ALFA, SALTO_MAX = 0.3, 5.0
ultimo, filtrado_t, filtrado_h = None, None, None
validas = rechazadas = 0
temperaturas, humedades, rocios = [], [], []
simple = histeresis = 0 # activaciones de cada tipo de alarma
minutos_simple = minutos_hist = 0
previa_simple = previa_hist = False
for t, h, t_sup in muestras:
ok = t is not None and h is not None and -40 <= t <= 85 and 0 <= h <= 100
if ok and ultimo is not None and abs(t - ultimo) > SALTO_MAX:
ok = False
if ok:
validas += 1
ultimo = t
filtrado_t = t if filtrado_t is None else filtrado_t + ALFA * (t - filtrado_t)
filtrado_h = h if filtrado_h is None else filtrado_h + ALFA * (h - filtrado_h)
else:
rechazadas += 1
rocio = punto_rocio(filtrado_t, filtrado_h)
temperaturas.append(filtrado_t)
humedades.append(filtrado_h)
rocios.append(rocio)
margen = t_sup - rocio
activa_simple = margen <= 1.0 # un solo umbral
if activa_simple and not previa_simple:
simple += 1
previa_simple = activa_simple
minutos_simple += 10 if activa_simple else 0
if not previa_hist and margen <= 1.0: # con histéresis: 1,0 para activar y 2,0 para liberar
previa_hist = True
histeresis += 1
elif previa_hist and margen >= 2.0:
previa_hist = False
minutos_hist += 10 if previa_hist else 0
print(f"Lecturas válidas: {validas}, rechazadas: {rechazadas}")
print(f"Temperatura filtrada: mín {min(temperaturas):.2f} máx {max(temperaturas):.2f} media {sum(temperaturas) / N:.2f} °C")
print(f"Humedad filtrada: mín {min(humedades):.1f} máx {max(humedades):.1f} %")
print(f"Punto de rocío medio: {sum(rocios) / N:.2f} °C")
print(f"Alarma de un umbral: {simple} activaciones, {minutos_simple} min en alarma")
print(f"Alarma con histéresis: {histeresis} activaciones, {minutos_hist} min en alarma")
Resultado: 142 lecturas válidas y 2 rechazadas (el pico de la muestra 40 y la falla de la 90). La temperatura filtrada va de 16,99 a 25,00 °C con media de 20,97 °C; la humedad de 44,5 a 72,3 %; el punto de rocío medio es 11,98 °C (el real, 12,0). La alarma de un solo umbral se activa 5 veces (380 minutos en alarma) porque el ruido hace temblar el margen; con histéresis, 2 veces (530 minutos): menos avisos y episodios más largos y continuos. Probá cambiar ALFA a 0.1 y observá cómo cambian las cifras.
La histéresis no reduce el riesgo: reduce los falsos cambios de estado. Por eso los minutos totales en alarma son mayores: los episodios tardan más en darse por terminados. El ejemplo omite deliberadamente la regla de «aceptar tras tres rechazos» y el estado «sin datos», que están en el código C; sirve para ver el efecto del filtro y de la alarma.
12 · Exploración interactiva
La estación virtual
La estación virtual ejecuta la lógica del taller: cada medición equivale a 10 minutos simulados. Fijá el ambiente (temperatura, humedad, calor del sensor, temperatura de la superficie), pedí mediciones de a una o dejá que corran solas, e inyectá fallas: un pico espurio, el SHT31 o el DS18B20 desconectados, un reinicio con el valor de arranque de 85,0 °C. Mirá cómo reaccionan la validación, el estado y el registro.
Estación de temperatura y humedad
Presioná «Una medición» para empezar.
| Hora | T aire (°C) | HR (%) | Rocío (°C) | T sup. (°C) | Estado |
|---|---|---|---|---|---|
| Sin mediciones todavía. | |||||
Recorrido guiado
- Con los valores iniciales (22 °C, 55 %, superficie a 16 °C), dá cuatro mediciones. La primera queda en «SIN DATOS»: el DS18B20 entrega el 85,0 °C de arranque y se rechaza. Desde la segunda: el punto de rocío es unos 12,5 °C, el margen unos 3,5 °C y el estado «OK». Los valores filtrados convergen en pocas mediciones.
- Bajá la temperatura de la superficie a 13,5 °C: el margen cae a unos 1,0 °C (0,96) y la estación pasa a «RIESGO». Subila a 14,5 °C: el margen es de unos 1,96 °C y el riesgo persiste, porque la liberación exige 2,0 °C. Subila a 15 °C: el margen llega a unos 2,5 °C y se libera.
- Inyectá un pico en la temperatura y dá una medición: aparece una lectura rechazada y la temperatura filtrada casi no se mueve.
- Marcá «DS18B20 desconectado» y dá tres mediciones: tras el tercer rechazo el estado pasa a «SIN DATOS» y se enciende el LED. Desmarcá la casilla: la estación se recupera sola con la primera lectura buena.
- Hacé lo mismo con el SHT31. Observá que el estado «SIN DATOS» llega aunque el otro sensor esté bien: sin los dos canales no se puede evaluar el riesgo.
- Presioná «Reiniciar la estación» y dá una medición: el primer valor del DS18B20 sería 85,0 °C (el valor de arranque) y la estación lo rechaza; en la siguiente medición ya lee la temperatura real.
- Subí el calentamiento del SHT31 a 3 °C: la humedad filtrada cae unos 10 puntos, pero el punto de rocío calculado se mantiene casi igual. Una buena razón para evaluar el riesgo con el punto de rocío y no con la humedad.
- Dejá correr el modo automático un rato largo: el registro llega a 144 y se mantiene en 144 (sobrescribe el más antiguo).
Desafío: el aviso que no tiembla
Con el aire en 22 °C y 55 %, llevá la superficie a 13,0 °C hasta que aparezca «RIESGO» (margen cercano a 0,5 °C). Luego subila a 14,0 °C: el margen es de unos 1,5 °C, por encima del umbral de activación pero por debajo del de liberación, y la estación sigue en «RIESGO» aunque el ruido mueva el margen unas décimas en cada medición. Con una alarma de un solo umbral en 1,0 °C habría vuelto a «OK». La banda entre 1,0 y 2,0 °C es lo que mantiene estable la decisión.
13 · Método de trabajo
Lo aprendido, para cualquier estación de medición
- Escribí los requisitos como preguntas que la estación debe responder («¿puede condensar?»), no como lista de sensores.
- Separá en capas: adquisición, validación, cálculo, decisión, registro. Cada una se prueba sola.
- Planificá los tiempos con la dinámica del fenómeno y la conversión del sensor más lento; no bloquees nunca.
- Un mismo código de validación para todos los canales, con los límites como parámetros.
- Tres estados, no dos: normal, riesgo y sin datos. El silencio de un sensor nunca debe parecer «todo bien».
- Usá histéresis en toda decisión que cruce un umbral con datos ruidosos.
- Acotá el registro: buffer circular, memoria contada, escrituras de EEPROM repartidas.
- Inyectá fallas a propósito y verificá la reacción antes de dar por buena la estación.
- Pensá en cómo se entera alguien de que la estación dejó de funcionar.
Este esquema es el esqueleto del proyecto del curso 7 (estación de medición de bajo consumo) y de la mayor parte de los equipos de monitoreo: cambia lo que se mide, no la estructura.
14 · Comprensión
Confusiones frecuentes
«Basta con leer los sensores y mostrar los números»
Eso es una demostración, no una estación. Sin validación, un sensor desconectado muestra 0, −127 o 85 °C como si fuera una temperatura, y un pico se registra como un dato real.
«Si no llegan datos, mantengo el último valor»
Mantenerlo indefinidamente oculta la falla. Hay que tolerar un par de ciclos y luego declarar «sin datos» de manera visible.
«La histéresis elimina los avisos falsos de riesgo»
Elimina los cambios de estado repetidos por ruido, no el riesgo real. El tiempo total en alarma incluso puede aumentar, porque se tarda más en liberar.
«Un buffer circular guarda toda la historia»
Guarda las últimas N muestras. Lo viejo se sobrescribe. Para conservar más, hay que volcar a un medio persistente a tiempo.
«Con la humedad relativa alcanza para vigilar la condensación»
La condensación depende de la temperatura de la superficie respecto del punto de rocío. Una humedad del 60 % es inofensiva si la pared está tibia y peligrosa si está fría.
«Esperar con delay(750) es lo más simple»
Funciona en una demostración, pero bloquea el programa durante cada conversión: no atiende el puerto serie ni otras tareas. El ciclo de dos fases con millis() cuesta pocas líneas más.
15 · Práctica guiada
Predecí antes de comprobar
1. Memoria del registro
Cada registro ocupa 6 bytes (tres enteros de 16 bits) y se guardan 144. ¿Cuántos bytes son y qué parte de la SRAM del ATmega328P (2 KB) ocupa? ¿Cuántos ocuparía un registro horario de 24 horas?
Ver solución
144 × 6 = 864 bytes, un 42 % de los 2048. Un registro horario de 24 muestras ocupa 24 × 6 = 144 bytes (7 %): la resolución temporal se reduce a cambio de memoria.
2. Vida de la EEPROM
Se guarda un registro cada hora. La EEPROM aguanta unos 100 000 escrituras por celda. ¿Cuánto dura si siempre se escribe en la misma celda, y si se rota entre 24?
Ver solución
Una celda: 100 000 / (24 × 365 = 8 760 por año) ≈ 11,4 años. Con 24 celdas rotando, cada una recibe una escritura cada 24 horas: 24 × 11,4 ≈ 274 años.
3. Seguir la alarma
La alarma se activa con margen ≤ 1,0 °C y se libera con margen ≥ 2,0 °C. Los márgenes de siete mediciones consecutivas son 3,0 · 1,8 · 0,9 · 1,5 · 2,2 · 1,0 · 2,5. ¿Cuál es el estado después de cada una (empieza en OK)?
Ver solución
OK, OK, RIESGO, RIESGO, OK, RIESGO, OK. Con 1,8 no se activa (es mayor que 1,0); con 0,9 se activa; con 1,5 sigue (todavía no llega a 2,0); con 2,2 se libera; con 1,0 se activa de nuevo (≤ 1,0); con 2,5 se libera. Un único umbral de 1,0 habría dado OK, OK, RIESGO, OK, OK, RIESGO, OK: cambia de estado en la cuarta medición.
4. Pico y cambio real
Las temperaturas del aire son 24,1 · 24,2 · 39,0 · 24,3, con un salto máximo permitido de 5 °C respecto de la última lectura aceptada. ¿Cuáles se aceptan? ¿Qué cambiaría si se comparara con la última lectura recibida?
Ver solución
Se aceptan 24,1, 24,2 y 24,3; se rechaza 39,0 (salto de 14,8 °C). Si se comparara con la última recibida, 24,3 se rechazaría también (salto de 14,7 con respecto a 39,0), así que un pico contagiaría a la lectura siguiente. Por eso se compara con la última aceptada.
5. Filtro exponencial a mano
Con α = 0,3, la temperatura filtrada es 24,0 °C y llegan dos lecturas de 26,0 °C. ¿Cuánto vale la salida después de cada una? ¿Cuánto tarda en seguir un cambio, en minutos, con ciclos de 10 minutos?
Ver solución
Primera: 24 + 0,3 × (26 − 24) = 24,6 °C. Segunda: 24,6 + 0,3 × (26 − 24,6) = 25,02 °C. La constante de tiempo es (1 − α)/α ≈ 2,3 muestras, es decir, unos 23 minutos: el filtro sigue un cambio con ese retardo.
6. Riesgo de condensación
El aire está a 22,0 °C con 65 % de humedad y la superficie a 16,0 °C. ¿Cuál es el punto de rocío y el estado de la alarma?
Ver solución
γ = ln(0,65) + 17,62 × 22 / (243,12 + 22) = −0,4308 + 1,4623 = 1,0315; Trocío = 243,12 × 1,0315 / (17,62 − 1,0315) ≈ 15,1 °C. El margen es 16,0 − 15,1 = 0,9 °C, menor que 1,0: la alarma pasa a RIESGO.
16 · Síntesis
Ideas para recordar
- Una estación de medición se organiza en capas: adquisición, validación y filtro, cálculo derivado, decisión y registro.
- El ciclo de medición tiene dos fases (iniciar y recoger) y no bloquea: se controla con
millis()y la conversión más lenta fija el tiempo de espera. - La validación rechaza lo imposible (
NAN, fuera de rango, 85,0 de arranque), limita saltos respecto de la última lectura aceptada y acepta un cambio sostenido tras tres rechazos. - Un mismo código de validación y filtrado sirve para todos los canales, con los límites como parámetros.
- El riesgo de condensación se evalúa con el margen entre la temperatura de la superficie y el punto de rocío, no con la humedad relativa.
- La alarma usa histéresis (activa en 1,0 °C, libera en 2,0 °C) para no temblar con el ruido.
- Hay tres estados, no dos: OK, RIESGO y SIN DATOS; el silencio de un sensor nunca se interpreta como normalidad.
- El registro es un buffer circular acotado; las estadísticas se llevan con acumuladores; las escrituras en EEPROM se reparten para no gastarla.
- Se verifica con valores conocidos y con inyección de fallas, y se piensa cómo detectar que la propia estación se detuvo.
¿Listo para el bloque 4?
Antes de avanzar, explicá por qué la comparación del salto se hace con la última lectura aceptada, calculá si una superficie a 16 °C corre riesgo con aire a 22 °C y 65 % de humedad, y justificá por qué la estación necesita un estado «sin datos». Si podés justificar las tres respuestas, tenés el patrón de un sistema de monitoreo robusto.
En el próximo bloque pasamos a la luz y el color, empezando por las fotorresistencias (LDR) y los fotodiodos: sensores analógicos de una magnitud muy variable, donde la respuesta del ojo y la del sensor no coinciden.