Temeller · 8 dk okuma

Arduino millis() ve delay(): Kodu Durdurmadan Zamanlama

Arduino millis() nedir, delay() neden programı durdurur? Blink without delay, aynı anda birden fazla iş, 49 günlük taşma, micros(), ESP32 ve Pico karşılıkları.

Arduino millis() fonksiyonu, kart çalışmaya başladığından beri geçen süreyi milisaniye cinsinden verir ve delay() kullanmadan zamanlama yapmanın temel yoludur. delay(1000) yazdığında Arduino bir saniye boyunca başka hiçbir şey yapmaz: butona basılsa fark etmez, sensör okunmaz, ekran güncellenmez. millis() ile ise "son işten bu yana ne kadar zaman geçti?" diye sorarsın; vakti gelen işi yaparsın, gelmeyeni atlarsın ve loop() hiç durmadan döner. Böylece tek bir Arduino aynı anda LED yakıp söndürebilir, buton okuyabilir, sensör ölçebilir ve seri porta rapor gönderebilir.

Bu rehberde delay() fonksiyonunun neden programı kilitlediğini, ünlü blink without delay (delay olmadan yanıp sönme) örneğini, birden fazla işi aynı anda yürütmeyi, 49,7 gün sonra yaşanan taşmayı (overflow) güvenle atlatmayı, mikrosaniye ölçen micros() fonksiyonunu ve tekrar kullanılabilir zamanlayıcı desenlerini öğreneceksin. Son bölümlerde aynı mantığın ESP32'de ve Raspberry Pi Pico'da (MicroPython) nasıl yazıldığını da göreceğiz.

Arduino Uno kartı ve dahili L LED'i
Arduino Uno: 13 numaralı pine bağlı dahili "L" LED'i sayesinde millis() denemeleri için ek parça gerekmez.

delay() nedir, neden programı durdurur?

delay(ms), verdiğin süre dolana kadar işlemciyi bir döngüde bekletir. Bu süre boyunca loop() içindeki bir sonraki satıra geçilmez; buna bloklayan (engelleyen) bekleme denir. Tek bir LED'i yakıp söndürmek için sorun yoktur, ama projeye bir buton eklediğin anda sorun başlar. Aşağıdaki kodda LED saniyede bir yanıp sönerken butona basılıp basılmadığına da bakılıyor:

Arduino (C++)
1const int LED_PIN = 13;                   // dahili LED / built-in LED
2const int BUTON_PIN = 2;                  // buton, diğer ucu GND / button, other leg to GND
3
4void setup() {
5  pinMode(LED_PIN, OUTPUT);
6  pinMode(BUTON_PIN, INPUT_PULLUP);
7  Serial.begin(9600);
8}
9
10void loop() {
11  digitalWrite(LED_PIN, HIGH);
12  delay(1000);                            // 1 sn bekle, buton okunmaz / wait 1 s, button is not read
13  digitalWrite(LED_PIN, LOW);
14  delay(1000);
15  if (digitalRead(BUTON_PIN) == LOW) {    // 2 saniyede bir bakılır / checked once every 2 s
16    Serial.println(F("Butona basildi / Button pressed"));
17  }
18}

Bu kodda buton yalnızca iki saniyede bir ve ancak tam o anda basılı tutuluyorsa algılanır; kısa bir basış iki delay() arasında kaybolur. Kesmeler (interrupt) delay() sırasında da çalışmaya devam eder: millis() saymayı sürdürür, seri porttan gelen baytlar 64 baytlık tampona yazılır. Ama senin kodun bekler. Projeye ekran, sensör ve buzzer ekledikçe her delay() diğer bütün işleri geciktirir ve cihaz hantal hissettirir. Çözüm, beklemeyi bırakıp zamanı ölçmektir.

millis() nedir, nasıl çalışır?

millis(), kart açıldığından ya da resetlendiğinden beri geçen milisaniyeleri unsigned long (32 bit, işaretsiz) türünde döndürür. Arduino Uno'da bu sayaç arka planda Timer0 donanım zamanlayıcısının taşma kesmesiyle güncellenir. 16 MHz'te Timer0 her 1,024 ms'de bir taşar; çekirdek kod aradaki 0,024 ms'lik farkı biriktirir ve yaklaşık her 43 ms'de bir sayacı 1 yerine 2 artırır. Yani millis() bazı değerleri hiç göstermez; bu yüzden millis() == 5000 gibi eşitlik kontrolleri güvenilmezdir, her zaman >= kullanılır. Ayrıca millis(), kesmeler kapalıyken (örneğin bir kesme fonksiyonunun içinde) ilerlemez.

FonksiyonNe yapar?Çözünürlük (Uno, 16 MHz)Taşma süresiProgramı durdurur mu?
delay(ms)Verilen milisaniye kadar bekler1 ms-Evet
delayMicroseconds(us)Kısa süre bekler (3–16383 µs arasında doğru)Yaklaşık 1 µs-Evet
millis()Açılıştan beri geçen milisaniye1 ms (ara ara 2 artar)Yaklaşık 49,7 günHayır
micros()Açılıştan beri geçen mikrosaniye4 µsYaklaşık 71,6 dakikaHayır
  1. Son işin yapıldığı anı tutmak için unsigned long türünde bir değişken oluştur (oncekiZaman).
  2. Her loop() turunda millis() ile şu anki zamanı al.
  3. simdi - oncekiZaman >= ARALIK koşuluyla yeterli süre geçip geçmediğine bak.
  4. Süre dolduysa işi yap (LED'i ters çevir) ve oncekiZaman değişkenini güncelle.
  5. Süre dolmadıysa hiçbir şey yapma, loop() devam etsin; diğer işler tam bu sırada çalışır.
Arduino (C++)
1const int LED_PIN = LED_BUILTIN;          // Uno'da 13 / pin 13 on the Uno
2const unsigned long ARALIK = 500;         // yanıp sönme aralığı (ms) / blink interval (ms)
3
4unsigned long oncekiZaman = 0;            // son değişim anı / time of the last toggle
5bool ledDurumu = false;
6
7void setup() {
8  pinMode(LED_PIN, OUTPUT);
9}
10
11void loop() {
12  unsigned long simdi = millis();         // şu anki zaman / current time
13  if (simdi - oncekiZaman >= ARALIK) {    // taşmaya karşı güvenli / overflow-safe
14    oncekiZaman += ARALIK;                // kaymayı önler / prevents drift
15    ledDurumu = !ledDurumu;
16    digitalWrite(LED_PIN, ledDurumu);
17  }
18  // burada başka işler beklemeden çalışır / other work runs here without waiting
19}

Kodda oncekiZaman = simdi yerine oncekiZaman += ARALIK yazdık. Fark küçük ama önemli: loop() bazen birkaç milisaniye geç kalırsa ilk yöntem bu gecikmeyi her seferinde biriktirir ve LED saatler içinde kayar; ikinci yöntem ise hedef zamanı sabit adımlarla ilerletir, ortalama periyot tam 500 ms kalır. Saat, metronom ya da düzenli ölçüm gibi işlerde += kullan. Program uzun süre bloklanabiliyorsa (örneğin Wi-Fi bağlantısını beklerken) += kaçırılan adımları arka arkaya yakalamaya çalışır; böyle durumlarda = simdi daha güvenlidir. LED'in parlaklığını da kodu durdurmadan değiştirmek istersen PWM ve analogWrite yazısına göz at.

Aynı anda birden fazla iş: millis() ile çoklu görev

Arduino Uno tek çekirdekli bir işlemcidir ve gerçek anlamda iki işi aynı anda yapamaz. Ama loop() saniyede on binlerce kez dönebildiği için, her işe sırası geldiğinde birkaç mikrosaniye ayırmak dışarıdan bakınca "aynı anda" görünür. Buna işbirlikçi çoklu görev (cooperative multitasking) denir. Kural basit: her görevin kendi zaman değişkeni ve kendi aralığı olur, hiçbir görev uzun süre beklemez. Aşağıdaki örnekte dört görev bir arada çalışıyor:

Arduino (C++)
1const int KIRMIZI_LED = 8;                // 300 ms'de bir yanıp söner / blinks every 300 ms
2const int YESIL_LED = 9;                  // 1000 ms'de bir yanıp söner / blinks every 1000 ms
3const int SARI_LED = 10;                  // butonla açılıp kapanır / toggled by the button
4const int BUTON = 2;                      // diğer ucu GND / other leg to GND
5
6unsigned long kirmiziZaman = 0, yesilZaman = 0, butonZaman = 0, seriZaman = 0;
7bool kirmizi = false, yesil = false, sari = false;
8int sonOkuma = HIGH;                      // son ham okuma / last raw reading
9int kararliDurum = HIGH;                  // titreşimsiz durum / debounced state
10
11void setup() {
12  pinMode(KIRMIZI_LED, OUTPUT);
13  pinMode(YESIL_LED, OUTPUT);
14  pinMode(SARI_LED, OUTPUT);
15  pinMode(BUTON, INPUT_PULLUP);
16  Serial.begin(9600);
17}
18
19void loop() {
20  unsigned long simdi = millis();
21
22  // Görev 1: kırmızı LED / Task 1: red LED
23  if (simdi - kirmiziZaman >= 300) {
24    kirmiziZaman = simdi;
25    kirmizi = !kirmizi;
26    digitalWrite(KIRMIZI_LED, kirmizi);
27  }
28
29  // Görev 2: yeşil LED / Task 2: green LED
30  if (simdi - yesilZaman >= 1000) {
31    yesilZaman = simdi;
32    yesil = !yesil;
33    digitalWrite(YESIL_LED, yesil);
34  }
35
36  // Görev 3: 50 ms debounce ile buton / Task 3: button with 50 ms debounce
37  int okuma = digitalRead(BUTON);
38  if (okuma != sonOkuma) {                // değişti, sayacı yeniden başlat / changed, restart the timer
39    butonZaman = simdi;
40    sonOkuma = okuma;
41  }
42  if (simdi - butonZaman >= 50 && okuma != kararliDurum) {
43    kararliDurum = okuma;
44    if (kararliDurum == LOW) {            // basıldı / pressed
45      sari = !sari;
46      digitalWrite(SARI_LED, sari);
47    }
48  }
49
50  // Görev 4: saniyede bir seri rapor / Task 4: serial report once per second
51  if (simdi - seriZaman >= 1000) {
52    seriZaman = simdi;
53    Serial.print(F("Calisma suresi (s) / Uptime (s): "));
54    Serial.println(simdi / 1000);
55  }
56}
GörevPinAralıkNe yapar?
Kırmızı LED8300 msHızlı yanıp söner
Yeşil LED91000 msYavaş yanıp söner
Buton + sarı LED2 ve 1050 ms debounceHer basışta sarı LED'i açar ya da kapatır
Seri rapor-1000 msÇalışma süresini saniye olarak yazar

Butonda kullanılan yöntem de millis() tabanlıdır: ham okuma her değiştiğinde sayaç yeniden başlar ve okuma 50 ms boyunca sabit kalırsa basış gerçek kabul edilir. Böylece butonun mekanik titreşimi (bouncing) tek basışı onlarca basış gibi göstermez ve kod hiç beklemez. Buton bağlantısı ve dahili pull-up direnci hakkında ayrıntı için Arduino'ya Buton Nasıl Bağlanır? yazısına bakabilirsin. LED'ler için 220 Ω direnç yeterlidir; farklı renk ve akımlar için LED direnci hesaplayıcı aracını kullan.

49,7 gün taşma (overflow) sorunu ve güvenli karşılaştırma

unsigned long en fazla 4.294.967.295 değerini tutabilir. millis() bu sayıya ulaşınca, yani yaklaşık 49 gün 17 saat 2 dakika sonra, yeniden 0'dan saymaya başlar. Kart haftalarca açık kalacaksa (sera kontrolü, akvaryum, alarm sistemi) bu an mutlaka gelir. İyi haber şu: simdi - onceki >= aralik biçiminde, çıkarma ile yazılmış kod taşmadan etkilenmez. İşaretsiz sayılarda çıkarma 2 üzeri 32'ye göre (modüler) yapılır. Örneğin onceki = 4.294.967.000 iken taşmadan sonra simdi = 200 olursa simdi - onceki işlemi eksi bir sayı vermez, doğru sonuç olan 496'yı verir. Bu nedenle beklediğin aralık 49,7 günden kısa olduğu sürece karşılaştırma her zaman doğru çalışır.

YazımSonuçNeden?
if (millis() - onceki >= aralik)Doğruİşaretsiz çıkarma, taşma anında da doğru farkı verir
if (millis() >= onceki + aralik)Hatalıonceki + aralik taşınca küçük bir sayıya döner, koşul erken doğru olur
if (millis() == hedef)Hatalımillis() bazen 2 artar, tam değer hiç görülmeyebilir
int onceki = millis();HatalıUno'da int 16 bittir, 32,7 saniyeden sonra yanlış değer tutar
long onceki = millis();Riskliİşaretli tür 24,8 gün sonra eksiye döner; karışıklığı önlemek için unsigned long kullan
Zaman değişkenlerini asla int olarak tanımlama. Arduino Uno'da int 16 bittir ve en fazla 32.767 tutar; yani program 33. saniyeden itibaren tuhaf davranmaya başlar. millis() ve micros() ile çalışan her değişken unsigned long (ya da uint32_t) olmalı. Büyük aralıkları yazarken de 60000UL biçimini kullan: Uno'da 60 * 1000 gibi bir çarpım int sınırını aşar ve eksi bir sayıya dönüşür.

micros() ile mikrosaniye ölçmek

micros(), aynı mantıkla açılıştan beri geçen mikrosaniyeleri verir. Arduino Uno ve Nano gibi 16 MHz kartlarda çözünürlüğü 4 µs, 8 MHz kartlarda 8 µs'dir ve yaklaşık 71,6 dakikada bir taşar; çıkarma kuralı burada da geçerlidir. Bir kod parçasının ne kadar sürdüğünü ölçmek, kısa darbeleri zamanlamak ya da ses üretmek gibi hassas işlerde kullanılır. Aşağıdaki örnek, yarım saniyede bir analogRead() çağrısının süresini ölçer; Uno'da yaklaşık 110–120 µs görürsün:

Arduino (C++)
1unsigned long raporZaman = 0;
2
3void setup() {
4  Serial.begin(9600);
5}
6
7void loop() {
8  if (millis() - raporZaman >= 500) {         // yarım saniyede bir ölç / measure every half second
9    raporZaman = millis();
10    unsigned long basla = micros();           // başlangıç (µs) / start (µs)
11    int deger = analogRead(A0);
12    unsigned long sure = micros() - basla;    // geçen süre (µs) / elapsed time (µs)
13    Serial.print(F("A0 = "));
14    Serial.print(deger);
15    Serial.print(F("  sure / time: "));
16    Serial.print(sure);
17    Serial.println(F(" us"));
18  }
19}

Daha uzun aralıklarda micros() yerine millis() tercih et; 71 dakikalık taşma süresi uzun beklemeler için kısa kalır. delayMicroseconds() ise yalnızca birkaç mikrosaniyelik, kaçınılmaz beklemeler içindir: örneğin HC-SR04 mesafe sensörünün TRIG pinine 10 µs'lik tetik darbesi göndermek gibi. Böyle kısa beklemeler diğer görevleri fark edilir biçimde geciktirmez.

Zamanlayıcı desenleri: periyodik, tek seferlik ve durum makinesi

  • Periyodik görev: Belirli aralıklarla tekrar eden iş; yanıp sönen LED, 2 saniyede bir sensör okuma, saniyede bir ekran yenileme. Kalıbını yukarıda gördün.
  • Tek seferlik zamanlayıcı (timeout): Bir olaydan belirli süre sonra bir kez çalışır. Merdiven ışığı iyi bir örnektir: hareket olunca ışığı yak, zamanı kaydet, 30 saniye boyunca yeni hareket gelmezse söndür. Yeni hareket geldiğinde zamanı yenilemek süreyi uzatır.
  • Zaman aşımı korumalı bekleme: Bir cevabı ya da sinyali beklerken sonsuza kadar takılmamak için bir üst sınır koyarsın; örneğin 2 saniye içinde veri gelmezse hata mesajı gösterirsin.
  • Durum makinesi (state machine): Cihazın bulunduğu adımı bir değişkende tutarsın, her adımın süresi dolunca bir sonrakine geçersin. Trafik lambası, çamaşır makinesi programı ya da bir oyunun seviyeleri böyle yazılır.
  • Uzun basış algılama: Butonun ne kadar süre basılı tutulduğunu millis() ile ölçerek kısa ve uzun basışa farklı görevler verebilirsin.
Arduino (C++)
1// Yeniden kullanılabilir zamanlayıcı / Reusable timer
2struct Zamanlayici {
3  unsigned long baslangic = 0;
4  unsigned long sure = 0;
5
6  void kur(unsigned long ms) {              // süreyi şimdiden başlat / start the period from now
7    baslangic = millis();
8    sure = ms;
9  }
10  bool doldu() const {                      // süre doldu mu? / has it expired?
11    return millis() - baslangic >= sure;
12  }
13};
14
15const int KIRMIZI = 11, SARI = 12, YESIL = 13;
16enum Durum { DUR, HAZIRLAN, GEC, YAVASLA }; // trafik lambası adımları / traffic light states
17Durum durum = DUR;
18Zamanlayici isik;
19
20void lambalar(bool k, bool s, bool y) {
21  digitalWrite(KIRMIZI, k);
22  digitalWrite(SARI, s);
23  digitalWrite(YESIL, y);
24}
25
26void setup() {
27  pinMode(KIRMIZI, OUTPUT);
28  pinMode(SARI, OUTPUT);
29  pinMode(YESIL, OUTPUT);
30  lambalar(true, false, false);
31  isik.kur(5000);                           // kırmızı 5 sn / red for 5 s
32}
33
34void loop() {
35  if (isik.doldu()) {
36    switch (durum) {
37      case DUR:      durum = HAZIRLAN; lambalar(true, true, false);  isik.kur(1500); break;
38      case HAZIRLAN: durum = GEC;      lambalar(false, false, true); isik.kur(5000); break;
39      case GEC:      durum = YAVASLA;  lambalar(false, true, false); isik.kur(2000); break;
40      case YAVASLA:  durum = DUR;      lambalar(true, false, false); isik.kur(5000); break;
41    }
42  }
43  // loop serbest: buton, sensör, ekran... / loop stays free: buttons, sensors, display...
44}

Bu kodda Zamanlayici adlı küçük bir yapı (struct), süreyi başlatmayı ve dolup dolmadığını sormayı tek satıra indiriyor. Trafik lambası dört adımdan oluşan bir durum makinesi: her adımda lambalar ayarlanıyor ve bir sonraki geçişin süresi kuruluyor. loop() hiç beklemediği için aynı karta bir yaya butonu, gece modu için bir LDR ışık sensörü ya da kalan süreyi gösteren bir ekran rahatça eklenebilir. Çok sayıda zamanlayıcı kullanacaksan Kütüphane Yöneticisi'ndeki hazır zamanlayıcı kütüphaneleri de aynı ilkeyle çalışır.

ESP32'de millis(), delay() ve Ticker

ESP32'nin Arduino çekirdeğinde millis() ve micros() aynı adla ve aynı 32 bitlik unsigned long türüyle çalışır; değerler 64 bitlik esp_timer_get_time() sayacından hesaplanır. Bu yüzden 49,7 gün ve 71,6 dakikalık taşma süreleri ESP32'de de geçerlidir ve aynı çıkarma kuralını kullanırsın. Önemli fark delay() tarafındadır: ESP32'de delay(), FreeRTOS'un vTaskDelay() fonksiyonunu çağırır. İşlemci boşa dönmez, bekleme süresince Wi-Fi ve Bluetooth gibi diğer görevler çalışır. Yine de senin loop() fonksiyonun bekler; web sunucusu, MQTT ya da buton okuma gibi işler gecikir. Periyodik işler için ESP32 çekirdeğiyle gelen Ticker kütüphanesi de kullanışlıdır:

Arduino (C++)
1#include <Ticker.h>
2
3const int LED1 = 2;                         // çoğu DevKit'te dahili LED / built-in LED on most DevKits
4const int LED2 = 4;
5
6Ticker ticker;
7bool led1Durum = false;
8bool led2Durum = false;
9unsigned long oncekiZaman = 0;
10
11void led1Degistir() {                       // kısa tut, uzun iş yapma / keep it short, no long work
12  led1Durum = !led1Durum;
13  digitalWrite(LED1, led1Durum);
14}
15
16void setup() {
17  Serial.begin(115200);
18  pinMode(LED1, OUTPUT);
19  pinMode(LED2, OUTPUT);
20  ticker.attach_ms(250, led1Degistir);      // her 250 ms'de çağır / call every 250 ms
21}
22
23void loop() {
24  unsigned long simdi = millis();
25  if (simdi - oncekiZaman >= 1000) {        // Arduino ile aynı desen / same pattern as on Arduino
26    oncekiZaman = simdi;
27    led2Durum = !led2Durum;
28    digitalWrite(LED2, led2Durum);
29    int64_t us = esp_timer_get_time();      // 64 bit, pratikte taşmaz / 64-bit, practically never overflows
30    Serial.printf("millis=%lu  esp_timer=%lld us\n", simdi, us);
31  }
32}

Ticker'a verilen fonksiyon arka plandaki bir zamanlayıcı görevi tarafından çağrılır; içinde uzun süren işler (HTTP isteği, uzun seri çıktı, delay) yapma, yalnızca bir bayrak kaldır ya da pin değiştir. 64 bitlik esp_timer_get_time() yüz binlerce yıl taşmaz; aylarca açık kalacak ve mikrosaniye hassasiyeti gereken ölçümlerde onu kullanabilirsin. Dahili LED'in pini karta göre değişebilir; kendi kartının pin haritasını Pinout aracında görebilirsin. ESP32'ye yeni başlıyorsan ESP32 Nedir? yazısı kurulum ve pin seçimi için iyi bir başlangıç.

Raspberry Pi Pico (MicroPython): ticks_ms() ve ticks_diff()

MicroPython'da delay() yerine time.sleep(), time.sleep_ms() ve time.sleep_us(); millis() yerine time.ticks_ms() kullanılır. Ama dikkat: ticks değerleri Arduino'daki gibi 2 üzeri 32'de değil, porta göre değişen daha küçük bir sınırda başa döner. Pico'da bu sınır 2 üzeri 30 ms, yani yaklaşık 12,4 gündür (kendi kartında time.ticks_add(0, -1) ile görebilirsin). Bu yüzden iki ticks değerini asla doğrudan çıkarma ya da karşılaştırma; farkı time.ticks_diff(yeni, eski) ile al, ileri bir zaman hesaplamak için time.ticks_add() kullan. Aşağıdaki kod Pico'nun dahili LED'ini kodu durdurmadan yakıp söndürürken GP14'teki butonla GP15'teki LED'i kontrol eder:

MicroPython
1from machine import Pin
2import time
3
4led = Pin("LED", Pin.OUT)                       # dahili LED (Pico ve Pico W) / onboard LED (Pico and Pico W)
5dis_led = Pin(15, Pin.OUT)                      # GP15'e harici LED / external LED on GP15
6buton = Pin(14, Pin.IN, Pin.PULL_UP)            # GP14 buton, diğer ucu GND / GP14 button, other leg to GND
7
8ARALIK = 500                                    # ms
9onceki = time.ticks_ms()
10son_okuma = 1                                   # son ham okuma / last raw reading
11kararli = 1                                     # titreşimsiz durum / debounced state
12degisim = time.ticks_ms()                       # son değişim anı / time of the last change
13
14while True:
15    simdi = time.ticks_ms()
16    if time.ticks_diff(simdi, onceki) >= ARALIK:   # taşmaya karşı güvenli / wrap-safe
17        onceki = time.ticks_add(onceki, ARALIK)    # kaymasız / drift-free
18        led.toggle()
19    okuma = buton.value()
20    if okuma != son_okuma:                      # değişti, sayacı yeniden başlat / changed, restart the timer
21        degisim = simdi
22        son_okuma = okuma
23    if time.ticks_diff(simdi, degisim) >= 50 and okuma != kararli:   # 50 ms debounce
24        kararli = okuma
25        if kararli == 0:                        # basıldı / pressed
26            dis_led.toggle()
27

Periyodik işler için MicroPython'da machine.Timer da vardır: Timer(period=500, mode=Timer.PERIODIC, callback=fonksiyon) satırı verdiğin fonksiyonu her 500 ms'de bir çağırır. Bu geri çağırma fonksiyonlarını da Ticker'daki gibi kısa tut. Pico'yu Arduino IDE ile (arduino-pico çekirdeği) programlıyorsan millis() ve micros() Arduino'daki gibi çalışır. Pico'ya MicroPython kurmak ve pinleri tanımak için Raspberry Pi Pico Nedir? yazısına bakabilirsin.

millis() ile proje fikirleri

  • Reaksiyon testi: LED'i rastgele bir anda yak, butona basılana kadar geçen süreyi millis() ile ölç ve sonucu milisaniye olarak göster.
  • Merdiven ışığı: PIR sensörü hareket algılayınca ışığı yak, son hareketten 30 saniye sonra söndür; bu süre boyunca kod diğer işlere devam etsin.
  • Hızlanan park sensörü: HC-SR04 ile ölçülen mesafe kısaldıkça buzzer'ın bip aralığını kısalt.
  • Bloklamayan melodi: Buzzer ile notaları delay() yerine millis() ile sırala; melodi çalarken butonlar da çalışsın.
  • Sensör ve ekran birlikte: DHT11 sensörünü 2 saniyede bir oku, ekranı ise her 250 ms'de bir yenile.
  • Kronometre: Başlat, durdur ve sıfırla butonlarıyla çalışan, süreyi bir 7 segment display üzerinde gösteren bir kronometre yap.

Sık yapılan hatalar ve çözümleri

HataBelirtiÇözüm
Zaman değişkeni int türündeProgram yaklaşık 33 saniye sonra saçmalıyorTüm zaman değişkenlerini unsigned long yap
millis() >= onceki + aralik yazmak49,7 gün sonra zamanlama bozuluyormillis() - onceki >= aralik biçimine geç
millis() == hedef kontrolüOlay bazen hiç tetiklenmiyorEşitlik yerine >= kullan
onceki değişkenini güncellemeyi unutmakSüre bir kez dolunca iş her turda tekrar ediyorKoşulun içinde onceki = simdi ya da onceki += aralik yaz
Zamanlayıcı kodunun içinde delay() kalmasıDiğer görevler yine gecikiyorUzun beklemeleri durum makinesine böl
Kesme fonksiyonunda millis() ile beklemekKart kilitleniyorISR içinde bekleme yapma, yalnızca bayrak kaldır
60 * 1000 gibi int çarpımıAralık eksi ya da çok kısa çıkıyor60000UL ya da 60UL * 1000 yaz
Kodunun gerçekten bloklamadan çalışıp çalışmadığını anlamanın kolay yolu, loop() fonksiyonunun saniyede kaç kez döndüğünü saymaktır: bir sayaç değişkenini her turda artır, saniyede bir seri porta yazdırıp sıfırla. Sayı birdenbire binlerden tek hanelere düşüyorsa bir yerde bekleyen bir kod (delay, pulseIn ya da uzun bir while döngüsü) vardır.

millis() mantığını bir kez kavradığında projelerin tamamen değişir: LED'ler, butonlar, sensörler ve ekranlar birbirini beklemeden birlikte çalışır. Bir sonraki adım için Arduino proje fikirleri listesinden zamanlama gerektiren bir proje seçebilirsin. BlueGrays uygulamasında Trafik Lambası, Polis Çakar, Akıllı Merdiven ve Koridor Aydınlatması ve Ses Uyarılı Reaksiyon Testi gibi projeleri adım adım devre şeması ve hazır koduyla inceleyip kendi kartında deneyebilirsin.

Sıkça sorulan sorular

Arduino millis() nedir?

millis(), Arduino'nun açıldığı ya da resetlendiği andan beri geçen süreyi milisaniye cinsinden veren fonksiyondur. Değeri unsigned long türündedir ve yaklaşık 49,7 gün sonra sıfırdan yeniden başlar. Programı durdurmadan zamanlama yapmak için kullanılır.

millis() ile delay() arasındaki fark nedir?

delay() verilen süre boyunca programı bekletir; bu sırada buton okuma ya da ekran güncelleme gibi işler yapılamaz. millis() ise yalnızca geçen süreyi söyler, beklemez. Geçen süreyi kontrol edip vakti gelen işi yaparak birden fazla işi aynı anda yürütebilirsin.

millis() 49 gün sonra sıfırlanınca program bozulur mu?

Zamanı millis() - onceki >= aralik biçiminde çıkarma ile kontrol ediyorsan bozulmaz; işaretsiz çıkarma taşma anında da doğru farkı verir. Sorun, millis() >= onceki + aralik gibi toplama ile yazılmış karşılaştırmalarda ve int türündeki değişkenlerde ortaya çıkar.

Arduino'da millis() sıfırlanabilir mi?

Resmî bir millis() sıfırlama fonksiyonu yoktur ve çekirdeğin iç değişkenini değiştirmek önerilmez. Buna gerek de yoktur: sıfırlamak istediğin anı bir değişkene kaydeder, geçen süreyi o andan itibaren hesaplarsın. Kartı resetlemek ise millis() değerini doğal olarak 0'dan başlatır.

Arduino aynı anda iki iş yapabilir mi?

Uno tek çekirdekli olduğu için iki komutu fiziksel olarak aynı anda çalıştıramaz, ama millis() ile her işi kısa adımlara bölersen loop() saniyede binlerce kez dönerek hepsine sıra verir. Dışarıdan bakınca LED, buton, sensör ve ekran aynı anda çalışıyormuş gibi görünür. ESP32'de ise iki çekirdek ve FreeRTOS görevleri de vardır.

millis() saat yapmak için yeterince doğru mu?

millis() kartın osilatörüne bağlıdır; Arduino Uno'daki seramik rezonatör yüzünden günde birkaç dakikaya varan sapmalar görülebilir. Kısa aralıkları ölçmek için yeterlidir, ama günlerce doğru kalması gereken bir saat için DS1307 ya da DS3231 gibi bir RTC modülü veya ESP32'de internetten NTP ile saat almak daha doğrudur.

Tüm projeleri gör
Led Yakma
#1Başlangıç

Led Yakma

Arduino ile tek bir LED'i belirli aralıklarla yakıp söndüren temel giriş projesi. digitalWrite ve delay fonksiyonlarının kullanımını öğretir.

4.7(163)
784
Trafik Lambası
#3Başlangıç

Trafik Lambası

Kırmızı, sarı ve yeşil LED'lerle gerçek bir trafik lambası simülasyonu yapan proje. Zamanlama ve sıralı kontrol mantığını öğretir.

4.8(123)
306
Polis Çakar
#4Başlangıç

Polis Çakar

İki LED'i hızlı aralıklarla yanıp söndürerek polis çakar lambası efekti oluşturan proje. for döngüsü ve zamanlama kontrolünü öğretir.

4.7(95)
212
Hız Ayarlı Yürüyen Led
#7Orta

Hız Ayarlı Yürüyen Led

Potansiyometre ile LED yürüme hızını gerçek zamanlı olarak ayarlayabilen proje. analogRead ile potansiyometre değerini okuyarak delay süresini dinamik kontrol etmeyi öğretir.

4.6(71)
133
Buton İle Led Yakma (On Off)
#10Başlangıç

Buton İle Led Yakma (On Off)

Her buton basışında LED'in durumunu değiştiren (aç/kapa) toggle projesi. Durum değişkeni ve debounce mantığını öğretir.

4.8(100)
158
Akıllı Merdiven ve Koridor Aydınlatması
#82Orta
Premium

Akıllı Merdiven ve Koridor Aydınlatması

Çift yönlü HC-SR04 mesafe sensörü ile hareket yönüne doğru sırayla yanan ve sönen şık bir otomasyon projesi.

5.0(35)
51
Arduino Ses Uyarılı Reaksiyon Testi
#97Orta
Premium

Arduino Ses Uyarılı Reaksiyon Testi

Arduino Reaksiyon Test İstasyonu Bu proje, bir kullanıcının görsel ve işitsel uyaranlara karşı refleks hızını milisaniye (ms) hassasiyetinde ölçmek için geliştirilmiş dijital bir test düzeneğidir. Sistem; bir Arduino kontrolcü, üç aşamalı LED bildirim grubu, sesli uyarı için bir buzzer ve sonuçları anlık gösteren I2C LCD ekrandan oluşur. Çalışma Prensibi: Test başladığında sistem sırasıyla kırmızı ve sarı LED'leri yakarak kullanıcıyı hazırlar. Rastgele belirlenen bir süre sonunda yeşil LED'in y

4.6(33)
49
4'lü 7 Segment Display İle Dijital Saat
#48Orta
Premium

4'lü 7 Segment Display İle Dijital Saat

DS1302 RTC modülü ve 4'lü 7 segment display ile saat ve dakikayı gösteren dijital saat projesi. Gerçek zamanlı saat verisi ile segment gösterimini birleştirir.

4.8(56)
73

delay() kullanınca butonun neden geç tepki verdiğini, millis() ile LED, buton ve sensörü aynı anda yönetmeyi, 49,7 günlük taşmaya karşı güvenli kod yazmayı ve ESP32 ile Pico'daki karşılıkları öğren.