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.
Interrupt'ı anlamanın en iyi yolu, alternatifiyle karşılaştırmaktır: polling (yoklama).
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.
/* 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 |
Bir interrupt tetiklendiğinde arka planda şunlar olur:
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.
Düğmeye bağlı bir pinin yükselen kenarını (rising edge) kesme olarak yapılandıralım.
RCC->AHBENR |= RCC_AHBENR_GPIOAEN; /* GPIOA saatini aç */
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; /* SYSCFG saatini aç */
GPIOA->MODER &= ~(3 << (0 * 2)); /* PA0 → giriş modu */
GPIOA->PUPDR |= (1 << (0 * 2)); /* Pull-up etkinleştir */
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0; /* EXTI0 → GPIOA */
EXTI->RTSR |= (1 << 0); /* Yükselen kenar (rising edge) */
EXTI->IMR |= (1 << 0); /* EXTI0 maskesini kaldır (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) */
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->PRbayrağını temizlemezseniz interrupt sürekli yeniden tetiklenir ve program askıda kalır. Bayrak temizleme her ISR'ın sonunda yapılmalıdır.
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).
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.
| 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 |
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ınISR 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();
}
}
}
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();
| 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 →