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.
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.
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.
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).
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.
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).
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.
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).
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.
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.
Hát igen.... Elérkeztünk a 100. cikkhez. Köszönjük az érdeklődést, a visszajelzéseket. Ezért valami érdekessel, nem annyira ismert témával ünnepeljük ezt a kerek fordulót. A választás a FreeRTOS-ra, egy valósidejű operációs rendszerre esett. Ezt a be. . . .
Folytatjuk tovább a FreeRTOS megismerését, de most a száraz elméletet félretéve, összeállítjuk az első projektünket.. . . .
Megismerkedünk most gyakorlati oldalról az USB-CDC használatával. Készítünk egy projektet az MpLab segítségével, amelynek segítségével LED-eket kapcsolunk be, illetve ki, de megnézzük azt is, hogy hogyan tudunk a mikrovezérlőből beolvasni string-et.. . . .