← Tüm makaleler

RTOS mu, Bare-Metal mi? Projeniz İçin Hangisi Doğru?

Super-loop deseninin nerede çöktüğü, RTOS'un gerçekte ne sağladığı ve hangisini seçeceğinize dair somut kriterler

RTOS mu, Bare-Metal mi? Projeniz İçin Hangisi Doğru?

Super-loop ne zaman yalan söylemeye başlar?

İlk projenizde sorun yoktu: bir while(1) döngüsü, birkaç görev, her şey sırayla. Ama proje büyüdükçe döngünün turu uzadı. Motor kontrol geç tetiklendi. UART verisi kayboldu. Düğmeye basıldı, cihaz birkaç döngü sonra fark etti. Kodu düzelttiniz, ama sonraki görev gelince aynı şey oldu.

Bu noktada iki yol var: super-loop'u daha fazla yamayarak ilerlemek ya da bir RTOS'un gerçekte ne sağladığını anlamak.

Gömülü sistem geliştirmeye başladığınızda ilk yazdığınız program muhtemelen şöyle görünüyordu: bir while(1) döngüsü içinde LED yakıp söndür, belki bir UART'tan veri oku. Bu bare-metal programlamadır — doğrudan donanım, doğrudan kontrol, araya giren hiçbir soyutlama katmanı yok.

Proje büyüdükçe şu soru kaçınılmaz biçimde ortaya çıkar: "Bir RTOS'a geçmeli miyim?"

Kısa cevap: çoğu zaman gerekmez. Ama gerekenler için RTOS olmadan yaşamak gerçekten zordur.


1. Bare-Metal: Super-Loop Deseni

Bare-metal programlamada temel yapı super-loop'tur: sonsuz bir ana döngü, donanımsal olayları kesmeler (interrupt) aracılığıyla yakalar.

int main(void) {
    system_init();

    while (1) {
        if (uart_data_ready()) {
            process_uart();
        }
        update_display();
        read_sensors();
        run_control_loop();
    }
}

void USART1_IRQHandler(void) {
    uart_buffer_push(USART1->RDR);
}

Bu yapı tahmin edilebilir, şeffaf ve verimsizdir — son kelimeyi olumsuz anlamda söylemiyorum. Tek bir görev yapılıyorsa, ya da görevler birbirini kesmiyorsa super-loop mükemmel bir çözümdür.

Super-loop'un avantajları


2. Super-Loop Nerede Çözülemez Hale Gelir?

Şu senaryoyu düşünün: STM32 tabanlı bir cihaz geliştiriyorsunuz. Aynı anda şunları yapması gerekiyor:

Super-loop bu durumda güçlükle karşılaşır çünkü döngünün her turu ne kadar sürer? Bilemezsiniz — read_sensors() ne kadar tutacak, update_display() ne kadar tutacak? Bir görev uzun sürerse diğerleri beklemek zorunda kalır.

Sorun 1: Zamanlama çakışması

while (1) {
    run_motor_control();   /* 8 ms sürüyor */
    update_display();      /* 6 ms sürüyor */
    read_uart();           /* anlık, ama bazen bekleme var */
    /* Toplam: ~14 ms — motor kontrolü 10 ms'de bir çalışması gerekiyordu! */
}

Motor kontrolü 10 ms'de bir çalışması gerekiyor, ama döngü 14 ms sürüyor. Geçici çözüm olarak her görevi ayrı bir sayaç ve zamanlayıcı kesmeyle tetikleyebilirsiniz — ama bu yaklaşım kaç göreve kadar ölçeklenir?

Sorun 2: Bloke eden işlemler

update_display() SPI üzerinden çalışıyor ve 6 ms boyunca meşgul-bekleyerek (busy-wait) bekliyorsa, bu süre boyunca başka hiçbir şey işlenemiyor. DMA kullanmak çözüm sunar ama kodu karmaşıklaştırır; tamamlanma callback'ini yönetmek zorunda kalırsınız.

Sorun 3: Öncelik sıralaması imkânsızlaşır

Düğmeye basma olayı kritik — 50 ms içinde yanıt verilmeli. Ama o an update_display() içindeyseniz ve o 6 ms boyunca döngü oraya kilitliyse ne yaparsınız? Kesme kullanırsınız elbette. Ama kesme içinde ne kadar iş yapılabilir? Ne kadar veri paylaşılabilir? Senkronizasyon olmadan yarış koşulları (race conditions) kapıda bekler.


3. RTOS Gerçekte Ne Sağlar?

Bir RTOS (Real-Time Operating System), tek bir yürütme akışı yerine birden fazla görev (task) çalıştırmanıza olanak tanır. Her görevin kendi yığını (stack) ve önceliği vardır. RTOS çekirdeği, görevler arasındaki geçişi (context switch) yönetir.

En yaygın gömülü RTOS'lardan biri FreeRTOS'tur. STM32 için ücretsiz, açık kaynaklı ve geniş topluluk desteğine sahiptir.

#include "FreeRTOS.h"
#include "task.h"

/* Motor kontrol görevi — her 10 ms'de bir çalışır */
void motor_control_task(void *params) {
    TickType_t last_wake = xTaskGetTickCount();
    while (1) {
        run_motor_control();
        vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(10));  /* Tam 10 ms bekle */
    }
}

/* Ekran güncelleme görevi — her 1000 ms'de bir çalışır */
void display_task(void *params) {
    while (1) {
        update_display();
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

/* UART okuma görevi — veri geldiğinde uyandırılır */
void uart_task(void *params) {
    while (1) {
        uint8_t byte;
        if (xQueueReceive(uart_queue, &byte, portMAX_DELAY)) {
            process_uart_byte(byte);
        }
    }
}

int main(void) {
    system_init();

    xTaskCreate(motor_control_task, "Motor", 256, NULL, 3, NULL);  /* Öncelik 3 */
    xTaskCreate(display_task,       "Display", 256, NULL, 1, NULL); /* Öncelik 1 */
    xTaskCreate(uart_task,          "UART",  256, NULL, 2, NULL);   /* Öncelik 2 */

    vTaskStartScheduler();
}

Burada olan şu: RTOS çekirdeği her görevi önceliğine göre zamanlıyor. Motor kontrol görevi (öncelik 3) en yüksek önceliğe sahip — vTaskDelayUntil ile tam olarak 10 ms'de bir uyandırılıyor, diğer görevler ne yapıyor olursa olsun.

RTOS'un sağladıkları

Görev önceliklendirme: Kritik görevler düşük öncelikli görevleri keser. Motor kontrol, ekran güncellemesinin bitmesini beklemez.

Bloke etmeyen bekleme: vTaskDelay() çağrıldığında görev uyku moduna geçer — CPU zamanını tüketmez. Diğer görevler o sürede çalışabilir.

Senkronizasyon nesneleri: Görevler arasında güvenli veri paylaşımı için kuyruklar (queue), semaforlar (semaphore) ve muteksler (mutex) kullanılabilir.

/* Kuyruk üzerinden güvenli veri paylaşımı */
QueueHandle_t uart_queue;

void USART1_IRQHandler(void) {
    uint8_t byte = USART1->RDR;
    BaseType_t higher_priority_woken = pdFALSE;
    xQueueSendFromISR(uart_queue, &byte, &higher_priority_woken);
    portYIELD_FROM_ISR(higher_priority_woken);
}

Kesme (IRQ) ile görev arasındaki veri transferi kuyruk üzerinden yapılıyor — yarış koşulu riski ortadan kalkıyor.


4. RTOS'un Maliyeti

RTOS bedava değildir. Her şeyin bir bedeli vardır.

Bellek maliyeti

Kaynak Bare-metal FreeRTOS (tipik)
Çekirdek Flash 0 KB ~5–10 KB
Her görev stack'i — 256–1024 byte (göreve göre)
Görev kontrol bloğu (TCB) — ~100 byte/görev
Kuyruk, semafor nesneleri — ~80 byte/nesne

STM32F070RB'nin 16 KB SRAM'i var. Üç görev ve birkaç senkronizasyon nesnesiyle kolayca 4–6 KB harcarsınız. Küçük değil, ama yönetilebilir.

Gecikme (Latency) maliyeti

Context switch — bir görevden diğerine geçiş — birkaç mikrosaniye sürer. Çoğu uygulama için önemsizdir. Ama mikrosaniye hassasiyeti gerektiren sinyal işleme veya motor kontrolü yapıyorsanız, bu gecikme sorun yaratabilir. Bu durumda o kritik görev için kesmeler tercih edilmelidir.

Karmaşıklık maliyeti

Hata ayıklama zorlaşır. Yarış koşulları hâlâ mümkündür — şimdi de kilitlenme (deadlock) ve öncelik terslemesi (priority inversion) riskleri vardır. RTOS araçları (FreeRTOS+Trace, Segger SystemView) bu sorunları görünür kılmaya yardımcı olur ama öğrenme eğrisi gerçektir.


5. Hangi Durumda Hangisini Seçmelisiniz?

Bare-metal seçin, eğer:

RTOS seçin, eğer:


6. Hibrit Yaklaşım

İkisi de birbirini dışlamaz. Sık kullanılan bir desen şudur: RTOS ile çalışırken zamanlama açısından kritik olan kısımları kesmeler ve donanım zamanlayıcıları aracılığıyla yönetmek, geriye kalan her şeyi görevlere bırakmak.

/* Kritik: donanım zamanlayıcısıyla tam zamanlama — RTOS'tan bağımsız */
void TIM2_IRQHandler(void) {
    run_motor_foc_algorithm();   /* Her 100 µs'de bir, kesinlikle */
}

/* Kritik olmayan: RTOS görevi */
void telemetry_task(void *params) {
    while (1) {
        send_status_over_uart();
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

Motor kontrol algoritması kesme içinde çalışıyor — RTOS scheduler'ı ne yaparsa yapsın, bu kod tam 100 µs'de bir tetikleniyor. Telemetri görevi ise RTOS üzerinde, kesintiye uğrayabilir bir görev olarak çalışıyor.


Özet

Kriter Bare-Metal RTOS
Bellek kullanımı Minimum +5–15 KB Flash, +birkaç KB SRAM
Zamanlama kesinliği Tam kontrol Görev düzeyinde; ISR ile desteklenir
Eşzamanlı görev yönetimi Zor, elle yazılır Yerleşik
Hata ayıklama kolaylığı Yüksek Orta (araç gerektirir)
Ölçeklenebilirlik Sınırlı İyi
Öğrenme eğrisi Düşük Orta
İdeal kullanım Tek amaçlı, zamanlama-kritik Çok görevli, bloke edebilen işlemler içeren

Bare-metal ile başlayın. Zamanlama çakışmalarını ve senkronizasyon sorunlarını super-loop'la çözmekte güçlük çekmeye başladığınızda, RTOS geçişini değerlendirin. Bir RTOS'un ne zaman gerçekten gerekli hale geldiğini anlatmak istiyorsanız, "Ne Zaman Bir RTOS'a İhtiyaç Duyarsınız?" başlıklı yazımıza da göz atabilirsiniz.

Kitap serisini incele →