Bu projemizde, Arduino Uno ve Shield tipi dokunmatik TFT LCD ekran kullanarak tamamen işlevsel, dört işlem yapabilen görsel bir hesap makinesi tasarlıyoruz. Proje, sadece buton çizip dokunmayı algılamanın ötesine geçerek; girilen sayıları hafızada tutma, işlem operatörlerini yönetme ve float (ondalıklı sayı) hesaplamaları yapabilme gibi gelişmiş mantıksal süreçleri içerir.

Görsel: TFT LCD Ekran Üzerinde Çalışan Dört İşlem Hesap Makinesi Arayüzü
Projenin donanımsal bileşenlerini kontrol etmek için iki temel kütüphaneye ihtiyaç duyarız:
Ekran yerleşimini kusursuz yapabilmek için dikey (Portrait) modda çalışıyoruz. Ekranı kabaca iki ana bölgeye ayırıyoruz:
Hesap makinesinin arkasındaki matematiksel mantık, durum makineleri (state machines) mantığına dayanır:
float tipine dönüştürülerek birinciSayi değişkenine kaydedilir. Seçilen işlem türü bir değişkende (örn. char islem = '+') tutulur ve ekran yeni sayı girişi için temizlenir.ikinciSayi olarak alınır. switch-case kontrol yapısı kullanılarak seçilen operatöre göre matematiksel işlem yapılır ve çıkan sonuç tekrar string'e dönüştürülerek gösterge ekranına basılır.Her TFT ekranın dokunmatik hassasiyeti ve koordinat sınırları birebir aynı değildir. Eğer dokunduğunuz buton yerine yanındaki buton tetikleniyorsa, kodun başındaki TS_MINX, TS_MAXX, TS_MINY ve TS_MAXY sınır değerlerini kendi ekranınıza göre kalibre etmeniz gerekir. Bunun için Arduino'nun analog pinlerinden gelen ham X ve Y değerlerini seri porttan okuyup kodunuzda güncellemelisiniz.
#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
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define WHITE 0xFFFF
#define GRAY 0x7BEF
#define DARK_GRAY 0x31A6
#define CYAN 0x07FF
TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
// 5-Nokta Kalibrasyon Katsayıları (Varsayılan dikey ekran değerleri)
float tsA = 0.3117;
float tsB = 0.0;
float tsC = -46.75;
float tsD = 0.0;
float tsE = -0.39;
float tsF = 366.8;
// EEPROM'da saklanacak kalibrasyon verisi yapısı
struct CalibData {
byte magic; // Kalibrasyon geçerlilik imzası (0xCD)
float A;
float B;
float C;
float D;
float E;
float F;
};
// 4x4 Hesap Makinesi Tuş Takımı Matrisi
char keys[4][4] = {
{'1', '2', '3', '+'},
{'4', '5', '6', '-'},
{'7', '8', '9', '*'},
{'C', '0', '=', '/'}
};
// Ekran ve Buton Düzeni Parametreleri
#define START_X 14 // İlk sütunun sol kenar uzaklığı
#define START_Y 125 // İlk satırın üst kenar uzaklığı
#define BTN_W 48 // Buton genişliği
#define BTN_H 40 // Buton yüksekliği
#define GAP_X 10 // Butonlar arası yatay boşluk
#define GAP_Y 8 // Butonlar arası dikey boşluk
#define MINPRESSURE 10
#define MAXPRESSURE 1000
// Hesap Makinesi Çalışma Değişkenleri
String currentNum = "";
double operand1 = 0;
char op = '\0';
String historyStr = "";
bool showResult = false;
double resultValue = 0;
void setup(void) {
Serial.begin(9600);
tft.reset();
tft.initDisplay();
tft.setRotation(0); // Dikey Ekran Yönü (240x320)
// İlk açılışta kalibrasyon yönetim ekranını başlat
kalibrasyonYonetimi();
// Hesap makinesi ana arayüzünü çiz
hesapMakinesiArayuzuCiz();
}
void kalibrasyonYonetimi() {
tft.fillScreen(BLACK);
// Başlık Şeridi
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);
// Butonları Çiz
// KALIBRE ET Butonu (Yeşil)
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 (Kırmızı)
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);
}
TSPoint p = ts.getPoint();
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
digitalWrite(XM, HIGH);
digitalWrite(YP, HIGH);
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
// Geçici katsayılarla kaba koordinat kontrolü yapıyoruz
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" buton alanı kontrolü
if (tx > 15 && tx < 225 && ty > 140 && ty < 185) {
dokunmaBirakilanaKadarBekle();
kalibrasyonYap5Nokta();
secimYapildi = true;
break;
}
// "GEC / BASLA" buton alanı kontrolü
if (tx > 15 && tx < 225 && ty > 205 && ty < 250) {
dokunmaBirakilanaKadarBekle();
eepromYukle();
secimYapildi = true;
break;
}
}
delay(50);
}
// Kullanıcı dokunmazsa EEPROM verilerini otomatik yükler
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);
// 5 Referans Noktasının LCD Üzerindeki Hedef Piksel Koordinatları
long tX[5] = {30, 210, 30, 210, 120}; // X Hedefleri
long tY[5] = {40, 40, 280, 280, 160}; // Y Hedefleri
// Dokunmatik panelden okunacak raw koordinatlar
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]);
// Gürültüyü engellemek için kararlı çoklu dokunma okuması yapıyoruz
long rawXSum = 0;
long rawYSum = 0;
int basariliOkuma = 0;
// İlk dokunuşu 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); // Sinyal kararlılığı için bekleme
break;
}
}
// 16 adet ardışık ham örnek alıp ortalamasını çıkartıyoruz
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;
// Bir sonraki noktaya geçmeden önce dokunmanın tamamen bittiğinden emin oluyoruz
dokunmaBirakilanaKadarBekle();
}
// --- MATEMATİKSEL MATRİS HESAPLAMA (En Küçük Kareler Çözümü) ---
float nA, nB, nC, nD, nE, nF;
if (calculateLeastSquares(rX, rY, tX, tY, nA, nB, nC, nD, nE, nF)) {
// Başarılı ise doğrulama ekranına yönlendir
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;
}
// Symmetric Matrix (M^T * M)
// [ Sxx Sxy Sx ]
// [ Sxy Syy Sy ]
// [ Sx Sy 5 ]
double a = Sxx;
double b = Sxy;
double c = Sx;
double e = Syy;
double f = Sy;
double k = 5.0; // 5 Nokta kullandığımız için 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; // Sıfıra bölünme hatası / Çakışık koordinatlar
}
// Adjugate matris elemanlarının hesabı
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 katsayılarının (A, B, C) hesabı
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 katsayılarının (D, E, F) hesabı
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);
// Milimetrik hedef çizimi
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 Alanı Sınırı
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 5-noktalı katsayılar ile milimetrik test koordinatı hesabı
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);
// Test çizim alanı içi
if (testX > 8 && testX < 228 && testY > 8 && testY < 220) {
tft.fillCircle(testX, testY, 2, GREEN);
}
// "KAYDET" butonu
if (testX > 10 && testX < 115 && testY > 240 && testY < 290) {
dokunmaBirakilanaKadarBekle();
// Yeni katsayıları ana çalışma değişkenlerine aktar
tsA = nA; tsB = nB; tsC = nC;
tsD = nD; tsE = nE; tsF = nF;
// EEPROM'a kalıcı olarak kaydet
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;
}
// "YENIDEN" butonu
if (testX > 125 && testX < 230 && testY > 240 && testY < 290) {
dokunmaBirakilanaKadarBekle();
kalibrasyonYap5Nokta(); // Kalibrasyon döngüsünü baştan başlatır
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 {
// Hafıza boş ise standart dikey ekran katsayılarını koru
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 = 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);
}
uint16_t butonRengiAl(char tus) {
if (tus == 'C') {
return RED;
} else if (tus == '=') {
return GREEN;
} else if (tus == '+' || tus == '-' || tus == '*' || tus == '/') {
return BLUE;
} else {
return DARK_GRAY;
}
}
void tusIslemiYap(char tus) {
Serial.print("Basilan Tus: ");
Serial.println(tus);
if (tus >= '0' && tus <= '9') {
if (showResult) {
temizle();
}
if (currentNum == "0") {
currentNum = String(tus);
} else {
currentNum += tus;
}
}
else if (tus == 'C') {
temizle();
}
else if (tus == '+' || tus == '-' || tus == '*' || tus == '/') {
if (showResult) {
operand1 = resultValue;
op = tus;
historyStr = formatSayi(operand1) + " " + String(op);
currentNum = "";
showResult = false;
}
else if (currentNum != "") {
operand1 = currentNum.toFloat();
op = tus;
historyStr = currentNum + " " + String(op);
currentNum = "";
}
else if (op != '\0') {
op = tus;
historyStr = formatSayi(operand1) + " " + String(op);
}
}
else if (tus == '=') {
if (op != '\0' && currentNum != "") {
double operand2 = currentNum.toFloat();
switch (op) {
case '+': resultValue = operand1 + operand2; break;
case '-': resultValue = operand1 - operand2; break;
case '*': resultValue = operand1 * operand2; break;
case '/':
if (operand2 != 0) {
resultValue = operand1 / operand2;
} else {
resultValue = NAN;
}
break;
}
historyStr = formatSayi(operand1) + " " + String(op) + " " + currentNum + " =";
showResult = true;
currentNum = "";
op = '\0';
}
}
ekraniGuncelle();
}
String formatSayi(double deger) {
if (isnan(deger)) return "Hata";
if (isinf(deger)) return "Limit Disi";
if (deger == (long)deger) {
return String((long)deger);
} else {
return String(deger, 2);
}
}
void temizle() {
currentNum = "";
operand1 = 0;
op = '\0';
historyStr = "";
showResult = false;
resultValue = 0;
}
void ekraniGuncelle() {
tft.fillRect(6, 46, 228, 68, BLACK);
// Üst Satır: İşlem Geçmişi
tft.drawString(15, 54, historyStr.c_str(), GRAY, 2);
// Alt Satır: Sonuç veya güncel girdi
if (showResult) {
tft.drawString(15, 84, formatSayi(resultValue).c_str(), GREEN, 3);
}
else if (currentNum == "" && op != '\0') {
tft.drawString(15, 84, formatSayi(operand1).c_str(), WHITE, 3);
}
else if (currentNum != "") {
tft.drawString(15, 84, currentNum.c_str(), WHITE, 3);
}
else {
tft.drawString(15, 84, "0", WHITE, 3);
}
}
void hesapMakinesiArayuzuCiz() {
tft.fillScreen(BLACK);
// Üst Mavi Şerit
tft.fillRect(0, 2, tft.width(), 38, BLUE);
tft.drawString(45, 14, "HESAP MAKINESI", WHITE, 2);
// Çerçeve
tft.drawRect(5, 45, 230, 70, BLUE);
ekraniGuncelle();
// Butonları Çiziyoruz (4x4)
for (int satir = 0; satir < 4; satir++) {
for (int sutun = 0; sutun < 4; sutun++) {
int btnX = START_X + (sutun * (BTN_W + GAP_X));
int btnY = START_Y + (satir * (BTN_H + GAP_Y));
char tusValue = keys[satir][sutun];
uint16_t btnColor = butonRengiAl(tusValue);
tft.fillRect(btnX, btnY, BTN_W, BTN_H, btnColor);
tft.drawRect(btnX, btnY, BTN_W, BTN_H, WHITE);
uint16_t textColor = (tusValue == '=') ? BLACK : WHITE;
tft.drawString(btnX + 18, btnY + 12, String(tusValue).c_str(), textColor, 2);
}
}
}
void loop() {
TSPoint p = ts.getPoint();
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
digitalWrite(XM, HIGH);
digitalWrite(YP, HIGH);
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
// 5-Noktalı LSM matrisinden hesaplanan 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);
// Buton Dokunma Kontrolü
for (int satir = 0; satir < 4; satir++) {
for (int sutun = 0; sutun < 4; 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 tusValue = keys[satir][sutun];
// Görsel Geri Bildirim (Buton Basıldı Efekti)
tft.fillRect(btnX, btnY, BTN_W, BTN_H, WHITE);
tft.drawString(btnX + 18, btnY + 12, String(tusValue).c_str(), BLACK, 2);
tusIslemiYap(tusValue);
delay(250); // Debounce engelleme
// Eski rengine geri döndürme
uint16_t btnColor = butonRengiAl(tusValue);
tft.fillRect(btnX, btnY, BTN_W, BTN_H, btnColor);
tft.drawRect(btnX, btnY, BTN_W, BTN_H, WHITE);
uint16_t textColor = (tusValue == '=') ? BLACK : WHITE;
tft.drawString(btnX + 18, btnY + 12, String(tusValue).c_str(), textColor, 2);
}
}
}
}
}
Eğer projeyi daha da ileriye taşımak isterseniz; kod yapısına ondalıklı sayılar için nokta (.) butonu ekleyebilir, sıfıra bölme hatasını (Error) engelleyen güvenlik sorguları yazabilir veya Arduino'nun EEPROM hafızasını kullanarak geçmiş işlemler için bir "Memory (M+, M-)" fonksiyonu entegre edebilirsiniz.
Henüz yorum yapılmamış. İlk yorumu siz yapın!