Arduino TFT LCD Dokunmatik Ekranlarda 5 Noktalı Hassas Kalibrasyon Nedir? Nasıl Çalışır?

👤 Yazar: ozcan 📅 Tarih: 15.07.2026 13:33 👁️ Görüntüleme: 19

Arduino TFT LCD Dokunmatik Ekranlarda 5 Noktalı Hassas Kalibrasyon Nedir? Nasıl Çalışır?

TFT LCD ekranlarla çalışırken çoğumuz aynı can sıkıcı problemle karşılaşırız: Ekranda bir butona basmaya çalışırsınız ancak dokunmatik panel, bastığınız yerin biraz solunu veya yukarısını algılar. Bu sapma (drift), özellikle ucuz rezistif dokunmatik ekranlarda üretim toleransları ve analog voltaj dalgalanmaları nedeniyle son derece normaldir.

Bu sorunu çözmenin tek profesyonel yolu Hassas Kalibrasyon yapmaktır. Bu yazıda, projelerinizde sıfır hata ile dokunmatik hassasiyeti elde etmenizi sağlayacak 5 Noktalı Kalibrasyon Algoritması ve bu ayarları kaybetmemek için EEPROM kullanımını inceliyoruz.

Rezistif ekranların mekanik katman yapısı ve temas noktaları

Görsel: Rezistif ekranların mekanik katman yapısı ve temas noktaları


Rezistif Ekranlar Neden Sapma Yapar?

Rezistif dokunmatik ekranlar, üst üste bindirilmiş iki esnek ve iletken katmandan oluşur. Siz ekrana bastığınızda bu iki katman birbirine değer ve Arduino, oluşan analog voltajı ADC (Analog-Dijital Dönüştürücü) pinleri üzerinden 0 ile 1023 arasında ham veri (Raw data) olarak okur.

Sorun şudur: Dokunmatik panelden aldığımız X: 120 - 910, Y: 90 - 890 gibi ham değerler, ekranımızın gerçek piksel koordinatları olan X: 0 - 240, Y: 0 - 320 ile doğrudan eşleşmez. Üstelik bu dönüşüm doğrusal olmayabilir, ekranın kenarlarına yaklaştıkça sapmalar artabilir.


5 Noktalı Kalibrasyon Algoritması ve Matematiksel Sihir

Çoğu basit kodda iki noktalı (sadece sol-üst ve sağ-alt) haritalama (standart map() fonksiyonu) kullanılır. Ancak bu yöntem ekranın ortasındaki ve diğer köşelerindeki bükülmeleri, dönmeleri ve doğrusal olmayan sapmaları hesaba katamaz.

5 Noktalı Kalibrasyon, ekranın belirlenen 5 stratejik noktasına (Sol-Üst, Sağ-Üst, Sol-Alt, Sağ-Alt ve tam Orta) dokunmanızı ister.

En Küçük Kareler Yöntemi (Least Squares Method)

Toplanan bu 5 ham dokunma verisi, ekranın gerçek piksel koordinatlarıyla eşleştirilerek En Küçük Kareler Yöntemi adını verdiğimiz bir matris denklemine sokulur. Bu algoritma bize şu 6 katsayıyı (A, B, C, D, E, F) hesaplar:

X_piksel = A * X_ham + B * Y_ham + C
Y_piksel = D * X_ham + E * Y_ham + F

Bu katsayılar sayesinde dokunmatik panelden gelen ham veriler milimetrik bir hassasiyetle gerçek piksellere dönüştürülür. Ekranınız biraz yamuk yapıştırılmış olsa bile dönme açısı (B ve D katsayıları sayesinde) matematiksel olarak dengelenir.


EEPROM ile Kalıcı Hafıza: Her Seferinde Kalibrasyon Yapmaya Son!

Kalibrasyon yapmak harika bir çözüm olsa da cihazı her açıp kapattığımızda bu hassas noktaları yeniden seçmek tam bir işkenceye dönüşebilir. İşte burada Arduino’nun kalıcı hafızası olan EEPROM devreye girer.

EEPROM, Arduino'nun gücü kesilse bile verileri saklayabilen minik bir depolama alanıdır. Kalibrasyon başarıyla tamamlandığında elde edilen 6 adet hassas float katsayısı bir struct (veri yapısı) haline getirilerek EEPROM'a yazılır. Cihaz her açıldığında setup() fonksiyonu bu verileri kontrol eder; eğer önceden kaydedilmiş geçerli bir kalibrasyon verisi varsa ekran doğrudan en yüksek hassasiyetle çalışmaya başlar.


Projenize Kalibrasyonu Entegre Etmek

Kendi projenizde bu algoritmayı ayağa kaldırmak için kodunuza eklemeniz gereken temel mantık adımları şunlardır:

  • Hedefleri Sırayla Çizmek: Ekranın 5 köşesine sırayla artı işareti (hedef) çizin ve kullanıcıdan dokunmasını isteyin.
  • Ham Verileri Toplamak: Kullanıcı her dokunduğunda dokunmatik pinden okunan ham p.x ve p.y değerlerini dizilere kaydedin.
  • Matris Hesaplamasını Çalıştırmak: Elde edilen 5 adet ham dokunma noktası ile gerçek 5 piksel koordinatını matematiksel fonksiyona gönderip A, B, C, D, E, F katsayılarını hesaplatın.
  • EEPROM'a Kaydetmek: Hesaplanan katsayıları bir doğrulama anahtarı (magic byte) ile birlikte EEPROM'un 0. adresine yazın.

(Yazmak istediğiniz kalibrasyon kod bloklarını, matematiksel katsayı hesaplama fonksiyonlarını ve EEPROM okuma/yazma rutinlerini aşağıdaki alana ekleyebilirsiniz.)

#include "TFTLCD.h"
#include "TouchScreen.h"
#include <EEPROM.h>

// Dokunmatik Ekran Donanımsal Pinleri
#define YP A1
#define XM A2
#define YM 7
#define XP 6

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

// TFT LCD Kontrol Pinleri
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4

// 16-bit Gelişmiş Renk Paleti (RGB565)
#define BLACK     0x0000
#define BLUE      0x001F
#define RED       0xF800
#define GREEN     0x07E0
#define WHITE     0xFFFF
#define YELLOW    0xFFE0
#define CYAN      0x07FF
#define GRAY      0x7BEF
#define DARK_GRAY 0x31A6

TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

// 5-Nokta Kalibrasyon Katsayıları (Matris Katsayıları)
float tsA = 0.3117, tsB = 0.0, tsC = -46.75;
float tsD = 0.0, tsE = -0.39, tsF = 366.8;

// EEPROM'da saklanacak veri yapısı
struct CalibData {
  byte magic; // Kalibrasyon geçerlilik imzası (0xCD)
  float A; float B; float C;
  float D; float E; float F;
};

#define MINPRESSURE 10
#define MAXPRESSURE 1000

void setup(void) {
  Serial.begin(9600);

  tft.reset();
  tft.initDisplay();
  tft.setRotation(0); // Dikey Ekran Yönü (240x320)

  // EEPROM'dan daha önce kaydedilen kalibrasyonu yüklemeyi dene
  eepromYukle();

  // Kalibrasyon yönetim ekranını başlat
  kalibrasyonYonetimi();
}

void loop() {
  // Bu yardımcı araçta ana loop boştur.
  // Tüm işlemler setup() içindeki kalibrasyon ve test döngülerinde gerçekleşir.
}

void kalibrasyonYonetimi() {
  tft.fillScreen(BLACK);
  
  // Başlık Alanı
  tft.fillRect(0, 2, tft.width(), 38, BLUE);
  tft.drawString(35, 14, "EKRAN AYARLARI", WHITE, 2);

  tft.drawString(15, 60, "Ekraninizi kalibre", WHITE, 2);
  tft.drawString(15, 85, "etmek ister misiniz?", WHITE, 2);

  // "KALIBRE ET" Butonu
  tft.fillRect(15, 140, 210, 45, GREEN);
  tft.drawRect(15, 140, 210, 45, WHITE);
  tft.drawString(55, 154, "KALIBRE ET", BLACK, 2);

  // "VAZGEC / BASLA" Butonu
  tft.fillRect(15, 205, 210, 45, RED);
  tft.drawRect(15, 205, 210, 45, WHITE);
  tft.drawString(45, 219, "VAZGEC / TEST", WHITE, 2);

  while (true) {
    TSPoint p = ts.getPoint();
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
    digitalWrite(XM, HIGH);
    digitalWrite(YP, HIGH);

    if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
      // Mevcut katsayılar ile kaba bir koordinat hesapla
      int tx = tsA * p.x + tsB * p.y + tsC;
      int ty = tsD * p.x + tsE * p.y + tsF;
      tx = constrain(tx, 0, tft.width() - 1);
      ty = constrain(ty, 0, tft.height() - 1);

      // "KALIBRE ET" Seçilirse
      if (tx > 15 && tx < 225 && ty > 140 && ty < 185) {
        dokunmaBirakilanaKadarBekle();
        kalibrasyonYap5Nokta();
        break;
      }
      // "VAZGEC" Seçilirse mevcut kalibrasyonla doğrudan test alanına geç
      if (tx > 15 && tx < 225 && ty > 205 && ty < 250) {
        dokunmaBirakilanaKadarBekle();
        dogrulamaVeTestEkrani(tsA, tsB, tsC, tsD, tsE, tsF);
        break;
      }
    }
    delay(50);
  }
}

void kalibrasyonYap5Nokta() {
  tft.fillScreen(BLACK);
  tft.drawString(30, 100, "5-NOKTALI HASSAS", CYAN, 2);
  tft.drawString(30, 130, "KALIBRASYON", CYAN, 2);
  tft.drawString(30, 160, "BASLIYOR...", WHITE, 2);
  delay(1500);

  // 5 Referans Hedef Noktası (LCD üzerindeki pikseller)
  long tX[5] = {30,  210, 30,  210, 120}; 
  long tY[5] = {40,  40,  280, 280, 160}; 
  
  // Dokunmatik panelden okunacak ham (Raw) değerler
  long rX[5] = {0, 0, 0, 0, 0};
  long rY[5] = {0, 0, 0, 0, 0};

  const char* adimIsimleri[5] = {
    "1/5: SOL-UST HEDEF",
    "2/5: SAG-UST HEDEF",
    "3/5: SOL-ALT HEDEF",
    "4/5: SAG-ALT HEDEF",
    "5/5: ORTA HEDEF"
  };

  for (int i = 0; i < 5; i++) {
    hedefCiz(tX[i], tY[i], adimIsimleri[i]);
    
    long rawXSum = 0;
    long rawYSum = 0;
    int basariliOkuma = 0;
    
    // Kullanıcının dokunmasını bekle
    while (true) {
      TSPoint p = ts.getPoint();
      pinMode(XM, OUTPUT);
      pinMode(YP, OUTPUT);
      digitalWrite(XM, HIGH);
      digitalWrite(YP, HIGH);
      if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
        delay(100); 
        break;
      }
    }

    // Paraziti filtrelemek için ardışık 16 örnek al ve ortala
    for (int numSamples = 0; numSamples < 16; numSamples++) {
      TSPoint p = ts.getPoint();
      pinMode(XM, OUTPUT);
      pinMode(YP, OUTPUT);
      digitalWrite(XM, HIGH);
      digitalWrite(YP, HIGH);
      if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
        rawXSum += p.x;
        rawYSum += p.y;
        basariliOkuma++;
      }
      delay(15);
    }

    rX[i] = rawXSum / basariliOkuma;
    rY[i] = rawYSum / basariliOkuma;

    // Sonraki adıma geçmeden önce dokunmanın bırakılmasını bekle
    dokunmaBirakilanaKadarBekle();
  }

  // En Küçük Kareler Regresyon Analizi ile katsayıları hesapla
  float nA, nB, nC, nD, nE, nF;
  if (calculateLeastSquares(rX, rY, tX, tY, nA, nB, nC, nD, nE, nF)) {
    dogrulamaVeTestEkrani(nA, nB, nC, nD, nE, nF);
  } else {
    // Hatalı hesaplama durumunda yeniden başlat
    tft.fillScreen(BLACK);
    tft.drawString(20, 120, "HATA: KALIBRASYON", RED, 2);
    tft.drawString(20, 150, "BASARISIZ OLDU!", RED, 2);
    delay(2000);
    kalibrasyonYap5Nokta();
  }
}

bool calculateLeastSquares(long rx[], long ry[], long tx[], long ty[], float &outA, float &outB, float &outC, float &outD, float &outE, float &outF) {
  double Sxx = 0, Sxy = 0, Sx = 0, Syy = 0, Sy = 0;
  double Vx0 = 0, Vx1 = 0, Vx2 = 0;
  double Vy0 = 0, Vy1 = 0, Vy2 = 0;

  for (int i = 0; i < 5; i++) {
    double x = rx[i];
    double y = ry[i];
    double X = tx[i];
    double Y = ty[i];

    Sxx += x * x;
    Sxy += x * y;
    Sx  += x;
    Syy += y * y;
    Sy  += y;

    Vx0 += x * X;
    Vx1 += y * X;
    Vx2 += X;

    Vy0 += x * Y;
    Vy1 += y * Y;
    Vy2 += Y;
  }

  // Simetrik Matris Denklem Çözümü (M^T * M)
  double a = Sxx; double b = Sxy; double c = Sx;
  double e = Syy; double f = Sy;  double k = 5.0; // N = 5

  // Determinant hesabı
  double det = a * (e * k - f * f) - b * (b * k - c * f) + c * (b * f - c * e);

  if (abs(det) < 0.0001) {
    return false; // Çakışık veya hatalı koordinat girdisi durumunda sıfıra bölünme hatası koruması
  }

  // Adjugate matris elemanları
  double m00 = e * k - f * f;
  double m01 = c * f - b * k;
  double m02 = b * f - c * e;
  double m11 = a * k - c * c;
  double m12 = b * c - a * f;
  double m22 = a * e - b * b;

  // X ekseni için katsayılar (A, B, C)
  outA = (m00 * Vx0 + m01 * Vx1 + m02 * Vx2) / det;
  outB = (m01 * Vx0 + m11 * Vx1 + m12 * Vx2) / det;
  outC = (m02 * Vx0 + m12 * Vx1 + m22 * Vx2) / det;

  // Y ekseni için katsayılar (D, E, F)
  outD = (m00 * Vy0 + m01 * Vy1 + m02 * Vy2) / det;
  outE = (m01 * Vy0 + m11 * Vy1 + m12 * Vy2) / det;
  outF = (m02 * Vy0 + m12 * Vy1 + m22 * Vy2) / det;

  return true;
}

void hedefCiz(int x, int y, const char* baslik) {
  tft.fillScreen(BLACK);
  tft.drawString(10, 10, baslik, CYAN, 2);
  
  // Hedef Artı (+) Göstergesi
  tft.drawLine(x - 20, y, x + 20, y, RED);
  tft.drawLine(x, y - 20, x, y + 20, RED);
  tft.drawCircle(x, y, 6, WHITE);
  tft.drawCircle(x, y, 12, BLUE);
  tft.drawCircle(x, y, 18, WHITE);
  
  tft.drawString(15, 140, "Kalem ucuyla tam", WHITE, 2);
  tft.drawString(15, 170, "merkeze dokunup", WHITE, 2);
  tft.drawString(15, 200, "bekleyin.", WHITE, 2);
}

void dogrulamaVeTestEkrani(float nA, float nB, float nC, float nD, float nE, float nF) {
  tft.fillScreen(BLACK);
  
  // Çizim Sınır Kutusu
  tft.drawRect(5, 5, 230, 220, GRAY);
  tft.drawString(10, 15, "TEST ALANI: Ekrana", CYAN, 1);
  tft.drawString(10, 30, "dokunarak cizgi cizin.", CYAN, 1);
  tft.drawString(10, 45, "Sapma yoksa KAYDET,", CYAN, 1);
  tft.drawString(10, 60, "varsa YENIDEN basin.", CYAN, 1);

  // "KAYDET" Butonu (Yeşil)
  tft.fillRect(10, 240, 105, 50, GREEN);
  tft.drawRect(10, 240, 105, 50, WHITE);
  tft.drawString(25, 258, "KAYDET", BLACK, 2);

  // "YENIDEN" Butonu (Kırmızı)
  tft.fillRect(125, 240, 105, 50, RED);
  tft.drawRect(125, 240, 105, 50, WHITE);
  tft.drawString(135, 258, "YENIDEN", WHITE, 2);

  bool kararVerildi = false;

  while (!kararVerildi) {
    TSPoint p = ts.getPoint();
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
    digitalWrite(XM, HIGH);
    digitalWrite(YP, HIGH);

    if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
      // Yeni hesaplanan katsayılarla test noktası hesapla
      int testX = nA * p.x + nB * p.y + nC;
      int testY = nD * p.x + nE * p.y + nF;

      testX = constrain(testX, 0, tft.width() - 1);
      testY = constrain(testY, 0, tft.height() - 1);

      // Çizim alanı içerisinde ise çizim yapılmasına izin ver (Yeşil noktalar)
      if (testX > 8 && testX < 228 && testY > 8 && testY < 220) {
        tft.fillCircle(testX, testY, 2, GREEN);
      }

      // "KAYDET" Buton Alanı Algılaması
      if (testX > 10 && testX < 115 && testY > 240 && testY < 290) {
        dokunmaBirakilanaKadarBekle();
        
        // Yeni katsayıları sisteme kaydet
        tsA = nA; tsB = nB; tsC = nC;
        tsD = nD; tsE = nE; tsF = nF;

        // EEPROM'a kalıcı olarak yazdır
        CalibData data;
        data.magic = 0xCD;
        data.A = tsA; data.B = tsB; data.C = tsC;
        data.D = tsD; data.E = tsE; data.F = tsF;
        EEPROM.put(0, data);

        // Seri Monitöre katsayıları yazdır (Yedeklemek isterseniz kullanabilirsiniz)
        Serial.println("--- KALIBRASYON KATSAYILARI KAYDEDILDI ---");
        Serial.print("tsA: "); Serial.println(tsA, 6);
        Serial.print("tsB: "); Serial.println(tsB, 6);
        Serial.print("tsC: "); Serial.println(tsC, 6);
        Serial.print("tsD: "); Serial.println(tsD, 6);
        Serial.print("tsE: "); Serial.println(tsE, 6);
        Serial.print("tsF: "); Serial.println(tsF, 6);

        tft.fillScreen(BLACK);
        tft.drawString(30, 120, "KALIBRASYON", GREEN, 2);
        tft.drawString(30, 150, "KAYDEDILDI!", GREEN, 2);
        delay(1500);
        kararVerildi = true;
        
        // Başlangıç ekranına geri dön
        kalibrasyonYonetimi();
      }

      // "YENIDEN" Buton Alanı Algılaması
      if (testX > 125 && testX < 230 && testY > 240 && testY < 290) {
        dokunmaBirakilanaKadarBekle();
        kalibrasyonYap5Nokta(); // Adımları tekrarla
        kararVerildi = true;
      }
    }
  }
}

void eepromYukle() {
  CalibData data;
  EEPROM.get(0, data);

  // İmzayı kontrol et (Daha önce kaydedilmiş veri var mı?)
  if (data.magic == 0xCD) {
    tsA = data.A; tsB = data.B; tsC = data.C;
    tsD = data.D; tsE = data.E; tsF = data.F;
    Serial.println("Kayıtlı kalibrasyon verileri EEPROM'dan yüklendi.");
  } else {
    // EEPROM boş ise kodun en başındaki standart dikey katsayıları koru
    Serial.println("Kayıtlı kalibrasyon bulunamadı. Varsayılan katsayılar yükleniyor.");
  }
}

void dokunmaBirakilanaKadarBekle() {
  delay(100);
  bool basili = true;
  while (basili) {
    TSPoint p = ts.getPoint();
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
    digitalWrite(XM, HIGH);
    digitalWrite(YP, HIGH);
    if (p.z < MINPRESSURE || p.z > MAXPRESSURE) {
      basili = false;
    }
    delay(30);
  }
  delay(100);
}

💬 Yorumlar

Henüz yorum yapılmamış. İlk yorumu siz yapın!

Bir Yorum Bırakın

Güvenlik Kontrolü:

Yenidir.com
↑ Sayfa Başı
📞 İletişim ✉ E-Posta Gönder 🟥 YouTube
© 2026 Yenidir.com
Yenidir CMS V0.1