RTOS üzenetsor (Message Queue)

A fejezet tartalma:
A Queue objektumosztály egy olyan mechanizmust nyújt, melynek segítségével az mbed-RTOS programszálai "üzeneteket", azaz adatokat, vagy adatstruktúrákra mutató pointereket adhatnak át egymásnak. A Message Queue (üzenetsor) elnevezés arra utal, hogy az elküldött üzenetek sorbarendezve, egy FIFO tárba kerülnek (FIFO jelentése: first in - first out, vagyis a legelsőnek berakott elem vehető ki elsőként). Az RTOS pedig kezeli az üzenetküldéssel, illetve üzenetre várakozással kapcsolatos várakoztatásokat és versenyhelyzeteket.

1. ábra: Az üzenetsor, mint kommunikációs mechanizmus

Az üzenetsor objektumok kezelése a put(), illetve get() metódussal végezhető. A put() hívásával újabb üzenetet helyezhetünk el. Ha az üzenetsorban már minden hely betelt, akkor a küldő programszál várakozásra kényszerül. A fogadó programszál a get() metódussal vehet ki újabb adatot. Ha az üzenetsor kiürült, akkor a fogadó programszál várakozásra kényszerül.

A put() és get() tagfüggvények megszakításból is meghívhatók, ám ebben az esetben várakoztatás nem engedhető meg (a timeout paramétert 0 értékkel kell megadni).

Üzenetsor létrehozása

Mivel az üzenetsor absztrakt üzeneteket kezel (nincs megkötve az üzenetek típusa) ezért a Queue objektumosztály templát osztályként van definiálva, így példányosításkor nekünk kell megadni az üzenetek típusát és az üzenetsor méretét (a tárolt üzenetek maximális darabszámát). Például:
typedef struct {
float voltage; /* ADC feszültségmérés eredménye */
float current; /* ADC árammérés eredménye */
uint32_t counter; /* Egy kiolvasott számláló értéke */
} message_t;

Queue <message_t, 16> queue; /* 16 elemű üzenetsor létrehozása */
Természetesen az üzenetsor csak a message_t típusú struktúrákra mutató pointereket tárolja el, a tényleges adatok tárolásáról a  küldő programszálnak kell gondoskodnia. Ennek dinamikus kezelése (a tárterület csak az üzenet fogadása és felhasználása után szabadítható fel) további bonyodalmakkal jár, melyekkel a következő fejezetben foglalkozunk.

Az egyszerűség kedvéért mi az alábbiakban csak egész típusú adatok küldésével (mint pl. egyetlen ADC kiolvasás eredménye) foglalkozunk, amelyek beleférnek a Queue által kezelt mutatók helyére. Ez egy kis trükközéssel jár: a Queue objektumban látszólag mutatókat kezelünk, valójában azonban adatként kezeljük azok értékét.

Az alábbi példában előjel nélküli egésznek definiáltuk, de tulajdonképpen mindegy, hogy message_t milyen típusú, az üzenetekben átadott adatokat úgysem fogjuk mutatóként használni. A második sorral a Queue objektumot példányosítottuk queue néven, melynek FIFO tára legfeljebb 16 darab *message_t típusú mutatót képes tárolni.

typedef uint32_t message_t;
Queue <message_t, 16> queue;

Üzenetek küldése


Az üzenetek küldése a Queue objektumpéldányok put() metódusával végezhető:
osStatus put ( T *  data, uint32_t  timeout = 0 )        
Paraméterek:

data - az absztrakt T típusú üzenetre mutató pointer, vagy - egyszerű esetben maga az adat, pointernek "álcázva" (azaz típuskényszerítve).

timeout - a maximális várakozási idő, ezredmásodpercekben megadva. Az alapértelmezett 0 érték azt jelenti, hogy nincs várakozás.

Visszatérési érték:

A függvény visszatérési értéke az alábbi státusz, vagy hibakódok valamelyike lehet:
  • osOK: az üzenet bekerült az üzenetsorba
  • osErrorResource: az üzenetsor betelt, nincs hely az üzenetnek
  • osErrorTimeoutResource: a megadott várakozási idő alatt ne szabadult fel memória a betelt üzenetsorban
  • osErrorParameter: érvénytelen paraméter

Megjegyzés: Ez a függvény megszakításból is hívható, de csak 0 várakozási értékkel.

Üzenetek fogadása, várakozás üzenetre

Az üzenetek fogadása a Queue objektumpéldányok get() metódusával végezhető:
osEvent get ( uint32_t  timeout = osWaitForever )        
Paraméterek:

timeout - a maximális várakozási idő, ezredmásodpercekben megadva. A várakozás alapértelmezetten korlátlan ideig tarthat. Ha 0 értéket adunk meg, az azt jelenti, hogy nincs várakozás, tehát ha van a tárban üzenet, akkor kivesszük, egyébként pedig hibakóddal tér vissza a függvény.

Visszatérési érték:

A függvény visszatérési értéke egy osEvent típusú struktúra, melynek status nevű eleme az alábbi státusz, vagy hibakódok valamelyike lehet:
  • osOK: nincs beérkezett üzenet (ha timeout 0-nak volt megadva)
  • osEventTimeout: a megadott várakozási idő alatt nem érkezett üzenet
  • osEventMessage: üzenet érkezett, melyet az osEvent típusú struktúra value nevű eleméből érték szerint value.v hivatkozással, mutatóként pedig value.p hivatkozással vehetünk elő.
  • osErrorParameter: érvénytelen paraméter, vagy a paraméter értéke kívül esik a megengedett tartományon.

Megjegyzés: Ez a függvény megszakításból is hívható, de csak 0 várakozási értékkel.

Mintapélda: Adatküldés a Queue objektumosztály használatával

Az alábbi példában egyszerű adatokat (a 16 bites ADC-ből kiolvasott értékeket) küldünk a Queue objektumosztály által nyújtott mechanizmus segítségével. Mivel az adataink kényelmesen beleférnek egy mutató helyére, így nincs szükség további tárterület lefoglalására az adatok számára.

Az ADC A0 (Arduino kompatibilis) bemenetére kössük rá egy ~ 10 kΩ-os potméter csúszkáját, melynek két végét pedig a +3.3V-os tápfeszültségre, illetve a tápegység közös pontjára kötjük. Így a potméter forgatásával a bemenő feszültséget 0 - 3.3V között változtathatjuk.

Az első programszál (a main() függvény törzse) rendszeres időközönként megméri a bejövő feszültséget, és az üzenetsorba illeszti a soron következő eredményt (a 16 bites ADC 0 és 65535 közötti értékeket szolgáltat). A queue.put() metódus hívásánál típuskényszerítést kell alkalmaznunk, hogy a fordító ne jelezzen hibát.

A második programszál üzenetre vár, s az üzenetben kapott adattal vezérli a kék LED fényerejét (PWM kimenettel). A LED fordított logikája miatt az A0 bemenetre adott nagyobb feszültséghez kisebb LED fényerő, a kisebb feszültséghez pedig nagyobb LED fényerő fog tartozni.

Vegyük észre, hogy az üzenet fogadásakor nincs szükség típuskényszerítésre, mivel a queue.get() metódus visszatérési  értékeként kapott osEvent struktúra value eleme unióként van deklarálva, így rajtunk áll, hogy adatként olvassuk ki (value.v), vagy mutatóként (value.p).

Hardver követelmények:

2. ábra: A potméter bekötése

1. lista: A 11_rtos_queue/main.cpp program listája

#include "mbed.h"
#include "rtos.h"

AnalogIn potmeter(A0);
PwmOut myled(LED_BLUE);

typedef uint32_t message_t;
Queue <message_t, 4> queue;

void led_thread(void const *argument) {
myled.period_us(16384); //Set period to 16.384 ms
myled.pulsewidth_us(8192); //Initialize to 50% duty cycle
while (true) {
osEvent evt = queue.get(); //Wait for a message
switch(evt.status) {
case osOK:
printf("osOK\n"); //no error, no message arrived
break;
case osEventMessage:
printf("osEventMessage = %#05x\n",evt.value.v); //message arrived
myled.pulsewidth_us((int)(evt.value.v>>2)); //roughly 0..16 ms
break;
case osEventTimeout:
printf("osEventTimeout\n"); //timeout occurred
break;
case osErrorParameter:
printf("osErrorParameter\n");
break; //invalid parameter or is out of range.
default:
printf("Unknown error flag: %#08x\n",(uint32_t)evt.status);
break;
};
}
}

int main (void) {
Thread thread2(led_thread);

while (true) {
Thread::wait(1000);
uint16_t raw = potmeter.read_u16(); // read raw 16-bit data
queue.put((message_t*)raw);
}
}

Megjegyzés: A kísérletezéshez a programot kiírásokkal is megtűzdeltük, ami egy gyakorlati alkalmazás esetén természetesen fölösleges. A kiíratások miatt az üzenetküldések gyakoriságát is csökkenteni kellett, emiatt a rendszer "reakcióideje" (a potméter elforgatásától a LED fényerejének megváltozásáig terjedő időtartam) túlságosan nagy.