Fundamentos · 14 min de leitura

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.

Sensor ultrassônico de distância HC-SR04
Sensores como o HC-SR04 fornecem uma leitura incorreta ocasional; um filtro de mediana elimina esses picos.

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ídoComo apareceCausa típicaSolução adequada
Ruído aleatórioO valor oscila em ±2–10 contagensOndulação da alimentação, fios longos, ruído do próprio ADCCálculo da média, média móvel, EMA
PicoUm valor ocasional muito distante do esperadoEco perdido no HC-SR04, mau contato, interferência do motorFiltro de mediana
Erro de degrau (quantização)O valor alterna entre dois números vizinhosResolução do ADC (5 V / 1.024 = 4,9 mV no Uno)Cálculo da média, histerese
Ruído periódicoOndulação regularCintilação de lâmpadas a 100 Hz, rede elétrica de 50 HzMédia ao longo de múltiplos de 20 ms
DerivaO valor muda lentamente ao longo de minutosTemperatura, queda da tensão da bateriaCalibraçã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ãoArduino UnoESP32Raspberry Pi Pico
Cursor do potenciômetroA0GPIO 34GP26 (ADC0)
Ponto central do divisor LDR + 10 kΩA1GPIO 35GP27 (ADC1)
HC-SR04 TRIGD9GPIO 5GP2
HC-SR04 ECHOD10GPIO 18 (com divisor)GP3 (com divisor)
LED ou relé (exemplo de histerese)D8--
AlimentaçãoPotenciômetro e LDR 5V, HC-SR04 5VPotenciômetro e LDR 3V3, HC-SR04 5VPotenciômetro e LDR 3V3, HC-SR04 VBUS
No ESP32 e no Pico, ligue o potenciômetro e o divisor com LDR ao 3V3; os pinos analógicos nunca devem receber mais de 3,3 V. O HC-SR04 funciona com 5 V e sua saída ECHO fornece 5 V, então, no ESP32 e no Pico, reduza o sinal do pino ECHO para 3,3 V com um divisor de tensão de 1 kΩ e 2 kΩ. Você pode conferir os valores do divisor com a ferramenta Divisor de tensão.

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.

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. 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.

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}
Janela (N)Redução do ruídoAtraso (uma leitura a cada 10 ms)Indicado para
4Cerca de 2×15 msControle que exige resposta rápida
10Cerca de 3×45 msPotenciômetro, joystick, LDR
32Cerca de 5,7×155 msTemperatura, umidade, tensão da bateria
10010×495 msValores 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.

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}
Você também pode combinar mediana e média: primeiro elimine os picos com uma mediana de 5 pontos, depois passe o resultado para uma média móvel ou EMA para suavizar a oscilação restante. Essa combinação é a solução mais comum para leituras que contêm tanto picos quanto oscilações, como as de sensores de distância e de fluxo. Não aumente demais a janela da mediana; 3 ou 5 bastam para a maioria das tarefas, e 9 ou mais acrescentam atraso sem necessidade.

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.

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)Janela equivalente aproximadaTempo de resposta de 63% (amostragem de 10 ms)Comportamento
0.5310–20 msMuito rápido, pouca suavização
0.2950 msRápido, suavização leve
0.119100 msEquilibrado, um bom ponto de partida para a maioria dos sensores
0.0539200 msEstável, lento
0.011991 sMuito 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.

  1. 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.
  2. No Arduino IDE, abra Ferramentas > Plotter Serial ou clique no ícone de gráfico no canto superior direito.
  3. Ajuste a velocidade no canto inferior direito para 115200 baud.
  4. Gire o potenciômetro devagar e depois rapidamente, observando como cada linha responde.
  5. Use as caixas ao lado dos rótulos para ativar e desativar linhas e comparar dois filtros diretamente.
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}

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.

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 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.

SensorIntervalo de leitura recomendadoObservação
DHT11 / DHT221 s / 2 sLer com mais frequência retorna o mesmo valor ou um erro
DS18B20 (12 bits)750 msEsse é o tempo de conversão
HC-SR04Pelo menos 60 msEspere o eco anterior desaparecer
Potenciômetro, joystick10–20 ms50–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édiaA cintilação da rede elétrica de 50 Hz se anula
MPU60505–10 msUma 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.

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}

É 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.

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}

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.

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)

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çãoFiltro sugeridoPor quê?
Um potenciômetro, joystick ou sensor analógico oscilaMé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 pontosElimina completamente o erro ocasional
Picos e oscilações juntosPrimeiro mediana, depois EMAResolve os dois problemas em sequência
Pouquíssima RAM, muitos sensoresEMA com inteirosUma variável por sensor
Uma decisão de ligar/desligar oscila no limiteHisterese e um tempo de confirmaçãoResolve o problema da decisão; um filtro sozinho não basta
Ondulação de lâmpadas ou da rede elétricaMédia de leituras igualmente espaçadas ao longo de 20 msCobre um período inteiro de 50 Hz
Combinação de vários sensores (inclinação, posição)Filtro de Kalman ou complementarCombina os pontos fortes de cada sensor

Erros comuns e soluções

SintomaCausa provávelSolução
O valor filtrado responde com atraso excessivoA janela é grande demais ou α é pequeno demaisDiminua a janela ou aumente α; calcule o atraso pelas tabelas
A média móvel sobe lentamente a partir de zero na inicializaçãoO buffer começa preenchido com zerosPreencha o buffer com a primeira leitura ou divida pela contagem até ele ficar cheio
A média fica descontrolada com 64 amostrasA 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 nadaA janela é 2 ou um número par, ou os picos chegam em sequênciaUse 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 corFormato incorreto dos rótulosUse label:value com vírgulas entre os pares e termine a linha com println
O valor da EMA nunca mudaDivisão inteira: 1/10 escrito em vez de 0.1Escreva o coeficiente como float (0.1) ou use a fórmula da EMA com inteiros
Os valores continuam oscilando apesar do filtroO problema não é ruído, mas deriva ou uma alimentação fracaVerifique a alimentação, adicione um capacitor de 100 nF e calibre
Sempre examine os dados brutos antes de escolher um filtro. Observar o valor bruto no Plotter Serial por alguns minutos revela imediatamente se o problema é oscilação, picos, ondulação periódica ou deriva. Depois, comece pelo filtro mais simples e só passe ao próximo quando precisar. Na maioria dos projetos, uma mediana de 5 pontos seguida de uma EMA com α = 0,1 funciona melhor que um filtro de Kalman complicado e é muito mais fácil de entender.

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.

Ver todos os projetos
Medição da intensidade da luz com LDR
#11Iniciante

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

4.6(81)
141
Medição de distância com HC-SR04 (Monitor Serial)
#27Iniciante

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

4.6(47)
74
Luz noturna com LDR
#12Iniciante

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

4.8(103)
210
Sensor de estacionamento com HC-SR04
#28Intermediário
Premium

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

4.8(64)
85
Projeto de medição de altura
#33Intermediário
Premium

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

4.5(25)
34
Uso de servo motor com potenciômetro
#36Iniciante
Premium

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

4.6(37)
53
Leitura da intensidade da luz com módulo LDR e ESP32
#113Iniciante

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.

4.6(36)
55
Medidor ultrassônico de distância HC-SR04 com ESP32
#116Intermediário

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.

4.9(28)
46