Filtragem de sensores com Arduino: média móvel, mediana, EMA e Kalman
Filtragem de sensores com Arduino: média móvel, filtro de mediana, EMA, histerese, taxa de amostragem, comparação no Plotter Serial e introdução ao filtro de Kalman.
A filtragem de sensores com Arduino transforma os valores instáveis e oscilantes vindos de analogRead() ou de um sensor de distância em um número estável e confiável que seu projeto pode usar. Ver 511, 513, 510, 514 no Monitor Serial sem tocar no potenciômetro, um HC-SR04 que de vez em quando indica 3 cm ou 400 cm sem motivo, uma luz noturna que pisca no limite entre claro e escuro: tudo isso são manifestações do mesmo problema. Na maioria das vezes, o sensor não está com defeito; ruído elétrico, erros de quantização do ADC ou um eco incorreto ocasional interferiram na leitura. A boa notícia: um filtro de poucas linhas resolve a maioria desses problemas.
Neste guia, você verá primeiro os tipos de ruído e as primeiras medidas a tomar no circuito. Depois, vamos programar cinco métodos, começando pelo mais simples: média de várias amostras, média móvel, filtro de mediana que elimina picos, filtro exponencial (EMA) que precisa de pouquíssima memória e histerese, que estabiliza as decisões nos limites. Vamos compará-los lado a lado no Plotter Serial do Arduino IDE, discutir como escolher a taxa de amostragem e apresentar brevemente o filtro de Kalman. As últimas seções abordam o ADC ruidoso do ESP32 e o código em MicroPython para o Raspberry Pi Pico.

Por que os dados dos sensores têm ruído?
É enganoso pensar no erro de medição como uma coisa só. Tipos diferentes de ruído precisam de filtros diferentes, então primeiro você precisa identificar seu problema. Para isso, basta imprimir os valores brutos no Monitor Serial por um tempo e observar os números. Se os valores ficam oscilando em torno de determinado número, há ruído aleatório; se a maioria das leituras está correta, mas de vez em quando chega um valor completamente absurdo, há picos; se o valor muda lentamente, o problema não é ruído, mas deriva.
| Tipo de ruído | Como aparece | Causa típica | Solução adequada |
|---|---|---|---|
| Ruído aleatório | O valor oscila em ±2–10 contagens | Ondulação da alimentação, fios longos, ruído do próprio ADC | Cálculo da média, média móvel, EMA |
| Pico | Um valor ocasional muito distante do esperado | Eco perdido no HC-SR04, mau contato, interferência do motor | Filtro de mediana |
| Erro de degrau (quantização) | O valor alterna entre dois números vizinhos | Resolução do ADC (5 V / 1.024 = 4,9 mV no Uno) | Cálculo da média, histerese |
| Ruído periódico | Ondulação regular | Cintilação de lâmpadas a 100 Hz, rede elétrica de 50 Hz | Média ao longo de múltiplos de 20 ms |
| Deriva | O valor muda lentamente ao longo de minutos | Temperatura, queda da tensão da bateria | Calibração e uma referência estável, não um filtro |
Primeiro o circuito: reduza o ruído na origem
Um filtro por software não consegue recuperar informações que o circuito nunca captou, então a primeira tarefa é dificultar a entrada de ruído. Mantenha os fios dos sensores analógicos curtos e afastados dos fios de componentes que consomem muita corrente, como motores e relés. Um capacitor cerâmico de 100 nF entre a entrada analógica e o GND, colocado perto do sensor, absorve boa parte das interferências rápidas. A frequência de corte de um filtro passa-baixa RC formado por um resistor e um capacitor é dada por f = 1 / (2πRC): 10 kΩ e 1 µF resultam em cerca de 16 Hz, suprimindo mudanças mais rápidas. Você pode calcular os valores com a ferramenta Constante de tempo RC. Em projetos com motores, também ajuda levar o GND dos sensores à placa por um caminho separado do GND do driver do motor e alimentar a placa com uma fonte estável. Explicamos como o ADC funciona e como a tensão de referência o afeta em O que é um ADC?.
Montagem de teste
Vamos experimentar o código deste artigo com três sensores simples. O potenciômetro é ideal para observar ruído aleatório, o LDR para ruído periódico e decisões por limite, e o HC-SR04 para picos. Ligue o LDR entre 5 V e o pino analógico e o resistor de 10 kΩ entre o pino analógico e o GND, para que a leitura aumente conforme a luz fica mais intensa. Para saber mais, veja os artigos sobre o sensor de luz LDR e o potenciômetro.
| Conexão | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Cursor do potenciômetro | A0 | GPIO 34 | GP26 (ADC0) |
| Ponto central do divisor LDR + 10 kΩ | A1 | GPIO 35 | GP27 (ADC1) |
| HC-SR04 TRIG | D9 | GPIO 5 | GP2 |
| HC-SR04 ECHO | D10 | GPIO 18 (com divisor) | GP3 (com divisor) |
| LED ou relé (exemplo de histerese) | D8 | - | - |
| Alimentação | Potenciômetro e LDR 5V, HC-SR04 5V | Potenciômetro e LDR 3V3, HC-SR04 5V | Potenciômetro e LDR 3V3, HC-SR04 VBUS |
1. Média de várias amostras
O filtro mais simples consiste em fazer várias leituras seguidas e usar sua média. O ruído aleatório desloca cada leitura em uma direção diferente, por isso se anula na soma. A estatística tem uma regra para isso: na média de N leituras independentes, o ruído diminui por um fator de aproximadamente √N. 4 amostras reduzem o ruído à metade e 16 amostras a um quarto; 64 amostras o reduzem a um oitavo, mas levam quatro vezes mais tempo. No Arduino Uno, uma única analogRead() leva cerca de 112 µs, então uma média de 16 amostras fica pronta em menos de 2 ms. As únicas desvantagens desse método são ocupar brevemente o processador a cada leitura e ser pouco eficaz contra picos: se uma das 16 amostras for absurda, ela também compromete a média.
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. Média móvel
Uma média móvel mantém a média das últimas N leituras e, a cada nova leitura, descarta a mais antiga e adiciona a nova. Assim, você faz uma leitura por ciclo, mas ainda aproveita uma média de N leituras. Em vez de somar N números toda vez, o código mantém uma variável sum: subtrai o valor que sai e adiciona o que entra. Assim, o cálculo tem a mesma velocidade, independentemente do tamanho da janela. O custo da média móvel é o atraso: a saída fica cerca de (N−1)/2 leituras atrás do valor real. Uma janela de 10 leituras amostradas a cada 10 ms tem um atraso de cerca de 45 ms; isso não importa para um potenciômetro ou para temperatura, mas pode ser percebido em um robô rápido.
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}| Janela (N) | Redução do ruído | Atraso (uma leitura a cada 10 ms) | Indicado para |
|---|---|---|---|
| 4 | Cerca de 2× | 15 ms | Controle que exige resposta rápida |
| 10 | Cerca de 3× | 45 ms | Potenciômetro, joystick, LDR |
| 32 | Cerca de 5,7× | 155 ms | Temperatura, umidade, tensão da bateria |
| 100 | 10× | 495 ms | Valores que mudam muito lentamente, indicação estável no display |
3. Filtro de mediana: eliminação de picos
O cálculo da média é pouco eficaz contra picos. Se o HC-SR04 ler 50 cm quatro vezes e depois perder o eco uma vez e ler 0, a média das cinco leituras será 40 cm; esse valor não é correto nem evidencia o erro. Já um filtro de mediana ordena as últimas N leituras da menor para a maior e pega a do meio. No mesmo exemplo, a lista ordenada é 0, 50, 50, 50, 50 e o valor central é 50; a leitura incorreta não chega ao resultado. Um filtro de mediana de 5 elementos elimina completamente até 2 leituras incorretas seguidas. A segunda propriedade interessante da mediana é que ela não suaviza uma mudança real e repentina como a média faz: quando um obstáculo realmente se aproxima, o valor salta para o novo nível em um degrau bem definido após poucas leituras, em vez de formar uma curva suave. O código abaixo faz uma leitura do HC-SR04 a cada 60 ms e imprime a mediana das últimas 5. Para saber mais, veja o artigo sobre o sensor de distância 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): suavização com pouca memória
A média móvel exponencial (EMA) não usa buffer; ela só guarda o resultado anterior. A cada nova leitura, o resultado se aproxima dela por uma pequena fração da diferença entre ambos: y = y + α × (x − y). Aqui, x é a nova leitura, y é o valor filtrado e α (alfa) é um coeficiente entre 0 e 1. Quanto menor α, mais estável, porém mais lento, o filtro se torna. Ele é chamado de exponencial porque a influência de cada leitura diminui exponencialmente com o tempo. É o equivalente por software do filtro passa-baixa RC da eletrônica. A constante de tempo da EMA é aproximadamente τ ≈ T / α (T é o intervalo de amostragem): com uma leitura a cada 10 ms e α = 0,1, o filtro atinge 63 por cento de uma mudança repentina em cerca de 100 ms e 95 por cento em cerca de 300 ms. O código tem uma versão com ponto flutuante e outra com inteiros, que roda mais rápido em placas pequenas como o Uno; a versão com inteiros usa α = 1/16 e um deslocamento (>>) em vez de uma divisão.
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) | Janela equivalente aproximada | Tempo de resposta de 63% (amostragem de 10 ms) | Comportamento |
|---|---|---|---|
| 0.5 | 3 | 10–20 ms | Muito rápido, pouca suavização |
| 0.2 | 9 | 50 ms | Rápido, suavização leve |
| 0.1 | 19 | 100 ms | Equilibrado, um bom ponto de partida para a maioria dos sensores |
| 0.05 | 39 | 200 ms | Estável, lento |
| 0.01 | 199 | 1 s | Muito estável, apenas para valores que mudam lentamente |
A “janela equivalente” da tabela é o tamanho aproximado de uma média móvel que oferece a mesma suavização e é calculada como N ≈ 2/α − 1. A maior vantagem da EMA é a memória: uma média móvel de 100 elementos precisa de 200 bytes de RAM, enquanto a EMA precisa de uma única variável. Se você precisa filtrar vários sensores nos 2 KB de RAM do Arduino Uno, essa diferença importa. Sua desvantagem é ser tão pouco eficaz contra picos quanto a média; um pico grande influencia o resultado por várias leituras.
Comparação dos filtros no Plotter Serial
A diferença entre filtros é difícil de perceber nos números, mas fica evidente em um gráfico. O código abaixo lê o potenciômetro a 100 Hz, passa a mesma leitura por quatro caminhos diferentes e imprime todos no formato label:value. Para deixar a diferença mais clara, o código adiciona um pico artificial a cerca de 3 por cento das leituras; remova essa linha em um projeto real. No gráfico, você verá que a mediana elimina completamente os picos, enquanto a média móvel e a EMA os transformam em pequenas elevações.
- Carregue o código e feche o Monitor Serial se estiver aberto; o Monitor Serial e o Plotter Serial não podem ficar abertos ao mesmo tempo.
- No Arduino IDE, abra Ferramentas > Plotter Serial ou clique no ícone de gráfico no canto superior direito.
- Ajuste a velocidade no canto inferior direito para 115200 baud.
- Gire o potenciômetro devagar e depois rapidamente, observando como cada linha responde.
- Use as caixas ao lado dos rótulos para ativar e desativar linhas e comparar dois filtros diretamente.
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}No gráfico, os rótulos aparecem coloridos no canto superior direito. Se você não usar vírgula entre os rótulos e dois-pontos entre o rótulo e o valor, o Plotter Serial não conseguirá separar as linhas corretamente. O plotter antigo do Arduino IDE 1.8 tem suporte limitado a rótulos; se houver problemas, envie apenas números separados por vírgulas. Para problemas de porta serial e baud rate, confira o artigo sobre o Monitor Serial do Arduino.
5. Histerese: evitando decisões que oscilam no limite
Um filtro estabiliza o valor, mas sozinho não resolve o problema da decisão. Uma luz noturna com a regra “ligar a lâmpada quando o valor de luminosidade cair abaixo de 350” liga e desliga várias vezes por segundo ao anoitecer, conforme o valor oscila entre 349 e 351. Isso desgasta rapidamente os contatos de um relé. A solução é a histerese: usar dois limites separados para ligar e desligar. Por exemplo, a lâmpada liga quando o valor cai abaixo de 300, mas ele precisa subir acima de 400 para ela desligar. A “zona morta” de 100 contagens entre eles impede que pequenas flutuações alterem a decisão. O código abaixo combina histerese com uma EMA e um curto tempo de confirmação: a condição precisa permanecer sem interrupção por 1 segundo, para que uma sombra passageira ou a luz momentânea de faróis também não alterem a decisão.
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 definir os limites, observe os valores do ambiente ao longo do dia no Plotter Serial. A zona morta deve ser pelo menos duas a três vezes mais larga que a oscilação do valor filtrado. O mesmo método é usado em termostatos (por exemplo, desligar o aquecedor a 22 °C e ligá-lo a 20 °C), em bombas de reservatórios de água e em circuitos de proteção de bateria. O debounce de botões também é, na prática, um tipo de tempo de confirmação; veja os detalhes em Como ligar um botão.
Como escolher a taxa de amostragem
Para um filtro funcionar bem, o sensor precisa ser lido na frequência adequada. Se você ler devagar demais, perde mudanças rápidas; se ler rápido demais, o sensor devolve o mesmo valor repetidamente ou ainda nem está pronto. A regra básica é a regra de Nyquist: amostrar a uma frequência pelo menos duas vezes maior que a mudança mais rápida que você quer acompanhar; na prática, 5–10 vezes é mais seguro. Alguns sensores também têm seu próprio limite mínimo de intervalo, e lê-los mais rápido não traz benefício. Se a cintilação da luz for relevante, lembre que, em uma rede de 50 Hz, as lâmpadas piscam 100 vezes por segundo. A maneira mais fácil de anular essa cintilação em um LDR ou outro sensor analógico é fazer leituras igualmente espaçadas durante um período que seja múltiplo de 20 ms e calcular a média; como a janela cobre períodos inteiros da rede elétrica, a cintilação se anula.
| Sensor | Intervalo de leitura recomendado | Observação |
|---|---|---|
| DHT11 / DHT22 | 1 s / 2 s | Ler com mais frequência retorna o mesmo valor ou um erro |
| DS18B20 (12 bits) | 750 ms | Esse é o tempo de conversão |
| HC-SR04 | Pelo menos 60 ms | Espere o eco anterior desaparecer |
| Potenciômetro, joystick | 10–20 ms | 50–100 Hz são mais que suficientes para movimentos humanos |
| LDR (para suprimir a cintilação de lâmpadas) | A cada 1 ms durante 20 ms, depois calcular a média | A cintilação da rede elétrica de 50 Hz se anula |
| MPU6050 | 5–10 ms | Uma faixa comum para medição de movimento a 100–200 Hz |
Uma breve introdução ao filtro de Kalman
O filtro de Kalman considera quanto ruído existe na medição e com que rapidez o valor real pode mudar, ajustando seu próprio ganho. Em sua forma unidimensional mais simples, ele tem dois parâmetros: R é a variância do ruído de medição (quanto o sensor oscila); Q é o ruído do processo (com que rapidez você espera que o valor real mude). A cada passo, o filtro primeiro aumenta sua incerteza em Q, depois calcula o ganho K = P / (P + R) e aproxima sua estimativa da medição pela fração K. Se a medição for ruidosa (R grande), K será pequeno e o filtro confiará menos na medição; se houver expectativa de mudança rápida do valor (Q grande), K aumentará e o filtro reagirá rapidamente. Um resultado marcante: quando Q e R são fixos, K se estabiliza em uma constante após algum tempo e o filtro de Kalman unidimensional se torna, na prática, uma EMA. A verdadeira força do Kalman aparece ao estimar várias grandezas juntas, por exemplo, combinar dados do acelerômetro e do giroscópio para obter um ângulo de inclinação. Você encontrará seu parente mais simples, o filtro complementar, no artigo sobre o 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}É fácil estimar R: faça 100 leituras brutas sem tocar no potenciômetro e observe a dispersão dos valores. Se eles variarem cerca de ±3 contagens em torno da média, o desvio padrão será de aproximadamente 3 e a variância, de aproximadamente 9. Encontre Q por experimentação: um Q pequeno (0,001) produz uma saída muito estável, porém lenta; um Q grande (1), uma saída rápida, mas mais oscilante. Como ponto de partida, escolha Q entre 1 e 10 por cento de R e faça o ajuste fino no Plotter Serial enquanto gira o potenciômetro.
Ruído do ADC e filtragem no ESP32
O ADC de 12 bits do ESP32 distingue degraus menores que o do Arduino Uno, mas é mais ruidoso e não linear, especialmente nas extremidades de 0–0,1 V e 3,1–3,3 V. Com o Wi-Fi ligado, o ruído aumenta ainda mais. Duas medidas facilitam a vida: usar analogReadMilliVolts(), que aplica a calibração de fábrica do chip em vez de retornar um valor bruto, e calcular a média de 16–64 amostras por leitura antes de aplicar uma EMA. O código abaixo lê o potenciômetro nessas duas etapas; em projetos que usam Wi-Fi, sempre ligue o sensor aos pinos 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}Filtragem com Raspberry Pi Pico e MicroPython
No Raspberry Pi Pico, ADC.read_u16() retorna um valor entre 0 e 65.535, mas a resolução real do ADC é de 12 bits e os bits menos significativos têm ruído. No MicroPython, a função sorted() torna a mediana muito simples, e uma lista com uma soma acumulada basta para uma média móvel. O código abaixo lê o potenciômetro a 100 Hz e imprime a saída de três filtros em um formato que o Thonny pode desenhar na janela Exibir > 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)O plotter do Thonny mostra os números de cada linha como curvas separadas e usa o texto antes dos números como rótulos. O MicroPython roda mais devagar que o Arduino, então não force os filtros a taxas muito altas; cerca de 100 Hz com janelas pequenas funciona com folga no Pico. Se você está começando com MicroPython, comece pelo artigo Fundamentos de MicroPython.
Qual filtro escolher?
| Situação | Filtro sugerido | Por quê? |
|---|---|---|
| Um potenciômetro, joystick ou sensor analógico oscila | Média móvel ou EMA (α = 0,1–0,2) | Simples, rápido, suavização suficiente |
| Um valor absurdo chega de vez em quando (HC-SR04, sensor de fluxo) | Mediana de 3 ou 5 pontos | Elimina completamente o erro ocasional |
| Picos e oscilações juntos | Primeiro mediana, depois EMA | Resolve os dois problemas em sequência |
| Pouquíssima RAM, muitos sensores | EMA com inteiros | Uma variável por sensor |
| Uma decisão de ligar/desligar oscila no limite | Histerese e um tempo de confirmação | Resolve o problema da decisão; um filtro sozinho não basta |
| Ondulação de lâmpadas ou da rede elétrica | Média de leituras igualmente espaçadas ao longo de 20 ms | Cobre um período inteiro de 50 Hz |
| Combinação de vários sensores (inclinação, posição) | Filtro de Kalman ou complementar | Combina os pontos fortes de cada sensor |
Erros comuns e soluções
| Sintoma | Causa provável | Solução |
|---|---|---|
| O valor filtrado responde com atraso excessivo | A janela é grande demais ou α é pequeno demais | Diminua a janela ou aumente α; calcule o atraso pelas tabelas |
| A média móvel sobe lentamente a partir de zero na inicialização | O buffer começa preenchido com zeros | Preencha o buffer com a primeira leitura ou divida pela contagem até ele ficar cheio |
| A média fica descontrolada com 64 amostras | A soma do tipo int estourou (64 × 1.023 = 65.472 > 32.767) | Use o tipo long para a variável de soma |
| O filtro de mediana não muda nada | A janela é 2 ou um número par, ou os picos chegam em sequência | Use uma janela ímpar (3, 5, 7) maior que o número de picos consecutivos |
| As linhas do Plotter Serial ficam unidas ou todas da mesma cor | Formato incorreto dos rótulos | Use label:value com vírgulas entre os pares e termine a linha com println |
| O valor da EMA nunca muda | Divisão inteira: 1/10 escrito em vez de 0.1 | Escreva o coeficiente como float (0.1) ou use a fórmula da EMA com inteiros |
| Os valores continuam oscilando apesar do filtro | O problema não é ruído, mas deriva ou uma alimentação fraca | Verifique a alimentação, adicione um capacitor de 100 nF e calibre |
A filtragem de sensores é a maneira mais barata de tornar as medições confiáveis: não exige componentes extras, e poucas linhas de código deixam seus projetos mais estáveis. Agora que você sabe combinar o cálculo da média, a média móvel, a mediana, a EMA e a histerese, displays instáveis, alarmes falsos e relés trepidando ficam para trás. No app BlueGrays, você pode explorar projetos como medição da intensidade da luz com LDR, luz noturna, medição de distância com HC-SR04, sensor de estacionamento e medidor de altura, com esquemas do circuito passo a passo e códigos prontos para usar, e observar os resultados ao adicionar esses filtros.
Perguntas frequentes
Por que os valores dos sensores no Arduino ficam mudando?
Nas leituras analógicas, a ondulação da alimentação, os fios longos, os motores próximos e o ruído do próprio ADC deslocam o valor em algumas contagens. Isso é normal e não significa que o sensor esteja com defeito. Adicionar um capacitor de 100 nF à entrada analógica e usar um filtro de média ou EMA por software estabiliza os valores.
O que é uma média móvel?
Uma média móvel é a média das últimas N leituras; a cada nova leitura, o valor mais antigo é removido e o novo é adicionado. Ela reduz o ruído por um fator de aproximadamente √N, mas atrasa a saída em (N−1)/2 leituras. No Arduino, bastam poucas linhas com um buffer circular e uma única variável de soma.
Qual é a diferença entre um filtro de mediana e uma média?
Uma média considera todos os valores, então uma única leitura absurda compromete o resultado. Um filtro de mediana ordena os valores e pega o do meio, ignorando completamente leituras incorretas ocasionais. Por isso, a mediana é melhor para sensores como o HC-SR04, que às vezes fornecem valores errados, e a média é melhor para leituras analógicas oscilantes.
Como escolher o coeficiente de um filtro exponencial (EMA)?
O coeficiente α fica entre 0 e 1; um α pequeno produz uma saída mais estável, porém mais lenta, e um α grande produz uma saída mais rápida, porém mais oscilante. Com uma leitura a cada 10 ms, α = 0,1 resulta em uma constante de tempo de cerca de 100 ms e é um bom ponto de partida para a maioria dos sensores. Você pode calcular o tempo de resposta com τ ≈ T / α.
Como traçar vários valores no Plotter Serial do Arduino?
Imprima os valores em cada linha no formato label:value, separados por vírgulas, e termine a linha com println, por exemplo Ham:512,EMA:509. O Plotter Serial mostra cada rótulo como uma linha colorida separada. Feche primeiro o Monitor Serial se estiver aberto e ajuste o baud rate para corresponder ao código.
É possível usar um filtro de Kalman no Arduino?
Sim, um filtro de Kalman unidimensional simples roda com poucas linhas até em um Arduino Uno. Ele é ajustado com o ruído de medição R e o ruído do processo Q; para um único sensor, seus resultados costumam ser muito próximos aos de uma EMA bem ajustada. Seu verdadeiro benefício aparece ao combinar vários sensores, como um acelerômetro e um giroscópio.
Projetos para montar depois desta leitura
Ver todos os projetos
Medição da intensidade da luz com LDR
Quer ver a luminosidade do seu quarto em números? O LDR, um pequeno componente sensível à luz, mede a luz ambiente, e o Arduino exibe o valor na tela do computador a cada meio segundo. Cubra o sensor

Medição de distância com HC-SR04 (Monitor Serial)
O sensor HC-SR04 funciona como um morcego que se orienta no escuro: emite um som que você não consegue ouvir, escuta o eco e mede a distância até o objeto à sua frente. O Arduino escreve essa distânci

Luz noturna com LDR
Uma pequena luz noturna que acende sozinha quando escurece e apaga ao amanhecer. Um sensor de luz mede a luminosidade do quarto, e o Arduino acende o LED quando está escuro o suficiente e apaga quando

Sensor de estacionamento com HC-SR04
Uma versão em miniatura do sensor de estacionamento de um carro: quanto mais perto um objeto chega, mais rápidos ficam os bipes. O sensor de distância mede a distância até o objeto à sua frente; se el

Projeto de medição de altura
Um medidor de altura preso ao batente da porta que informa, em centímetros, a altura de quem está embaixo dele. Primeiro, você pressiona o primeiro botão sem ninguém embaixo, e o sensor de distância m

Uso de servo motor com potenciômetro
Conforme você gira o botão do potenciômetro, o braço do servo motor gira junto: uma extremidade do botão corresponde a zero grau, e a outra, a meia volta. O Arduino lê continuamente a posição do botão

Leitura da intensidade da luz com módulo LDR e ESP32
Acompanhe em tempo real no Monitor Serial o valor bruto do ADC do módulo LDR e a porcentagem relativa de luz calculada a partir de referências de escuridão e claridade. Essa porcentagem não é uma medida em lux. O projeto ensina a lógica de calibração do módulo. É ideal fazê-lo antes do projeto 10, pois ajuda a determinar o valor do limiar.

Medidor ultrassônico de distância HC-SR04 com ESP32
O sensor ultrassônico HC-SR04 calcula a distância medindo o tempo que a onda sonora emitida leva para atingir um objeto e retornar (tempo de voo). Neste projeto, enviaremos um pulso de 10 microssegundos do ESP32 ao pino TRIG do sensor e leremos a duração do eco no pino ECHO com pulseIn(). Exibiremos a distância calculada em centímetros em um LCD I2C 16x2. Como o HC-SR04 opera a 5V, é preciso usar um divisor de tensão no pino ECHO; caso contrário, o GPIO de 3,3V do ESP32 será danificado.
Mais artigos
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

