Fundamentos · 15 min de lectura

Filtrar datos de sensor en Arduino: media, mediana, EMA y Kalman

Filtrar datos de sensor en Arduino: media móvil, filtro de mediana, EMA, histéresis, muestreo, comparación en el Serial Plotter e introducción a Kalman.

Filtrar los datos de un sensor en Arduino consiste en convertir los valores temblorosos y saltarines que llegan de analogRead() o de un sensor de distancia en un número tranquilo y fiable que tu proyecto pueda usar. Ver 511, 513, 510, 514 en el Monitor Serie sin tocar el potenciómetro, un HC-SR04 que de vez en cuando mide 3 cm o 400 cm sin motivo, una luz nocturna que parpadea en el límite entre oscuro y claro: todo son caras del mismo problema. Casi nunca el sensor está estropeado; en la medida se han colado ruido eléctrico, errores de escalón del ADC o algún eco falso. La buena noticia: un filtro de pocas líneas resuelve la mayoría de estos problemas.

En esta guía verás primero los tipos de ruido y las primeras medidas en el lado del circuito. Después programaremos cinco métodos, empezando por el más sencillo: la media de varias muestras, la media móvil (moving average), el filtro de mediana que elimina picos, el filtro exponencial (EMA), que apenas usa memoria, y la histéresis, que calma los umbrales inestables. Los dibujaremos todos juntos en el Serial Plotter del Arduino IDE, hablaremos de cómo elegir la frecuencia de muestreo y haremos una breve introducción al filtro de Kalman. Las últimas secciones tratan el ADC ruidoso del ESP32 y el código MicroPython para la Raspberry Pi Pico.

Sensor de distancia ultrasónico HC-SR04
Sensores como el HC-SR04 dan alguna lectura errónea de vez en cuando; el filtro de mediana elimina esos picos.

¿Por qué los datos de los sensores tienen ruido?

Pensar en el error de medida como una sola cosa es engañoso. Cada tipo de ruido pide un filtro distinto, así que primero hay que saber cuál es tu problema. Para eso basta con imprimir valores en bruto en el Monitor Serie un rato y mirar los números. Si los valores tiemblan sin parar alrededor de un número, hay ruido aleatorio; si casi todas las medidas son buenas pero de vez en cuando llega un valor absurdo, hay picos (spikes); si el valor se desplaza poco a poco, el problema no es ruido sino deriva.

Tipo de ruidoCómo se veCausa típicaSolución adecuada
Ruido aleatorioEl valor tiembla ±2–10 puntosAlimentación inestable, cables largos, ruido propio del ADCMedia, media móvil, EMA
Pico (spike)De vez en cuando un valor muy distintoEco perdido en el HC-SR04, contacto flojo, interferencia de motoresFiltro de mediana
Error de escalón (cuantización)El valor salta entre dos números vecinosResolución del ADC (5 V / 1024 = 4,9 mV en el Uno)Media, histéresis
Ruido periódicoOndulación regularParpadeo de 100 Hz de las lámparas, red de 50 HzMedia durante múltiplos de 20 ms
Deriva (drift)El valor cambia despacio en minutosTemperatura, tensión de batería que bajaCalibración y referencia estable, no un filtro

Primero el circuito: reduce el ruido en su origen

Un filtro por software no puede recuperar información que el circuito nunca captó, así que lo primero es ponérselo difícil al ruido. Usa cables cortos para los sensores analógicos y aléjalos de los cables de piezas que consumen mucha corriente, como motores y relés. Un condensador cerámico de 100 nF entre la entrada analógica y GND, cerca del sensor, absorbe gran parte de las interferencias rápidas. La frecuencia de corte de un filtro paso bajo RC hecho con una resistencia y un condensador se calcula con f = 1 / (2πRC): 10 kΩ y 1 µF dan unos 16 Hz, así que se atenúan los cambios más rápidos. Puedes calcular los valores con la herramienta constante de tiempo RC. En proyectos con motores también ayuda llevar el GND de los sensores a la placa por un camino distinto al del driver del motor y alimentar la placa con una fuente estable. Explicamos cómo funciona el ADC y cómo influye la tensión de referencia en ¿Qué es un ADC?.

Montaje de prueba

Probaremos el código de este artículo con tres sensores sencillos. El potenciómetro es ideal para ver ruido aleatorio, la LDR para ruido periódico y decisiones con umbral, y el HC-SR04 para los picos. Conecta la LDR entre 5 V y el pin analógico y la resistencia de 10 kΩ entre el pin analógico y GND; así la lectura sube cuanta más luz hay. Tienes los detalles en los artículos del sensor de luz LDR y del potenciómetro.

ConexiónArduino UnoESP32Raspberry Pi Pico
Cursor del potenciómetroA0GPIO 34GP26 (ADC0)
Punto medio LDR + divisor de 10 kΩA1GPIO 35GP27 (ADC1)
HC-SR04 TRIGD9GPIO 5GP2
HC-SR04 ECHOD10GPIO 18 (con divisor)GP3 (con divisor)
LED o relé (ejemplo de histéresis)D8--
AlimentaciónPot y LDR 5V, HC-SR04 5VPot y LDR 3V3, HC-SR04 5VPot y LDR 3V3, HC-SR04 VBUS
En el ESP32 y la Pico conecta el potenciómetro y el divisor de la LDR a 3V3; los pines analógicos nunca deben recibir más de 3,3 V. El HC-SR04 funciona a 5 V y su salida ECHO da 5 V, así que en el ESP32 y la Pico baja el pin ECHO a 3,3 V con un divisor de tensión de 1 kΩ y 2 kΩ. Puedes comprobar los valores del divisor con la herramienta Divisor de tensión.

1. Media de varias muestras

El filtro más sencillo consiste en tomar varias lecturas seguidas y usar su media. El ruido aleatorio desvía cada lectura hacia un lado distinto, así que en la suma se compensa. La estadística tiene una regla para esto: en la media de N lecturas independientes, el ruido baja aproximadamente √N veces. 4 muestras reducen el ruido a la mitad y 16 a la cuarta parte; 64 lo dejan en la octava parte, pero tardan cuatro veces más. En el Arduino Uno un analogRead() tarda unos 112 µs, así que una media de 16 muestras se hace en menos de 2 ms. Sus únicos defectos son que ocupa el procesador un momento en cada lectura y que es débil frente a los picos: si una de las 16 muestras es absurda, estropea también la media.

C++ (Arduino IDE)
1const int POT_PIN = A0;
2
3// Art arda "adet" ölçüm al, ortalamasını döndür / take "adet" readings in a row, return the average
4int ortalamaOku(int pin, int adet) {
5  long toplam = 0;                  // taşmasın diye long / long to avoid overflow
6  for (int i = 0; i < adet; i++) {
7    toplam += analogRead(pin);
8    delayMicroseconds(100);         // ölçümler arasında kısa ara / short gap between readings
9  }
10  return toplam / adet;
11}
12
13void setup() {
14  Serial.begin(115200);
15}
16
17void loop() {
18  int ham = analogRead(POT_PIN);
19  int ort4 = ortalamaOku(POT_PIN, 4);
20  int ort16 = ortalamaOku(POT_PIN, 16);
21  // Serial Plotter için etiket:değer biçimi / label:value format for the Serial Plotter
22  Serial.print("Ham:");
23  Serial.print(ham);
24  Serial.print(",Ort4:");
25  Serial.print(ort4);
26  Serial.print(",Ort16:");
27  Serial.println(ort16);
28  delay(20);
29}

2. Media móvil (moving average)

La media móvil guarda la media de las últimas N lecturas y, con cada lectura nueva, descarta la más antigua y añade la nueva. Así haces una sola lectura por vuelta, pero sigues aprovechando una media de N lecturas. En lugar de sumar N números cada vez, el código lleva una variable suma: resta el valor que sale y suma el que entra. Por eso el cálculo va igual de rápido sea cual sea el tamaño de la ventana. El precio de la media móvil es el retardo: la salida va unas (N−1)/2 lecturas por detrás del valor real. Una ventana de 10 con una lectura cada 10 ms se retrasa unos 45 ms; para un potenciómetro o una temperatura no importa, en un robot rápido se nota.

C++ (Arduino IDE)
1const int POT_PIN = A0;
2const int PENCERE = 10;            // son 10 ölçüm / last 10 readings
3
4int tampon[PENCERE];               // dairesel tampon / circular buffer
5int sira = 0;
6long toplam = 0;
7bool dolu = false;
8
9int hareketliOrtalama(int yeni) {
10  toplam -= tampon[sira];          // en eski değeri çıkar / remove the oldest value
11  tampon[sira] = yeni;
12  toplam += yeni;                  // yeni değeri ekle / add the new value
13  sira = (sira + 1) % PENCERE;
14  if (sira == 0) dolu = true;
15  return dolu ? toplam / PENCERE : toplam / sira;   // ilk turda bölen küçük / smaller divisor at start
16}
17
18unsigned long sonOlcum = 0;
19
20void setup() {
21  Serial.begin(115200);
22}
23
24void loop() {
25  if (millis() - sonOlcum >= 10) { // 100 Hz örnekleme / 100 Hz sampling
26    sonOlcum = millis();
27    int ham = analogRead(POT_PIN);
28    int filtreli = hareketliOrtalama(ham);
29    Serial.print("Ham:");
30    Serial.print(ham);
31    Serial.print(",Hareketli:");
32    Serial.println(filtreli);
33  }
34}
Ventana (N)Reducción de ruidoRetardo (una lectura cada 10 ms)Uso adecuado
4Unas 2 veces15 msControl que necesita respuesta rápida
10Unas 3 veces45 msPotenciómetro, joystick, LDR
32Unas 5,7 veces155 msTemperatura, humedad, tensión de batería
10010 veces495 msValores que cambian muy despacio, visualización tranquila

3. Filtro de mediana: eliminar picos

La media es débil frente a los picos. Si el HC-SR04 mide 50 cm cuatro veces y una vez pierde el eco y mide 0, la media de las cinco es 40 cm; ese valor ni es correcto ni muestra el error. El filtro de mediana, en cambio, ordena las últimas N lecturas de menor a mayor y se queda con la del medio. En el mismo ejemplo la lista ordenada es 0, 50, 50, 50, 50 y el valor central es 50; la lectura errónea no llega al resultado. Una mediana de 5 elementos elimina por completo hasta 2 lecturas erróneas seguidas. La segunda ventaja de la mediana es que no difumina un cambio brusco y real como hace la media: cuando un obstáculo se acerca de verdad, el valor pasa al nuevo nivel en un escalón limpio en pocas lecturas, en lugar de en una curva suave. El código de abajo mide con el HC-SR04 cada 60 ms e imprime la mediana de las últimas 5 lecturas. Para más detalles, mira el artículo del sensor de distancia HC-SR04.

C++ (Arduino IDE)
1const int TRIG_PIN = 9;
2const int ECHO_PIN = 10;
3const int N = 5;                   // medyan penceresi, tek sayı olmalı / median window, must be odd
4
5int olcumler[N];
6int sira = 0;
7
8int mesafeOlc() {
9  digitalWrite(TRIG_PIN, LOW);
10  delayMicroseconds(2);
11  digitalWrite(TRIG_PIN, HIGH);
12  delayMicroseconds(10);
13  digitalWrite(TRIG_PIN, LOW);
14  unsigned long sure = pulseIn(ECHO_PIN, HIGH, 25000);   // ~4 m zaman aşımı / ~4 m timeout
15  return sure / 58;                                       // yankı yoksa 0 / 0 if no echo
16}
17
18int medyan() {
19  int kopya[N];
20  for (int i = 0; i < N; i++) kopya[i] = olcumler[i];
21  for (int i = 1; i < N; i++) {    // eklemeli sıralama / insertion sort
22    int deger = kopya[i];
23    int j = i - 1;
24    while (j >= 0 && kopya[j] > deger) {
25      kopya[j + 1] = kopya[j];
26      j--;
27    }
28    kopya[j + 1] = deger;
29  }
30  return kopya[N / 2];             // ortadaki eleman / middle element
31}
32
33void setup() {
34  Serial.begin(115200);
35  pinMode(TRIG_PIN, OUTPUT);
36  pinMode(ECHO_PIN, INPUT);
37  for (int i = 0; i < N; i++) {    // tamponu ilk ölçümlerle doldur / fill the buffer first
38    olcumler[i] = mesafeOlc();
39    delay(60);
40  }
41}
42
43void loop() {
44  int ham = mesafeOlc();
45  olcumler[sira] = ham;
46  sira = (sira + 1) % N;
47  Serial.print("Ham:");
48  Serial.print(ham);
49  Serial.print(",Medyan:");
50  Serial.println(medyan());
51  delay(60);                       // HC-SR04 için en az 60 ms / at least 60 ms for HC-SR04
52}
También puedes combinar mediana y media: primero quita los picos con una mediana de 5 y después pasa el resultado por una media móvil o una EMA para suavizar el temblor que quede. Esta pareja es la solución más habitual para medidas con picos y temblor a la vez, como las de sensores de distancia y de caudal. No hagas grande la ventana de la mediana; 3 o 5 bastan para casi todo, y 9 o más solo añaden retardo.

4. Filtro exponencial (EMA): suavizar con poca memoria

La media móvil exponencial (Exponential Moving Average, EMA) no usa ningún búfer; solo recuerda el resultado anterior. Con cada lectura nueva, el resultado se acerca a la lectura una pequeña parte de la diferencia entre ambos: y = y + α × (x − y). Aquí x es la lectura nueva, y el valor filtrado y α (alfa) un coeficiente entre 0 y 1. Cuanto menor es α, más tranquilo pero más lento es el filtro. Se llama exponencial porque la influencia de cada lectura se apaga de forma exponencial con el tiempo. Es la versión por software del filtro paso bajo RC de la electrónica. La constante de tiempo de la EMA es aproximadamente τ ≈ T / α (T es el intervalo de muestreo): con una lectura cada 10 ms y α = 0,1, el filtro alcanza el 63 por ciento de un cambio brusco en unos 100 ms y el 95 por ciento en unos 300 ms. El código incluye una versión con coma flotante y otra con enteros que va más rápida en placas pequeñas como el Uno; la versión entera usa α = 1/16 y un desplazamiento (>>) en lugar de una división.

C++ (Arduino IDE)
1const int LDR_PIN = A1;
2const float ALFA = 0.1;            // 0-1 arası; küçük = daha sakin / 0-1; smaller = smoother
3
4float ema = -1;                    // henüz başlamadı / not started yet
5long emaX16 = -1;                  // tamsayılı EMA x16 / integer EMA times 16
6
7unsigned long sonOlcum = 0;
8
9void setup() {
10  Serial.begin(115200);
11}
12
13void loop() {
14  if (millis() - sonOlcum >= 10) {
15    sonOlcum = millis();
16    int ham = analogRead(LDR_PIN);
17
18    if (ema < 0) ema = ham;        // ilk değerle başlat / start from the first reading
19    ema += ALFA * (ham - ema);     // y = y + a(x - y)
20
21    if (emaX16 < 0) emaX16 = (long)ham << 4;
22    emaX16 += ham - (emaX16 >> 4); // alfa = 1/16, float yok / alpha = 1/16, no float
23
24    Serial.print("Ham:");
25    Serial.print(ham);
26    Serial.print(",EMA:");
27    Serial.print(ema, 1);
28    Serial.print(",EMA_int:");
29    Serial.println(emaX16 >> 4);
30  }
31}
α (alfa)Ventana equivalente aproximadaTiempo de respuesta al 63 % (muestreo de 10 ms)Carácter
0,5310–20 msMuy rápido, poco suavizado
0,2950 msRápido, suavizado ligero
0,119100 msEquilibrado, buen punto de partida para casi todos los sensores
0,0539200 msTranquilo, lento
0,011991 sMuy tranquilo, solo para valores que cambian despacio

La "ventana equivalente" de la tabla es el tamaño aproximado de una media móvil que da el mismo suavizado y se calcula con N ≈ 2/α − 1. La gran ventaja de la EMA es la memoria: una media móvil de 100 elementos necesita 200 bytes de RAM, mientras que la EMA usa una sola variable. Si tienes que filtrar varios sensores en los 2 KB de RAM del Arduino Uno, esa diferencia importa. Su inconveniente es que frente a los picos es tan débil como la media; un pico grande arrastra el resultado durante varias lecturas.

Comparar filtros en el Serial Plotter

La diferencia entre filtros cuesta verla en números, pero en una gráfica salta a la vista. El código de abajo lee el potenciómetro a 100 Hz, pasa la misma lectura por cuatro caminos distintos y lo imprime todo con el formato etiqueta:valor. Para que la diferencia se vea mejor, el código añade un pico artificial a cerca del 3 por ciento de las lecturas; en un proyecto real debes borrar esa línea. En la gráfica verás que la mediana elimina los picos por completo, mientras que la media móvil y la EMA los convierten en pequeños bultos.

  1. Carga el código y cierra el Monitor Serie si está abierto; el Monitor Serie y el Serial Plotter no pueden estar abiertos a la vez.
  2. En el Arduino IDE abre Herramientas > Serial Plotter o pulsa el icono de gráfica de arriba a la derecha.
  3. Pon la velocidad de abajo a la derecha en 115200 baudios.
  4. Gira el potenciómetro despacio y después deprisa, y observa cómo responde cada línea.
  5. Activa y desactiva las líneas con las casillas junto a las etiquetas para comparar dos filtros directamente.
C++ (Arduino IDE)
1const int POT_PIN = A0;
2
3// Hareketli ortalama / moving average
4const int PENCERE = 10;
5int maTampon[PENCERE];
6int maSira = 0;
7long maToplam = 0;
8
9int hareketliOrtalama(int yeni) {
10  maToplam += yeni - maTampon[maSira];
11  maTampon[maSira] = yeni;
12  maSira = (maSira + 1) % PENCERE;
13  return maToplam / PENCERE;
14}
15
16// 5'lik medyan / median of 5
17int mdTampon[5];
18int mdSira = 0;
19
20int medyan5(int yeni) {
21  mdTampon[mdSira] = yeni;
22  mdSira = (mdSira + 1) % 5;
23  int a[5];
24  for (int i = 0; i < 5; i++) a[i] = mdTampon[i];
25  for (int i = 1; i < 5; i++) {    // eklemeli sıralama / insertion sort
26    int d = a[i], j = i - 1;
27    while (j >= 0 && a[j] > d) { a[j + 1] = a[j]; j--; }
28    a[j + 1] = d;
29  }
30  return a[2];
31}
32
33// Üstel filtre / exponential filter
34float ema = 0;
35const float ALFA = 0.1;
36
37unsigned long sonOlcum = 0;
38
39void setup() {
40  Serial.begin(115200);
41  int ilk = analogRead(POT_PIN);
42  for (int i = 0; i < PENCERE; i++) maTampon[i] = ilk;   // tamponları doldur / prefill buffers
43  maToplam = (long)ilk * PENCERE;
44  for (int i = 0; i < 5; i++) mdTampon[i] = ilk;
45  ema = ilk;
46  randomSeed(analogRead(A5));
47}
48
49void loop() {
50  if (millis() - sonOlcum < 10) return;   // 100 Hz
51  sonOlcum = millis();
52
53  int ham = analogRead(POT_PIN);
54  if (random(100) < 3) {                  // TEST: %3 yapay sıçrama, projede sil / TEST: 3% fake spikes, remove in a project
55    ham = constrain(ham + random(-400, 400), 0, 1023);
56  }
57  ema += ALFA * (ham - ema);
58
59  Serial.print("Ham:");
60  Serial.print(ham);
61  Serial.print(",Hareketli:");
62  Serial.print(hareketliOrtalama(ham));
63  Serial.print(",Medyan:");
64  Serial.print(medyan5(ham));
65  Serial.print(",EMA:");
66  Serial.println(ema, 1);
67}

En la gráfica, las etiquetas aparecen en color arriba a la derecha. Si no pones una coma entre etiquetas y dos puntos entre etiqueta y valor, el Serial Plotter no puede separar bien las líneas. El trazador antiguo del Arduino IDE 1.8 tiene un soporte limitado de etiquetas; si te da problemas, envía solo números separados por comas. Para problemas con el puerto serie y la velocidad en baudios, echa un vistazo al artículo del Monitor Serie de Arduino.

5. Histéresis: acabar con las decisiones que parpadean en el umbral

El filtro calma el valor, pero por sí solo no resuelve el problema de la decisión. Una luz nocturna que dice "enciende la lámpara cuando el valor de luz baje de 350" se enciende y apaga varias veces por segundo al atardecer, mientras el valor oscila entre 349 y 351. Eso desgasta enseguida los contactos de un relé. La solución es la histéresis: se usan dos umbrales distintos para encender y para apagar. Por ejemplo, la lámpara se enciende cuando el valor baja de 300, pero para apagarse tiene que subir por encima de 400. La "zona muerta" de 100 puntos entre ambos impide que pequeñas oscilaciones cambien la decisión. El código de abajo combina la histéresis con una EMA y un breve tiempo de confirmación: la condición debe cumplirse sin interrupción durante 1 segundo, así que tampoco cambian la decisión una sombra que pasa o un destello de faros.

C++ (Arduino IDE)
1const int LDR_PIN = A1;
2const int LAMBA_PIN = 8;           // LED ya da röle modülü / LED or relay module
3const int YAK_ESIK = 300;          // bunun altı karanlık / below this is dark
4const int SONDUR_ESIK = 400;       // bunun üstü aydınlık / above this is bright
5const unsigned long ONAY_MS = 1000;   // koşul 1 sn sürmeli / condition must last 1 s
6
7float ema = -1;
8bool lambaAcik = false;
9unsigned long kosulBasladi = 0;
10bool kosulVar = false;
11
12void setup() {
13  Serial.begin(115200);
14  pinMode(LAMBA_PIN, OUTPUT);
15}
16
17void loop() {
18  int ham = analogRead(LDR_PIN);
19  if (ema < 0) ema = ham;
20  ema += 0.05 * (ham - ema);       // yavaş EMA / slow EMA
21
22  // Durum değiştirmek için hangi koşul gerekiyor? / which condition flips the state?
23  bool degistir = lambaAcik ? (ema > SONDUR_ESIK) : (ema < YAK_ESIK);
24
25  if (degistir) {
26    if (!kosulVar) {
27      kosulVar = true;
28      kosulBasladi = millis();     // koşul şimdi başladı / condition just started
29    } else if (millis() - kosulBasladi >= ONAY_MS) {
30      lambaAcik = !lambaAcik;      // 1 sn sürdü, kararı değiştir / lasted 1 s, flip the decision
31      digitalWrite(LAMBA_PIN, lambaAcik ? HIGH : LOW);
32      kosulVar = false;
33    }
34  } else {
35    kosulVar = false;              // koşul bozuldu, sayacı sıfırla / condition broke, reset the timer
36  }
37
38  Serial.print("EMA:");
39  Serial.print(ema, 0);
40  Serial.print(",Yak:");
41  Serial.print(YAK_ESIK);
42  Serial.print(",Sondur:");
43  Serial.print(SONDUR_ESIK);
44  Serial.print(",Lamba:");
45  Serial.println(lambaAcik ? 600 : 100);   // grafikte görünsün / visible on the plot
46  delay(10);
47}

Antes de fijar los umbrales, observa en el Serial Plotter los valores de la habitación a lo largo del día. La zona muerta debe ser al menos dos o tres veces más ancha que el temblor del valor filtrado. El mismo método se usa en termostatos (por ejemplo, apagar la calefacción a 22 °C y encenderla a 20 °C), en bombas de depósitos de agua y en circuitos de protección de baterías. El antirrebote de los botones es en el fondo un tiempo de confirmación; tienes los detalles en Cómo conectar un botón.

¿Cómo se elige la frecuencia de muestreo?

Para que un filtro funcione bien hay que leer el sensor a la velocidad adecuada. Si lees demasiado despacio, te pierdes los cambios rápidos; si lees demasiado deprisa, el sensor devuelve el mismo valor una y otra vez o ni siquiera está listo. La regla básica es la regla de Nyquist: muestrea al menos al doble de la frecuencia del cambio más rápido que quieras seguir; en la práctica, de 5 a 10 veces es más seguro. Algunos sensores tienen además su propio límite y leerlos más rápido no sirve de nada. Si mides con luz de lámparas, ojo: en una red de 50 Hz las lámparas parpadean 100 veces por segundo. La forma más fácil de anular ese parpadeo en una LDR u otro sensor analógico es tomar lecturas repartidas por igual durante un tiempo que sea múltiplo de 20 ms y promediarlas; como la ventana abarca periodos completos de la red, el parpadeo se cancela solo.

SensorIntervalo de lectura recomendadoNota
DHT11 / DHT221 s / 2 sLeyendo más a menudo obtienes el mismo valor o un error
DS18B20 (12 bits)750 msEs lo que tarda la conversión
HC-SR04Al menos 60 msHay que esperar a que se apague el eco anterior
Potenciómetro, joystick10–20 ms50–100 Hz bastan de sobra para movimientos humanos
LDR (para eliminar el parpadeo de lámparas)Cada 1 ms durante 20 ms y luego la mediaEl parpadeo de la red de 50 Hz se cancela
MPU60505–10 msRango habitual para medir movimiento a 100–200 Hz

Breve introducción al filtro de Kalman

El filtro de Kalman tiene en cuenta cuánto ruido tiene la medida y lo rápido que puede cambiar el valor real, y ajusta por sí mismo su ganancia. En su forma unidimensional más sencilla tiene dos ajustes: R es la varianza del ruido de medida (cuánto tiembla el sensor) y Q el ruido del proceso (lo rápido que esperas que cambie el valor real). En cada paso, el filtro aumenta primero su incertidumbre en Q, calcula después la ganancia K = P / (P + R) y mueve su estimación hacia la medida en la proporción K. Si la medida tiene mucho ruido (R grande), K es pequeña y el filtro se fía poco de la medida; si se espera que el valor cambie deprisa (Q grande), K crece y el filtro reacciona rápido. Un resultado curioso: si Q y R no cambian, K se estabiliza al cabo de un rato en un valor fijo y el Kalman unidimensional se convierte en la práctica en una EMA. La verdadera fuerza de Kalman aparece cuando estima varias magnitudes a la vez, por ejemplo al combinar acelerómetro y giroscopio para hallar un ángulo de inclinación. Su pariente más sencillo, el filtro complementario, lo encontrarás en el artículo del MPU6050.

C++ (Arduino IDE)
1const int POT_PIN = A0;
2
3struct Kalman1D {
4  float q;      // süreç gürültüsü / process noise
5  float r;      // ölçüm gürültüsü (varyans) / measurement noise (variance)
6  float p;      // tahminin belirsizliği / estimate uncertainty
7  float x;      // tahmin / estimate
8  bool ilk;
9};
10
11Kalman1D kf = {0.05, 9.0, 1.0, 0.0, true};   // r = 9: yaklaşık ±3 basamak titreşim / about ±3 counts of jitter
12
13float kalmanGuncelle(Kalman1D &k, float olcum) {
14  if (k.ilk) { k.x = olcum; k.ilk = false; return k.x; }
15  k.p += k.q;                          // 1. tahmin: belirsizlik artar / predict: uncertainty grows
16  float kazanc = k.p / (k.p + k.r);    // 2. Kalman kazancı / Kalman gain
17  k.x += kazanc * (olcum - k.x);       // 3. ölçüme doğru düzelt / correct toward the measurement
18  k.p *= (1.0 - kazanc);               // 4. belirsizlik azalır / uncertainty shrinks
19  return k.x;
20}
21
22void setup() {
23  Serial.begin(115200);
24}
25
26void loop() {
27  int ham = analogRead(POT_PIN);
28  float tahmin = kalmanGuncelle(kf, ham);
29  Serial.print("Ham:");
30  Serial.print(ham);
31  Serial.print(",Kalman:");
32  Serial.println(tahmin, 1);
33  delay(10);
34}

R es fácil de estimar: toma 100 lecturas en bruto sin tocar el potenciómetro y mira cuánto se dispersan. Si rondan ±3 puntos alrededor de la media, la desviación típica es de unos 3 y la varianza de unos 9. Q se encuentra probando: una Q pequeña (0,001) da una salida muy tranquila pero lenta, y una Q grande (1) una salida rápida pero más temblorosa. Para empezar, elige Q entre el 1 y el 10 por ciento de R y afínala en el Serial Plotter girando el potenciómetro.

Ruido del ADC y filtrado en el ESP32

El ADC de 12 bits del ESP32 mide con más detalle que el del Arduino Uno, pero tiene más ruido y no es lineal, sobre todo en los extremos de 0–0,1 V y 3,1–3,3 V. Con el WiFi encendido el ruido aumenta todavía más. Dos cosas facilitan el trabajo: usar analogReadMilliVolts(), que aplica la calibración de fábrica del chip en lugar de devolver un valor en bruto, y promediar 16–64 muestras en cada lectura antes de aplicar una EMA. El código de abajo lee el potenciómetro con estos dos pasos; en proyectos con WiFi conecta siempre el sensor a los pines ADC1, GPIO 32–39.

C++ (Arduino IDE)
1const int POT_PIN = 34;            // ADC1 pini, Wi-Fi ile çalışır / ADC1 pin, works with Wi-Fi
2const int ORNEK = 32;              // her ölçümde 32 örnek / 32 samples per reading
3const float ALFA = 0.15;
4
5float ema = -1;
6
7int millivoltOku() {
8  uint32_t toplam = 0;
9  for (int i = 0; i < ORNEK; i++) {
10    toplam += analogReadMilliVolts(POT_PIN);   // kalibre mV / calibrated mV
11  }
12  return toplam / ORNEK;
13}
14
15void setup() {
16  Serial.begin(115200);
17  analogReadResolution(12);        // 0-4095
18}
19
20void loop() {
21  int ham = analogRead(POT_PIN);   // karşılaştırma için tek ham ölçüm / single raw reading for comparison
22  int mv = millivoltOku();
23  if (ema < 0) ema = mv;
24  ema += ALFA * (mv - ema);
25  Serial.printf("Ham:%d,mV:%d,EMA_mV:%.1f\n", ham, mv, ema);
26  delay(20);
27}

Filtrado con la Raspberry Pi Pico y MicroPython

En la Raspberry Pi Pico, ADC.read_u16() devuelve un valor entre 0 y 65535, pero la resolución real del ADC es de 12 bits y los bits bajos tienen ruido. En MicroPython, la función sorted() hace muy fácil la mediana, y para la media móvil basta con una lista y una suma acumulada. El código de abajo lee el potenciómetro a 100 Hz e imprime la salida de tres filtros en un formato que Thonny puede dibujar en su ventana Ver > Trazador (Plotter).

MicroPython
1from machine import ADC
2import time
3
4pot = ADC(26)                     # GP26 = ADC0
5
6PENCERE = 10
7tampon = [pot.read_u16()] * PENCERE   # hareketli ortalama tamponu / moving average buffer
8toplam = sum(tampon)
9sira = 0
10son5 = tampon[:5]                 # medyan için son 5 değer / last 5 values for the median
11ema = float(tampon[0])
12ALFA = 0.1
13
14while True:
15    ham = pot.read_u16()
16
17    toplam += ham - tampon[sira]  # hareketli ortalama / moving average
18    tampon[sira] = ham
19    sira = (sira + 1) % PENCERE
20    hareketli = toplam // PENCERE
21
22    son5.pop(0)                   # medyan / median
23    son5.append(ham)
24    medyan = sorted(son5)[2]
25
26    ema += ALFA * (ham - ema)     # üstel filtre / exponential filter
27
28    print("Ham:", ham, "Hareketli:", hareketli, "Medyan:", medyan, "EMA:", int(ema))
29    time.sleep_ms(10)

El trazador de Thonny muestra los números de cada línea como líneas separadas y usa el texto que va delante de los números como etiqueta. MicroPython es más lento que Arduino, así que no fuerces los filtros a frecuencias muy altas; unos 100 Hz con ventanas pequeñas funcionan con soltura en la Pico. Si eres nuevo en MicroPython, empieza por el artículo fundamentos de MicroPython.

¿Qué filtro deberías elegir?

SituaciónFiltro recomendado¿Por qué?
Un potenciómetro, joystick o sensor analógico tiemblaMedia móvil o EMA (α = 0,1–0,2)Sencillo, rápido, suavizado suficiente
De vez en cuando llega un valor absurdo (HC-SR04, caudalímetro)Mediana de 3 o 5Elimina por completo los errores aislados
Hay picos y temblor a la vezPrimero mediana, luego EMAResuelve los dos problemas por orden
Muy poca RAM y muchos sensoresEMA con enterosUna variable por sensor
Una decisión de encender o apagar parpadea en el umbralHistéresis y tiempo de confirmaciónResuelve la decisión; un filtro solo no basta
Ondulación por lámparas o por la redMedia de lecturas repartidas durante 20 msCubre un periodo completo de 50 Hz
Combinar varios sensores (inclinación, posición)Filtro de Kalman o complementarioUne los puntos fuertes de cada sensor

Errores frecuentes y soluciones

SíntomaCausa probableSolución
El valor filtrado responde demasiado tardeVentana demasiado grande o α demasiado pequeñaReduce la ventana o sube α; calcula el retardo con las tablas
La media móvil sube despacio desde cero al arrancarEl búfer empieza lleno de cerosRellena el búfer con la primera lectura o divide por el número de datos hasta llenarlo
La media se vuelve loca con 64 muestrasLa suma int se desbordó (64 × 1023 = 65 472 > 32 767)Declara la variable suma como long
El filtro de mediana no cambia nadaVentana de 2 o par, o los picos llegan seguidosUsa una ventana impar (3, 5, 7) mayor que el número de picos seguidos
Las líneas del Serial Plotter salen unidas o de un solo colorFormato de etiquetas incorrectoUsa etiqueta:valor con comas entre medias y termina la línea con println
El valor de la EMA no cambia nuncaDivisión entera: se escribió 1/10 en vez de 0.1Escribe el coeficiente como float (0.1) o usa la fórmula de EMA con enteros
Los valores oscilan pese al filtroEl problema no es ruido sino deriva o alimentación débilRevisa la alimentación, añade un condensador de 100 nF y calibra
Mira siempre los datos en bruto antes de elegir un filtro. Ver el valor en bruto unos minutos en el Serial Plotter te dice enseguida si el problema es temblor, picos, ondulación periódica o deriva. Después empieza por el filtro más sencillo y pasa al siguiente solo si hace falta. En la mayoría de los proyectos, una mediana de 5 más una EMA con α = 0,1 funcionan mejor que un Kalman complicado y se entienden mucho más fácilmente.

Filtrar los datos de los sensores es la forma más barata de conseguir medidas fiables: no necesita piezas extra y unas pocas líneas de código hacen que tus proyectos funcionen mucho más estables. Ahora que sabes combinar media, media móvil, mediana, EMA e histéresis, las pantallas temblorosas, las falsas alarmas y los relés que castañetean quedan atrás. En la app BlueGrays puedes explorar proyectos como medir la luz con una LDR, una luz nocturna, medir distancias con el HC-SR04, un sensor de aparcamiento y un medidor de altura, con esquemas paso a paso y código listo para usar, y comprobar tú mismo el resultado añadiéndoles estos filtros.

Preguntas frecuentes

¿Por qué los valores del sensor en Arduino cambian todo el rato?

En las lecturas analógicas, las variaciones de la alimentación, los cables largos, los motores cercanos y el ruido propio del ADC mueven el valor unos cuantos puntos. Es normal y no significa que el sensor esté roto. Un condensador de 100 nF en la entrada analógica y un filtro de media o EMA en el software calman los valores.

¿Qué es una media móvil?

Una media móvil es la media de las últimas N lecturas; con cada lectura nueva se quita el valor más antiguo y se añade el nuevo. Reduce el ruido unas √N veces, pero retrasa la salida (N−1)/2 lecturas. En Arduino se escribe en pocas líneas con un búfer circular y una única variable de suma.

¿Qué diferencia hay entre el filtro de mediana y la media?

La media tiene en cuenta todos los valores, así que una sola lectura absurda estropea el resultado. El filtro de mediana ordena los valores y toma el del medio, por lo que ignora por completo las lecturas erróneas aisladas. Por eso la mediana es mejor para sensores como el HC-SR04, que fallan de vez en cuando, y la media para valores analógicos temblorosos.

¿Cómo se elige el coeficiente de un filtro exponencial (EMA)?

El coeficiente α va de 0 a 1; un α pequeño da una salida más tranquila pero más lenta, y uno grande una más rápida pero más temblorosa. Con una lectura cada 10 ms, α = 0,1 da una constante de tiempo de unos 100 ms y es un buen punto de partida para la mayoría de sensores. Puedes calcular el tiempo de respuesta con τ ≈ T / α.

¿Cómo se dibujan varios valores en el Serial Plotter de Arduino?

Imprime en cada línea los valores con el formato etiqueta:valor, separados por comas, y termina la línea con println, por ejemplo Ham:512,EMA:509. El Serial Plotter muestra cada etiqueta como una línea de otro color. Si el Monitor Serie está abierto, ciérralo antes y pon la misma velocidad en baudios que en el código.

¿Se puede usar un filtro de Kalman en Arduino?

Sí, un filtro de Kalman unidimensional sencillo funciona en pocas líneas incluso en un Arduino Uno. Se ajusta con el ruido de medida R y el ruido de proceso Q; con un solo sensor, sus resultados suelen ser muy parecidos a los de una EMA bien ajustada. Su verdadera utilidad aparece al combinar varios sensores, como un acelerómetro y un giroscopio.

Ver todos los proyectos
Medir la intensidad de luz con LDR
#11Principiante

Medir la intensidad de luz con LDR

¿Quieres ver en números cuánta luz hay en tu habitación? El LDR, una pequeña pieza sensible a la luz, mide la luz ambiente y el Arduino escribe el valor en la pantalla del ordenador cada medio segundo

4.6(81)
141
HC-SR04 Medición de distancia (pantalla en serie)
#27Principiante

HC-SR04 Medición de distancia (pantalla en serie)

El sensor HC-SR04 funciona igual que un murciélago que se orienta en la oscuridad: emite un sonido que no puedes oír, escucha el eco y mide a qué distancia está el objeto que tiene delante. El Arduino

4.6(47)
74
Luz nocturna con LDR
#12Principiante

Luz nocturna con LDR

Una pequeña lamparita que se enciende sola cuando oscurece y se apaga cuando llega la mañana. Un sensor de luz mide la claridad de la habitación, y el Arduino enciende el LED cuando está lo bastante o

4.8(103)
210
Sensor de aparcamiento con HC-SR04
#28Intermedio
Premium

Sensor de aparcamiento con HC-SR04

Una versión en miniatura del sensor de aparcamiento de los coches: cuanto más se acerca algo, más rápidos son los pitidos. El sensor de distancia mide el objeto que tiene delante; si está a más de 20

4.8(64)
85
Proyecto de medición de la altura
#33Intermedio
Premium

Proyecto de medición de la altura

Un medidor de altura que se cuelga del marco de la puerta y te dice en centímetros cuánto mide quien está debajo. Primero pulsas el primer botón sin nadie debajo y el sensor de distancia mide cuánto h

4.5(25)
34
Funcionamiento del servomotor con potenciómetro
#36Principiante
Premium

Funcionamiento del servomotor con potenciómetro

A medida que giras el mando del potenciómetro, el brazo del servomotor gira con él: un extremo del mando equivale a cero grados y el otro a media vuelta. El Arduino lee sin parar la posición del mando

4.6(37)
53
Lectura de la intensidad luminosa con el módulo LDR del ESP32
#113Principiante

Lectura de la intensidad luminosa con el módulo LDR del ESP32

Visualización en tiempo real en el monitor serie del valor bruto del ADC del módulo LDR y del porcentaje relativo de luz calculado a partir de referencias de oscuridad y claridad. Este porcentaje no es una medición en lux. Explica el funcionamiento de la calibración del módulo. Es recomendable realizarlo antes del Proyecto 10, ya que se utiliza para determinar el valor umbral.

4.6(36)
55
ESP32 HC-SR04 Medidor de distancia por ultrasonidos
#116Intermedio

ESP32 HC-SR04 Medidor de distancia por ultrasonidos

El sensor ultrasónico HC-SR04 calcula la distancia midiendo el tiempo de vuelo (Time of Flight) de la onda sonora que emite al chocar contra un objeto y rebotar. En este proyecto, enviaremos un pulso de 10 microsegundos desde el ESP32 al pin TRIG del sensor y leeremos el tiempo de eco procedente del pin ECHO mediante pulseIn(). Mostraremos la distancia calculada en centímetros en una pantalla LCD I2C de 16x2. Dado que el HC-SR04 funciona a 5 V, es obligatorio utilizar un divisor de tensión

4.9(28)
46