Temeller · 10 dk okuma

Arduino Interrupt (Kesme): attachInterrupt, ISR ve Debounce

Arduino interrupt (kesme) nasıl kullanılır? attachInterrupt, Uno D2/D3 pinleri, FALLING/CHANGE, volatile, debounce, encoder ve RPM örnekleri, ESP32 ve Pico.

Arduino interrupt (kesme), bir pinde beklediğin değişiklik ya da bir zamanlayıcı olayı gerçekleştiği anda işlemcinin o an yaptığı işi bırakıp senin yazdığın özel bir fonksiyonu çalıştırmasıdır. Kısacası loop() içinde sürekli "butona basıldı mı?" diye sormak yerine Arduino'ya "basıldığında bana haber ver" dersin. Bunu attachInterrupt() komutuyla yaparsın; Arduino Uno'da kesme verebilen pinler D2 ve D3'tür, ESP32 ve Raspberry Pi Pico'da ise hemen her GPIO kesme üretebilir.

Kesmeler; çok hızlı gelen darbeleri (encoder, devir sensörü, su akış sensörü) kaçırmadan saymak, uzun süren işlerin arasında kısa bir olayı yakalamak ve kartı uykudan uyandırmak için vazgeçilmezdir. Bu rehberde kesmenin donanımda nasıl işlediğini, hangi kartta hangi pinlerin kullanılabildiğini, RISING, FALLING ve CHANGE modlarının farkını, kesme fonksiyonu (ISR) yazarken uyman gereken kuralları, buton sıçramasını (debounce) ve buton, encoder ve devir sayma örneklerini adım adım göreceksin. Sonunda ESP32'deki IRAM_ATTR etiketini ve Pico'da MicroPython ile irq kullanımını da öğreneceksin.

Arduino Uno kesme pinleri D2 (INT0) ve D3 (INT1)
Arduino Uno'da harici kesme verebilen yalnızca iki pin vardır: D2 (INT0) ve D3 (INT1).

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

Kapı zilini düşün: zil olmasaydı birinin gelip gelmediğini anlamak için her dakika kapıya gidip bakman gerekirdi. Buna yoklama (polling) denir ve loop() içinde digitalRead() ile pin okumak tam olarak budur. Zil ise bir kesmedir: sen başka bir işle uğraşırken çalar, işini bırakır, kapıya bakar ve kaldığın yere dönersin. Mikrodenetleyicide de durum aynıdır. Pinde seçtiğin değişiklik olduğunda donanım bir kesme bayrağı kaldırır; işlemci o an çalıştırdığı komutu bitirir, kaydedicileri saklar, kesme vektörü üzerinden senin ISR (Interrupt Service Routine, kesme hizmet fonksiyonu) fonksiyonuna atlar ve ISR bitince loop() içinde tam kaldığı yerden devam eder. Uno'da bu geçiş yalnızca birkaç mikrosaniye sürer.

ÖzellikYoklama (polling)Kesme (interrupt)
Nasıl çalışır?loop() pini sürekli okurDonanım değişikliği yakalar, ISR çalışır
Tepki süresiloop() ne kadar yavaşsa o kadar gecikirBirkaç mikrosaniye
Kodda delay() varkenOlay kaçabilirOlay yine yakalanır
Çok hızlı darbelerKaçırılırTek tek sayılır
Kod karmaşıklığıBasitvolatile ve atomik okuma gerekir
En uygun kullanımButonlar, yavaş sensörlerEncoder, devir ve akış sensörü, uykudan uyanma

Hangi pinler kesme verir? Arduino, ESP32 ve Pico

KartHarici kesme pinleriNot
Arduino Uno R3, Nano, Pro MiniD2, D3D2 = INT0, D3 = INT1
Arduino Uno R4 (Minima / WiFi)D2, D3Resmî Arduino belgesinde listelenen pinler
Arduino Mega 25602, 3, 18, 19, 20, 2120 ve 21 I2C için kullanılıyorsa kesme verilemez
Arduino Leonardo, Micro0, 1, 2, 3, 70 ve 1 aynı zamanda seri port pinleridir
Arduino Nano EveryTüm dijital pinlerATmega4809 işlemci
ESP32Tüm GPIO'larGPIO 34–39 yalnız giriştir, dahili pull-up içermez
ESP8266 (NodeMCU)GPIO 16 (D0) hariç tümüISR'ler IRAM_ATTR ile işaretlenmeli
Raspberry Pi Pico / Pico WGP0–GP28 tümüPinler 3,3 V'tur, 5 V uygulama

Kodda pin numarasını doğrudan değil, digitalPinToInterrupt(pin) ile kesme numarasına çevirerek vermelisin, çünkü kesme numarası karttan karta değişir: Uno'da D2 kesme 0, D3 kesme 1 iken Leonardo'da kesme 0 D3'e, kesme 1 D2'ye karşılık gelir. Uno'da ikiden fazla kesmeye ihtiyacın varsa pin değişim kesmeleri (Pin Change Interrupt, PCINT) neredeyse tüm pinlerde çalışır; ancak yalnızca "değişti" bilgisini verir ve aynı porttaki pinler tek bir ISR'yi paylaşır. PinChangeInterrupt ya da EnableInterrupt kütüphaneleri bu işi kolaylaştırır. Kartının tam pin haritasını Pinout aracında, Uno'nun pin görevlerini ise Arduino Uno pinleri yazısında bulabilirsin.

En sık yapılan hata attachInterrupt(2, fonksiyon, FALLING) yazmaktır. Burada 2, pin numarası değil kesme numarası olarak yorumlanır; Uno'da 2 numaralı kesme olmadığı için hiçbir şey olmaz ve hata mesajı da çıkmaz. Doğrusu attachInterrupt(digitalPinToInterrupt(2), fonksiyon, FALLING) biçimidir.

attachInterrupt kullanımı ve kesme modları

Komutun yapısı şöyledir: attachInterrupt(digitalPinToInterrupt(pin), ISR, mod). İkinci parametre, kesme geldiğinde çalışacak fonksiyonun adıdır; bu fonksiyon parametre almaz ve değer döndürmez (void). Üçüncü parametre kesmenin hangi olayda tetikleneceğini belirler. Kesmeyi kapatmak için detachInterrupt(digitalPinToInterrupt(pin)), tüm kesmeleri kısa bir süre durdurmak için noInterrupts(), yeniden açmak için interrupts() kullanılır.

ModNe zaman tetiklenir?Tipik kullanım
LOWPin LOW kaldığı sürece tekrar tekrarUykudan uyandırma
CHANGEHer değişimde (iki kenarda da)Encoder, darbe süresi ölçme, debounce
RISINGLOW'dan HIGH'a geçişte (yükselen kenar)Sensör darbesinin başlangıcı, pull-down'lı buton
FALLINGHIGH'dan LOW'a geçişte (düşen kenar)Pull-up'lı butona basış, açık kollektörlü sensörler
HIGHPin HIGH kaldığı süreceYalnız Due, Zero ve MKR1000'de; ESP32'de ONHIGH

Modu devrene göre seç. Butonu INPUT_PULLUP ile bağladıysan pin normalde HIGH'tır ve basınca LOW'a düşer; yani basışı FALLING yakalar. LOW modunu dikkatli kullan: pin LOW kaldığı sürece ISR art arda çalışır ve loop() neredeyse hiç ilerlemez. Bu yüzden genellikle yalnızca uykudan uyandırmak için kullanılır; Uno'yu en derin uyku modundan (power-down) uyandırmak için veri sayfası LOW seviye kesmesini ya da pin değişim kesmesini önerir. ESP32'de ise LOW ve HIGH sabitleri kesme modu olarak beklediğin gibi çalışmaz: LOW kesmeyi hiç açmaz, HIGH ise yükselen kenar gibi davranır. Seviye kesmesi için orada ONLOW ve ONHIGH yazılır.

İlk örnek: butona basışları kesmeyle saymak

  1. Butonun bir bacağını D2'ye, diğer bacağını GND'ye bağla. Harici direnç gerekmez; dahili pull-up kullanacağız.
  2. Aşağıdaki kodu yükle ve Seri Monitör'ü 9600 baud hızında aç.
  3. Butona birkaç kez bas ve sayacın arttığını izle. loop() içindeki delay(500)'e rağmen hiçbir basış kaçmaz.
  4. Bazı basışlarda sayının 2–3 birden arttığını görürsen şaşırma: bu, butonun kontak sıçramasıdır ve birazdan çözeceğiz.
C++ (Arduino IDE)
1const byte BUTTON_PIN = 2;                // kesme pini D2 (INT0) / interrupt pin D2 (INT0)
2volatile unsigned int pressCount = 0;     // ISR ile paylaşılan sayaç / counter shared with the ISR
3
4void onPress() {                          // ISR: kısa ve hızlı / ISR: short and fast
5  pressCount++;
6}
7
8void setup() {
9  Serial.begin(9600);
10  pinMode(BUTTON_PIN, INPUT_PULLUP);      // dahili pull-up, buton GND'ye / internal pull-up, button to GND
11  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onPress, FALLING);
12}
13
14void loop() {
15  noInterrupts();                         // 2 baytlık değeri güvenle kopyala / copy the 2-byte value safely
16  unsigned int count = pressCount;
17  interrupts();
18
19  Serial.print(F("Basis / Presses: "));
20  Serial.println(count);
21  delay(500);                             // loop yavaş olsa da basış kaçmaz / presses are not missed even if loop is slow
22}

Kodda üç önemli nokta var. Birincisi, onPress() yalnızca sayacı bir artırır ve hemen biter; ISR'nin görevi olayı not etmektir, işi yapmak değil. İkincisi, sayaç volatile olarak tanımlanmıştır; böylece derleyici değişkeni bir kaydedicide tutup eski değeri okumaz, her seferinde bellekten taze değeri alır. Üçüncüsü, loop() sayacı okurken kesmeleri bir anlığına kapatır. unsigned int Uno'da 2 bayttır ve 8 bitlik işlemci onu iki adımda okur. Sayaç 255'ten 256'ya, yani 0x00FF'ten 0x0100'e geçerken loop() önce düşük baytı (0xFF) okur, tam o anda kesme gelip sayacı artırırsa ardından yüksek baytı (0x01) okur ve sonuç 0x01FF, yani 511 olur. Bu hata çok nadir görüldüğü için bulması da zordur. AVR kartlarda util/atomic.h içindeki ATOMIC_BLOCK(ATOMIC_RESTORESTATE) makrosu da aynı korumayı sağlar; bool ve byte gibi tek baytlık değişkenlerde bu sorun yoktur.

ISR kuralları: kısa tut, volatile kullan, delay yok

  • Kısa ve hızlı tut: ISR çalışırken diğer kesmeler bekler. Uno'da millis() sayacı ve seri port da kesmeyle çalıştığı için uzun bir ISR zamanlamayı bozar ve gelen seri verileri kaybettirebilir. İdeal bir ISR birkaç mikrosaniyede biter.
  • delay() kullanma: delay() zamanı Timer0 kesmesiyle güncellenen sayaçlarla ölçer; ISR içinde kesmeler kapalı olduğu için çalışmaz. millis() okunabilir ama ISR boyunca artmaz, micros() ise 1–2 ms sonra hatalı değer verir. Çok kısa bir bekleme şartsa delayMicroseconds() çalışır.
  • Serial.print() kullanma: Seri gönderim de kesmeye dayanır; ISR'yi uzatır ve tampon dolarsa beklemeye girer. Yazdırma işini loop()'a bırak.
  • Paylaşılan değişkenleri volatile yap: Hem ISR'de hem loop()'ta kullanılan her değişken volatile tanımlanmalıdır.
  • Çok baytlı değişkenleri atomik oku: int, long, float ve unsigned long değişkenleri loop()'ta noInterrupts() ile interrupts() arasında kopyala.
  • Parametre ve dönüş değeri yok: ISR void olmalı ve parametre almamalıdır; veri alışverişi global değişkenlerle yapılır.
  • Ağır işleri ISR'ye koyma: I2C ekrana yazmak, SD karta kaydetmek ya da Wire gibi kendisi kesmeye ihtiyaç duyan kütüphaneleri çağırmak ISR içinde takılmalara yol açar.
En sağlam düzen "bayrak kaldır, işi loop'ta yap" yaklaşımıdır: ISR yalnızca volatile bir bool değişkeni true yapar ya da bir sayacı artırır; ekrana yazma, motor sürme ve hesaplama gibi işleri loop() bu bayrağı görünce yapar. loop()'u millis() ile delay kullanmadan yazarsan sistem hem hızlı hem tutarlı çalışır.

Kesmeyle buton okuma ve debounce (sıçrama önleme)

Mekanik bir butona bastığında metal kontaklar hemen oturmaz; yaklaşık 0,1 ile 10 milisaniye boyunca birkaç kez açılıp kapanır. loop() bunu fark etmeyecek kadar yavaş olabilir ama kesme her kenarı yakalar; bu yüzden tek basış 3–5 kesme üretebilir. Üstelik sıçrama yalnızca basarken değil, bırakırken de olur; yalnızca FALLING ile süre kilidi kullanan kodlar, butonu uzun basılı tutup bıraktığında çift sayım yapabilir. Aşağıdaki kod CHANGE modunu kullanır ve basışı yalnızca pin LOW iken ve hat ondan önce en az 50 ms sessiz kaldıysa kabul eder. Böylece hem basma hem bırakma sıçramaları elenir.

C++ (Arduino IDE)
1const byte BUTTON_PIN = 2;                // kesme pini / interrupt pin
2const byte LED_PIN = 13;                  // dahili LED / onboard LED
3const unsigned long QUIET_MS = 50;        // gereken sessizlik süresi / required quiet time
4
5volatile bool ledState = false;           // ISR ile paylaşılır / shared with the ISR
6
7void onButtonChange() {                   // iki kenarda da çalışır / runs on both edges
8  static unsigned long lastEdgeMs = 0;
9  unsigned long now = millis();           // ISR'de okunur ama artmaz / readable in an ISR but does not advance
10  bool pressed = (digitalRead(BUTTON_PIN) == LOW);
11
12  if (pressed && now - lastEdgeMs > QUIET_MS) {   // hat sessizdi: gerçek basış / line was quiet: real press
13    ledState = !ledState;
14  }
15  lastEdgeMs = now;                       // her kenar süreyi yeniden başlatır / every edge restarts the timer
16}
17
18void setup() {
19  pinMode(LED_PIN, OUTPUT);
20  pinMode(BUTTON_PIN, INPUT_PULLUP);
21  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onButtonChange, CHANGE);
22}
23
24void loop() {
25  digitalWrite(LED_PIN, ledState ? HIGH : LOW);   // bool tek bayt, okuması atomik / bool is one byte, atomic read
26  // burada uzun işler olsa da basış kaçmaz / long tasks here will not make you miss a press
27}

Yazılıma ek olarak sıçramayı donanımla da bastırabilirsin: butonun iki ucu arasına 100 nF'lık bir kondansatör, pine de 10 kΩ'luk bir pull-up direnci koyarsan zaman sabiti τ = R × C = 10 kΩ × 100 nF = 1 ms olur ve kısa sıçramalar yumuşar; Uno girişlerindeki Schmitt tetikleyici de kenarı temizler. Farklı değerleri RC zaman sabiti aracıyla hesaplayabilirsin. Dürüst olmak gerekirse insan eliyle basılan butonlar için kesme çoğu zaman şart değildir; loop() hızlıysa yoklama yeterlidir. Pull-up, pull-down ve millis() ile debounce için Arduino'ya buton nasıl bağlanır? yazısına bakabilirsin. Kesmeyi asıl hızlı ve kısa sinyallerde kullan.

Rotary encoder'ı kesmeyle okumak

Rotary encoder (ör. KY-040) döndürüldükçe CLK ve DT adlı iki pinden birbirine göre kaydırılmış kare dalgalar üretir. Hızlı çevrildiğinde darbeler milisaniyenin altına iner ve yoklamayla adım kaçırmak kolaylaşır. En basit yöntem CLK'yi kesme pinine bağlamak ve CLK düştüğü anda DT'nin seviyesine bakmaktır: DT HIGH ise bir yöne, LOW ise diğer yöne dönülmüştür. Bu sürüm her tıkta bir adım sayar; yön ters çıkarsa ++ ile -- satırlarının yerini değiştir. Ucuz encoder'lar da sıçradığı için CLK ve DT ile GND arasına 10–100 nF kondansatör eklemek sonucu belirgin biçimde iyileştirir. İki pini de kesmeye bağlayan ve durum tablosuyla hiç adım kaçırmayan sürüm için rotary encoder (KY-040) rehberine göz at.

C++ (Arduino IDE)
1const byte PIN_CLK = 2;                   // kesme pini / interrupt pin
2const byte PIN_DT = 4;                    // normal dijital giriş / regular digital input
3
4volatile long position = 0;               // 4 baytlık konum / 4-byte position
5
6void onClkFall() {
7  if (digitalRead(PIN_DT) == HIGH) {      // yön DT seviyesinden / direction from the DT level
8    position++;
9  } else {
10    position--;
11  }
12}
13
14void setup() {
15  Serial.begin(115200);
16  pinMode(PIN_CLK, INPUT_PULLUP);
17  pinMode(PIN_DT, INPUT_PULLUP);
18  attachInterrupt(digitalPinToInterrupt(PIN_CLK), onClkFall, FALLING);
19}
20
21void loop() {
22  static long lastPos = 0;
23
24  noInterrupts();                         // atomik kopya / atomic copy
25  long pos = position;
26  interrupts();
27
28  if (pos != lastPos) {
29    Serial.println(pos);
30    lastPos = pos;
31  }
32}

Devir sayma: motor RPM ölçmek

Bir motorun ya da tekerleğin devrini ölçmek kesmelerin en klasik kullanımıdır. Sensör olarak ya LM393'lü yarıklı optik hız sensörü ile 20 yarıklı bir encoder diski ya da A3144 gibi bir Hall sensörü ile mile yapıştırılmış bir mıknatıs kullanılır. Her yarık ya da mıknatıs geçişi bir darbe üretir. Bir saniyede sayılan darbe sayısından devir şöyle hesaplanır: RPM = darbe/saniye × 60 / tur başına darbe. Örneğin 20 yarıklı diskte saniyede 100 darbe sayılıyorsa motor 100 × 60 / 20 = 300 dev/dk dönüyordur.

C++ (Arduino IDE)
1const byte SENSOR_PIN = 3;                // kesme pini D3 (INT1) / interrupt pin D3 (INT1)
2const byte PULSES_PER_REV = 20;           // diskteki yarık sayısı / slots on the encoder disc
3
4volatile unsigned long pulseCount = 0;
5
6void onPulse() {
7  pulseCount++;
8}
9
10void setup() {
11  Serial.begin(9600);
12  pinMode(SENSOR_PIN, INPUT_PULLUP);
13  attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), onPulse, FALLING);
14}
15
16void loop() {
17  static unsigned long lastMs = 0;
18
19  if (millis() - lastMs >= 1000) {        // her saniye hesapla / compute once per second
20    lastMs += 1000;
21
22    noInterrupts();                       // kopyala ve sıfırla / copy and reset
23    unsigned long pulses = pulseCount;
24    pulseCount = 0;
25    interrupts();
26
27    float rpm = pulses * 60.0 / PULSES_PER_REV;
28    Serial.print(F("RPM: "));
29    Serial.println(rpm, 1);
30  }
31}

Burada loop() her saniye sayacı kopyalayıp sıfırlar; bu iki işlem arasında kesme gelip bir darbe kaybolmasın diye ikisi de aynı noInterrupts() bloğunun içindedir. 20 yarıklı diskte çözünürlük 3 dev/dk'dır. Düşük hızlarda daha hassas sonuç istersen darbe saymak yerine iki darbe arasındaki süreyi ISR'de micros() ile ölç ve RPM = 60.000.000 / (süre_µs × tur başına darbe) formülünü kullan. Aynı yöntem YF-S201 su akış sensörü, Hall sensörüyle hız ölçümü ve redüktörlü DC motor tekerlek devri için de geçerlidir.

Zamanlayıcı (timer) kesmelerine kısa bir not

Kesmeler yalnızca pinlerden gelmez. Mikrodenetleyicinin içindeki zamanlayıcılar da belirli aralıklarla kesme üretebilir; böylece loop() ne yaparsa yapsın bir fonksiyon tam olarak her 1 ms'de ya da her 500 ms'de çalışır. Uno'da üç zamanlayıcı vardır ve bazıları zaten Arduino'nun kendi fonksiyonları tarafından kullanılır:

ZamanlayıcıBoyutKullananPWM pinleri
Timer08 bitmillis(), delay(), micros()5, 6
Timer116 bitServo kütüphanesi, TimerOne9, 10
Timer28 bittone(), IRremote3, 11

Uno'da en kolay yol TimerOne kütüphanesidir: Timer1.initialize(500000) ile periyodu mikrosaniye cinsinden verir, Timer1.attachInterrupt(fonksiyon) ile her 500 ms'de çalışacak ISR'yi bağlarsın. Timer1'i kullandığı için Servo kütüphanesiyle ve 9–10 numaralı pinlerdeki analogWrite() ile çakışır. ESP32'de Arduino çekirdeği 3.x sürümünde zamanlayıcı kesmesi timerBegin(1000000), timerAttachInterrupt() ve timerAlarm() ile kurulur; 2.x'teki timerAlarmWrite() ve timerAlarmEnable() kaldırıldığı için eski örnekler "was not declared in this scope" hatası verir. Pico'da MicroPython ile Timer(period=500, mode=Timer.PERIODIC, callback=fonksiyon) tek satırda periyodik bir kesme kurar. Zamanlayıcıların PWM ile ilişkisi için PWM nedir? yazısına bakabilirsin.

ESP32'de kesme: IRAM_ATTR ve portMUX

ESP32'de kesme için özel bir pin aramana gerek yoktur; tüm GPIO'lar kesme üretebilir. Yalnızca GPIO 34–39 dahili pull-up içermez, buton bağlarsan harici 10 kΩ pull-up gerekir; ayrıca GPIO 36 ve 39'un ADC kullanılırken sahte kesmeler üretebildiği Espressif'in hata listesinde belirtilmiştir. ESP32'deki asıl fark ISR'nin önüne yazılan IRAM_ATTR etiketidir. Bu etiket fonksiyonu flash bellek yerine hızlı dahili RAM'e (IRAM) yerleştirir. Flash'a yazma sırasında (Preferences, SPIFFS, OTA) önbellek kapatılır; yapılandırmaya bağlı olarak o anda flash'taki koda erişen bir kesme kartı "Guru Meditation Error: Core 1 panic'ed (Cache disabled but cached memory region accessed)" hatasıyla yeniden başlatabilir. ESP8266'da kural daha da katıdır: IRAM_ATTR yazılmamış bir ISR, attachInterrupt çağrıldığı anda "ISR not in IRAM!" mesajıyla kartı durdurur.

C++ (Arduino IDE)
1const int BUTTON_PIN = 27;                // her GPIO kesme verebilir / any GPIO can interrupt
2const int LED_PIN = 2;                    // çoğu DevKit'te dahili LED / onboard LED on most DevKits
3const uint32_t QUIET_MS = 50;             // gereken sessizlik süresi / required quiet time
4
5volatile uint32_t pressCount = 0;
6portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;   // paylaşılan veri kilidi / lock for shared data
7
8void IRAM_ATTR onButtonChange() {         // ISR IRAM'de çalışır / ISR runs from IRAM
9  static uint32_t lastEdgeMs = 0;
10  uint32_t now = millis();
11  bool pressed = (digitalRead(BUTTON_PIN) == LOW);
12
13  if (pressed && now - lastEdgeMs > QUIET_MS) {
14    portENTER_CRITICAL_ISR(&mux);
15    pressCount++;
16    portEXIT_CRITICAL_ISR(&mux);
17  }
18  lastEdgeMs = now;
19}
20
21void setup() {
22  Serial.begin(115200);
23  pinMode(LED_PIN, OUTPUT);
24  pinMode(BUTTON_PIN, INPUT_PULLUP);
25  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onButtonChange, CHANGE);
26}
27
28void loop() {
29  static uint32_t lastCount = 0;
30
31  portENTER_CRITICAL(&mux);               // loop tarafında kilit / lock on the loop side
32  uint32_t count = pressCount;
33  portEXIT_CRITICAL(&mux);
34
35  if (count != lastCount) {
36    lastCount = count;
37    digitalWrite(LED_PIN, (count % 2) ? HIGH : LOW);   // her basışta LED değişir / LED toggles on every press
38    Serial.printf("Basis / Press: %lu\n", (unsigned long)count);
39  }
40}

ESP32'de FreeRTOS çalıştığı ve iki çekirdek bulunduğu için paylaşılan değişkenler Uno'daki noInterrupts() yerine portMUX_TYPE kilidiyle korunur: ISR içinde portENTER_CRITICAL_ISR(), loop() içinde portENTER_CRITICAL() kullanılır. 32 bitlik bir değişkeni okumak ESP32'de zaten tek adımdır, ama okuyup sıfırlamak ya da birden çok değişkeni birlikte güncellemek gibi işlemlerde bu kilit şarttır. Aynı fonksiyonu birden çok pine bağlamak istersen attachInterruptArg() ile ISR'ye pin numarası gibi bir parametre de geçirebilirsin. ESP32 temelleri için ESP32 nedir?, görevler ve çekirdekler için ESP32 FreeRTOS ve çift çekirdek yazılarına bakabilirsin.

Raspberry Pi Pico'da kesme: MicroPython irq

Raspberry Pi Pico'da GP0'dan GP28'e kadar tüm pinler kesme üretebilir. MicroPython'da kesme, pin nesnesinin irq() metoduyla kurulur: trigger için Pin.IRQ_FALLING, Pin.IRQ_RISING ya da ikisinin birleşimi verilir; handler ise kesme geldiğinde çalışacak fonksiyondur ve tetikleyen pini parametre olarak alır. Python'da volatile yoktur; ISR'de değiştirdiğin değişkenleri global olarak bildirmen yeterlidir. Zamanı ölçerken time.ticks_ms() ve taşmaya dayanıklı time.ticks_diff() kullanılır. Aşağıdaki kod Arduino örneğindeki sessizlik mantığını Pico'ya taşır:

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)
5button = Pin(14, Pin.IN, Pin.PULL_UP)     # buton GP14 ile GND arasında / button between GP14 and GND
6
7QUIET_MS = 50                             # gereken sessizlik süresi / required quiet time
8presses = 0
9last_edge = time.ticks_ms()
10
11def on_change(pin):                       # handler tetikleyen pini alır / handler receives the pin
12    global presses, last_edge
13    now = time.ticks_ms()
14    if pin.value() == 0 and time.ticks_diff(now, last_edge) > QUIET_MS:
15        presses += 1
16        led.toggle()
17    last_edge = now
18
19button.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=on_change)
20
21while True:
22    print("Basis / Presses:", presses)    # ana döngü uyurken bile basışlar sayılır / presses are counted while the loop sleeps
23    time.sleep(1)

Pico'da irq varsayılan olarak yumuşak (soft) çalışır: fonksiyon MicroPython tarafından sıraya alınıp en kısa sürede çalıştırılır, bu yüzden içinde print kullanmak ya da liste oluşturmak sorun çıkarmaz ama gecikme biraz artar. hard=True verirsen gecikme mikrosaniyelere iner; buna karşılık ISR içinde bellek ayıramazsın, yani yeni bir liste, metin ve hatta float hesabı bile "MemoryError" hatası verebilir. Pico'yu Arduino IDE ile (Earle Philhower çekirdeği) programlıyorsan attachInterrupt() tüm GPIO'larda aynen çalışır. Kurulum için Raspberry Pi Pico nedir?, temel komutlar için MicroPython temelleri yazısına göz at.

Kesme kullanan proje fikirleri

  • Reaksiyon ölçer: LED yandıktan sonra butona basma süreni ISR'de micros() ile milisaniyenin altında bir doğrulukla ölç.
  • Bisiklet hız göstergesi: Tekerleğe bir mıknatıs, kadroya bir Hall sensörü koy; tur süresinden hızı ve mesafeyi hesapla.
  • Su sayacı: YF-S201 akış sensörünün darbelerini sayarak harcanan litreyi göster.
  • Düz giden robot araba: Tekerlek encoder'larıyla iki motorun devrini ölç ve hızlarını eşitle.
  • Menü kontrolü: Rotary encoder ile ekrandaki menüde gez ve ayarları değiştir.
  • Pille çalışan alarm: Kart uyurken PIR sensörünün çıkışı onu uyandırsın; ESP32 için deep sleep yazısına bak.

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

BelirtiSebepÇözüm
Kesme hiç çalışmıyorattachInterrupt(2, ...) ile pin numarası verilmiş ya da kesme desteklemeyen bir pin seçilmişdigitalPinToInterrupt(pin) kullan; Uno'da D2 ya da D3'e bağlan
Tek basışta sayaç 2–5 artıyorKontak sıçraması (bounce)Yazılımla debounce yap ve/veya 100 nF kondansatör ekle
Değişken loop()'ta hiç değişmiyorvolatile unutulmuşPaylaşılan değişkeni volatile tanımla
Ara sıra saçma büyük değerlerÇok baytlı değişken atomik okunmamışnoInterrupts() ile interrupts() arasında kopyala
Program donuyor ya da millis() kayıyorISR içinde delay(), Serial ya da uzun bir işlem varISR'de yalnızca bayrak ya da sayaç güncelle
Butona dokunmadan kesme geliyorPin boşta (floating), pull-up yokINPUT_PULLUP ya da harici 10 kΩ direnç kullan
Yeniden attachInterrupt sonrası ISR hemen bir kez çalışıyorKesme kapalıyken gelen kenar bayrağı kaldırmış (AVR)Uno'da attachInterrupt'tan önce EIFR = bit(INTF0); yaz (D3 için INTF1)
ESP32 rastgele yeniden başlıyorISR IRAM'de değil ya da ISR'de ağır iş varIRAM_ATTR ekle, ISR'yi kısalt

Kesmeler, loop() başka işlerle meşgulken bile hiçbir olayı kaçırmamanı sağlayan güçlü bir araçtır; tek yapman gereken üç kuralı unutmamak: ISR'yi kısa tut, paylaşılan değişkenleri volatile yap ve loop()'ta atomik oku. BlueGrays uygulamasında buton sayaçlarını, reaksiyon testini, ESP32 buton projelerini ve Pico'da kesme (IRQ) ile yazılım debounce'unu karşılaştıran projeyi adım adım devre şeması ve koduyla bulabilirsin.

Sıkça sorulan sorular

Arduino Uno'da hangi pinler interrupt (kesme) destekler?

Arduino Uno R3, Nano ve Pro Mini'de harici kesme verebilen yalnızca iki pin vardır: D2 (INT0) ve D3 (INT1). Mega 2560'ta 2, 3, 18, 19, 20 ve 21, Leonardo'da ise 0, 1, 2, 3 ve 7 kullanılabilir. Diğer pinlerde yalnızca değişimi bildiren pin değişim kesmeleri (PCINT) kullanılabilir.

Kesme fonksiyonunda (ISR) neden delay() çalışmaz?

delay() zamanı Timer0 kesmesiyle güncellenen sayaçlarla ölçer. ISR çalışırken diğer kesmeler beklediği için bu sayaçlar ilerlemez ve delay() düzgün çalışmaz. Beklemen gereken bir iş varsa ISR'de yalnızca bir bayrak kaldır, beklemeyi loop() içinde millis() ile yap.

volatile ne işe yarar, ne zaman kullanılmalı?

volatile, derleyiciye bu değişkenin program akışının dışından, örneğin bir ISR tarafından her an değişebileceğini söyler; böylece değer bir kaydedicide saklanmaz, her seferinde bellekten okunur. Hem ISR'de hem loop()'ta kullanılan her değişken volatile olmalıdır. Ancak volatile çok baytlı okumayı atomik yapmaz; bunun için noInterrupts() kullanılır.

Arduino Uno'da 2'den fazla kesme kullanılabilir mi?

Evet. Pin değişim kesmeleri (PCINT) sayesinde Uno'nun neredeyse tüm pinleri kesme üretebilir. Bunlar yalnızca değişimi bildirir ve aynı porttaki pinler (D0–D7, D8–D13, A0–A5) tek bir ISR'yi paylaşır. PinChangeInterrupt ya da EnableInterrupt kütüphaneleriyle kullanımları attachInterrupt'a çok benzer.

Interrupt mı kullanmalıyım, polling mi?

İnsan eliyle basılan butonlar ve yavaş değişen sensörler için loop() içinde yoklama (polling) çoğu zaman yeterli ve daha basittir. Encoder, devir ve akış sensörü gibi hızlı ve kısa darbelerde ya da kartı uykudan uyandırırken kesme kullanmalısın. loop() içinde uzun delay() komutları varsa önce onları millis() ile değiştirmek de çoğu sorunu çözer.

ESP32'de IRAM_ATTR yazmazsam ne olur?

Kod çoğu zaman derlenir ve çalışıyor gibi görünür; ancak ISR flash bellekte kalır, daha yavaş başlar ve yapılandırmaya bağlı olarak flash'a yazma sırasında "Cache disabled but cached memory region accessed" hatasıyla çökebilir. ESP8266'da ise IRAM_ATTR olmadan kart "ISR not in IRAM!" mesajıyla hemen durur. Bu yüzden ESP kartlarında her ISR'nin başına IRAM_ATTR yaz.

Tüm projeleri gör
Buton İle Led Yakma (On Off)
#10Başlangıç

Buton İle Led Yakma (On Off)

Bir butona bir kez basınca ışık yanar, bir daha basınca söner; tıpkı odandaki lamba düğmesi gibi. Arduino her basışı fark edip LED'in durumunu tersine çevirir, düğme içindeki minik titreşimler ışığı k

4.8(100)
167
Raspberry Pi Pico Buton Debounce Karşılaştırma: IRQ vs Software Polling
#159Orta
Premium

Raspberry Pi Pico Buton Debounce Karşılaştırma: IRQ vs Software Polling

Butona bir kere bastığında kod 3-4 kere saydırdıysa nedeni contact bounce — buton metal kontakları temas anında mikrosaniyelerde defalarca aç-kapa yapar! Bu projede aynı butonu 2 farklı yöntemle sayıp Serial'da yan yana karşılaştırıyoruz: RAW polling (bounce'u yakalar, hatalı sayar) vs IRQ + software debounce (doğru sayar). Öğrenme: contact bounce fiziği (metal titreşim), software debounce (ticks_diff() > 50ms), Pin.irq() interrupt kaydı, IRQ_FALLING trigger tipi,

0.0(0)
12
ESP32 Buton ile LED Kontrol (Toggle)
#105Başlangıç

ESP32 Buton ile LED Kontrol (Toggle)

Butona her basıldığında LED'in durumu değişir: açıksa kapanır, kapalıysa açılır. Klasik toggle (geçiş) pattern'i. Debounce (sıçrama önleme) ile butonun mekanik gürültüsünü temizleyerek tek basışta tek değişim sağlarız.

4.6(39)
57
Raspberry Pi Pico Buton ile LED Yakma (Toggle)
#156Başlangıç

Raspberry Pi Pico Buton ile LED Yakma (Toggle)

Buton ile LED — bir üst seviye! Basit buton projesinin aksine burada butonu basılı tutmana gerek yok. Bir kez bas ve bırak → LED açılır. Tekrar bas ve bırak → LED söner (toggle). Devre aynı (LED GP14, Buton GP2), ama kod büyük fark içeriyor. Yeni öğrenme: edge detection (kenar algılama — pinin 1'den 0'a düştüğü an), state tracking (önceki durumu hafızada tut), debounce (buton mekanik "bounce" sonrası yanlış tetikleme önleme). MicroPython'ın led.toggle() metodu ile tek satırda değişim.

0.0(0)
9
7 Segment Display Ve Buton İle Sayaç
#43İleri
Premium

7 Segment Display Ve Buton İle Sayaç

Üç butonlu basit bir sayaç: birine bastıkça ekrandaki rakam bir artar, diğerine bastıkça bir azalır, üçüncüsü sıfıra döndürür. Tek haneli 7 segment ekran 0 ile 9 arasında sayar; sınıra dayandığında kö

4.6(35)
47
Arduino Ses Uyarılı Reaksiyon Testi: Refleksini Milisaniyeyle Ölç (LCD + Buzzer + 3 LED)
#97Orta
Premium

Arduino Ses Uyarılı Reaksiyon Testi: Refleksini Milisaniyeyle Ölç (LCD + Buzzer + 3 LED)

Refleksin ne kadar hızlı? Butona bas: kırmızı LED hazırlan, sarı LED dikkat der ve 1,5 ile 3,5 saniye arası rastgele bir bekleme başlar. Yeşil LED yanıp buzzer öttüğü an yeniden bas; tepki süren I2C LCD'de milisaniye olarak çıkar, altında oturum rekorun ve ortalaman durur. Yeşilden önce basarsan ekranda TOO EARLY! yazar ve kayıp sesi çalar. Butonu basılı tutunca yeni oyuncu başlar, herkes aynı bekleme sırasıyla adil yarışır.

4.6(33)
50
ESP32 WebSocket Sayaç (2 Buton + LCD + Web Realtime)
#130İleri
Premium

ESP32 WebSocket Sayaç (2 Buton + LCD + Web Realtime)

Bu projede ESP32'ye bağlı iki butonla (GPIO 5 artırır, GPIO 4 azaltır) bir sayaç değiştirilir. Değer hem 16x2 I2C LCD'de hem de ESPAsyncWebServer'ın WebSocket desteğiyle bağlı tüm tarayıcılarda anında güncellenir; sayfayı yenilemek gerekmez. HTTP polling yerine tek bir kalıcı bağlantı kullanılır ve ESP32 yeni değeri ws.textAll() ile herkese gönderir.

4.5(14)
23
Arduino Hafıza Oyunu (4 LED + 4 Buton + Buzzer)
#93İleri
Premium

Arduino Hafıza Oyunu (4 LED + 4 Buton + Buzzer)

Hafızana güveniyor musun? Dört renkli LED sırayla yanar ve her biri kendi notasını çalar; sen de aynı sırayı butonlara basarak tekrar edersin. Her doğru turda sıraya yeni bir renk eklenir, küçük bir z

4.8(46)
69