4

FreeRTOS Notları #4: Yazılımsal Timer

    FreeRTOS‘da yazılımsal timer kernel kontrolünde çalışır. Kernel ise systick timer’ı kullandığı için yazılımsal timer systick timer’a göre çalışıyor diyebiliriz. Donanımsal bir timer değildir. Projeye dahil etmek için “timers.h” kütüphanesi eklenmeli ve FreeRTOSConfig.h dosyası içerisindeki configUSE_TIMERS makrosu 1 olarak ayarlanmalıdır.

Software Timer Callback Fonksiyonu

    Callback fonksiyonu void tipinde tanımlanmış yani herhangi bir değer döndürmeyen, parametre olarak TimerHandle_t tipinde bir değer alan aşağıdaki gibi bir fonksiyondur.

    Callback fonksiyonu olabildiğince kısa tutulmalı ve içerisinde vTaskDelay,xQueueReceive vb. gibi taskı bloklayacak bir kod bulunmamalıdır.
Periyot: Software timer’ın periyodu timer başlatıldıktan callback fonksiyonu çağrılana kadar geçen süredir. Timer tipine göre her periyot sonunda timer callback fonksiyonu yürütülür.

One-shot ve Auto-Reload Timer

    İki tip yazılımsal timer vardır.
  • One-shot: Callback fonksiyonu sadece bir kez yürütülür. Manuel olarak tekrar başlatılmalıdır. Kendini otomatik olarak tekrar başlatmaz.
  • Auto-reload: Timer callback fonksiyonu otomatik olarak yürütülür. Periyoda bağlı olarak kendini tekrar başlatır. Yani callback fonksiyonu periyodik olarak yürütülür.

Yazılımsal Timer Durumları

    Bir yazılımsal timer aşağıdaki iki durumdan birinde bulunabilir.
  • Dormant(uykuda): Callback fonksiyonunun kodlarının yürütülmediği, timerın bir nevi uykuda olduğu durumdur.
  • Running: Bu durumda bulunan timer periyod süresini tamamladıktan sonra callback fonksiyonunu çalıştırır.
    Timer dormant durumuna aşağıdaki diyagramda görülen fonksiyonlar ile sokulabilir.
freertos-timer

Auto-Reload Timer

freertos-timer

One-Shot Timer

Timer Daemon (Timer Service) Task

    Bütün timer callback fonksiyonları daemon(deamon değil!) task adı verilen bir task içerisinde yürütülür. Daemon task standart FreeRTOS taskıdır ve otomatik olarak scheduler başlatıldığında oluşturulur. Daemon task’ın önceliği ve stack boyutu FreeRTOSConfig.h dosyasındaki configTIMER_TASK_PRIORITY ve configTIMER_TASK_STACK_DEPTH makrosuna göre belirlenir.

Timer Komut Kuyruğu

    Yazılımsal timer fonksiyonları ,çağrıldığı tasktan daemon task a timer komut kuyruğu(timer command queue) olarak isimlendirilen bir kuyruk ile komut gönderir. Bu kuyruk da scheduler başlatıldığında otomatik tanımlanır.
timer-queue

Timer Oluşturma

    FreeRTOS da timer oluşturmak için xTimerCreate() fonksiyonu kullanılır. Bu fonksiyon TimerHandle_t tipinde bir handle döndürür. Başlangıçta software timer dormant state’de bulunur. Yani oluşturulur oluşturulmaz hemen saymaya başlamaz. Timer scheduler dan önce veya sonra bir task içerisinde oluşturulabilir.

  • pcTimerName: Timer a isim vermek için kullanılır. Bu parametre FreeRTOS tarafından kullanılmaz , debug vb işlemler için kullanılabilir.
  • xTimerPeriodlnTicks: Tick cinsinden timer periyodudur. pdMS_TO_TICKS() fonksiyonu kullanılarak periyod milisaniye cinsinden ayarlanabilir.
  • uxAutoReload:  Bu parametre pdTRUE olarak girilirse timer auto-reload olarak çalışır. Eğer pdFALSE olarak girilirse one-shot timer olarak çalışır.
  • pvTimerID: Her yazılımsal timerın bir ID değeri vardır. Bu ID void pointer olarak tanımlıdır ve yazılımcı tarafından herhangi bir amaç için kullanılabilir. ID genelde aynı callback fonskiyonunu birden fazla timerın kullandığı uygulamalarda kullanışlıdır.
  • pxCallbackFunction: Timer callback fonksiyonunun adı bu parametre ile girilir.
  • Return Değeri: Döndürdüğü değer NULL ise timer oluşturmada hata olmuş demektir. Eğer hatasız oluşturulmuş ise bir handle değeri döner.

Timer’ı Başlatma

    Timer’ı baslatmak için xTimerStart() fonksiyonu kullanılabilir. Bu fonksiyon hali hazırda oluşturulmuş ve dormant state de bulunan veya önceden başlatılmış(running state de bulunan) bir timer’ı tekrar başlatır. Timer’ı durdurmak için ise xTimerStop() fonksiyonu kullanılır. xTimerStart() fonksiyonu scheduler başlatılmadan önce de kullanılabilir fakat scheduler başlamadan timer saymaya başlamaz.
    Timer’ı interrupt fonksiyonu içinden başlatmak için xTimerStartFromISR() fonksiyonu kullanılmalıdır.

  • xTimer: xTimerCreate fonskiyonunun döndürdüğü handle girilir.
  • xTicksToWait: Timerın komut kuyruğu kullandığını söylemiştik. Bu parametre kuyruk dolu ise ne kadar süre bekleneceğini belirtmek için kullanılır. Eğer xTimerStart fonksiyonu scheduler başlatma fonksiyonundan önce kullanılırsa FreeRTOS bu parametreyi dikkate almaz.
  • Return Değeri: timer başlatma komutu kuyruğa başarılı bir şekilde gönderildi ise pdTRUE, gönderilemedi ise pdFALSE değeri döndürür.
    FreeRTOS da yazılımsal timer ile ilgili diğer fonksiyonlara buraya tıklayarak ulaşabilirsiniz.

FreeRTOS Yazılımsal Timer Kullanımına Ait Bir Örnek

    Bu örnekte iki adet yazılımsal timer oluşturalım. Bunlardan biri periyodu 3500 milisaniye olan one-shot timer, diğeri periyodu 1000 milisaniye olan auto-reload timer olsun. Callback fonksiyonlarının nasıl çalıştığını görmek için her iki timer callback fonksiyonu timer başlatıldıktan sonra geçen süreyi UART üzerinden göndersin.

    Öncelikle FreeRTOSConfig.h dosyası içerisindeki timer ile ilgili olan tanımlamalara bir bakalım. Timer kullanabilmek için configUSE_TIMERS makrosu 1 olmalıdır. configTIMER_TASK_PRIORITY ile Daemon task’ın önceliği tanımlanır. En fazla configMAX_PRIORITIES-1 e kadar tanımlanabilir. Son tanımlama ise Timer Service Task ( Daemon Task) için ayrılacak belleği temsil eder. Buradaki değer byte değil word(32 bit)tür.

    Bu örnekte include ettiğimiz kütüphanelere bir yenisi daha eklendi. FreeRTOS da yazılımsal timer kullanabilmek için “timers.h” kütüphanesi eklenmelidir.

    İki adet timer handle oluşturuldu. Bu handle timer’ı başlatmak, durdurmak gibi işlemleri yapan fonksiyonlara parametre olarak gönderilir.

    Timer periyodları 3500 ve 1000 milisaniye olarak ayarlanmıştır. Bu örnekte timer id si kullanılmadığı için sıfır olarak girilmiştir.

    Timerlar schedulerdan önce başlatılmıştır. Burada blok süresi sıfırdan farklı girilse bile FreeRTOS bu parametreye dikkat etmez çünkü henüz scheduler başlatılmamıştır. Yani timer başlatma fonksiyonları çağrılsa bile scheduler başlatılmadan timer saymaya başlamaz. Timer herhangi bir task fonksiyonu içerisinden de başlatılabilirdi. Bu örnekte normal bir task fonksiyonu tanımlanmamıştır. Bu yüzden scheduler başlatıldıktan sonra çalışacak olan tek task “Idle Task” tır. Timer periyotlarına göre arada Daemon task da çalışacaktır.

    Timer callback fonksiyonları ise yukarıdaki gibidir. Burada scheduler dolayısı ile timerlar başlatıldıktan sonra geçen süreyi çekmek için xTaskGetTickCount() fonksiyonu kullanılmıştır. Burada geçen süre TickType_t cinsindendir. UART üzerinden gönderilen stringler aşağıdaki gibidir.
freertos-timer-example
    Yukarıda görüldüğü gibi One-Shot timer sadece bir kere çalışmıştır. Burada Tick rate 1000 hz olduğu için pdMS_TO_TICKS() fonksiyonunun kullanılmasına gerek yoktur fakat ileriye dönük tick rate frekansının değiştirilebileceği göz önünde bulundurularak pdMS_TO_TICKS() fonksiyonunu kullanmak daha iyidir.

Tüm Kodlar (FreeRTOS İle)

CMSIS-RTOS ile Yazılımsal Timer Kullanımı

    Handle tanımlamaları yukarıdaki gibidir. Burada CubeMX kullandığımız için defaultTask’ı silmemize izin vermedi.

    Timer tanımlamak için osTimerDef makrosu kullanılır. Burada ilk parametre timer’a verilen isim diğeri ise callback fonksiyonudur.

    Timer oluşturmak için osTimerCreate() fonksiyonu kullanılır.

  • *timer_def: Bu parametre osTimerDef_t tipinde olmalı ve osTimer ile referans verilerek girilmelidir.
  • type: Timer’ın tipini seçmek için kullanılan parametredir. osTimerOnce ise one-shot timer , osTimerPeriodic ise Auto-Reload Timer olarak ayarlanır.
  • *argument : Timer callback fonksiyonuna değer göndermek için kullanılır. Bu örnek te değer gönderilmediği için NULL olarak girilmiştir.
  • Return değeri: Eğer timer sorunsuz oluşturuldu ise osTimerId tipinde bir handle döndürür. Sorun oluştu ise NULL değeri döner.

    CMSIS-RTOS da timerı başlatmak için osTimerStart() fonksiyonu kullanılır. Bu fonksiyonun aldığı parametreler aşağıdaki gibidir.
  • timer_id: osTimerCreate() fonksiyonunun döndürdüğü değer girilir.
  • millisec: Milisaniye cinsinden timer periyodunu belirlemek için girilen parametredir.
   Burada dikkat ettiyseniz timer periyodunu ,timer başlatılırken belirledik. FreeRTOS da timer oluşturulurken belirleniyordu. Timer için oluşturulan callback fonksiyonları aşağıdaki gibidir. Bu fonksiyonların yaptığı işlemler FreeRTOS örneğindeki ile aynıdır.

    CMSIS-RTOS ile yapılan örneğin UART çıktısı aşağıdaki gibidir. FreeRTOS örneği ile aynı çıktıyı verdiği görülebilir.

Timer Example

CMSIS-RTOS da yazılımsal timer ile ilgili diğer fonksiyonlara buraya tıklayarak ulaşabilirsiniz.

CubeMX Ayarları

    CubeMX ile timer ı kullanabilmek için “Config Parameters” sekmesindeki “USE_TIMERS”  “Enabled” olarak seçilmelidir. Diğer ayarlar yazılımcıya göre değişebilir. Önceliğin callback fonksiyonu için değil Timer Service Task (Daeomon Task) için olduğunu hatırlayalım. Burada timer kuyruğunu dolduracak hususlar şunlardır;
  • Scheduler başlatılmadan önce birden fazla timer API fonksiyonunun çağrılması
  • Interrup-safe adı verilen yani ISR içerisinden çağrılan sonu “fromISR” ile biten timer fonksiyonları kullanmak(kuyruğu dolduracak kadar fazla).
  • Timer Service Task(Daemon Task)  ın önceliğinden daha yüksek öncelikteki bir taskın içerisinde birden fazla timer API fonksiyonları kullanmak.
Stack boyutu Daemon Taskın stack boyutudur. Callback fonksiyonlarının sayısına göre stack boyutu ayarlanmalıdır.

    Timer oluşturma ayarları ise yukarıdaki gibidir. Burada Timer periyodunu CubeMX ile ayarlayamıyoruz. Bunu kod kısmında Timer’ı başlatırken yapıyoruz.

Tüm Kodlar( CMSIS-RTOS ile)

Kaynaklar

Mehmet Topuz

4 Comments

  1. Hocam merhaba diyelim ki 3 tane timer yarattık üçü de 1 saniyelik olsun.Ben bunların sırası ile örneğin önce 1.fonksiyona sonra 2 .fonksiyona sonra 3 e girmesini nasıl sağlayabilirim?

  2. Merhaba, FreeRTOS da birden fazla timer tanımlandığında aralarında bir öncelik belirtemiyoruz malesef. Bu demek değildir ki aynı periyoda sahip olan callback fonksiyonları rastgele çalışacak. Şöyle düşünelim ; bu callback fonksiyonlarını yürüten Daemon task ve bu task a bu callback fonksiyonları arkaplanda bir kuyruk yardımıyla gönderiliyor. Yani önce hangi callback fonksiyonunu yürüteceğini bu kuyruktaki verilere göre belirliyor. Bu kuyruğa önce hangi callback fonksiyonu eklenirse ilk o yürütülecektir(tabi periyotları aynı olduğu sürece). Peki kuyruğa ilk çalışmasını istediğim callback fonksiyonunu nasıl ekleyebiliriz? Bunu ilk olarak ilgili timer’ı başlatarak yapabilirsiniz. Örneğin;
    ******
    xTimerStart(Timer1,0);
    xTimerStart(Timer2,0);
    xTimerStart(Timer3,0);
    vTaskStartScheduler();
    *****
    şeklinde kullanabilirsiniz. Bu kullanımda sırasıyla timer1 ,timer2 ve timer3 e ait olan callback fonksiyonları yürütülecektir.

  3. Peki hocam xTimerStart(Timer1,0); bu fonksiyon 2.den daha uzun sürdüğünde Timer2 nin fonksiyonu devreye girer mi?Benim istediği diyelim ki timer1 in fonksiyonu içeride 300 ms sürsün Timer 2 ninki 10 ms sürsün bu durumda önce timer2nin işini mi yapar?Yaparsa benim istediğim şekilde yani önce 1 tetiklenecek 1 in fonksiyonu tamalanacak sonra 2 ,sonra 3?

    • Timer1 callback fonksiyonu işlemlerini bitirmeden Timer2 fonksiyonu çalışmaz. Demaon task önce Timer1’in sonra Timer2’nin ve eğer kuyrukta başka çalıştırılmayı bekleyen bir callback fonksiyonu varsa onu da çalıştırıp sonra diğer normal tasklar çalışmaya başlar. Yani ister 300 ms sürsün isterse 1ms, önce kuyruğa ilk eklenen çalışır. RTOS ile ilgili kitaplarda veya başka kaynaklarda bu fonksiyonların içinin olabildiğince kısa tutulması söylenir. Gerçek zamanlı sistemlerde 300 ms aslında çok uzun bir süre.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.