2

FreeRTOS Notları #3: Queue(Kuyruk)

    Kuyruk(Queue) veri yapılarından da hatırlayacağımız üzere doğrusal bir veri saklama yapısıdır. Bu konuyu basit bir örnekle somutlaştırabiliriz. Bir bankamatikten para çekmek istedik ve bankamatiğin önüne gittiğimizde bankamatiğin önünde uzunca bir sıra olduğunu gördük. Bu sıra veya kuyruk nasıl işler? Kuyruğun bir başı vardır yani bankamatikten o anda para çeken bir kişi vardır ve muhtemelen kuyruğa ilk olarak o girmiştir. Ondan sonra gelenler onun arkasına sıralanır ve sıranın başındaki kişi bankamatikte işini hallettikten sonra kuyruktan ayrılır ve bir arkasındaki kişi kuyruğun başına geçer. Bu işlem sürekli tekrar edilerek en sonunda kuyrukta kimse kalmayana kadar devam eder. RTOS daki kuyruk kullanımı da buna benzer. Bu örnekte kuyruktaki insanlar RTOS’da veriler veya mesajlardır. Bu verileri tasklar sırası ile okuyarak belli işlemler yaparlar ve kuyruk boş iken tasklar bloklanabilir. Kuyruk kullanımının güzelliği de burada başlar. Kuyruğa bir veri geldiğinde istenilen task harekete geçirilebilir.

    Bir kuyruk(queue) sabit boyutlu değişkenler(8 bit,16 bit vb) tutabilir. Kuyrukta bulunan bu değişkenlerin maksimum sayısı ise “length” olarak isimlendirilir. Bu iki parametre kuyruk ilk oluşturulduğunda tanımlanır.
    Kuyruklar genelde FIFO(First In First Out) buffer kullanır. Yani kuyruğa ilk yazılan veri ilk önce okunur. Bir veri kuyruğa yazılırken genellikle sondan eklenir,veri okunacağı zaman kuyruğun başından okunur.
    Kuyruklar kendi başlarına bir nesnedir aslında. Herhangi bir task veya ISR(Interrupt Service Routine) içerisinden erişilebilir. Birden fazla task kuyruğa veri yazabilir ve aynı kuyruktan veri okuyabilir. Genelde birden fazla taskın kuyruğa veri yazması birden fazla taskın kuyruktan veri okumasından daha yaygındır.
    Tasklar kuyruk ile bloklanabilir. Eğer kuyrukta veri yok ise kuyruğa veri gelene kadar task bloklanabilir. Bu bloklama kuyruğa veri gelene kadar yapılabileceği gibi opsiyonel olarak belli bir süre ile de yapılabilir. Yani task belli bir süre kuyruğa veri gelmesini bekleyip bu süre içerisinde veri gelmez ise işlemlerine kaldığı yerden devam edebilir. Kuyruktan veri okuyan birden fazla task varsa bu task’lar aynı anda bloklanabilir fakat kuyruğa gelen veriyi ilk olarak en yüksek öncelikli task alır. Eğer kuyruktan veri bekleyen taskların öncelikleri eşit ise veriyi en uzun süredir bekleyen task kuyruktan okur.
    Bir task kuyruğa veri yazarken de bloklanabilir. Eğer kuyruk dolu ise kuyruğa veri yazma işlemi yapan task kuyruktan veri silinene kadar bloklanabilir. Yine bu durumda da birden fazla task kuyruğa veri yazma işlemi yaparken bloklanabilir. En yüksek öncelikli task kuyrukta yer açıldığında veriyi kuyruğa yazar. Eğer öncelikler eşit ise kuyruğa veri yazmak için en uzun süre bekleyen task kuyruğa veriyi yazar.

Kuyruk Oluşturma

    Kuyruk(Queue) da aslında bir RTOS nesnesidir. FreeRTOS‘da kuyruk oluşturmak için xQueueCreate() fonksiyonu kullanılır. Bu fonksiyon QueueHandle_t tipinde bir değer döner. Kuyruk oluşturulduğunda heap içerisinde dolayısı ile RAM de alan ayrılır. Eğer bu alan ayırma ile ilgili bir sorun oluşursa xQueueCreate() fonksiyonu NULL değeri döndürür. Eğer kuyruk sorunsuz bir şekilde oluşturuldu ise bir handle döndürür.

  • uxQueueLength: Kuyruğun uzunluğunu yani kuyruğa maksimum kaç eleman girileceğinin belirlendiği parametredir.
  • uxItemSize: Kuyrukta depolanacak her verinin kaç bayt olacağı bu parametre ile girilir.
  • Return Değeri: Kuyruk sorunsuz oluşturulduysa handle, oluşturulamadı ise NULL değeri döndürür.

Kuyruğa Veri Yazma

    Bir kuyruğa veri yazmak için iki tane fonksiyon kullanılabilir. Bunlar; xQueueSendToBack() ve  xQueueSendToFront() API fonksiyonlarıdır. Biri kuyruğun başına yazarken, diğeri kuyruğun sonuna yazar. Bunlardan hariç bir de xQueueSend()  fonksiyonu vardır. Bu fonksiyon xQueueSendToBack() fonksiyonu ile aynı işlemi yapar yani kuyruğun sonuna yazar.

    Eğer bir interrupt fonksiyonu içerisinden kuyruğa veri yazılacaksa bu fonksiyonları kullanamayız. Bunun için ayrı tanımlanmış xQueueSendToBackFromISR() veya xQueueSendToFrontFromISR() fonksiyonunu kullanmamız gerekir. Bunlar için daha detaylı bilgi interrupt ile ilgili notta verilecektir.

  • xQueue: Kuyruk oluşturulurken kullanılan xQueueCreate() fonksiyonunun döndürdüğü handle parametresi.
  • pvItemToQueue: Kuyruğa eklenecek olan veri. Bu verinin boyutu kuyruk oluşturulurken tanımlanmıştır.
  • xTicksToWait: Kuyruk dolu iken ne kadar süre bekleneceğinin girildiği parametre. Blok süresi burada tick periyoduna göre belirlenir. Yani bekleme süresi tick frekansına göre değişiklik gösterir. Bu parametre yerine portMAXDELAY girilebilir. Bu parametre girildiğinde eğer FreeRTOSConfig.h dosyası içerisindeki INCLUDE_vTaskSuspend  1 olarak ayarlanmış ise portMAXDELAY süresiz olarak beklemek anlamına gelir.
  • Return Değeri: Eğer veri kuyruğa yazılmış ise pdPASS , kuyruk dolu ise errQUEUE_FULL değeri döndürülür.

Kuyruktan Veri Okuma

    Kuyruktan veri okumak için xQueueReceive() fonksiyonu kullanılabilir. Bu fonksiyon kuyruktan veri okuduktan sonra okuduğu veriyi kuyruktan siler. Eğer interrupt fonksiyonu içerisinden kuyruktan veri okunacak ise xQueueReceiveFromISR() fonksiyonunu kullanmak daha iyidir.

  • xQueue: Kuyruk oluşturulurken kullanılan xQueueCreate() fonksiyonunun döndürdüğü handle parametresi.
  • pvBuffer: Okunan verinin tutulacağı değişken.
  • xTicksToWait: Kuyruk dolu iken ne kadar süre bekleneceğinin girildiği parametre.
  • Return Değeri: Eğer veri kuyruğa yazılmış ise pdPASS , kuyruk boş ise errQUEUE_EMPTY değeri döndürülür.
    Burada eğer xTicksToWait sıfırdan farklı ise kuyruk boş olduğunda task ,xTickToWait süresi kadar bloklanır. Başka bir task bu kuyruğa veri gönderince kuyruktan veri okuyan ve blocked state de bulunan task running state e geçer ve kuyruktaki veriyi çeker.
    Not: xQueueReceive() fonksiyonu kuyruktan veri okuduktan sonra okuduğu değeri kuyruktan siler. Eğer kuyruktaki verileri silinmeden okumak istersek  xQueuePeek() fonksiyonu kullanılabilir.

uxQueueMessagesWaiting() API Fonksiyonu

    Bu fonksiyon kuyrukta kaç tane veri olduğunu sorgulamak için kullanılır.

  • xQueue: Kuyruk oluşturulurken kullanılan xQueueCreate() fonksiyonunun döndürdüğü handle parametresi.
  • Return Değeri: Kuyrukta o anda bulunan verilerin sayısını döndürür. Eğer return değeri sıfır ise kuyruk boştur.
    FreeRTOS daki Queue yapısı ile ilgili diğer fonksiyonlara buraya tıklayarak ulaşabilirsiniz.

Kuyruk Kullanımını İle İlgili Örnek

    Amaç: İki adet task ve bir adet queue oluştur. Tasklardan biri bu kuyruğa veri yazarken diğeri kuyruktan veri okusun. Kuyruktan veri okuyan task aynı zamanda okuduğu verileri UART üzerinden göndersin.

    İlk olarak FreeRTOS ile ilgili olarak aşağıdaki kütüphaneleri include edelim.

    Bir Queue Handle oluşturduk.

    xQueueCreate() fonksiyonu ile 5 tane 16 bitlik sayı tutabilen bir kuyruk oluşturduk.

    Burada “if” şartı ile kuyruğun hatasız oluşturulup oluşturulmadığını sorguladık. Eğer kuyruk oluşturmada hata oluşmuş ise scheduler başlamayacaktır.

    Oluşturulan task fonksiyonları aşağıdaki gibidir. SenderTask başlangıçta 2 saniye bloklanmıştır. Buradaki amaç ReceiverTask fonksiyonun gerçekten bloklanıp bloklanmadığını görmektir. ReceiverTask fonksiyonunu incelersek, xQueueReceive(Queue,&received_value,pdMS_TO_TICKS(5000)) fonksiyonu ile kuyrukta veri yok iken taskın maksimum 5 sn süre ile bloklandığını görebiliriz. Eğer 5 sn içerisinde kuyruğa veri gelmez ise task kaldığı yerden çalışmaya devam eder ve UART üzerinden “Queue is empty!” mesajını gönderir. Eğer burada pdMS_TO_TICKS(5000) yerine portMAX_DELAY kullanılsaydı task süresiz olarak bloklanacaktı (FreeRTOSConfig.h dosyasındaki INCLUDE_vTaskSuspend  1 olduğu zaman).

    SenderTask içerisinde bir for döngüsü ile bir kereye mahsus kuyruğa 10 adet veri yazılmaya çalışılmıştır. Kuyruk uzunluğu 5 olduğu için sadece 50 ye kadar olan sayıları tutabilecektir. Bu yüzden 50 den sonra xQueueSendToBack(Queue,&SendValue,0) fonksiyonu errQUEUE_FULL değerini döndürecektir ve xTicksToWait parametresi sıfır olarak girildiği için task kuyruktan veri eksilmesini beklemeden işlemlerine devam edecektir. Bu yüzden for döngüsünün içerisindeki kodlar 10 defa çalışsa bile kuyruğa sadece 50 ye kadar olan sayılar yazılacaktır. Kuyruktaki bütün veriler ReceiverTask tarafından okunduktan sonra tekrar status = xQueueReceive(Queue,&received_value,pdMS_TO_TICKS(5000)); satırına geldiğinde task 5 saniye boyunca bloklanacaktır. Bu 5 saniye sonunda ReceiverTask kaldığı yerden çalışmaya devam edecektir. Buradaki status değişkeni kuyruk boş olduğu için errQUEUE_EMPTY olacaktır ve task fonksiyonu içerisindeki Uart_Print(“Queue is empty!\n”) kodu yürütülecektir.

    Eğer SenderTask içerisindeki xQueueSendToBack() fonksiyonu aşağıdaki gibi düzenleseydik nasıl bir sonuç alırdık?

    Bu satırda yapılan değişiklik ile SenderTask kuyruk dolduğunda maksimum 100 milisaniye bloklanacaktır. Bu 100 milisaniye dolmadan ReceiverTask kuyruktan veri çekeceği için kuyrukta yer açılıp SenderTask tarafından sonraki veri gönderilecektir. Bu örneği çalıştırdığımız zaman aşağıdaki gibi bir çıktı alırız.

    Bu örnekte görüldüğü üzere kuyruk uzunluğu 5 olsa bile 10 adet sayıyı kuyruk ile başka bir task’a gönderdik.

Kodların Tümü(FreeRTOS ile)

CMSIS-RTOS ile Queue Kullanımı

    CubeMX ile kuyruk oluşturmak istediğimizde aşağıdaki gibi bir pencere açılır. Bu pencerede bizim için önemli olan ilk üç parametredir. Queue Name kısmına istenilen isim verilebilir. Burada verdiğimiz isim handle oluşturmada kullanılır. İsmin sonuna “Handle” eklenerek CubeMX tarafından oluşturulur. Örneğin; Queue Name = queue1 ise Queue handle   queue1Handle şeklinde olur. CubeMx teki diğer ayarlar bir önceki yazıdaki ayarlar ile aynıdır. O yüzden yazıyı fazla uzatmadan o kısımları atlıyorum.

    CMSIS-RTOS da queue handle oluşturmak için osMessageQId kullanılır.

    osMessageQDef fonksiyonu ile kuyruğun boyutu ve değişken tipi tanımlanır.

    Burada name yerine kuyruğa verilecek isim yazılır. CubeMX ile bunu myQueue yaptık. Kuyruk ve kuyrukta bulunan her değişkenin boyutu FreeRTOS ile olan örnekteki gibi tanımlanmıştır. Kuyruk yapısı ile sadece integer değil char tipinde değişkenlerde gönderilebilir. Hatta string bile gönderilebilir.

    osMessageCreate fonksiyonu ile kuyruk oluşturulur. Bu fonksiyonun aldığı parametreler aşağıdaki gibidir.

    Bu fonksiyon bir handle döndürür ve aşağıdaki gibi kullanılabilir. Ayrıca bu fonksiyonun ilk parametresi osMessageQ(myQueue) şeklinde girilmelidir. Buradaki thread_id parametresi ile ilgili bir örnek bulamadım. Tahminime göre bu parametre yerine osThreadCreate fonksiyonu kullanılarak bir task oluşturulabilir. Bu parametrenin genellikle NULL olarak girildiğini gördüm.

     Kuyruğa veri yazan ve veri okuyan tasklar aşağıdaki tanımlanmıştır.

    CMSIS-RTOS da kuyruğa veri yazmak için osMessagePut() fonksiyonu kullanılabilir. Bu fonksiyonun tanımlaması cmsis_os.c dosyası içerisinde aşağıdaki gibidir.

    Burada info parametresi 32 bitlik olsa da tanımladığımız kuyruk 16 bitlik veriler tutar. Bekleme süresi olarak doğrudan milisaniye cinsinden sayılar girebiliriz. Eğer süresiz olarak bekletmek istiyorsak “osWaitForever” yazabiliriz.

    Kuyruktan veri okumak için osMessageGet() fonksiyonu kullanılabilir. Bu fonksiyonda yine cmsis_os.c dosyası içerisinde aşağıdaki tanımıdır.

    Burada karşımıza FreeRTOS dan farklı bir yapı çıkmaktadır. osMessageGet() fonksiyonu osEvent tipinde bir değer döndürmektedir. osEvent in tanımlı olduğu yere gidersek aşağıdaki bir struct olduğunu görürüz.

    osEvent CMSIS-RTOS da sadece kuyruk için kullanılmaz. Kuyruk ile kullanırken sadece “status, v,  message_id” değişkenlerini kullanacağız.
    Burada status değişkeni kuyruktan veri çekildiğinde “osEventMessage” değerini alır. Bu değeri kontrol ederek kuyruğun boş olup olmadığını anlayabiliriz. Kuyruktaki verinin değerine ulaşmak istersek “message.value.v” şeklinde çekebiliriz.
    Bu örnekte de kuyruk kullanımı bir öncekine göre aynıdır. CMSIS-RTOS da şöyle bir şey farkettim. osMessagePut() fonksiyonuna “millisec” parametresini sıfır girmeme rağmen kuyruğa 100 e kadar olan sayıları yazdı. FreeRTOS da kuyruk dolduğu için 50 ye kadar olan sayıları kuyruğa ekleyebilmiştik. CMSIS-RTOS da her nedense bu böyle olmadı.
    CMSIS-RTOS daki Queue ile ilgili diğer fonksiyonlar hakkında daha fazla bilgi için buraya tıklayabilirsiniz.

Tüm Kodlar ( CMSIS-RTOS ile)

Kaynaklar

Mehmet Topuz

2 Comments

Bir cevap yazın

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