Arduino ile Temel Programlama
Arduino mimarisinde kod geliştirirken kullanılan çekirdek fonksiyonlar, veri tipleri, lojik yapılar ve modüler programlama mantığı.
Çekirdek (Core) Fonksiyon Mimarisi
Bir bilgisayar programı çalıştırıldığında işletim sistemi işletim sırasını yönetir. Ancak Arduino gibi bir mikrodenetleyicide (mikrokontrolcü) bir işletim sistemi yoktur; kod doğrudan ham donanım üzerinde (bare-metal) çalışır. Bu nedenle işlemcinin hayata gözlerini açtığı andan itibaren ne yapacağını bilen iki ana bloğa ihtiyacı vardır:
- 1. void setup() — Yapılandırma Bloğu Arduino'ya ilk kez enerji verildiğinde, USB kablosu takıldığında ya da üzerindeki Reset butonuna basıldığında mikrodenetleyici bu bloğun içine girer. İçindeki kodları yukarıdan aşağıya doğru sadece bir kez çalıştırır. Amacı, sistemin ana işleyişine başlamadan önce donanımsal çevre birimlerini hazır hale getirmektir. Hangi pinlerin akım vereceği (giriş mi çıkış mı olacağı), sensörlerin ilk kalibrasyon değerleri ve bilgisayarla yapılacak haberleşme protokolleri burada kurulur. Eğer bu blok boş bırakılırsa donanım ne yapacağını şaşırabilir.
- 2. void loop() — Ana Döngü Bloğu `setup()` bloğundaki tüm satırlar başarıyla icra edilip bittikten hemen sonra, işlemci `loop()` bloğuna geçiş yapar. Bu blok, adından da anlaşılacağı üzere sonsuz bir döngüdür. Blok içindeki son satır çalıştırıldığında, işlemci otomatik olarak tekrar en üstteki satıra geri döner. Kartın enerjisi kesilene kadar bu işlem saniyede binlerce kez tekrarlanır. Sensörlerin anlık olarak okunması, sıcaklık sınırının kontrol edilmesi veya motorların tetiklenmesi gibi sürekli dinamik olan tüm işlemler bu blokta hayat bulur.
// Giriş/Çıkış Komutlarının Derin Analizi
Arduino'nun dış dünya ile iletişim kurmasını sağlayan en temel 3 fonksiyon şunlardır:
-
pinMode(pin_numarasi, mod);: İşlemciye, belirtilen numaralı fiziksel bacağın elektriksel karakterini söyler. Eğer `mod` kısmınaOUTPUTyazılırsa o pinden dışarıya akım verileceği (örn: LED yakmak, motor sürmek) belirtilir. EğerINPUTyazılırsa dış dünyadan bir voltaj sinyali okunacağı (örn: butona basıldı mı, sensör veri gönderdi mi) beyan edilmiş olur. -
digitalWrite(pin_numarasi, deger);: Sadece ÇIKIŞ (`OUTPUT`) olarak ayarlanmış dijital pinlerde işe yarar. Eğer değer kısmınaHIGHyazılırsa o pinden tam 5V (veya 3.3V) elektrik çıkışı verilir.LOWyazılırsa pinin elektriği tamamen kesilir ve 0V (GND) seviyesine çekilir. Dijital dünyada bunun karşılığı Lojik 1 ve Lojik 0'dır. -
delay(milisaniye);: İşlemcinin saat kristalini oyalayarak belirtilen süre boyunca hiçbir iş yapmadan durmasını sağlar. İçine yazılan değer milisaniye cinsindendir ($1 \text{ saniye} = 1000 \text{ milisaniye}$). Önemli Not: `delay` çalışırken Arduino tamamen "donar", gelen sensör verilerini veya buton basışlarını o an kaçırabilir.
// Arduino IDE derleyicisi kodları derlemeye buradan başlar. void setup() { // Arduino üzerindeki dahili L ledinin bağlı olduğu 13. pini // dışarıya enerji aktarabilecek şekilde ÇIKIŞ olarak yapılandırıyoruz. pinMode(LED_BUILTIN, OUTPUT); } // Setup bitti. Şimdi aşağıdaki loop bloğu sonsuza kadar dönecek: void loop() { digitalWrite(LED_BUILTIN, HIGH); // 13. pine 5V elektrik ver. LED yanmaya başladı. delay(1000); // İşlemciyi 1 saniye boyunca bu satırda kilitle, durum değişmesin. digitalWrite(LED_BUILTIN, LOW); // 13. pinin elektriğini kes (0V). LED söndü. delay(1000); // Sönük vaziyette 1 saniye bekle. // Süre bittiğinde otomatik olarak loop'un ilk satırına (Line 9) geri zıpla. }
- Dahili LED'in 2 saniye açık kalıp, yarım saniye (500ms) kapalı kalacağı asimetrik bir ritim oluşturun.
- Mors alfabesindeki "S" harfi gibi (3 kez hızlıca peş peşe) yanıp sönen bir sinyal algoritması kurun.
Konsol Dünyasına Giriş & Seri İletişim
Arduino tek başına çalışırken arka planda ne düşündüğünü, sensörlerden hangi sayısal değerleri okuduğunu veya kodun neresinde takıldığını göremeyiz. Çünkü kartın üzerinde bir ekran yoktur. İşte bu noktada imdadımıza Seri Haberleşme (Universal Asynchronous Receiver-Transmitter - UART) protokolü yetişir. Arduino, USB kablosu üzerinden bilgisayardaki "Seri Port Ekranı" (Serial Monitor) ile konuşabilir.
// Seri Port Fonksiyonlarının Farkları
-
Serial.begin(baudrate);: Haberleşme hattını açan anahtardır. İçine yazılan sayı (genellikle 9600), saniyede kaç bitlik veri aktarılacağını (Baud Rate) belirtir. Bilgisayardaki ekran ile Arduino'nun hızı birebir uyuşmak zorundadır, yoksa ekranda anlamsız uzaylı karakterleri () görürsünüz. -
Serial.print("metin");: Belirtilen metni veya değişkenin değerini seri monitöre yan yana yazdırır. İmleç yazının bittiği yerde sabit kalır. -
Serial.println("metin");: Yazdırma işlemini yaptıktan sonra otomatik olarak görünmez bir "Enter" tuşuna basarak imleci bir alt satırın başına indirir. Düzenli log kayıtları tutmak için kritiktir.
void setup() {
// Haberleşme kanalı saniyede 9600 bit hızında açılıyor.
Serial.begin(9600);
// İlk açılış mesajları basılıyor. Sadece 1 kez görünecekler.
Serial.println("--- SISTEM BASLATILDI ---");
Serial.print("Yazilim Yukleniyor... ");
delay(1000); // Kullanıcı yazıyı okuyabilsin diye yapay gecikme
Serial.println("[OK]"); // print'ten ötürü bu yazı hemen yanına eklenecek
}
void loop() {
// Aşağıdaki metin her 3 saniyede bir alt alta ekrana akacak.
Serial.println("Sistem hatti aktif, veri bekleniyor...");
delay(3000);
}
Yazilim Yukleniyor... [OK]
Sistem hatti aktif, veri bekleniyor...
Sistem hatti aktif, veri bekleniyor...
- `setup()` bloğu içerisinde ASCII karakterlerini kullanarak ekrana bir kare kutu veya `~/root` logosu çizin.
- Sonsuz döngü (`loop`) içerisinde her adımda ekrana "İşlem yapılıyor..." yazan ve her satırın başına nokta koyan bir akış tasarlayın.
Bellek Yönetimi & Değişkenler
Değişkenler, program akışında sensörlerden gelen verileri veya matematiksel hesaplama sonuçlarını geçici olarak sakladığımız RAM (SRAM) kutucuklarıdır. Klasik bir bilgisayarda gigabaytlarca RAM bulunurken, bir Arduino Uno'da sadece 2 Kilobayt (2048 bayt) RAM bulunur. Bu yüzden değişken tiplerini doğru seçmek hayati önem taşır; yanlış seçim belleğin taşmasına (`stack overflow`) ve kartın kilitlenmesine neden olur.
// Veri Tiplerinin Teknik Anatomisi
| Veri Tipi | Bellek Boyutu | Saklayabildiği Değer Aralığı | Kullanım Amacı |
|---|---|---|---|
| boolean | 1 Bayt (8-bit) | true (1) veya false (0) | Mantıksal anahtarlar, açık/kapalı durumları. |
| char | 1 Bayt (8-bit) | -128 ile 127 (Tek bir ASCII karakteri) | 'A', 'k', '9' gibi tek tırnak içinde yazılan karakterler. |
| int | 2 Bayt (16-bit) | -32,768 ile 32,767 | Standart tam sayılar, döngü sayaçları. |
| float | 4 Bayt (32-bit) | -3.4028235E+38 ile 3.4028235E+38 | Kesirli ve ondalıklı sayılar (Örn: Hassas sıcaklık verisi). |
// Değişken Tanımlama Formülü: Veri_Tipi degisken_adi = ilk_deger; int tamsayiX = 45; // Hafızada 2 bayt yer kaplar float ondalikY = 12.85; // Hafızada 4 bayt yer kaplar char karakterA = 'K'; // Karakterler TEK tırnak içinde yazılır boolean sistemDurumu = true; // Sadece true veya false alabilir void setup() { Serial.begin(9600); // Farklı veri tipleri matematiksel işleme girebilir ancak veri kaybına dikkat edilmelidir. // int ile float toplanırsa sonuç doğruluğu korumak için float tipinde saklanmalıdır. float toplam = tamsayiX + ondalikY; Serial.print("Veri A: "); Serial.println(karakterA); Serial.print("Matematiksel Toplam: "); Serial.println(toplam); // Ekrana 57.85 yazar Serial.print("Sistem Durumu (Lojik): "); Serial.println(sistemDurumu); // Ekrana true yerine '1' basar } void loop() {}
- Kök dizinde iki adet `int` tipinde kenar uzunluğu tanımlayıp, dikdörtgenin alanını ve çevresini hesaplayarak Seri Monitör'e basan algoritmayı kurun.
- Bir havuz problemi simülasyonu yapın: Saatte 5.4 litre su akıtan bir musluğun 6 saat sonunda havuzda biriktirdiği toplam su miktarını ondalıklı (`float`) olarak ekrana yazdırın.
Karar Yapıları & Mantıksal Algoritmalar
Akıllı bir sistem, gelen verileri değerlendirip kendi kendine karar verebilen sistemdir. Program akışını dallandırmak için if (eğer), else if (değilse eğer) ve else (hiçbiri değilse) bloklarını kullanırız. İşlemci parantez içindeki ifadenin doğru (`true`) olup olmadığına bakar. Doğruysa o bloğun içine dalar, yanlışsa o bloğu pas geçer.
// Mantıksal Operatörler Kılavuzu
&&**(VE Operatörü):** Bağlanan tüm koşulların **hepsinin** doğru olması gerekir. Bir tane bile yanlış varsa sonuç `false` çıkar.||**(VEYA Operatörü):** Koşullardan sadece **bir tanesinin** doğru olması bloğun çalışması için yeterlidir.!=**(Eşit Değildir):** İki değerin birbiriyle eşleşmediği durumlarda `true` sonucunu üretir.
int sensorSicaklik = 34; // Sensörden bu değerin okunduğunu varsayalım void setup() { Serial.begin(9600); // Koşul 1: Sıcaklık 40 dereceye eşit veya daha büyük mü? if(sensorSicaklik >= 40) { Serial.println("ALARM: Kritik Yuksek Sicaklik!"); } // Koşul 2: İlk koşul sağlanmadıysa buraya bak. Değer 20 ile 40 arasında mı? else if(sensorSicaklik >= 20 && sensorSicaklik < 40) { Serial.println("DURUM: Sicaklik Degerleri Normal."); // 34 bu aralığa uyar } // Koşul 3: Yukarıdaki olasılıkların hiçbiri gerçekleşmediyse otomatik burası çalışır. else { Serial.println("UYARI: Dusuk Sicaklik Gerilimi."); } } void loop() {}
- Girilen bir `int vize` ve `int final_notu` değişkenlerinin ortalamasını alıp (Vize %40, Final %60), ortalama 50 altındaysa "Kaldı", üstündeyse "Geçti" basan sistemi yazın.
- Bir araç hız simülasyonu kurun: Hız 120'den büyükse "Ceza Puanı İşlendi", 90-120 arasındaysa "Yüksek Hız Sınırı", 90 altındaysa "Güvenli Sürüş" çıktıları üretilsin.
Döngüler (Loops)
Bazen bir işlemi tek bir kez değil, belirli bir sayıda veya bir şart gerçekleşene kadar ardı ardına yapmamız gerekir. Örneğin, bir uyarı buzzer'ını 5 kez peş peşe öttürmek veya bağlı olan 10 tane LED'i sırayla açmak gibi. Kod satırlarını kopyala-yapıştır yapmak yerine döngü mimarilerini kullanırız. En sık kullanılan döngü yapısı for döngüsüdür.
// For Döngüsünün 3 Silahşörü
Bir `for` parantezi içerisine noktalı virgüllerle ayrılmış 3 parametre yazılır:
for(başlangıç_değeri; döngü_şartı; değişim_miktarı)
- Başlangıç: Döngüde sayacı tutacak olan değişkenin doğduğu yerdir (Örn: `int i = 1;`).
- Şart: Döngünün çalışmaya devam edip etmeyeceğini denetler (Örn: `i <= 5;`). Şart doğru olduğu sürece döngü döner.
- Değişim: Blok içindeki kodlar çalışıp bittikten sonra sayacın ne kadar artacağını veya azalacağını belirler (Örn: `i++` ifadesi sayacı 1 arttırır).
void setup() {
Serial.begin(9600);
Serial.println("Siralama Dongusu Basliyor:");
// i değişkeni 1'den başlar, 5'e eşit veya küçük olduğu sürece döner, her turda i bir artar.
for(int i = 1; i <= 5; i++) {
Serial.print("Dongu Tur Numarasi: ");
Serial.println(i);
delay(250); // Her turun arasında çeyrek saniye bekle
}
Serial.println("Dongu bitti, siradaki kod satirlarina geciliyor.");
}
void loop() {}
- `for` veya `while` döngüsü kullanarak 1 ile 50 arasındaki yalnızca 3'e tam bölünebilen sayıları süzüp ekrana basan kodu yazın.
- Seri ekranda yıldız (`*`) karakterini kullanarak yan yana 10 adet yıldız basan, ardından bir alt satıra geçen döngü mekanizmasını tasarlayın.
Diziler (Arrays)
Aynı türdeki verileri (örneğin 10 farklı odanın sıcaklık bilgisini) saklamak için 10 ayrı değişken (`sicaklik1`, `sicaklik2`...) tanımlamak hamallıktır. Bunun yerine, ortak bir isim altında bir çekmece dolusu veri saklamamıza imkan veren yapılara Dizi (Array) denir. Yazılım dünyasında dizilerin ilk elemanı her zaman 0. indekste yer alır.
// Kritik İndeks ve Sınır Kuralları
Eğer bir diziyi 4 elemanlı tanımladıysanız (`int veri[4]`), bu dizinin geçerli odacıkları 0, 1, 2 ve 3'tür. Olmayan 4. indeksteki elemanı okumaya veya değiştirmeye çalışırsanız (`veri[4]`), Arduino RAM'deki başka rastgele bir verinin üzerine yazar. Buna "Sınır Taşması Hatası" denir ve projelerde çözülmesi en zor donanımsal sapmalara yol açar.
// 4 eleman kapasiteli tam sayı dizisi beyanı // Indeksler: 0. 1. 2. 3. int analogNotlar[4] = {70, 85, 40, 95}; void setup() { Serial.begin(9600); // Dizinin 3. elemanına (yani 2. indekse) ulaşalım: Serial.print("Dizinin 3. Elemani: "); Serial.println(analogNotlar[2]); // Ekrana 40 basar // Bir elemanın değerini güncelleme: analogNotlar[0] = 100; // Artık 70 silindi, yerine 100 yazıldı. } void loop() {}
- İçinde 5 adet tam sayı barındıran bir dizi oluşturun. Bir `for` döngüsü yardımıyla bu dizideki tüm sayıların aritmetik ortalamasını hesaplayıp ekrana yazdırın.
- Dizi elemanlarını tarayarak içlerindeki en büyük sayıyı (Maximum Value) bulup ekrana yazdıran arama algoritmasını kurgulayın.
Fonksiyonlar (Metotlar)
Yazılım mühendisliğinin en temel kuralı **DRY (Don't Repeat Yourself - Kendini Tekrar Etme)** ilkesidir. Kod yazarken aynı hesaplamayı veya işlem serisini defalarca kullanıyorsak, bunu bağımsız bir paket haline getirmeliyiz. İşte bu paketlere Fonksiyon denir. Fonksiyonlar bir işi yapmak için parametre (girdi) alabilir ve işlem sonucunu çağrıldıkları yere geri fırlatabilirler (çıktı).
// Void ve Geri Dönüş Tiplerinin Ayrımı
Eğer bir fonksiyon yapacağı işin sonunda geriye matematiksel bir sayı veya lojik bir cevap döndürmeyecekse, sadece içine yazılan kod satırlarını çalıştırıp görevi bitirecekse başına void kelimesi konur (Tıpkı `setup()` ve `loop()` gibi). Ancak hesaplama yapıp çağrıldığı yere bir değer verecekse, döndüreceği verinin tipi (`int`, `float`, `char`) yazılmak zorundadır ve fonksiyon içerisinde return anahtar kelimesiyle süreç sonlandırılır.
// Geri dönüş değeri 'int' (tam sayı) olan, dışarıdan iki parametre alan fonksiyon: int alanHesapla(int taban, int yukseklik) { int sonuc = (taban * yukseklik) / 2; return sonuc; // Hesaplanan değeri fonksiyonu çağıran satıra teslim et } void setup() { Serial.begin(9600); // Fonksiyonun çağrılması: 10 ve 5 değerleri kurye gibi fonksiyona taşınır int ucgenAlani = alanHesapla(10, 5); Serial.print("Hesaplanan Ucgen Alani: "); Serial.println(ucgenAlani); // Ekrana 25 basar } void loop() {}
- Kendisine parametre olarak gönderilen bir sayının tek mi çift mi olduğunu analiz edip geriye `true` ya da `false` dönen mantıksal bir fonksiyon yazın.
- Seri ekrandan çağrıldığında ekrana sürekli olarak 30 adet kısa çizgi (`------------------------------`) çeken geri dönüşsüz (`void`) bir süsleme fonksiyonu oluşturun.