Haberler

Arduino ile Çoklu Görev – Millis(), RTOS & Daha Fazlası!

Arduino mikrodenetleyicileri, elektronik ve programlama için başlangıç dostu ve düşük maliyetli bir platformdur. LED’in yanıp sönmesi gibi basit kontrol görevleri için harikadırlar, ancak tek bir Arduino’nun potansiyelini ne kadar zorlayabiliriz? Diğer bir deyişle, Arduino ile çoklu görev yapmak mümkün mü? Temel Arduino programlamayı öğrendiyseniz ve bunu bir üst seviyeye taşımak istiyorsanız, bu makale kesinlikle sizin için!

Arduino ile Çoklu Görev Rehberimizde şunları ele alacağız:

  • Eski Delay() ile İlgili Sorunlar
  • Millis() ile Zamanı Tutmak
  • Öğretici: Millis() ile Arduino Çoklu Görevini Başarmak
  • Nesne Yönelimli Programlama ile Çoklu Görevi Ölçeklendirme
  • RTOS’a (Gerçek Zamanlı İşletim Sistemleri) Giriş
  • Öğretici: FreeRTOS ile Arduino Çoklu Görevini Başarmak

Eski Delay() ile İlgili Sorunlar

Doğru Arduino çoklu görevine girmeden önce, çok popüler delay() fonksiyonundan başlayarak zamanı tutmanın farklı yöntemlerini konuşmamız gerekiyor. Öncelikle, fonksiyonun tanımına bakalım resmi Arduino belgelerinden.

delay(ms)
// Programı, parametre olarak belirtilen süre (milisaniye cinsinden) kadar duraklatır. (Bir saniyede 1000 milisaniye vardır.)

// ms: duraklatılacak milisaniye sayısı. İzin verilen veri türleri: unsigned long.

delay() fonksiyonunun talihsiz bir dezavantajı, bunun engelleyici bir gecikme olmasıdır. Açıklayayım. delay() fonksiyonu çağrısı süresince, Arduino’muzun CPU’su meşguldür. Bu, sensör girişlerine yanıt veremeyeceği, herhangi bir hesaplama yapamayacağı veya herhangi bir çıktı gönderemeyeceği anlamına gelir.

Bu davranışın sonuçlarını daha iyi açıklamak için bir örneğe bakalım. Aynı sayfadaki yanıp sönen örneği alarak, delay() fonksiyonunu kullanarak LED’i düzenli aralıklarla yanıp söndüren Arduino kodumuz şöyle görünebilir.

int ledPin = 13;              // LED dijital pin 13'e bağlı

void setup() {
  pinMode(ledPin, OUTPUT);    // dijital pini çıkış olarak ayarlar
}

void loop() {
  digitalWrite(ledPin, HIGH); // LED'i açar
  delay(1000);                // bir saniye bekler
  digitalWrite(ledPin, LOW);  // LED'i kapatır
  delay(1000);                // bir saniye bekler
}

Peki, iki LED’i farklı aralıklarla yanıp söndürmek istersek ne olur? Belki aşağıdaki kodu denemek isteyebilirsiniz.

int ledPin1 = 13;              // LED 1 dijital pin 13'e bağlı
int ledPin2 = 14;              // LED 2 dijital pin 14'e bağlı

void setup() {
  pinMode(ledPin1, OUTPUT);    // dijital pini çıkış olarak ayarlar
  pinMode(ledPin2, OUTPUT);    // dijital pini çıkış olarak ayarlar
}

void loop() {
  digitalWrite(ledPin1, HIGH); // LED 1'i açar
  delay(1000);                 // bir saniye bekler
  digitalWrite(ledPin1, LOW);  // LED 1'i kapatır
  delay(1000);                 // bir saniye bekler
  
  digitalWrite(ledPin2, HIGH); // LED 2'yi açar
  delay(2000);                 // iki saniye bekler
  digitalWrite(ledPin2, LOW);  // LED 2'yi kapatır
  delay(2000);                 // iki saniye bekler
}

Kodu satır satır incelediğimizde, LED’lerin birer birer açıldığını göreceğiz. Ancak, bunu aynı anda yapmayacaklardır.


Millis() Nedir?

Önceki sorunumuza çözüm temelde basittir. Gecikme süresini doğrudan tanımlamak yerine, bir sonraki eylemin gerçekleştirilmesi için yeterli zamanın geçip geçmediğini belirlemek için sürekli saati kontrol ederiz. Bunu yapmak için en yaygın olarak millis() fonksiyonu kullanılır.

time = millis()
// Arduino kartı mevcut programı çalıştırmaya başladığından beri geçen milisaniye sayısını döndürür. Bu sayı yaklaşık 50 gün sonra taşar (sıfıra döner).

Yani, burada çok faydalı bir fonksiyon var ki bu, zamanda referanslar belirleyecek, böylece Arduino şemalarımızda zamanlama programlayabileceğiz! Bunu yanıp sönen örneğe uygulayalım ve Arduino’dan BlinkWithoutDelay şemasına bakalım.

const int ledPin =  LED_BUILTIN;      // LED pininin numarası
int ledState = LOW;                   // LED'i ayarlamak için kullanılan ledState

// Genel olarak, zaman tutan değişkenler için "unsigned long" kullanmalısınız
// Değer, bir int'in saklayabileceğinden çok daha büyük hale gelecektir

unsigned long previousMillis = 0;     // LED'in en son güncellendiği zamanı saklayacak

const long interval = 1000;           // yanıp sönme aralığı (milisaniye)

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // LED'in yanıp sönmesi için zamanın gelip gelmediğini kontrol et; yani, mevcut zaman ile LED'i en son yaktığınız zaman arasındaki fark
  // yanıp sönme aralığından büyükse.
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // LED'i en son yaktığınız zamanı kaydedin
    previousMillis = currentMillis;

    // LED kapalıysa açın ve tersine:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // LED'i değişkenin ledState'i ile ayarlayın:
    digitalWrite(ledPin, ledState);
  }
}

Bu şemada neyin bu kadar özel olduğunu sorabilirsiniz? İlk fark edeceğiniz şey, kodumuzda artık delay() fonksiyonu çağrılarının olmadığıdır! Yine de, LED’lerimizi aynı şekilde yanıp söndürebiliyoruz.

Yeni şemamız, temelde şu iki sürekli çalışan bölümden oluşmaktadır:

  1. ledState değişkenini kontrol eder ve LED durumunu buna göre yazar.
  2. millis() ile geçen zamanı kontrol eder ve ledState değişkenini buna göre değiştirir.

Aslında, bu yapı bir Durum Makinesi olarak bilinir. Bir durum makinesi, makinenin mevcut duruma bağlı olarak önceden tanımlanmış çıktılar gerçekleştirdiği bir davranış modelidir.

Özetlemek Gerekirse: delay() kullanımından kaçının, bunun yerine millis() kullanın!


Arduino Mikrodenetleyicileri: Tavsiyelerim

Daha gelişmiş Arduino çoklu görevlerini nasıl gerçekleştireceğimiz üzerine öğreticilere daha derinlemesine girmeden önce, Seeed’de sahip olduğumuz bazı Arduino uyumlu mikrodenetleyicileri paylaşmak istiyorum. Sonuçta, elektroniklerde donanım yazılımdan en az onun kadar önemlidir!

Seeeduino XIAO

Seeeduino XIAO, Seeeduino Ailesi’ndeki en küçük Arduino kartıdır. Küçük boyutuna rağmen, Seeeduino XIAO güçlü SAMD21 mikroçipi ve geniş donanım arayüzleri ile donatılmıştır ve ultra uygun fiyatı beş doların altındadır.

Ürün Özellikleri:

  • ARM Cortex-M0+ 32bit 48MHz mikrodenetleyici (SAMD21G18) ile 256KB Flash, 32KB SRAM
  • Arduino IDE & MicroPython ile uyumlu
  • Kolay Proje İşlemi: Breadboard dostu
  • Küçük Boyut: Giyilebilir cihazlar ve küçük projeler için parmak büyüklüğünde (20×17.5mm).
  • Birden fazla geliştirme arayüzü: 11 dijital/analog pin, 10 PWM Pin, 1 DAC çıkışı, 1 SWD Bağlantı noktası arayüzü, 1 I2C arayüzü, 1 UART arayüzü, 1 SPI arayüzü.

Seeeduino XIAO hakkında daha fazla bilgi edinmek ister misiniz? Şimdi ürün sayfasını Seeed Online Mağazamızda ziyaret edin!

Wio Terminal

Wio Terminal, ATSAMD51 tabanlı tam bir Arduino geliştirme platformudur ve Realtek RTL8720DN ile güçlendirilmiş kablosuz bağlantıya sahiptir. Hepsi bir arada bir mikrodenetleyici olarak, üzerinde 2.4” LCD Ekran, IMU, mikrofon, buzzer, microSD kart yuvası, ışık sensörü ve kızılötesi verici bulunmaktadır. İhtiyacınız olan son Arduino budur!

“`html

Ürün Özellikleri:

  • Güçlü MCU: 120MHz hızında çalışan ARM Cortex-M4F çekirdekli Microchip ATSAMD51P19
  • Güvenilir Kablosuz Bağlantı: Realtek RTL8720DN ile donatılmış, çift bantlı 2.4GHz / 5GHz Wi-Fi (sadece Arduino tarafından desteklenir)
  • Yüksek Entegre Tasarım: 2.4” LCD Ekran, IMU ve daha pratik ek bileşenler, yerleşik mıknatıslar ve montaj delikleri ile kompakt bir kasada yer alır
  • Raspberry Pi 40-pin Uyumlu GPIO
  • IoT ile keşfetmek için 300’den fazla tak&oynat Grove modülü ile uyumlu
  • USB OTG Desteği
  • Arduino, CircuitPython, Micropython, ArduPy, AT Firmware, Visual Studio Code desteği
  • TELEC Sertifikalı

Bir Wio Terminal almakla ilgileniyorsanız, lütfen Seeed Online Store’daki ürün sayfasını ziyaret edin!


Millis() ile Arduino Çoklu Görev Yönetimi Sağlayın

Millis() kullanmanın delay() üzerindeki en büyük avantajı, engellemeyi ortadan kaldırmasıdır. Bu, birden fazla işlemi aynı anda çalıştırma olanağını açar! Bunu nasıl yapabileceğinizi göstermek için, önceki (çok da başarılı olmayan) iki LED’i farklı aralıklarla yakıp söndürme denememizi düzeltmeye çalışalım.

int ledPin1 = 13;              // LED 1 dijital pin 13'e bağlı
int ledPin2 = 14;              // LED 2 dijital pin 14'e bağlı

int ledState1 = LOW;           // LED 1 başlangıçta kapalı
int ledState2 = LOW;           // LED 2 başlangıçta kapalı

unsigned long millis1;         // LED 1 için millis işaretleyicisi başlatılır
unsigned long millis2;         // LED 2 için millis işaretleyicisi başlatılır

void setup() {
  pinMode(ledPin1, OUTPUT);    // dijital pini çıkış olarak ayarlar
  pinMode(ledPin2, OUTPUT);    // dijital pini çıkış olarak ayarlar
}

void loop() {

  // LED Durumlarını Güncelle
  digitalWrite(ledPin1, ledState1);
  digitalWrite(ledPin2, ledState2);

  // Süre dolduysa LED durumlarını değiştir

  if ( (millis() - millis1) > 1000) {
    millis1 = millis();
    if (ledState1 == LOW) {
      ledState1 = HIGH;
    } else if (ledState1 == HIGH) {
      ledState1 = LOW;
    }
  }
  
  if ( (millis() - millis2) > 2000) {
    millis2 = millis();
    if (ledState2 == LOW) {
      ledState2 = HIGH;
    } else if (ledState2 == HIGH) {
      ledState2 = LOW;
    }
  }
}

Bu durumun önceki millis() örneğinden çok da farklı olmadığını göreceksiniz. Aslında, belirli LED ile ilgili kod satırlarını çoğaltmış olduk ve ikinci LED’in her 2 saniyede bir yanıp sönmesi için parametreleri ayarladık – Evet, gerçekten bu kadar basit!

Daha pratik bir örnek vermek için, Wio Terminal Özelleştirilebilir Zamanlayıcı projemden bahsedeceğim. O şemada, temelde dört şeyi aynı anda yapması gereken bir geri sayım zamanlayıcısı uygulamak istedim.

  1. Kalan süreyi dakika ve saniye cinsinden göstermek
  2. İlerlemeyi yavaş yavaş doldurulan bir çubuk şeklinde göstermek
  3. Bir tuş vuruşu ile geri sayımı kesme yeteneği sağlamak
  4. Zamanlayıcıyı sonlandırmak için zamanı tutmak

Şimdi, kodumuza bir göz atalım.

long start_millis = millis();
int progress, t_min, t_sec;
long seconds_elapsed = 0;
long seconds_remain;

while(seconds_elapsed<duration && clicker_state == 0) {
    seconds_elapsed = (millis() - start_millis)/1000;
    seconds_remain = duration - seconds_elapsed;
    t_min = seconds_remain/60;
    t_sec = seconds_remain%60;
    
    
    ttext.setCursor(0, 0);
    ttext.setTextColor(0xFFE0, 0);
    ttext.setTextSize(3);
    ttext.printf("%02d:%02d", t_min, t_sec);
    ttext.pushSprite(220,200);

    progress = 200*seconds_elapsed/duration;
    tft.fillRoundRect(10, 210, progress, 10, 4, TFT_WHITE);

    if (digitalRead(WIO_5S_PRESS) == LOW) {
        clicker_state = 1;
        skipped_timer = true;
    }
}
clicker_state = 0;

Yukarıdaki iki önemli nokta, millis() ve clicker_state kullanımıdır. Teknik olarak delay(1000) kullanabilir ve kalan saniye sayısını basitçe azaltabilirsiniz, ancak gerçek sorun zamanlayıcının kesilme yeteneğini tanıtmaya çalıştığınızda ortaya çıkar.

Unutmayın, bir delay() sırasında herhangi bir girdi sağlayamayız. Düşürme sırasında buton basımını kontrol etmek için bir ifade yazsak bile, çıkış koşulu her saniyede bir saniyenin çok küçük bir kısmında kontrol edilecektir. Bu durumda, kullanıcının döngüden çıkması neredeyse imkansızdır.


Objeye Dayalı Programlama ile Arduino Çoklu Görev Yönetimini Ölçeklendirin

Önceki bölümde, başka bir LED eklemenin basitçe bazı kod bölümlerini çoğaltmamızı gerektirdiğinden bahsetmiştim. Daha fazla cihaz eklemek için, o kodu gerektiği kadar kopyalayıp yapıştırabiliriz. Dezavantajı, şemanızın oldukça uzun hale gelmesi ve programcıların uzun kodları sevmemesidir. Daha temiz bir kod, daha küçük bir program anlamına gelir ki bu da Arduino’muzda daha fazla değerli alan tasarrufu sağlar!

Neyse ki, C++ tabanlı Arduino kodu, her LED için yeniden kullandığımız tüm kodu bir sınıf olarak bilinen bir paradigma veya çerçeveye paketlememizi sağlayan Obje Tabanlı Programlamayı (OOP) destekler.

Bunu yapmak aslında oldukça basit. Kendi sınıfımızı yazmaya başlamak için şunlara ihtiyacımız olacak:

  • bir sınıf yapıcı,
  • bazı üye değişkenleri,
  • ve sınıf metotları (veya fonksiyonlar).

Öncelikle, bir blinkingLED sınıfı tanımlayıp bazı değişkenler ekleyeceğiz. Bunlar daha önce kullandığımız ile aynıdır.

class blinkingLED {
  int ledPin;
  int ledState = LOW;
  long duration;
  unsigned long millisMarker;
};

Sonraki adım, bir sınıf yapıcısı oluşturmaktır. Yapıcı, o sınıfın bir nesnesini oluşturmak için çağrılan bir fonksiyondur. Sınıfımızla aynı isme sahiptir ve genellikle oluşturduğumuz belirli nesnenin bazı özelliklerini tanımlamak için birkaç girdi parametresi taşır.

Aşağıdaki yapıcıda, ledFlasher nesnemizin PIN’ini ve süresini tanımlarken değişkenlerini başlatıyoruz.

class blinkingLED {
  int ledPin;
  int ledState = LOW;
  long duration;
  unsigned long millisMarker;


  public:
  blinkingLED(int pin, long interval){
    ledPin = pin;
    pinMode(ledPin, OUTPUT);

    duration = interval;
    millisMarker = 0;
  };
};

Hala benimle misiniz? Güzel. Sınıfımızı oluşturmanın bir sonraki ve son adımı, LED durumlarını değiştiren ve PIN çıkışlarını güncelleyen bir metot tanımlamaktır!

Bu arada, setup() ve loop() fonksiyonlarımızı da emeklerimizden faydalanacak şekilde güncelleyeceğiz! Öncelikle, her LED için bir sınıf oluşturarak her birinin bildirimlerini ekleyeceğiz, ardından her birinin Update() metodunun döngümüzde sürekli çalışmasına izin vereceğiz!

class blinkingLED {
  int ledPin;
  int ledState = LOW;
  long duration;
  unsigned long millisMarker;


  public:
  blinkingLED(int pin, long interval){
    ledPin = pin;
    pinMode(ledPin, OUTPUT);

    duration = interval;
    millisMarker = 0;
  };


  void Update() {
    digitalWrite(ledPin, ledState);

    if ( (millis() - millisMarker) > duration) {
      millisMarker = millis();
      if (ledState == LOW) {
        ledState = HIGH;
      } else if (ledState == HIGH) {
        ledState = LOW;
      }
    }
  }
};

blinkingLED LED1(12, 1000);
blinkingLED LED2(13, 2000);

void setup() {
}

void loop() {
  LED1.Update();
  LED2.Update();
}

Ve işte bu kadar – daha öncekiyle aynı şeyi yapan bir kod! Ancak, üçüncü, dördüncü veya beşinci bir LED eklemek istersek, her biri sadece iki satır daha kod eklememizi gerektirir!


RTOS ile Arduino Çoklu Görev Yönetimi Sağlayın

Millis() kullanmak, çoğu Arduino programında zamanı tutmak için yeterlidir. Ancak, keşfedilecek başka seçenekler de vardır – bunlardan biri RTOS’tur.

RTOS Nedir ve Nasıl Çalışır?

RTOS, Gerçek Zamanlı İşletim Sistemi anlamına gelir ve günümüzün gömülü sistemlerinin en önemli bileşenlerinden biridir. RTOS, programların öngörülebilir bir şekilde çalışmasını sağlamak için tasarlanmıştır ve genellikle çok hafif bir yapıya sahiptir.

“`

RTOS, Linux gibi işletim sistemlerinde temel bir bileşen olan bir çekirdek aracılığıyla çalışır. Çalıştırılan her program bir görev (veya iş parçacığı) olup, işletim sistemi tarafından kontrol edilir. Eğer bir işletim sistemi bu şekilde birden fazla görevi yerine getirebiliyorsa, çoklu görev yaptığı söylenebilir.

Çoklu Görev ve Zamanlama

Geleneksel olarak, işlemciler yalnızca bir görevi aynı anda yerine getirebilir, ancak bir işletim sistemi her görevi hızlı bir şekilde arasında geçiş yaparak eşzamanlı olarak çalışıyormuş gibi gösterebilir.

Kaynak: FreeRTOS

Bir işletim sisteminin görevler arasında nasıl geçiş yapacağına karar vermesi için bir zamanlayıcı kullanır; bu, belirli bir anda hangi görevlerin çalıştırılacağına karar vermekten sorumludur. Ayrıca, belirli bir görevin yaşam döngüsü boyunca birden fazla kez duraklatılabileceği ve yeniden başlatılabileceği de önemlidir.


Öğretici: FreeRTOS ile Arduino’da Çoklu Görev

Bu bölümde, FreeRTOS ile çoklu görev elde etmenin yollarını göstereceğim. FreeRTOS, IoT RTOS sahnesinde iyi bilinen bir işletim sistemidir ve on yıldan fazla bir süredir kapsamlı bir şekilde geliştirilmiştir. Mikrodenetleyiciler için özel olarak geliştirilmiş olan FreeRTOS, düşük bellek ayak izi ve güç optimizasyonu özellikleri sunar.

Gerekli Malzemeler

Devam etmek için, SAMD mikroçipi tabanlı Seeed tarafından geliştirilmiş herhangi bir mikrodenetleyici kartını kullanabilirsiniz, bunlar arasında:

FreeRTOS ile Arduino’ya Hızlı Başlangıç

Adım 1: Seeed_Arduino_FreeRTOS deposunu ZIP dosyası olarak indirin.

Adım 2: ZIP dosyasını Arduino IDE aracılığıyla bir kütüphane olarak yükleyin. Lütfen detaylı talimatlar için burayı kontrol edin.

Adım 3: Aşağıdaki kodu yeni bir taslağa kopyalayın ve Arduino kartınıza yükleyin.

#include <Seeed_Arduino_FreeRTOS.h>
 
TaskHandle_t Handle_aTask;
TaskHandle_t Handle_bTask;
 
static void ThreadA(void* pvParameters) {
    Serial.println("Thread A: Başlatıldı");
 
    while (1) {
        Serial.println("Merhaba Dünya!");
        delay(1000);
    }
}
 
static void ThreadB(void* pvParameters) {
    Serial.println("Thread B: Başlatıldı");
 
    for (int i = 0; i < 10; i++) {
        Serial.println("---Bu Thread B---");
        delay(2000);
    }
    Serial.println("Thread B: Siliniyor");
    vTaskDelete(NULL);
}
 
void setup() {
 
    Serial.begin(115200);
 
    vNopDelayMS(1000); // başlangıçta usb sürücüsünün çökmesini önler, bunu atlamayın
    while(!Serial);  // Program başlamadan önce Seri terminalin portu açmasını bekleyin
 
    Serial.println("");
    Serial.println("******************************");
    Serial.println("        Program başlıyor      ");
    Serial.println("******************************");
 
    // rtos tarafından yönetilecek iş parçacıklarını oluşturun
    // Her görevin yığın boyutunu ve önceliğini ayarlar
    // Ayrıca, görevlerle iletişim kurmak ve bilgi almak için önemli olan her görev için bir işleyici işaretçisi başlatır
    xTaskCreate(ThreadA,     "Görev A",       256, NULL, tskIDLE_PRIORITY + 2, &Handle_aTask);
    xTaskCreate(ThreadB,     "Görev B",       256, NULL, tskIDLE_PRIORITY + 1, &Handle_bTask);
 
    // RTOS'u başlatın, bu fonksiyon asla geri dönmeyecek ve görevleri zamanlayacaktır.
    vTaskStartScheduler();
}
 
void loop() {
    // HİÇBİR ŞEY
}

Adım 4: Arduino IDE’de Seri Monitörü açın ve sihrin gerçekleşmesini izleyin!

Bu Merhaba Dünya Örneği, farklı hızlarda Seri Monitöre farklı dizeler yazdıran iki iş parçacığı oluşturur.

  • Thread A “Merhaba Dünya” yazdırır,
  • Thread B ise “—Bu Thread B—” yazdırır!

Ve bu hızlı öğreticinin sonuna geldik!


FreeRTOS ile Arduino Çoklu Görev: Daha Fazla Örnek

FreeRTOS ile benzer şekilde heyecan verici Arduino çoklu görevleri yapabilirsiniz, örneğin farklı LED’leri aynı anda yanıp söndürmek veya LCD ekrandaki öğeleri ayrı ayrı güncellemek! Bu örnekler hakkında daha fazla bilgi için, Seeed Wiki sayfasını ziyaret etmenizi şiddetle tavsiye ederim!


Sonuç

Bu Arduino ile Çoklu Görev kılavuzunu okuduğunuz için teşekkürler! Özetlemek gerekirse, delay()’in tuzaklarını ve çoklu görevin millis() ve durum makineleri kavramı kullanılarak nasıl elde edilebileceğini ele aldık. Ayrıca, biraz FreeRTOS ile de tanıştık!

Yine de, bunlar projelerinizde kullanılacak araçlar ve yöntemlerden başka bir şey değildir. Neden yeni becerilerinizi test etmek için kendinize bir Arduino projesi seçmiyorsunuz?

Daha fazla kaynak için, aşağıdaki bağlantıları kontrol etmeyi unutmayın!

Leave a Reply

Your email address will not be published. Required fields are marked *