millis() ve delay() Farkı
delay() öğrenmesi en kolay, ama projeleriniz büyüdükçe önünüze en çok engel çıkaran fonksiyondur. Bu yazı, ondan nasıl kurtulacağınızı anlatır.
Konu ileri seviye görünür ama değildir. Aksine, ne kadar erken öğrenirseniz o kadar çok zaman kazanırsınız.
delay() Ne Yapar?
delay(1000); // 1 saniye bekledelay() programı tamamen durdurur. Bu süre boyunca Arduino:
- Buton okuyamaz
- Sensör kontrol edemez
- Seri porttan gelen komutu göremez
- Başka hiçbir iş yapamaz
Sadece bekler.
Sorunun somut hali
Bir LED’i saniyede bir yanıp söndüren ve aynı anda bir butonu dinleyen program yazmak istiyorsunuz:
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(1000);
digitalWrite(LED_PIN, LOW);
delay(1000);
if (digitalRead(BUTON_PIN) == LOW) { // neredeyse hic calismaz
// ...
}
}Buton kontrolü döngüde saniyede yalnızca bir kez çalışır. Butona 1,5 saniye boyunca basılı tutmazsanız program bunu kaçırır.
millis() Ne Yapar?
millis(), Arduino açıldığından beri geçen süreyi milisaniye cinsinden döndürür. Programı durdurmaz, sadece “şu an saat kaç?” sorusuna cevap verir.
unsigned long simdi = millis();Fikir şudur: beklemek yerine, belirli bir sürenin geçip geçmediğini kontrol edin.
Temel Kalıp
const int LED_PIN = 13;
const unsigned long ARALIK = 1000;
unsigned long oncekiZaman = 0;
bool ledDurumu = LOW;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
unsigned long simdi = millis();
if (simdi - oncekiZaman >= ARALIK) {
oncekiZaman = simdi;
ledDurumu = !ledDurumu;
digitalWrite(LED_PIN, ledDurumu);
}
// Program burada calismaya devam eder!
// Buton, sensor, seri port... hepsi kontrol edilebilir.
}loop() saniyede binlerce kez döner. Her turda “1000 ms geçti mi?” diye bakılır; geçmediyse hiçbir şey yapılmadan devam edilir.
Neden unsigned long?
millis() değeri sürekli büyür. int (maksimum 32.767) sadece 33 saniyede taşar.
unsigned long (maksimum 4.294.967.295) ise yaklaşık 49,7 gün dayanır.
Zaman değişkenlerini her zaman unsigned long olarak tanımlayın.
Taşma (Overflow) Sorunu ve Doğru Çözüm
49,7 gün sonra millis() sıfıra döner. Bu, uzun süre çalışan projelerde gerçek bir sorundur.
Kritik nokta şudur: simdi - oncekiZaman >= ARALIK yazımı taşmaya karşı doğal olarak güvenlidir. unsigned long aritmetiğinde çıkarma işlemi taşma anında bile doğru sonucu verir.
Buna karşılık şu yazım hatalıdır ve taşma anında kilitlenir:
// YANLIS - tasma aninda calismaz
if (millis() >= oncekiZaman + ARALIK) {
// ...
}Her zaman fark alma biçimini kullanın.
Birden Fazla Zamanlayıcı
millis() yaklaşımının asıl gücü buradadır. Farklı hızlarda çalışan işleri aynı anda yönetebilirsiniz:
const int LED1 = 8;
const int LED2 = 9;
unsigned long led1Zaman = 0;
unsigned long led2Zaman = 0;
unsigned long sensorZaman = 0;
const unsigned long LED1_ARALIK = 500;
const unsigned long LED2_ARALIK = 1300;
const unsigned long SENSOR_ARALIK = 2000;
bool led1Durum = LOW;
bool led2Durum = LOW;
void setup() {
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
Serial.begin(9600);
}
void loop() {
unsigned long simdi = millis();
// Gorev 1: LED1 her 500 ms
if (simdi - led1Zaman >= LED1_ARALIK) {
led1Zaman = simdi;
led1Durum = !led1Durum;
digitalWrite(LED1, led1Durum);
}
// Gorev 2: LED2 her 1300 ms
if (simdi - led2Zaman >= LED2_ARALIK) {
led2Zaman = simdi;
led2Durum = !led2Durum;
digitalWrite(LED2, led2Durum);
}
// Gorev 3: Sensor her 2 saniye
if (simdi - sensorZaman >= SENSOR_ARALIK) {
sensorZaman = simdi;
Serial.print("Sensor: ");
Serial.println(analogRead(A0));
}
// Gorev 4: Buton her dongude kontrol edilir
if (digitalRead(2) == LOW) {
Serial.println("Butona basildi");
}
}Bu üç iş farklı ritimlerde, birbirini engellemeden çalışır. delay() ile bunu yapmak mümkün değildir.
Buton Debounce (Bloke Etmeyen)
const int BUTON_PIN = 2;
const unsigned long DEBOUNCE = 50;
int sonOkuma = HIGH;
int kararliDurum = HIGH;
unsigned long sonDegisim = 0;
void loop() {
int okuma = digitalRead(BUTON_PIN);
if (okuma != sonOkuma) {
sonDegisim = millis();
}
if (millis() - sonDegisim > DEBOUNCE) {
if (okuma != kararliDurum) {
kararliDurum = okuma;
if (kararliDurum == LOW) {
Serial.println("Yeni basis");
}
}
}
sonOkuma = okuma;
}Zaman Aşımı (Timeout) Kontrolü
millis() yalnızca periyodik işler için değil, “belirli bir sürede olmazsa vazgeç” mantığı için de kullanılır:
unsigned long baslangic = millis();
bool basarili = false;
while (millis() - baslangic < 5000) { // en fazla 5 saniye dene
if (sensorHazirMi()) {
basarili = true;
break;
}
}
if (!basarili) {
Serial.println("Sensor 5 saniyede yanit vermedi");
}Durum Makinesi (State Machine)
Karmaşık sıralı işler için en temiz yapı budur. Program hangi aşamada olduğunu bir değişkende tutar.
enum Durum {
BEKLIYOR,
ISINIYOR,
OLCUYOR,
GONDERIYOR
};
Durum mevcutDurum = BEKLIYOR;
unsigned long durumZamani = 0;
void durumDegistir(Durum yeni) {
mevcutDurum = yeni;
durumZamani = millis();
}
void loop() {
unsigned long gecen = millis() - durumZamani;
switch (mevcutDurum) {
case BEKLIYOR:
if (gecen >= 10000) { // 10 saniyede bir baslat
Serial.println("Isitiliyor...");
durumDegistir(ISINIYOR);
}
break;
case ISINIYOR:
if (gecen >= 3000) { // 3 saniye isinma
Serial.println("Olculuyor...");
durumDegistir(OLCUYOR);
}
break;
case OLCUYOR:
if (gecen >= 500) {
int deger = analogRead(A0);
Serial.print("Deger: ");
Serial.println(deger);
durumDegistir(GONDERIYOR);
}
break;
case GONDERIYOR:
if (gecen >= 1000) {
Serial.println("Gonderildi. Bekleme moduna gecildi.");
durumDegistir(BEKLIYOR);
}
break;
}
// Bu satir her durumda calisir
if (digitalRead(2) == LOW) {
Serial.println("Acil durdurma!");
durumDegistir(BEKLIYOR);
}
}Aynı işi delay() ile yazsaydınız, acil durdurma butonu hiçbir zaman çalışmazdı.
delay() Ne Zaman Kullanılabilir?
delay() tamamen yasak değildir. Şu durumlarda kullanmakta sakınca yoktur:
setup()içinde: Sensör ısınması, modül başlatma.- Çok kısa gecikmeler: Bir sensör protokolünde gereken 10 µs’lik darbe (
delayMicroseconds()). - Tek işi olan basit projeler: Sadece bir LED yakıp söndüren bir test kodu.
- Öğrenirken: İlk örnekleri anlamak için.
Kural şudur: Programınız aynı anda birden fazla iş yapacaksa delay()’den vazgeçin.
micros() ve Diğer Zaman Fonksiyonları
| Fonksiyon | Birim | Taşma süresi |
|---|---|---|
millis() | milisaniye | ~49,7 gün |
micros() | mikrosaniye | ~70 dakika |
delay(ms) | milisaniye | — |
delayMicroseconds(us) | mikrosaniye | — |
micros() çözünürlüğü 4 mikrosaniyedir (Uno’da); daha hassas değil.
delayMicroseconds() 16.383 µs’ye kadar güvenilirdir; daha uzun beklemeler için delay() kullanın.
Kesme (Interrupt) İçinde millis()
Kesme fonksiyonları (attachInterrupt ile bağlananlar) içinde millis() artmaz — çünkü millis() de bir kesmeyle çalışır ve kesmeler iç içe çalışmaz.
Kesme içinde millis() okuyabilirsiniz (değer doğrudur) ama uzun süre beklerseniz zaman donar. Kesme fonksiyonlarını çok kısa tutun: bir bayrak set edin, işi loop() içinde yapın.
volatile bool olayVar = false;
void kesmeFonksiyonu() {
olayVar = true; // sadece isaretle
}
void loop() {
if (olayVar) {
olayVar = false;
// asil isi burada yap
}
}Özet
delay() programı tamamen durdurur; millis() ise geçen süreyi ölçerek programın akmaya devam etmesini sağlar. Birden fazla iş yapan her projede millis() kullanılmalıdır.
Bu derste şunları öğrendik:
delay()’in neden diğer işleri engellediğiniif (simdi - onceki >= ARALIK)kalıbını- Zaman değişkenlerinin neden
unsigned longolması gerektiğini - Taşmaya karşı güvenli yazım biçimini
- Birden fazla zamanlayıcıyı aynı anda yönetmeyi
- Zaman aşımı kontrolünü ve durum makinesi yapısını
- Kesme içinde
millis()davranışını