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.

¿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 ruido | Cómo se ve | Causa típica | Solución adecuada |
|---|---|---|---|
| Ruido aleatorio | El valor tiembla ±2–10 puntos | Alimentación inestable, cables largos, ruido propio del ADC | Media, media móvil, EMA |
| Pico (spike) | De vez en cuando un valor muy distinto | Eco perdido en el HC-SR04, contacto flojo, interferencia de motores | Filtro de mediana |
| Error de escalón (cuantización) | El valor salta entre dos números vecinos | Resolución del ADC (5 V / 1024 = 4,9 mV en el Uno) | Media, histéresis |
| Ruido periódico | Ondulación regular | Parpadeo de 100 Hz de las lámparas, red de 50 Hz | Media durante múltiplos de 20 ms |
| Deriva (drift) | El valor cambia despacio en minutos | Temperatura, tensión de batería que baja | Calibració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ón | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Cursor del potenciómetro | A0 | GPIO 34 | GP26 (ADC0) |
| Punto medio LDR + divisor de 10 kΩ | A1 | GPIO 35 | GP27 (ADC1) |
| HC-SR04 TRIG | D9 | GPIO 5 | GP2 |
| HC-SR04 ECHO | D10 | GPIO 18 (con divisor) | GP3 (con divisor) |
| LED o relé (ejemplo de histéresis) | D8 | - | - |
| Alimentación | Pot y LDR 5V, HC-SR04 5V | Pot y LDR 3V3, HC-SR04 5V | Pot y LDR 3V3, HC-SR04 VBUS |
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.
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.
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 ruido | Retardo (una lectura cada 10 ms) | Uso adecuado |
|---|---|---|---|
| 4 | Unas 2 veces | 15 ms | Control que necesita respuesta rápida |
| 10 | Unas 3 veces | 45 ms | Potenciómetro, joystick, LDR |
| 32 | Unas 5,7 veces | 155 ms | Temperatura, humedad, tensión de batería |
| 100 | 10 veces | 495 ms | Valores 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.
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}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.
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 aproximada | Tiempo de respuesta al 63 % (muestreo de 10 ms) | Carácter |
|---|---|---|---|
| 0,5 | 3 | 10–20 ms | Muy rápido, poco suavizado |
| 0,2 | 9 | 50 ms | Rápido, suavizado ligero |
| 0,1 | 19 | 100 ms | Equilibrado, buen punto de partida para casi todos los sensores |
| 0,05 | 39 | 200 ms | Tranquilo, lento |
| 0,01 | 199 | 1 s | Muy 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.
- 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.
- En el Arduino IDE abre Herramientas > Serial Plotter o pulsa el icono de gráfica de arriba a la derecha.
- Pon la velocidad de abajo a la derecha en 115200 baudios.
- Gira el potenciómetro despacio y después deprisa, y observa cómo responde cada línea.
- Activa y desactiva las líneas con las casillas junto a las etiquetas para comparar dos filtros directamente.
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.
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.
| Sensor | Intervalo de lectura recomendado | Nota |
|---|---|---|
| DHT11 / DHT22 | 1 s / 2 s | Leyendo más a menudo obtienes el mismo valor o un error |
| DS18B20 (12 bits) | 750 ms | Es lo que tarda la conversión |
| HC-SR04 | Al menos 60 ms | Hay que esperar a que se apague el eco anterior |
| Potenciómetro, joystick | 10–20 ms | 50–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 media | El parpadeo de la red de 50 Hz se cancela |
| MPU6050 | 5–10 ms | Rango 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.
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.
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).
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ón | Filtro recomendado | ¿Por qué? |
|---|---|---|
| Un potenciómetro, joystick o sensor analógico tiembla | Media 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 5 | Elimina por completo los errores aislados |
| Hay picos y temblor a la vez | Primero mediana, luego EMA | Resuelve los dos problemas por orden |
| Muy poca RAM y muchos sensores | EMA con enteros | Una variable por sensor |
| Una decisión de encender o apagar parpadea en el umbral | Histéresis y tiempo de confirmación | Resuelve la decisión; un filtro solo no basta |
| Ondulación por lámparas o por la red | Media de lecturas repartidas durante 20 ms | Cubre un periodo completo de 50 Hz |
| Combinar varios sensores (inclinación, posición) | Filtro de Kalman o complementario | Une los puntos fuertes de cada sensor |
Errores frecuentes y soluciones
| Síntoma | Causa probable | Solución |
|---|---|---|
| El valor filtrado responde demasiado tarde | Ventana demasiado grande o α demasiado pequeña | Reduce la ventana o sube α; calcula el retardo con las tablas |
| La media móvil sube despacio desde cero al arrancar | El búfer empieza lleno de ceros | Rellena el búfer con la primera lectura o divide por el número de datos hasta llenarlo |
| La media se vuelve loca con 64 muestras | La suma int se desbordó (64 × 1023 = 65 472 > 32 767) | Declara la variable suma como long |
| El filtro de mediana no cambia nada | Ventana de 2 o par, o los picos llegan seguidos | Usa 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 color | Formato de etiquetas incorrecto | Usa etiqueta:valor con comas entre medias y termina la línea con println |
| El valor de la EMA no cambia nunca | División entera: se escribió 1/10 en vez de 0.1 | Escribe el coeficiente como float (0.1) o usa la fórmula de EMA con enteros |
| Los valores oscilan pese al filtro | El problema no es ruido sino deriva o alimentación débil | Revisa la alimentación, añade un condensador de 100 nF y calibra |
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.
Proyectos para hacer después de leer esto
Ver todos los proyectos
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

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

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

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

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

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

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.

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
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

