← Tüm makaleler

Interrupt (Kesme) Nedir? Gömülü Sistemlerde Gerçek Zamanlı Tepki

Polling ile interrupt farkı, ISR yazımı, NVIC öncelik sistemi ve STM32'de EXTI yapılandırması

Interrupt (Kesme) Nedir? Gömülü Sistemlerde Gerçek Zamanlı Tepki

Bir düğmeye bastınız. Cihaz ne zaman fark edecek?

Eğer program sürekli o düğmeyi kontrol ediyorsa, bir sonraki kontrol turuna kadar beklemesi gerekir. Peki kontrol turu 50 ms sürdüğünde ne olur? 200 ms sürdüğünde? İşte bu noktada interrupt devreye girer.

Interrupt, Türkçe'ye kesme olarak çevrilir. Donanımsal bir olay gerçekleştiğinde — bir düğmeye basılması, bir UART baytının gelmesi, bir zamanlayıcının dolması — işlemcinin şu an yaptığı işi durdurup o olayı işlemesi mekanizmasıdır. Tamamlanınca kaldığı yere döner.

Bu mekanizma, gömülü sistemlerin "gerçek zamanlı" davranmasını sağlayan temel yapı taşıdır.


1. Polling mu, Interrupt mi?

Interrupt'ı anlamanın en iyi yolu, alternatifiyle karşılaştırmaktır: polling (yoklama).

Polling yaklaşımı

while (1) {
    if (GPIOA->IDR & (1 << 0)) {   /* PA0'ı sürekli oku */
        handle_button_press();
    }
    do_other_work();               /* Bu iş ne kadar sürerse sürsün */
}

Polling basittir: döngü her turunda pini okur. Ama bir sorunu var — do_other_work() ne kadar sürer, bilemezsiniz. Düğmeye bastığınız an ile kodun bunu fark ettiği an arasındaki süre, döngünün ne kadar sürdüğüne bağlıdır. Bu tahmin edilemez bir gecikmedir.

Dahası, işlemci hiçbir şey olmuyor olsa bile sürekli o pini okumak zorundadır. CPU zamanı boşa harcanır.

Interrupt yaklaşımı

/* Ana döngü sadece kendi işini yapar */
while (1) {
    do_other_work();
}

/* Donanım olayı geldiğinde CPU bu fonksiyona atlar */
void EXTI0_IRQHandler(void) {
    handle_button_press();
    EXTI->PR |= (1 << 0);   /* Bayrağı temizle */
}

Düğmeye basılana kadar EXTI0_IRQHandler hiç çalışmaz. Basıldığı anda donanım CPU'yu keser; ISR çalışır ve biter; CPU kaldığı yerden devam eder. Gecikme mikrosaniyedir.

Kriter Polling Interrupt
Tepki süresi Döngü süresine bağlı Mikrosaniye
CPU kullanımı Sürekli meşgul Olay gelince aktif
Kod karmaşıklığı Basit Orta (ISR + bayrak yönetimi)
Uygun olduğu yer Basit, tek görevli sistemler Gerçek zamanlı tepki gereken her yer

2. Interrupt Nasıl Çalışır? Donanım Tarafı

Bir interrupt tetiklendiğinde arka planda şunlar olur:

  1. Donanım kesme bayrağını (flag) set eder. Örneğin bir UART baytı geldiğinde USART_SR içindeki RXNE biti 1 olur.
  2. NVIC (Nested Vectored Interrupt Controller) bunu fark eder. ARM Cortex-M işlemcilerinin tamamında bulunan NVIC, hangi kesmeler etkinleştirilmiş ve öncelikleri nelerdir bilgisini tutar.
  3. İşlemci mevcut bağlamı yığına (stack) kaydeder. Program sayacı (PC), durum kaydı (xPSR) ve birkaç register otomatik olarak yığına itilir.
  4. Vektör tablosundan ISR adresi alınır. Her interrupt türünün sabit bir adresi vardır.
  5. ISR çalışır. Sizin yazdığınız kesme işleme fonksiyonu.
  6. ISR biter, bağlam geri yüklenir. CPU kaldığı satırdan devam eder.

Tüm bu süreç donanım tarafından yönetilir ve yaklaşık 12 saat döngüsü sürer — STM32F070RB'de 48 MHz ile bu ~250 nanosaniyedir.


3. STM32'de Bir Interrupt Yapılandırma

Düğmeye bağlı bir pinin yükselen kenarını (rising edge) kesme olarak yapılandıralım.

Adım 1: GPIO ve SYSCFG saat etkinleştirme

RCC->AHBENR  |= RCC_AHBENR_GPIOAEN;   /* GPIOA saatini aç */
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; /* SYSCFG saatini aç */

Adım 2: Pini giriş olarak yapılandır

GPIOA->MODER &= ~(3 << (0 * 2));   /* PA0 → giriş modu */
GPIOA->PUPDR |= (1 << (0 * 2));    /* Pull-up etkinleştir */

Adım 3: EXTI hattını PA0'a bağla

SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;   /* EXTI0 → GPIOA */

Adım 4: EXTI yükselen kenar tetiklemesini etkinleştir

EXTI->RTSR |= (1 << 0);    /* Yükselen kenar (rising edge) */
EXTI->IMR  |= (1 << 0);    /* EXTI0 maskesini kaldır (etkinleştir) */

Adım 5: NVIC'te interrupt'ı etkinleştir

NVIC_EnableIRQ(EXTI0_1_IRQn);        /* STM32F0 serisinde EXTI0-1 birleşik */
NVIC_SetPriority(EXTI0_1_IRQn, 1);   /* Öncelik: 1 (0 en yüksek) */

Adım 6: ISR yaz

void EXTI0_1_IRQHandler(void) {
    if (EXTI->PR & (1 << 0)) {
        /* Düğmeye basıldı */
        GPIOA->ODR ^= (1 << 5);   /* PA5 (LED) toggle */

        EXTI->PR |= (1 << 0);     /* Bayrağı temizle — zorunlu! */
    }
}

Önemli: ISR içinde EXTI->PR bayrağını temizlemezseniz interrupt sürekli yeniden tetiklenir ve program askıda kalır. Bayrak temizleme her ISR'ın sonunda yapılmalıdır.


4. NVIC Öncelik Sistemi

ARM Cortex-M işlemcilerinde birden fazla interrupt aynı anda tetiklenebilir. NVIC, hangisinin önce işleneceğine karar verir.

/* Düşük sayı = yüksek öncelik */
NVIC_SetPriority(EXTI0_1_IRQn, 0);   /* En yüksek öncelik */
NVIC_SetPriority(TIM2_IRQn,    1);   /* Orta öncelik */
NVIC_SetPriority(USART1_IRQn,  2);   /* Düşük öncelik */

STM32F070RB'de 2 bit öncelik seviyesi vardır (0–3 arası). Daha büyük Cortex-M4 işlemcilerde bu 4 bite kadar çıkar (0–15).

Preemption (Öncelik Kesme)

Düşük öncelikli bir ISR çalışırken, daha yüksek öncelikli bir interrupt gelirse ne olur?

[TIM2_ISR çalışıyor, öncelik 1]
    ↑ EXTI0 geldi, öncelik 0
        [EXTI0_ISR çalışıyor]
        [EXTI0_ISR bitti]
    ↓ TIM2_ISR kaldığı yerden devam etti
[TIM2_ISR bitti]

Bu "iç içe interrupt" (nested interrupt) mekanizması NVIC tarafından otomatik olarak yönetilir.


5. Sık Kullanılan Interrupt Kaynakları

Kaynak Ne Zaman Tetiklenir Tipik Kullanım
EXTI GPIO pin kenar değişimi Düğme, enkoder, dış sinyal
TIM (Zamanlayıcı) Sayaç taşması veya karşılaştırma eşleşmesi Periyodik görev, PWM, zaman ölçümü
USART RX Bir bayt alındı UART haberleşme
SPI Transfer tamamlandı Sensör okuma
I2C Adres/veri olayı I2C cihaz haberleşmesi
ADC Dönüşüm tamamlandı Analog okuma (CPU bloke etmeden)
DMA Transfer yarılandı / tamamlandı Büyük veri transferi

6. ISR Yazarken Dikkat Edilecekler

Kısa tutun

ISR içinde uzun işlemler yapmayın. Uzun bir ISR, diğer interrupt'ların gecikmesine yol açar. Yaygın desen şudur: ISR yalnızca veriyi bir tampona (buffer) koyar, ana döngü işler.

/* ISR: sadece veriyi tampona at */
volatile uint8_t rx_buffer[64];
volatile uint8_t rx_head = 0;

void USART1_IRQHandler(void) {
    if (USART1->ISR & USART_ISR_RXNE) {
        rx_buffer[rx_head++ & 63] = USART1->RDR;
    }
}

/* Ana döngü: tamponu işle */
while (1) {
    if (rx_head != rx_tail) {
        process_byte(rx_buffer[rx_tail++ & 63]);
    }
}

volatile kullanın

ISR ile ana kod arasında paylaşılan değişkenler volatile olarak işaretlenmelidir. Aksi takdirde derleyici bu değişkeni optimize ederek önbelleğe alabilir ve değişimin fark edilmemesine neden olabilir.

volatile uint8_t button_pressed = 0;

void EXTI0_1_IRQHandler(void) {
    button_pressed = 1;
    EXTI->PR |= (1 << 0);
}

int main(void) {
    while (1) {
        if (button_pressed) {
            button_pressed = 0;
            handle_press();
        }
    }
}

Kritik bölgeleri koruyun

ISR, ana kodun ortasında çalışabilir. Eğer ana kod çok baytlı bir değişkeni güncellerken ISR aynı değişkeni okursa tutarsız veri elde edilir. Kritik bölgeleri interrupt'ları geçici olarak devre dışı bırakarak koruyun:

/* Ana kod: çok baytlı değişkeni güvenle oku */
__disable_irq();
uint32_t snapshot = shared_counter;
__enable_irq();

Özet

Kavram Açıklama
Interrupt / Kesme Donanım olayının CPU'yu ana akıştan koparıp ISR'a yönlendirmesi
ISR Interrupt Service Routine — olayı işleyen fonksiyon
NVIC ARM Cortex-M'deki interrupt denetleyicisi; öncelik yönetimi yapar
EXTI GPIO kenar değişimlerini interrupt'a çeviren STM32 birimi
Bayrak temizleme Her ISR sonunda zorunlu; temizlenmezse interrupt sonsuz döngüye girer
volatile ISR ile paylaşılan değişkenler mutlaka volatile olmalı
Kısa ISR ISR yalnızca veriyi alır; işleme ana döngüde yapılır

Interrupt mekanizmasını kavramak, gömülü sistemlerde "gerçek zamanlı" davranış yazmanın ilk adımıdır. UART haberleşmesinden motor kontrolüne, sensör okumadan güç yönetimine kadar neredeyse her ciddi gömülü sistem interrupt üzerine kuruludur.

Kitap serisini incele →