Arduino Sensör Filtreleme: Ortalama, Medyan, EMA ve Kalman
Arduino sensör filtreleme: hareketli ortalama, medyan filtre, EMA, histerezis, örnekleme hızı, Serial Plotter karşılaştırması ve Kalman filtresine giriş.
Arduino sensör filtreleme, analogRead() ya da bir mesafe sensöründen gelen titrek, zıplayan değerleri projede kullanılabilecek sakin ve güvenilir bir sayıya çevirme işidir. Potansiyometreye hiç dokunmadığın hâlde Seri Monitör'de 511, 513, 510, 514 görmen, HC-SR04'ün arada bir sebepsiz yere 3 cm ya da 400 cm ölçmesi, gece lambasının karanlıkla aydınlık arasındaki sınırda yanıp sönmesi aynı sorunun farklı yüzleridir. Çoğu zaman sensör bozuk değildir; ölçümün içine elektriksel gürültü, ADC'nin basamak hatası ya da tek tük hatalı yankılar karışmıştır. İyi haber şu: birkaç satırlık bir filtre bu sorunların çoğunu çözer.
Bu rehberde önce gürültünün türlerini ve devre tarafında alınacak ilk önlemleri göreceksin. Ardından en basitinden başlayarak beş yöntemi kodlayacağız: çoklu örnek ortalaması, hareketli ortalama (moving average), sıçramaları temizleyen medyan filtre, az bellekle çalışan üstel filtre (EMA) ve kararsız eşikleri sakinleştiren histerezis. Hepsini Arduino IDE'nin Serial Plotter'ında yan yana çizip karşılaştıracak, örnekleme hızının nasıl seçileceğini konuşacak ve Kalman filtresine kısa bir giriş yapacağız. Son bölümlerde ESP32'nin gürültülü ADC'si ve Raspberry Pi Pico için MicroPython kodları var.

Sensör verisi neden gürültülü olur?
Bir ölçümdeki hatayı tek bir şey sanmak yanıltıcıdır. Farklı gürültü türleri farklı filtre ister; önce elindeki sorunun hangisi olduğunu anlamak gerekir. Seri Monitör'e bir süre ham değer yazdırıp sayılara bakmak bunun için yeterlidir. Değerler belli bir sayının etrafında sürekli titriyorsa rastgele gürültü vardır; çoğu ölçüm doğru ama arada bir tamamen saçma bir değer geliyorsa sıçrama (spike) vardır; değer yavaş yavaş kayıyorsa sorun gürültü değil kaymadır.
| Gürültü türü | Nasıl görünür? | Tipik sebep | Uygun çözüm |
|---|---|---|---|
| Rastgele gürültü | Değer ±2–10 basamak titrer | Besleme dalgalanması, uzun kablo, ADC'nin kendi gürültüsü | Ortalama, hareketli ortalama, EMA |
| Sıçrama (spike) | Tek tük çok farklı değer | HC-SR04'te yankı kaçırma, gevşek kontak, motor paraziti | Medyan filtre |
| Basamak (kuantalama) hatası | Değer iki komşu sayı arasında gidip gelir | ADC çözünürlüğü (Uno'da 5 V / 1024 = 4,9 mV) | Ortalama, histerezis |
| Periyodik gürültü | Düzenli dalgalanma | Lambaların 100 Hz titreşimi, 50 Hz şebeke | 20 ms'nin katları boyunca ortalama |
| Kayma (drift) | Değer dakikalar içinde yavaşça değişir | Sıcaklık, pil gerilimi düşmesi | Filtre değil kalibrasyon, kararlı referans |
Önce devre: gürültüyü kaynağında azalt
Yazılım filtresi devrede olmayan bir bilgiyi geri getiremez; bu yüzden ilk iş gürültünün girmesini zorlaştırmaktır. Analog sensör kablolarını kısa tut ve motor, röle gibi akım çeken parçaların kablolarından uzak geçir. Analog girişle GND arasına sensöre yakın bir 100 nF seramik kondansatör koymak, hızlı parazitleri büyük ölçüde emer. Bir direnç ve bir kondansatörle kurulan RC alçak geçiren filtrenin kesim frekansı f = 1 / (2πRC) formülüyle bulunur: 10 kΩ ve 1 µF yaklaşık 16 Hz eder, yani bundan hızlı değişimleri bastırır. Değerleri RC zaman sabiti aracıyla hesaplayabilirsin. Motorlu projelerde sensörlerin GND'sini motor sürücünün GND'sinden ayrı bir yoldan karta getirmek ve kartı kararlı bir kaynaktan beslemek de fark yaratır. ADC'nin nasıl çalıştığını ve referans geriliminin etkisini ADC nedir? yazısında anlattık.
Test düzeneği
Bu yazıdaki kodları üç kolay sensörle deneyeceğiz. Potansiyometre rastgele gürültüyü görmek için, LDR periyodik gürültü ve eşik kararları için, HC-SR04 de sıçramalar için idealdir. LDR'yi 5 V ile analog pin arasına, 10 kΩ'luk direnci analog pin ile GND arasına bağla; böylece ışık arttıkça okunan değer de artar. Ayrıntılar için LDR ışık sensörü ve potansiyometre yazılarına bakabilirsin.
| Bağlantı | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Potansiyometre orta bacak | A0 | GPIO 34 | GP26 (ADC0) |
| LDR + 10 kΩ bölücü orta noktası | A1 | GPIO 35 | GP27 (ADC1) |
| HC-SR04 TRIG | D9 | GPIO 5 | GP2 |
| HC-SR04 ECHO | D10 | GPIO 18 (bölücüyle) | GP3 (bölücüyle) |
| LED ya da röle (histerezis örneği) | D8 | - | - |
| Besleme | Pot ve LDR 5V, HC-SR04 5V | Pot ve LDR 3V3, HC-SR04 5V | Pot ve LDR 3V3, HC-SR04 VBUS |
1. Çoklu örnek ortalaması
En basit filtre, aynı anda art arda birkaç ölçüm alıp ortalamasını kullanmaktır. Rastgele gürültü her ölçümde farklı yöne saptığı için toplamda birbirini götürür. İstatistikte bunun bir kuralı vardır: birbirinden bağımsız N ölçümün ortalamasında gürültü yaklaşık √N kat azalır. 4 örnek gürültüyü yarıya, 16 örnek dörtte bire indirir; 64 örnek ise sekizde bire indirir ama dört kat daha uzun sürer. Arduino Uno'da tek bir analogRead() yaklaşık 112 µs sürer, yani 16 örneklik bir ortalama 2 ms'den kısa sürede biter. Bu yöntemin tek kusuru, her ölçümde işlemciyi kısa bir süre meşgul etmesi ve sıçramalara karşı zayıf olmasıdır: 16 örnekten biri saçma bir değerse ortalamayı da bozar.
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. Hareketli ortalama (moving average)
Hareketli ortalama, son N ölçümün ortalamasını tutar ve her yeni ölçümde en eskisini atıp yenisini ekler. Böylece her döngüde bir ölçüm alırsın ama yine de N ölçümlük bir ortalamadan yararlanırsın. Kodda her seferinde N sayıyı yeniden toplamak yerine bir toplam değişkeni tutuyoruz: çıkan değeri toplamdan çıkarıp gireni ekliyoruz. Bu yüzden pencere ne kadar büyük olursa olsun hesap aynı hızda yapılır. Hareketli ortalamanın bedeli gecikmedir: çıktı, gerçek değerin yaklaşık (N−1)/2 ölçüm gerisinden gelir. 10 ms'de bir ölçüm alan 10'luk bir pencere yaklaşık 45 ms geç kalır; potansiyometre ya da sıcaklık için önemsizdir, hızlı bir robot için hissedilebilir.
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}| Pencere (N) | Gürültü azalması | Gecikme (10 ms'de bir ölçümle) | Uygun kullanım |
|---|---|---|---|
| 4 | Yaklaşık 2 kat | 15 ms | Hızlı tepki gereken kontrol |
| 10 | Yaklaşık 3 kat | 45 ms | Potansiyometre, joystick, LDR |
| 32 | Yaklaşık 5,7 kat | 155 ms | Sıcaklık, nem, pil gerilimi |
| 100 | 10 kat | 495 ms | Çok yavaş değişen değerler, ekranda sakin gösterim |
3. Medyan filtre: sıçramaları temizlemek
Ortalama, sıçramalara karşı zayıftır. HC-SR04 dört kez 50 cm, bir kez de yankıyı kaçırıp 0 ölçerse beşinin ortalaması 40 cm çıkar; bu değer ne doğrudur ne de hatayı gösterir. Medyan filtre ise son N ölçümü küçükten büyüğe sıralayıp ortadakini alır. Aynı örnekte sıralı dizi 0, 50, 50, 50, 50 olur ve ortadaki değer 50'dir; hatalı ölçüm sonuca hiç girmez. 5 elemanlı bir medyan filtre art arda gelen en fazla 2 hatalı ölçümü tamamen siler. Medyanın ikinci güzel özelliği, ani ve gerçek bir değişimi ortalama gibi yaymamasıdır: engel gerçekten yaklaştığında değer, yumuşak bir eğri yerine birkaç ölçüm içinde temiz bir basamakla yeni değere geçer. Aşağıdaki kod HC-SR04 ile her 60 ms'de bir ölçüm alır ve son 5 ölçümün medyanını yazar. Ayrıntılar için HC-SR04 mesafe sensörü yazısına bakabilirsin.
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. Üstel filtre (EMA): az bellekle yumuşatma
Üstel hareketli ortalama (Exponential Moving Average, EMA) tampon kullanmaz, yalnızca bir önceki sonucu hatırlar. Her yeni ölçümde sonuç, ölçümle arasındaki farkın küçük bir kısmı kadar ölçüme yaklaşır: y = y + α × (x − y). Burada x yeni ölçüm, y filtrelenmiş değer, α (alfa) ise 0 ile 1 arasında bir katsayıdır. α küçüldükçe filtre daha sakin ama daha yavaş olur. Yeni ölçümün etkisi zamanla üstel olarak azaldığı için adı üstel filtredir. Elektronikteki RC alçak geçiren filtrenin yazılımdaki karşılığı budur. EMA'nın zaman sabiti yaklaşık τ ≈ T / α ile bulunur (T örnekleme aralığı): 10 ms'de bir ölçüm ve α = 0,1 ile filtre, ani bir değişimin yüzde 63'üne yaklaşık 100 ms'de, yüzde 95'ine yaklaşık 300 ms'de ulaşır. Kodda hem kayan noktalı (float) hem de Uno gibi küçük kartlarda daha hızlı çalışan tamsayılı bir sürüm var; tamsayılı sürümde α = 1/16 seçilir ve bölme yerine kaydırma (>>) kullanılır.
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) | Yaklaşık eşdeğer pencere | %63 tepki süresi (10 ms örnekleme) | Karakter |
|---|---|---|---|
| 0,5 | 3 | 10–20 ms | Çok hızlı, az yumuşatma |
| 0,2 | 9 | 50 ms | Hızlı, hafif yumuşatma |
| 0,1 | 19 | 100 ms | Dengeli, çoğu sensör için iyi başlangıç |
| 0,05 | 39 | 200 ms | Sakin, yavaş |
| 0,01 | 199 | 1 s | Çok sakin, yalnızca yavaş değişen değerler |
Tablodaki "eşdeğer pencere", aynı yumuşatmayı veren hareketli ortalamanın yaklaşık boyutudur ve N ≈ 2/α − 1 ile hesaplanır. EMA'nın en büyük avantajı bellektir: 100 elemanlı bir hareketli ortalama 200 bayt RAM ister, EMA ise yalnızca tek bir değişken. Arduino Uno'nun 2 KB RAM'inde birden fazla sensörü filtrelemen gerekiyorsa bu fark önemlidir. Dezavantajı ise sıçramalara karşı ortalama kadar zayıf olmasıdır; büyük bir sıçrama birkaç ölçüm boyunca sonucu çeker.
Filtreleri Serial Plotter'da karşılaştırmak
Filtrelerin farkını sayılardan anlamak zordur, ama grafikte bir bakışta görülür. Aşağıdaki kod potansiyometreyi 100 Hz'te okur, aynı ölçümü dört farklı yoldan geçirir ve hepsini etiket:değer biçiminde yazar. Farkı daha net görebilmen için kod, ölçümlerin yaklaşık yüzde 3'üne yapay bir sıçrama ekler; gerçek projede bu satırı silmelisin. Grafikte medyanın sıçramaları tamamen sildiğini, hareketli ortalamanın ve EMA'nın ise sıçramaları küçük tümseklere çevirdiğini göreceksin.
- Kodu yükle ve Seri Monitör açıksa kapat; Seri Monitör ile Serial Plotter aynı anda açık olamaz.
- Arduino IDE'de Araçlar > Seri Çizici (Serial Plotter) menüsünü aç ya da sağ üstteki grafik simgesine tıkla.
- Sağ alttaki hız ayarını 115200 baud yap.
- Potansiyometreyi yavaşça ve sonra hızlıca çevir; her çizginin tepkisini izle.
- Etiketlerin yanındaki kutucuklarla çizgileri tek tek açıp kapatarak iki filtreyi doğrudan karşılaştır.
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}Grafikte etiketler sağ üstte renkli olarak görünür. Etiketler arasında virgül, etiketle değer arasında iki nokta kullanmazsan Serial Plotter çizgileri doğru ayıramaz. Arduino IDE 1.8'deki eski çizicide etiket desteği sınırlıdır; sorun yaşarsan yalnızca virgülle ayrılmış sayılar gönder. Seri port ve baud hızıyla ilgili sorunlar için Arduino Seri Monitör yazısına göz atabilirsin.
5. Histerezis: eşikte titreyen kararları durdurmak
Filtre değeri sakinleştirir ama karar sorununu tek başına çözmez. "Işık değeri 350'nin altına inince lambayı yak" diyen bir gece lambası, alacakaranlıkta değer 349 ile 351 arasında gidip geldikçe saniyede birkaç kez yanıp söner. Bu bir rölenin kontağını kısa sürede yıpratır. Çözüm histerezistir: açmak ve kapatmak için iki ayrı eşik kullanılır. Örneğin değer 300'ün altına inince lamba yanar, ama sönmesi için 400'ün üstüne çıkması gerekir. Aradaki 100 birimlik "ölü bölge" küçük dalgalanmaların kararı değiştirmesini engeller. Aşağıdaki kod histerezisi EMA ve kısa bir onay süresiyle birleştirir: koşulun 1 saniye boyunca kesintisiz sağlanması gerekir, böylece önünden geçen bir gölge ya da bir anlık far ışığı da kararı değiştirmez.
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}Eşikleri belirlerken önce Serial Plotter'da odanın gün içindeki değerlerini izle. Ölü bölge, filtrelenmiş değerin titreşiminden en az iki üç kat geniş olmalıdır. Aynı yöntem termostatlarda (örneğin 22 °C'de ısıtıcıyı kapat, 20 °C'de aç), su deposu pompalarında ve pil koruma devrelerinde de kullanılır. Butonlardaki ark önleme (debounce) mantığı da aslında bir tür onay süresidir; ayrıntılar Buton nasıl bağlanır? yazısında.
Örnekleme hızı nasıl seçilir?
Filtrenin iyi çalışması için sensörü doğru hızda okumak gerekir. Çok yavaş okursan hızlı değişimleri kaçırırsın; çok hızlı okursan sensör aynı değeri tekrar tekrar verir ya da hiç hazır olmaz. Temel kural Nyquist kuralıdır: izlemek istediğin en hızlı değişimin frekansının en az iki katı hızda örnekleme yapmalısın; pratikte 5–10 katı daha güvenlidir. Bazı sensörlerin ise kendi alt sınırı vardır ve onlardan hızlı okumanın faydası yoktur. Lambaların titreşimini ölçüyorsan dikkat: 50 Hz şebekede lambalar saniyede 100 kez titrer. LDR ya da analog bir sensörde bu titreşimi yok etmenin en kolay yolu, ölçümleri 20 ms'nin katı bir süre boyunca eşit aralıklarla alıp ortalamaktır; bu süre şebekenin tam periyotlarını kapsadığı için titreşim kendini götürür.
| Sensör | Önerilen okuma aralığı | Not |
|---|---|---|
| DHT11 / DHT22 | 1 s / 2 s | Daha sık okursan aynı değeri ya da hata alırsın |
| DS18B20 (12 bit) | 750 ms | Dönüşüm süresi bu kadar |
| HC-SR04 | En az 60 ms | Önceki yankının sönmesi beklenir |
| Potansiyometre, joystick | 10–20 ms | 50–100 Hz insan hareketi için fazlasıyla yeterli |
| LDR (lamba titreşimini bastırmak için) | 20 ms boyunca 1 ms'de bir, sonra ortalama | 50 Hz şebeke titreşimi kendini götürür |
| MPU6050 | 5–10 ms | 100–200 Hz hareket ölçümü için yaygın aralık |
Kalman filtresine kısa giriş
Kalman filtresi, ölçümün ne kadar gürültülü olduğunu ve gerçek değerin ne kadar hızlı değişebileceğini hesaba katan, kendi kazancını kendisi ayarlayan bir filtredir. Tek boyutlu en basit hâlinde iki ayar vardır: R, ölçüm gürültüsünün varyansıdır (sensörün ne kadar titrediği); Q ise süreç gürültüsüdür (gerçek değerin ne kadar hızlı değişmesini beklediğin). Filtre her adımda önce belirsizliğini Q kadar artırır, sonra K = P / (P + R) kazancını hesaplar ve tahminini ölçüme doğru K oranında kaydırır. Ölçüm gürültülü (R büyük) ise K küçük olur ve filtre ölçüme az güvenir; değerin hızlı değiştiği beklenirse (Q büyük) K büyür ve filtre çabuk tepki verir. Dikkat çekici bir sonuç: Q ve R sabit kaldığında K bir süre sonra sabit bir değere oturur ve tek boyutlu Kalman filtresi pratikte bir EMA'ya dönüşür. Kalman'ın asıl gücü birden çok büyüklüğü birlikte tahmin ettiğinde, örneğin ivmeölçer ve jiroskop verisini birleştirip eğim açısı bulduğunda ortaya çıkar. Bunun daha basit bir karşılığı olan tamamlayıcı (complementary) filtreyi MPU6050 yazısında bulabilirsin.
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 değerini tahmin etmek kolaydır: potansiyometreye dokunmadan 100 ham ölçüm al ve değerlerin ne kadar saçıldığına bak. Değerler ortalamanın ±3 basamak çevresinde dolaşıyorsa standart sapma yaklaşık 3, varyans da yaklaşık 9'dur. Q'yu ise deneyerek bul: küçük Q (0,001) çok sakin ama yavaş, büyük Q (1) hızlı ama daha titrek bir çıktı verir. Başlangıç için Q'yu R'nin yüzde 1'i ile 10'u arasında seç ve Serial Plotter'da potansiyometreyi çevirerek ayarla.
ESP32'de ADC gürültüsü ve filtreleme
ESP32'nin 12 bitlik ADC'si Arduino Uno'nunkinden daha ince ölçer, ama daha gürültülüdür ve özellikle 0–0,1 V ile 3,1–3,3 V uçlarında doğrusal değildir. Wi-Fi açıkken gürültü daha da artar. İki şey işini kolaylaştırır: ham değer yerine çekirdeğin fabrika kalibrasyonunu kullanan analogReadMilliVolts() fonksiyonunu kullanmak ve her ölçümde 16–64 örneğin ortalamasını alıp ardından EMA uygulamak. Aşağıdaki kod potansiyometreyi bu iki adımla okur; Wi-Fi kullanan projelerde sensörü mutlaka GPIO 32–39 arasındaki ADC1 pinlerine bağla.
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}Raspberry Pi Pico ve MicroPython ile filtreleme
Raspberry Pi Pico'da ADC.read_u16() 0–65535 arasında bir değer döndürür, ama ADC'nin gerçek çözünürlüğü 12 bittir ve alt bitler gürültülüdür. MicroPython'da medyan için sorted() fonksiyonu işini çok kolaylaştırır; hareketli ortalama için de bir liste ile toplam tutmak yeterlidir. Aşağıdaki kod potansiyometreyi 100 Hz'te okur ve üç filtrenin çıktısını Thonny'nin Görünüm > Çizici (Plotter) penceresinde çizilebilecek biçimde yazar.
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)Thonny'nin çizicisi her satırdaki sayıları ayrı çizgiler olarak gösterir ve sayıların önündeki metinleri etiket olarak kullanır. MicroPython Arduino'ya göre daha yavaş çalıştığı için filtreleri çok yüksek hızlarda zorlama; 100 Hz civarı ve küçük pencereler Pico'da rahat çalışır. MicroPython'a yeniysen MicroPython temelleri yazısından başlayabilirsin.
Hangi filtreyi seçmelisin?
| Durum | Önerilen filtre | Neden? |
|---|---|---|
| Potansiyometre, joystick, analog sensör titriyor | Hareketli ortalama ya da EMA (α = 0,1–0,2) | Basit, hızlı, yeterli yumuşatma |
| Ara sıra saçma değer geliyor (HC-SR04, akış sensörü) | 3'lü ya da 5'li medyan | Tek tük hatayı tamamen siler |
| Hem sıçrama hem titreşim var | Önce medyan, sonra EMA | İki sorunu sırayla çözer |
| RAM çok kısıtlı, çok sayıda sensör | Tamsayılı EMA | Sensör başına tek değişken |
| Açma kapama kararı eşikte titriyor | Histerezis ve onay süresi | Karar sorununu çözer, filtre tek başına yetmez |
| Lamba ya da şebeke kaynaklı dalgalanma | 20 ms boyunca eşit aralıklı ortalama | 50 Hz periyodunu tam kapsar |
| Birden çok sensörü birleştirmek (eğim, konum) | Kalman ya da tamamlayıcı filtre | Sensörlerin güçlü yanlarını birleştirir |
Sık hatalar ve çözümleri
| Belirti | Olası sebep | Çözüm |
|---|---|---|
| Filtreli değer çok geç tepki veriyor | Pencere çok büyük ya da α çok küçük | Pencereyi küçült ya da α'yı artır, gecikmeyi tablolardan hesapla |
| Hareketli ortalama açılışta sıfırdan yavaşça yükseliyor | Tampon sıfırlarla başlıyor | Tamponu ilk ölçümle doldur ya da dolana kadar sıra kadar böl |
| Ortalama 64 örnekte saçmalıyor | int toplam taştı (64 × 1023 = 65 472 > 32 767) | Toplam değişkenini long yap |
| Medyan filtre hiçbir şeyi değiştirmiyor | Pencere 2 ya da çift sayı, ya da sıçramalar art arda geliyor | Tek sayılı pencere (3, 5, 7) kullan, sıçrama sayısından büyük seç |
| Serial Plotter çizgileri birleşik ya da tek renk | Etiket biçimi yanlış | etiket:değer ve aralarında virgül kullan, satırı println ile bitir |
| EMA değeri hiç değişmiyor | Tamsayı bölme: 0.1 yerine 1/10 yazılmış | Katsayıyı float yaz (0.1) ya da tamsayılı EMA formülünü kullan |
| Filtreye rağmen değerler dalgalı | Sorun gürültü değil, kayma ya da zayıf besleme | Beslemeyi kontrol et, 100 nF kondansatör ekle, kalibrasyon yap |
Sensör filtreleme, ölçümleri güvenilir hâle getirmenin en ucuz yoludur: hiçbir ek parça gerektirmez, birkaç satır kodla projelerin daha kararlı çalışır. Ortalama, hareketli ortalama, medyan, EMA ve histerezisi bir arada kullanmayı öğrendiğine göre artık titreyen ekranlar, sebepsiz alarmlar ve yanıp sönen röleler geride kaldı. BlueGrays uygulamasında LDR ile ışık ölçme, gece lambası, HC-SR04 ile mesafe ölçme, park sensörü ve boy ölçme gibi projeleri adım adım devre şemaları ve hazır kodlarıyla inceleyebilir, bu filtreleri o projelere ekleyerek sonucu kendin görebilirsin.
Sıkça sorulan sorular
Arduino'da sensör değerleri neden sürekli değişiyor?
Analog ölçümlerde besleme dalgalanması, uzun kablolar, yakındaki motorlar ve ADC'nin kendi gürültüsü değeri birkaç basamak oynatır. Bu normaldir ve sensörün bozuk olduğunu göstermez. Analog girişe 100 nF kondansatör eklemek ve yazılımda ortalama ya da EMA filtresi kullanmak değerleri sakinleştirir.
Hareketli ortalama nedir?
Hareketli ortalama, son N ölçümün ortalamasıdır ve her yeni ölçümde en eski değer çıkarılıp yenisi eklenir. Gürültüyü yaklaşık √N kat azaltır ama çıktıyı (N−1)/2 ölçüm kadar geciktirir. Arduino'da dairesel bir tampon ve tek bir toplam değişkeniyle birkaç satırda yazılır.
Medyan filtre ile ortalama arasındaki fark nedir?
Ortalama bütün değerleri hesaba kattığı için tek bir saçma ölçüm sonucu bozar. Medyan filtre ise değerleri sıralayıp ortadakini aldığı için tek tük hatalı ölçümleri tamamen yok sayar. Bu yüzden HC-SR04 gibi ara sıra hatalı değer veren sensörlerde medyan, titreşen analog değerlerde ise ortalama daha iyidir.
Üstel filtre (EMA) katsayısı nasıl seçilir?
Katsayı α, 0 ile 1 arasındadır; küçük α daha sakin ama daha yavaş, büyük α daha hızlı ama daha titrek bir çıktı verir. 10 ms'de bir ölçümde α = 0,1 yaklaşık 100 ms'lik bir zaman sabiti verir ve çoğu sensör için iyi bir başlangıçtır. Tepki süresini τ ≈ T / α formülüyle hesaplayabilirsin.
Arduino Serial Plotter'da birden fazla değer nasıl çizilir?
Her satırda değerleri etiket:değer biçiminde, aralarında virgül olacak şekilde yaz ve satırı println ile bitir; örneğin Ham:512,EMA:509. Serial Plotter her etiketi ayrı renkli bir çizgi olarak gösterir. Seri Monitör açıksa önce onu kapatmalı ve baud hızını koddakiyle aynı yapmalısın.
Kalman filtresi Arduino'da kullanılır mı?
Evet, tek boyutlu basit bir Kalman filtresi birkaç satırla Arduino Uno'da bile çalışır. Ölçüm gürültüsü R ve süreç gürültüsü Q ile ayarlanır; tek bir sensör için sonuçları çoğu zaman iyi ayarlanmış bir EMA'ya çok yakındır. Asıl faydası ivmeölçer ve jiroskop gibi birden çok sensörü birleştirirken ortaya çıkar.
Bu yazıdan sonra yapabileceğin projeler
Tüm projeleri gör
Ldr İle Işık Şiddeti Okuma
Odanın ne kadar aydınlık olduğunu sayıyla görmek ister misin? Işığa duyarlı küçük bir parça olan LDR ortam ışığını ölçer ve Arduino bu değeri yarım saniyede bir bilgisayar ekranına yazar. Elinle üstün

HC-SR04 Mesafe Ölçme (Serial Ekran)
Yarasalar karanlıkta yollarını nasıl buluyorsa HC-SR04 sensörü de öyle çalışır: duyamadığın bir ses gönderir, yankısını dinler ve önündeki cismin ne kadar uzakta olduğunu ölçer. Arduino bu mesafeyi sa

Ldr İle Gece Lambası
Hava karardığında kendiliğinden yanan, sabah olunca sönen küçük bir gece lambası. Işık sensörü odadaki aydınlığı ölçer; Arduino ortam yeterince karanlıksa LED'i yakar, aydınlanınca söndürür. Sokak lam

HC-SR04 ile Park Sensörü
Arabalardaki park sensörünün küçük bir benzeri: bir şey yaklaştıkça bip sesleri hızlanır. Mesafe sensörü önündeki nesneyi ölçer; 20 santimetreden uzaktaysa yeşil LED yanar, daha yakına gelince kırmızı

Boy Ölçüm Projesi
Kapı kasasına asılan ve altında duranın boyunu santimetre olarak söyleyen bir boy ölçer. Önce altında kimse yokken birinci butona basarsın, mesafe sensörü yere olan uzaklığı ölçer; sonra biri altına g

Potansiyometre İle Servo Motor Kullanım
Potansiyometrenin düğmesini çevirdikçe servo motorun kolu da onunla birlikte döner; düğmenin bir ucu sıfır dereceye, öbür ucu yarım tura karşılık gelir. Arduino düğmenin konumunu sürekli okur ve servo

ESP32 LDR Modülü ile Işık Şiddeti Okuma
LDR modülünün ham ADC değerini ve karanlık/aydınlık referanslarına göre hesaplanan göreli ışık yüzdesini Serial Monitor'da gerçek zamanlı izleme. Bu yüzde lux ölçümü değildir. Modülün kalibrasyon mantığını öğretir. Proje 10'un öncesinde yapılması ideal; çünkü eşik değerini belirlemek için kullanılır.

ESP32 HC-SR04 Ultrasonik Mesafe Ölçer
HC-SR04 ultrasonik sensör, gönderdiği ses dalgasının nesneye çarpıp geri dönme süresini (Time of Flight) ölçerek mesafe hesaplar. Bu projede ESP32 ile TRIG pinine 10 mikrosaniyelik darbe gönderip, ECHO pininden gelen yankı süresini pulseIn() ile okuyacağız. Hesaplanan mesafeyi cm cinsinden 16x2 I2C LCD'de göstereceğiz. HC-SR04 5V çalıştığı için ECHO pinine gerilim bölücü zorunludur — aksi halde ESP32'nin 3.3V GPIO'su zarar görür.
Diğer yazılar
BlueGrays uygulaması
Bu devreyi telefonunda adım adım kur
Devre şeması, kod ve parça listesi tek ekranda. 200'den fazla Arduino, ESP32 ve Pico projesi seni bekliyor.
Telefonunla tara
Kamerayı koda tut; telefonunun uygulama mağazası açılır.
ya da mağazada aç

