WebElektronika

FreeRTOS alapjai IV. (heap, sorok)

person access_time 2015.06.17.
Visszatérünk a "száraz" elmélethez még egy cikk erejéig. Megnézzük, mi a szerepe a heap_1,2,3 file-ok egyikének, mi az a sor, a FreeRTOS-nál ez hogyan jelenik meg. Nem törekszünk most sem a részletes ismertésre, a cél az, hogy az alapokat megismerjük, amelyek segítségével a demoprojektek érthetővé, érthetőbbé válnak.


Tekintettel arra, hogy a projektünkkel együtt fordul le és kerül be a mikrovezérlőbe a FreeRTOS operációs rendszer is, ezért nagyon fontos a megfelelő memóriamenedzsment.

 

Memóriakezelés

A taskok használatához elengedhetetlen a memóriakezelés. Egy jól megválasztott memóriamenedzsmenttel a projektünk mérete, a taskok száma jelentősen növelhető. A FreeRTOS-nál három lehetőségünk van a memóriakezelésre. Attól függően, hogy melyiket szeretnénk használni, annak függvényében kell a projektünkhöz adni a következő file-ok egyikét.

  • heap_1.c
    • egyszerű, csak lefoglalni tud memóriaterületet, felszabadítani nem
  • heap_2.c
    • tud memóriaterületet lefoglalni és felszabadítani is, legjobban illeszkedő memóriaterületet keresi
  • heap_3.c
    • a lefoglalandó memóriaterület méretét a fordítónál adjuk meg a config file helyett, a memóriakezelésnél a malloc() és a free() függvényeket használja. Ekkor az ütemező nem működik.

Az első két lehetőségnél a lefoglalandó memóriaterület méretét a configTOTAL_HEAP_SIZE definíciónál állíthatjuk be a config file-ban (FreeRTOSConfig.h).

Az első ábrán látjuk az "A" esetet, ahol még nem történt meg a memóriafoglalás, a felhasználható memória mérete a configTOTAL_HEAP_SIZE definíciónál megadott érték. A következőnél (1/B) látjuk, hogy egy tasknak történt memóriafoglalás. Minden tasknak van stack-je és TCB-je. A következő résznél (1/C ábra) már három task van. Mind a három tasknak külön stack-je és TCB-je van, és a rendelkezésre álló memória megtelt.

kep
1. ábra   Memóriaszervezés (Nicolas Melot, Study of an operating system: FreeRTOS)
 

A config file-ban megadott memóriaterület megtelt, a heap_1.c alkalmazásával újabb task már nem hozható létre.

Ha a heap_2.c file-t használjuk a projektünk létrehozásakor, akkor a memóriahasználat már úgy történik, hogy nem csak lefoglalni tudunk területeket, hanem felszabadítani is. Amikor felszabadítjuk a piros színnel jelölt task lefoglalt memóriaterületét, akkor be tudunk tenni egy másik taskot (sárga színnel került jelölésre), amely jelen esetben kisebb méretű stack-kel rendelkezik (2. ábra).

kep
2. ábra   Memóriaszervezés (Nicolas Melot, Study of an operating system: FreeRTOS)
 

Egy memóriaterületre adat tehető be vagy ki, ezt nevezhetjük "postaládának" is. Ha adatot teszünk be, akkor termelő egy task, ha adatot veszünk ki, akkor a task fogyasztó. Ezt a memóriaterületet egyszerre több task is használhatja.

 

Sorok (queue.h)

A sorok a task-ok közötti kommunikációra szolgálnak, a memóriaterületük rögzített. A sorok működése nagyon hasonlít a FIFO működésére. Blokkolhat task-okat, ha a sor tele van, vagy ha üres.

A harmadik ábrán két taskot és egy sort látunk. A sor szerepe az, hogy a taskok egymással tudjanak adatokat cserélni. Ebben a példában láthatjuk, hogy a sor öt elem tárolására képes (az elem típusát még nem tudjuk az ábra alapján).

kep
3. ábra   A sor létrehozása
 

A 4.ábra mutatja, hogy a "Task A" egész típusú változójának (x) értékét beírja a sor végébe. Az ötelemű sor idáig üres volt. A "Task B" nem olvassa ki ezt az értéket.

kep
4. ábra   Írás a sorba
 

A "Task A" további munkája során az "x" változó értéke megváltozott, és ezt az értéket is beteszi a sorba, a korábbi érték elé. Most már csak három hely van az esetleges többi egész típusú számnak (4. ábra).

kep
5. ábra   Újabb adat írása a sorba
 

Hogyan lehet létrehozni, adott esetben törölni sort? Hogyan lehet beleírni a sorba, illetve kiolvasni onnan? Ezeknek a műveleteknek az elvégzésére az API különböző lehetőségeket kínál.

 

Sorok használata

  • xQueueCreate() : sorok létrehozása
  • vQueueDelete() : sor törlése
  • xQueueSend() : elhelyezés a sorban
  • xQueueSendFromISR() : elhelyezés a sorban
  • xQueueSendToFront() : elhelyezés a sor elején
  • xQueueSendToBack() : elhelyezés a sor végén
  • xQueueReceive() : adatot olvasunk ki kivétellel
  • xQueueReceiveISR() : adatot olvasunk ki a sorból törléssel
  • xQueuePeek() : adatot olvasunk ki törlés nélkül

 

xQueueCreate() használata :

A sor létrehozása tehát a xQueueCreate() segítségével történik. Látható, hogy kényelmesen tudunk létrehozni egy sort, meg kell adnunk paraméterként a leendő sor hosszát és az elemeinek a méretét byte-okban. Ha a visszatérési érték különbözik a NULL-tól, akkor a sor létrehozása sikeres volt.

xQueueCreate(
                       unsigned portBASE_TYPE   uxQueueLength,
                      unsigned portBASE_TYPE   uxItemSize
                       );

A "uxQueueLength" a sorban tárolható maximális egységek száma. Ha nem sikerül a sort létrehozni, akkor 0-t kapunk vissza.

 

vQueueDelete( xQueueHandle xQueue ) használata :

Ezzel tudjuk törölni a sort.

vQueueDelete(
                        xQueueHandle xQueue
                        );

xQueueSend() használata :

Ennek segítségével a sorba írhatunk. Ha egy task írni szeretne a sorba, akkor várakoznia kell addig, amíg lesz szabad hely. Ha több task szeretne írni egyszerre, akkor a lenagyobb prioritásúnak lesz erre először lehetősége.

​xQueueSend(
                        xQueueHandle  xQueue,
                        const  *pvItemToQueue,
                        portTickType  xTicksToWait
                        );

 

Ezzel a lehetőséggel a sor elejére írhatunk, a következővel a sor végére.

​xQueueSendToFront(
                        xQueueHandle xQueue,
                        const *pvItemToQueue,
                        portTickType xTicksToWait
                        );

 

​xQueueSendToBack(
                        xQueueHandle xQueue,
                        const *pvItemToQueue,
                        portTickType xTicksToWait
                        );

Ha a sorban nem tudtunk írni, akkor a pdPASS értéket kapjuk vissza.

 

xQueueReceive() használata :

Ha több task szeretne egyszerre olvasni a sorból, akkor a legnagyobb prioritású task kapja meg először az olvasási lehetőséget. Ha a prioritások megegyeznek, akkor az a tasknak lesz lehetősége az olvasásra, amelyik előbb kérte ezt.
Amikor egy task elkezdi olvasni a sort, akkor az állapota blokkolt lesz. Az olvasás után az állapota megváltozik blokkoltról készre.

​xQueueReceive(
                        xQueueHandle  xQueue,
                        const  *pvBuffer,
                        portTickType  xTicksToWait
                        );

 

Ha az olvasás nem volt sikeres, akkor az xQueueReceive() pdPASS-t ad vissza, egyébként pdTRUE-t. Fontos még megjegyezni azt is, hogy az olvasás után ez az elem törlődik a sorból.

Ha nem szeretnénk, hogy az olvasás törléssel járjon, akkor az xQueuePeek()-et használjuk az xQueueReceive() helyett.

 

xQueueReceiveISR() használata :

Interrupt segítségével tudunk kiolvasni a sorból, a működése, paraméterezése, visszatérési lehetőségei megegyeznek a xQueueReceive()-vel.