Arduino TFT LCD Dokunmatik Ekranlı ve Kalibrasyonlu Akıllı Kilit Sistemi

👤 Yazar: ozcan 📅 Tarih: 15.07.2026 14:16 👁️ Görüntüleme: 22

Arduino TFT LCD Dokunmatik Ekranlı ve Kalibrasyonlu Akıllı Kilit Sistemi

Bu rehberde, amatör projelerden sıyrılıp endüstriyel cihaz kalitesine göz kırpan gelişmiş bir Arduino projesini inceliyoruz: TFT LCD Dokunmatik Ekranlı Akıllı Kilit Sistemi. Bu sistemi özel kılan şey, hem milimetrik dokunma doğruluğu sağlayan 5 Noktalı Kalibrasyon Algoritması'nı barındırması hem de şifre ve motor kontrolünü tek bir kararlı Durum Makinesi (State Machine) mimarisi ile yönetmesidir.

arduino ili9325 sifreli kilit

Görsel: Arduino platformu üzerinde kilit ve ekran sürücü devrelerinin test edilmesi


Sistem Mimarisi ve Kodun Öne Çıkan Özellikleri

Projenin arkasındaki yazılım tasarımı, karmaşık işlemleri bile Arduino'yu yormadan tıkır tıkır çalıştıracak şekilde optimize edilmiştir. Kodun en dikkat çekici mühendislik detayları şunlardır:

  • Gelişmiş Durum Makinesi (State Machine): Kod; KILITLI, ACILDI, ADMIN_MENUSU ve KALIB olmak üzere 4 ana durum üzerinden döner. Bu sayede buton basma algoritmaları birbirine karışmaz ve gereksiz kaynak tüketimi önlenir.
  • EEPROM Çift Bölge Yönetimi: Arduino'nun kalıcı hafızası son derece verimli kullanılmıştır. 0. adresten itibaren kalibrasyon katsayıları (float formatında), 30. adresten itibaren ise 4 haneli kilit şifresi güvenli imza (Magic Byte) kontrolleriyle saklanır.
  • En Küçük Kareler Yöntemi ile Kalibrasyon: Ekran ilk açıldığında kullanıcıya kalibrasyon seçeneği sunulur. Seçilirse, ekranın 5 stratejik noktasından alınan ham analog veriler doğrusal regresyon hesaplamasından (Least Squares) geçirilerek dönme ve kayma hataları tamamen sıfırlanır.
  • Akıllı Güç Tasarrufu (Screen Saver): Ekran 10 saniye boyunca dokunulmadan beklerse otomatik olarak kapanır. İlk dokunuşta ekran uyanır ancak uyanma dokunuşu yanlışlıkla bir butonu tetiklemesin diye koruma filtresi devreye girer.
  • Güvenlik Cezası (Brute-Force Koruması): Şifre 3 kez üst üste yanlış girilirse, sistem 30 saniye boyunca kendini tamamen dondurarak dışarıdan gelecek deneme saldırılarını engeller.

Donanım Kurulumu ve Pin Şeması

Projenin donanımsal altyapısı standart bir Uno ve TFT Shield üzerine kuruludur:

Bileşen / Pin Arduino Pin Bağlantısı Görevi
Dokunmatik Pinleri (YP, XM, YM, XP) A1, A2, D7, D6 Ekranın dokunmatik analog koordinatlarını okur.
TFT Kontrol Pinleri A3 (CS), A2 (CD), A1 (WR), A0 (RD), A4 (RST) Ekran grafik çizim verilerini yönetir.
Kilit Servosu Dijital 13 Şifre doğru olduğunda 90° dönerek fiziksel sürgüyü açar.

TFT Ekran Kalibrasyon ve Kilit Projesinin Tam Kodu

Geliştirdiğimiz projenin tüm kütüphane bağlantılarını, matematiksel matris hesaplamalarını ve ekran koruyucu döngülerini içeren tam ve çalışır haldeki kaynak koduna aşağıdan ulaşabilirsiniz:

#include "TFTLCD.h"
#include "TouchScreen.h"
#include <EEPROM.h>
#include <Servo.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);

// Servo Motor Yapılandırması
Servo kilitServosu;
#define SERVO_PIN 13 // Servo sinyal pini

// 5-Nokta Kalibrasyon Katsayıları (EEPROM adres: 0-24 arası kullanılır)
float tsA = 0.3117, tsB = 0.0, tsC = -46.75;
float tsD = 0.0, tsE = -0.39, tsF = 366.8;

struct CalibData {
  byte magic; // Kalibrasyon geçerlilik imzası (0xCD)
  float A; float B; float C;
  float D; float E; float F;
};

// Şifre Kayıt Yapısı (EEPROM adres: 30'dan itibaren başlar)
#define PASSCODE_EEPROM_ADDR 30
struct PasscodeData {
  byte magic;      // Şifre geçerlilik imzası (0x55)
  char code[5];    // 4 haneli şifre + null terminator
};

// Durum Makinesi (State Machine) Tanımları
enum SistemDurumu { KILITLI, ACILDI, ADMIN_MENUSU, KALIB };
SistemDurumu mevcutDurum = KILITLI;

// Çalışma Değişkenleri
String girilenSifre = "";
char kayitliSifre[5] = "1234"; // Varsayılan şifre
int hataliDenemeSayisi = 0;
unsigned long ekranZamanlayici = 0;

// Ekran Koruyucu (Hareketsizlik) Değişkenleri
unsigned long sonAktiviteZamani = 0;
bool ekranKapali = false;

// Tuş Takımı Matrisi (3x4)
char keys[4][3] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'C', '0', 'E'} // C: Temizle, E: Onayla (Enter)
};

// Buton Yerleşim Ölçüleri
#define START_X 15   
#define START_Y 120  
#define BTN_W   65   
#define BTN_H   40   
#define GAP_X   12   
#define GAP_Y   8    

#define MINPRESSURE 150
#define MAXPRESSURE 1000

// Geliştirilmiş Güvenli Dokunmatik Okuma Fonksiyonu
TSPoint guvenliDokunmatikOku() {
  // 1. Ekran pinlerindeki statik yükü (parazit akımı) tamamen boşaltmak için INPUT yapıyoruz
  pinMode(XP, INPUT);
  pinMode(XM, INPUT);
  pinMode(YP, INPUT);
  pinMode(YM, INPUT);
  delayMicroseconds(150); // Statik yükün sönümlenmesi için bekliyoruz

  // Dokunmatik panelden anlık ham verileri alıyoruz
  TSPoint p = ts.getPoint();

  // Ekran kontrol pinlerinin paylaşımlı olması sebebiyle çıkış ayarlarını yeniliyoruz
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  digitalWrite(XM, HIGH);
  digitalWrite(YP, HIGH);

  return p;
}

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

  // Servo motoru başlat ve kilitli konuma (0 derece) getir
  kilitServosu.attach(SERVO_PIN);
  kilitServosu.write(0); 

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

  // EEPROM'dan kalibrasyon ve şifre yükleme işlemleri
  eepromYukle();
  sifreYukle();

  // İlk açılışta kalibrasyon yönetim ekranını başlat
  kalibrasyonYonetimi();

  // Hareketsizlik zamanlayıcısını başlatıyoruz
  sonAktiviteZamani = millis();

  // Kilit ekranını çizdir
  kilitEkraniCiz();
}

void loop() {
  // 10 saniye boyunca hiçbir dokunma olmazsa ekranı karartarak kapatıyoruz
  if (!ekranKapali && (millis() - sonAktiviteZamani > 10000)) {
    ekranKapali = true;
    tft.fillScreen(BLACK);
  }

  // Yenilenen güvenli okuma mekanizması
  TSPoint p = guvenliDokunmatikOku();

  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
    // Kalibre edilmiş hassas koordinatlar
    int x = tsA * p.x + tsB * p.y + tsC;
    int y = tsD * p.x + tsE * p.y + tsF;

    x = constrain(x, 0, tft.width() - 1);
    y = constrain(y, 0, tft.height() - 1);

    // Her dokunma algılandığında hareketsizlik zamanlayıcısını sıfırlıyoruz
    sonAktiviteZamani = millis();

    // Eğer ekran kapalıysa, ilk dokunuşta sadece ekranı açıyoruz (Buton tetiklenmez)
    if (ekranKapali) {
      ekranKapali = false;
      dokunmaBirakilanaKadarBekle(); // Ekranı açan dokunuşun yanlış tuşlara basmasını engelliyoruz

      // Kaldığı duruma göre ekranı yeniden çizdir
      if (mevcutDurum == KILITLI) {
        kilitEkraniCiz();
      } else if (mevcutDurum == ACILDI) {
        erisimVerildi();
      } else if (mevcutDurum == ADMIN_MENUSU) {
        adminMenusuCiz();
      }
      return; // Bu döngü adımını burada sonlandır, basılan tuşu işleme alma
    }

    switch (mevcutDurum) {
      case KILITLI:
        kilitEkraniDokunmaYonet(x, y);
        break;
      case ACILDI:
        acildiEkraniDokunmaYonet(x, y);
        break;
      case ADMIN_MENUSU:
        adminEkraniDokunmaYonet(x, y);
        break;
      case KALIB:
        break;
    }
  }

  // Kapı açıldıktan 5 saniye sonra otomatik olarak kilitleme kontrolü
  if (mevcutDurum == ACILDI && millis() - ekranZamanlayici > 5000) {
    kilitServosu.write(0); // Kilidi kapat
    kilitEkraniCiz();
    sonAktiviteZamani = millis(); // Otomatik kilitlenme sonrasında zamanlayıcıyı güncelliyoruz
  }
}

// --- ARAYÜZ ÇİZİM FONKSİYONLARI ---

void kilitEkraniCiz() {
  mevcutDurum = KILITLI;
  girilenSifre = "";
  tft.fillScreen(BLACK);

  // Üst Mavi Şerit
  tft.fillRect(0, 2, tft.width(), 38, BLUE);
  tft.drawString(35, 14, "GUVENLIK SISTEMI", WHITE, 2);

  // Şifre Gösterge Kutusu
  tft.drawRect(15, 55, 210, 50, BLUE);
  gostergeGuncelle();

  // Tuş Takımını Çiz (3x4)
  for (int satir = 0; satir < 4; satir++) {
    for (int sutun = 0; sutun < 3; sutun++) {
      int btnX = START_X + (sutun * (BTN_W + GAP_X));
      int btnY = START_Y + (satir * (BTN_H + GAP_Y));
      char tusVal = keys[satir][sutun];

      uint16_t btnColor = DARK_GRAY;
      if (tusVal == 'C') btnColor = RED;
      else if (tusVal == 'E') btnColor = GREEN;

      tft.fillRect(btnX, btnY, BTN_W, BTN_H, btnColor);
      tft.drawRect(btnX, btnY, BTN_W, BTN_H, WHITE);

      uint16_t textColor = (tusVal == 'E') ? BLACK : WHITE;
      tft.drawString(btnX + 25, btnY + 12, String(tusVal).c_str(), textColor, 2);
    }
  }
}

void gostergeGuncelle() {
  tft.fillRect(16, 56, 208, 48, BLACK);
  
  String maskeliSifre = "";
  for (int i = 0; i < girilenSifre.length(); i++) {
    maskeliSifre += "* ";
  }
  for (int i = girilenSifre.length(); i < 4; i++) {
    maskeliSifre += "_ ";
  }
  
  tft.drawString(55, 70, maskeliSifre.c_str(), CYAN, 2);
}

void kilitEkraniDokunmaYonet(int x, int y) {
  for (int satir = 0; satir < 4; satir++) {
    for (int sutun = 0; sutun < 3; sutun++) {
      int btnX = START_X + (sutun * (BTN_W + GAP_X));
      int btnY = START_Y + (satir * (BTN_H + GAP_Y));

      if (x > btnX && x < (btnX + BTN_W) && y > btnY && y < (btnY + BTN_H)) {
        char tusVal = keys[satir][sutun];
        
        // Basıldı Efekti
        tft.fillRect(btnX, btnY, BTN_W, BTN_H, WHITE);
        tft.drawString(btnX + 25, btnY + 12, String(tusVal).c_str(), BLACK, 2);
        delay(150);

        // Eski rengine döndür
        uint16_t btnColor = DARK_GRAY;
        if (tusVal == 'C') btnColor = RED;
        else if (tusVal == 'E') btnColor = GREEN;
        tft.fillRect(btnX, btnY, BTN_W, BTN_H, btnColor);
        tft.drawRect(btnX, btnY, BTN_W, BTN_H, WHITE);
        uint16_t textColor = (tusVal == 'E') ? BLACK : WHITE;
        tft.drawString(btnX + 25, btnY + 12, String(tusVal).c_str(), textColor, 2);

        // Tuş İşlemleri
        if (tusVal >= '0' && tusVal <= '9') {
          if (girilenSifre.length() < 4) {
            girilenSifre += tusVal;
            gostergeGuncelle();
          }
        } 
        else if (tusVal == 'C') {
          girilenSifre = "";
          gostergeGuncelle();
        } 
        else if (tusVal == 'E') {
          if (girilenSifre == String(kayitliSifre)) {
            // ŞİFRE DOĞRU
            erisimVerildi();
          } else {
            // ŞİFRE YANLIŞ
            erisimReddedildi();
          }
        }
        delay(150); // Debounce
      }
    }
  }
}

void erisimVerildi() {
  mevcutDurum = ACILDI;
  hataliDenemeSayisi = 0;
  ekranZamanlayici = millis();
  
  tft.fillScreen(BLACK);
  tft.fillRect(10, 40, 220, 100, GREEN);
  tft.drawRect(10, 40, 220, 100, WHITE);
  tft.drawString(30, 65, "ERISIM ONAYLANDI", BLACK, 2);
  tft.drawString(55, 95, "KAPI ACILDI", BLACK, 2);

  // "YÖNETİCİ MENÜSÜ" Butonu
  tft.fillRect(15, 180, 210, 50, BLUE);
  tft.drawRect(15, 180, 210, 50, WHITE);
  tft.drawString(35, 196, "SIFREYI DEGISTIR", WHITE, 2);

  // "KİLİTLE" Butonu
  tft.fillRect(15, 245, 210, 50, RED);
  tft.drawRect(15, 245, 210, 50, WHITE);
  tft.drawString(75, 261, "KILITLE", WHITE, 2);

  // Servoyu Açık Konuma Getir (90 derece)
  kilitServosu.write(90);
}

void erisimReddedildi() {
  hataliDenemeSayisi++;
  tft.fillRect(16, 56, 208, 48, BLACK);
  tft.drawString(30, 70, "HATALI SIFRE!", RED, 2);
  girilenSifre = "";
  delay(1500);
  
  if (hataliDenemeSayisi >= 3) {
    tft.fillScreen(BLACK);
    tft.drawString(20, 120, "GIVENLIK KILIDI", RED, 2);
    tft.drawString(20, 150, "30 SN BEKLEYIN!", RED, 2);
    delay(30000); // 30 saniye cezalı bekleme
    hataliDenemeSayisi = 0;
  }
  
  kilitEkraniCiz();
}

void acildiEkraniDokunmaYonet(int x, int y) {
  // "ŞİFREYİ DEĞİŞTİR" Butonu (Yönetici Girişi)
  if (x > 15 && x < 225 && y > 180 && y < 230) {
    dokunmaBirakilanaKadarBekle();
    adminMenusuCiz();
  }
  // "KİLİTLE" Butonu
  else if (x > 15 && x < 225 && y > 245 && y < 295) {
    dokunmaBirakilanaKadarBekle();
    kilitServosu.write(0); // Kilitle
    kilitEkraniCiz();
  }
}

void adminMenusuCiz() {
  mevcutDurum = ADMIN_MENUSU;
  girilenSifre = "";
  tft.fillScreen(BLACK);

  tft.fillRect(0, 2, tft.width(), 38, BLUE);
  tft.drawString(45, 14, "YONETICI MENUSU", WHITE, 2);

  tft.drawString(15, 50, "Yeni Sifre Belirleyin:", WHITE, 1.5);
  tft.drawRect(15, 65, 210, 45, BLUE);
  gostergeGuncelle();

  // Tuş takımını tekrar çiz (3x4)
  for (int satir = 0; satir < 4; satir++) {
    for (int sutun = 0; sutun < 3; sutun++) {
      int btnX = START_X + (sutun * (BTN_W + GAP_X));
      int btnY = START_Y + (satir * (BTN_H + GAP_Y));
      char tusVal = keys[satir][sutun];

      uint16_t btnColor = DARK_GRAY;
      if (tusVal == 'C') btnColor = RED;
      else if (tusVal == 'E') btnColor = GREEN;

      tft.fillRect(btnX, btnY, BTN_W, BTN_H, btnColor);
      tft.drawRect(btnX, btnY, BTN_W, BTN_H, WHITE);

      uint16_t textColor = (tusVal == 'E') ? BLACK : WHITE;
      tft.drawString(btnX + 25, btnY + 12, String(tusVal).c_str(), textColor, 2);
    }
  }
}

void adminEkraniDokunmaYonet(int x, int y) {
  for (int satir = 0; satir < 4; satir++) {
    for (int sutun = 0; sutun < 3; sutun++) {
      int btnX = START_X + (sutun * (BTN_W + GAP_X));
      int btnY = START_Y + (satir * (BTN_H + GAP_Y));

      if (x > btnX && x < (btnX + BTN_W) && y > btnY && y < (btnY + BTN_H)) {
        char tusVal = keys[satir][sutun];
        
        // Basıldı efekti
        tft.fillRect(btnX, btnY, BTN_W, BTN_H, WHITE);
        tft.drawString(btnX + 25, btnY + 12, String(tusVal).c_str(), BLACK, 2);
        delay(150);

        uint16_t btnColor = DARK_GRAY;
        if (tusVal == 'C') btnColor = RED;
        else if (tusVal == 'E') btnColor = GREEN;
        tft.fillRect(btnX, btnY, BTN_W, BTN_H, btnColor);
        tft.drawRect(btnX, btnY, BTN_W, BTN_H, WHITE);
        uint16_t textColor = (tusVal == 'E') ? BLACK : WHITE;
        tft.drawString(btnX + 25, btnY + 12, String(tusVal).c_str(), textColor, 2);

        if (tusVal >= '0' && tusVal <= '9') {
          if (girilenSifre.length() < 4) {
            girilenSifre += tusVal;
            // Admin ekranında şifre girilirken gizlemeden gösterelim ki hatayı görsün
            tft.fillRect(16, 66, 208, 43, BLACK);
            tft.drawString(75, 80, girilenSifre.c_str(), YELLOW, 2);
          }
        } 
        else if (tusVal == 'C') {
          girilenSifre = "";
          tft.fillRect(16, 66, 208, 43, BLACK);
        } 
        else if (tusVal == 'E') {
          if (girilenSifre.length() == 4) {
            // Şifreyi Kaydet
            sifreKaydet(girilenSifre);
            tft.fillScreen(BLACK);
            tft.drawString(20, 120, "SIFRE BASARIYLA", GREEN, 2);
            tft.drawString(20, 150, "KAYDEDILDI!", GREEN, 2);
            delay(2000);
            kilitServosu.write(0); // Kapıyı kilitle
            kilitEkraniCiz();
          } else {
            tft.fillRect(16, 66, 208, 43, BLACK);
            tft.drawString(20, 80, "4 HANE OLMALI!", RED, 2);
            delay(1500);
            girilenSifre = "";
            tft.fillRect(16, 66, 208, 43, BLACK);
          }
        }
        delay(150);
      }
    }
  }
}

// --- EEPROM VE KALİBRASYON YÖNETİMİ ---

void sifreYukle() {
  PasscodeData pData;
  EEPROM.get(PASSCODE_EEPROM_ADDR, pData);
  if (pData.magic == 0x55) {
    strcpy(kayitliSifre, pData.code);
    Serial.println("Kayıtlı Şifre EEPROM'dan Yüklendi ! ");
  } else {
    Serial.println("Kayıtlı şifre bulunamadı. Varsayılan şifre etkin !");
  }
}

void sifreKaydet(String yeniSifre) {
  PasscodeData pData;
  pData.magic = 0x55;
  yeniSifre.toCharArray(pData.code, 5);
  EEPROM.put(PASSCODE_EEPROM_ADDR, pData);
  strcpy(kayitliSifre, pData.code);
  Serial.print("Yeni Şifre EEPROM'a Yazıldı: ");
  Serial.println(kayitliSifre);
}

void kalibrasyonYonetimi() {
  tft.fillScreen(BLACK);
  
  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);

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

  tft.drawString(15, 275, "Otomatik gecis süresi:", GRAY, 1);

  unsigned long startTime = millis();
  int remainingSeconds = 5;
  bool secimYapildi = false;

  while (millis() - startTime < 5000) {
    int elapsed = (millis() - startTime) / 1000;
    int currentRemaining = 5 - elapsed;
    
    if (currentRemaining != remainingSeconds) {
      remainingSeconds = currentRemaining;
      tft.fillRect(160, 272, 40, 20, BLACK);
      tft.drawString(160, 272, (String(remainingSeconds) + " sn").c_str(), RED, 1);
    }

    // Güvenli dokunmatik okuma revizyonu
    TSPoint p = guvenliDokunmatikOku();

    if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
      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);

      if (tx > 15 && tx < 225 && ty > 140 && ty < 185) {
        dokunmaBirakilanaKadarBekle();
        kalibrasyonYap5Nokta();
        secimYapildi = true;
        break;
      }
      if (tx > 15 && tx < 225 && ty > 205 && ty < 250) {
        dokunmaBirakilanaKadarBekle();
        eepromYukle();
        secimYapildi = true;
        break;
      }
    }
    delay(50);
  }

  if (!secimYapildi) {
    eepromYukle();
  }
}

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);

  long tX[5] = {30,  210, 30,  210, 120}; 
  long tY[5] = {40,  40,  280, 280, 160}; 
  
  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;
    
    // Güvenli ilk algılama
    while (true) {
      TSPoint p = guvenliDokunmatikOku();
      if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
        delay(100); 
        break;
      }
    }

    // Gürültüyü süzmek için ardışık 16 örnek toplama
    for (int numSamples = 0; numSamples < 16; numSamples++) {
      TSPoint p = guvenliDokunmatikOku();
      if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
        rawXSum += p.x;
        rawYSum += p.y;
        basariliOkuma++;
      }
      delay(15);
    }

    // Olası bir sıfıra bölünme hatasını engelleme
    if (basariliOkuma > 0) {
      rX[i] = rawXSum / basariliOkuma;
      rY[i] = rawYSum / basariliOkuma;
    } else {
      i--; // Ölçüm başarısız, adımı tekrarla
      continue;
    }

    dokunmaBirakilanaKadarBekle();
  }

  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 {
    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;
  }

  double a = Sxx; double b = Sxy; double c = Sx;
  double e = Syy; double f = Sy;  double k = 5.0;

  double det = a * (e * k - f * f) - b * (b * k - c * f) + c * (b * f - c * e);

  if (abs(det) < 0.0001) {
    return false; 
  }

  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;

  outA = (m00 * Vx0 + m01 * Vx1 + m02 * Vx2) / det;
  outB = (m01 * Vx0 + m11 * Vx1 + m12 * Vx2) / det;
  outC = (m02 * Vx0 + m12 * Vx1 + m22 * Vx2) / det;

  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);
  
  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);
  
  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) {
    // Güvenli okuma
    TSPoint p = guvenliDokunmatikOku();

    if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
      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);

      if (testX > 8 && testX < 228 && testY > 8 && testY < 220) {
        tft.fillCircle(testX, testY, 2, GREEN);
      }

      if (testX > 10 && testX < 115 && testY > 240 && testY < 290) {
        dokunmaBirakilanaKadarBekle();
        
        tsA = nA; tsB = nB; tsC = nC;
        tsD = nD; tsE = nE; tsF = nF;

        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);

        tft.fillScreen(BLACK);
        tft.drawString(30, 120, "KALIBRASYON", GREEN, 2);
        tft.drawString(30, 150, "KAYDEDILDI!", GREEN, 2);
        delay(1500);
        kararVerildi = true;
      }

      if (testX > 125 && testX < 230 && testY > 240 && testY < 290) {
        dokunmaBirakilanaKadarBekle();
        kalibrasyonYap5Nokta(); 
        kararVerildi = true;
      }
    }
  }
}

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

  if (data.magic == 0xCD) {
    tsA = data.A; tsB = data.B; tsC = data.C;
    tsD = data.D; tsE = data.E; tsF = data.F;
    Serial.println("Kayitli kalibrasyon verileri yuklendi.");
  } else {
    tsA = 0.3117; tsB = 0.0; tsC = -46.75;
    tsD = 0.0; tsE = -0.39; tsF = 366.8;
    Serial.println("Gecerli kalibrasyon yok! Varsayilan yuklendi.");
  }
}

void dokunmaBirakilanaKadarBekle() {
  delay(100);
  bool basili = true;
  while (basili) {
    TSPoint p = guvenliDokunmatikOku();
    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