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.

Görsel: Rezistif ekranların mekanik katman yapısı ve temas noktaları
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.
Ç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.
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:
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.
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.
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);
}
Henüz yorum yapılmamış. İlk yorumu siz yapın!