Operacijski sistem (OS), ki se nahaja v ugnezdenih napravah, se imenuje RTOS (Real-Time Operating System). V ugnezdenih napravah so opravila v realnem času ključnega pomena, kjer ima čas zelo pomembno vlogo. Naloge v realnem času so časovno deterministične, kar pomeni, da je odzivni čas na kateri koli dogodek vedno enak, tako da se lahko zanesemo na to, da se bo vsak dogodek zaznal v nekem določenem času. RTOS je zasnovan za izvajanje aplikacij z zelo natančnim časovnim razporedom in visoko stopnjo zanesljivosti. RTOS pomaga tudi pri izvajanju večopravilnosti z enim samim jedrom.
V preteklosti smo že obravnavali uporabo RTOS-a v ugnezdenih sistemih, kjer lahko izveste več o RTOS-u, o razliki med splošnimi operacijskimi sistemi in RTOS-om, različnih vrstah RTOS-ov in tako naprej. Povezava na ta članek [1] .
V tem članku bomo začeli s FreeRTOS. FreeRTOS je razred RTOS za ugnezdene naprave, ki je dovolj majhen, da se lahko izvaja na 8/16-bitnih mikrokontrolerjih, čeprav njegova uporaba ni omejena le na te mikrokontrolerje. Je popolnoma odprtokodna programska koda in je vsem na voljo na githubu. Če poznamo nekaj osnovnih pojmov RTOS, je FreeRTOS zelo enostaven za uporabo, saj ima dobro dokumentirane API-je (Application Programming Interface, programski vmesnik za aplikacije), ki jih je mogoče neposredno uporabiti, ne da bi morali poznati vsebino programske kode v njihovem ozadju. Popolno FreeRTOS dokumentacijo boste našli na naslovu [2].
Ta vsebina je samo za naročnike
Ker FreeRTOS lahko deluje tudi na 8-bitnih mikrokontrolerjih, ga je mogoče izvajati tudi na Arduino Uno razvojni ploščici. V Arduino IDE moramo prenesti le knjižnico FreeRTOS in nato začeti izvajati kodo z uporabo API-jev. Ta članek in primer sta namenjena popolnim začetnikom in ima spodaj naštete teme, ki jih bomo v tem članku FreeRTOS za Arduino obravnavali:
- Kako RTOS deluje
- Nekateri pogosto uporabljeni izrazi v zvezi z RTOS
- Namestitev FreeRTOS v Arduino IDE
- Kako ustvariti FreeRTOS opravila s primerom
Kako RTOS deluje?
Preden začnemo z delom na RTOS, poglejmo, kaj je opravilo (task). Opravilo je del programske kode, ki jo je mogoče načrtovati za izvajanje na neki glavni procesni enoti (CPU). Torej, če želite izvesti neko opravilo, jo je treba razporediti za izvajanje z uporabo zakasnitve jedra operacijskega sistema (kernel) ali z uporabo prekinitev. To delo opravi načrtovalnik, ki je prisoten v jedru operacijskega sistema (kernelu). V eno-jedrnem procesorju načrtovalnik pomaga opravilom, da se izvajajo v določenem časovnem oknu, zato se zdi, da se različna opravila izvajajo hkrati. Vsako opravilo se izvaja glede na stopnjo prednosti, ki mu je določena.
Zdaj pa poglejmo, kaj se dogaja v procesnem jedru RTOS, če želimo ustvariti opravilo za LED, ki naj bi utripala z eno-sekundnim intervalom, ob tem pa bomo temu opravilu dali tudi najvišjo prednost.
Poleg naloge prižiganja in ugašanja LED-ice bo tu še ena naloga, ki jo ustvari jedro operacijskega sistema, znana kot neaktivna (idle) naloga. Neaktivna naloga se ustvari, ko za izvajanje ni na voljo nobena druga naloga. Ta naloga se vedno izvaja z najnižjo prioriteto, prioriteto 0 (nič). Če analiziramo časovni graf, ki je prikazan na sliki 3, je dobro razvidno, da se izvajanje začne z LED-nalogo in deluje določen čas, nato pa se preostali čas izvaja neaktivna naloga, dokler ne pride do periodične prekinitve (tick). Ob tej prekinitvi se
jedro odloči, katero nalogo je treba izvesti glede na prioriteto naloge in glede na skupni pretečeni čas v zvezi z opravilom LED. Ko preteče ena sekunda, jedro znova izbere LED opravilo za izvajanje, ker ima večjo prednost kot neaktivna naloga, z drugimi besedami pa lahko rečemo, da naloga LED prevzame prednost pred neaktivno nalogo. Če obstajata več kot dve nalogi z enako prednostjo, se bosta določen čas izvajali v krožnem načinu.
Na sliki 4 je prikazano preklapljanje nedelujočega opravila v delujoče stanje.
Vsako na novo ustvarjeno opravilo gre v stanje pripravljenosti (del stanja, ko se opravilo ne izvaja). Če ima ustvarjena naloga (Task1) večjo prioriteto kot druge naloge, se bo premaknila v stanje delovanja. Če to nalogo, ki se izvaja, prepreči druga naloga, se bo spet vrnila v stanje pripravljenosti. V primeru pa, da je Task1 blokiran z uporabo API-ja za zaustavitev, potem CPU ne bo upošteval te naloge, dokler ne poteče časovna omejitev, ki jo določi uporabnik.
Če je Task1 onemogočen v delujočem stanju z uporabo API-jev za zaustavitev, bo Task1 prešel v stanje nedelovanja »Suspended« in razporejevalniku opravil znova ne bo na voljo. Če nadaljujemo z opravilom Task1 v stanju mirovanja, se bo vrnilo v stanje pripravljenosti, kot lahko vidite v blok diagramu.
To je osnovna ideja v RTOS, kako se opravila izvajajo in spreminjajo svoja stanja. V tem članku bomo v programu Arduino Uno izvedli dve nalogi z uporabo FreeRTOS API.
Pogosto uporabljeni izrazi v RTOS
- Opravilo: To je del kode, ki jo je na CPU mogoče načrtovati za izvajanje.
- Načrtovalnik: odgovoren je za izbiro naloge s seznama iz stanja pripravljenosti v stanje, ko se dejansko izvaja. Načrtovalniki se pogosto uporabljajo tako, da zasedejo vse računalniške vire (kot pri uravnoteženju obremenitve).
- Prednost: Gre za dejanje začasne prekinitve izvajanje neke naloge, ki se že izvaja, z namenom njene odstranitve iz stanja izvajanja brez njenega sodelovanja.
- Preklapljanje konteksta: pri prednostnem vrstnem redu načrtovalnik primerja prednost izvajanja nalog s prednostjo seznama pripravljenih opravil na vsaki periodični prekinitvi (tick). Če je na seznamu kakšna naloga, katere prednost je večja od trenutno izvajanega opravila, pride do preklopa konteksta. V bistvu se v tem procesu vsebina različnih opravil shrani v njihov pomnilniški sklad.
- Vrste pravil razporejanja opravil načrtovalnika:
- Prednostno enakovredno razporejanje: pri tej vrsti razporejanja se naloge izvajajo z enakim časovnim rezom, ne da bi upoštevali prioritete.
- Prednostna obravnava nalog: Naloga z visoko prioriteto se bo izvedla prva.
- Razporejanje s sodelovanjem: preklapljanje konteksta se bo zgodilo samo s sodelovanjem tekočih nalog. Opravilo se bo izvajalo neprekinjeno, dokler se do kraja ne izvede.
- Predmeti jedra operacijskega sistema: Za signaliziranje naloge pri izvedbi nekega opravila se uporablja postopek sinhronizacije. Za izvajanje tega postopka se uporabljajo objekti procesnega jedra. Nekateri izmed objektov jedra operacijskega sistema so dogodki, semaforji, čakalne vrste, razporejevalnik opravil, poštni nabiralniki itd. Kako jih bomo uporabljali, bomo videli v naslednjih vajah.
Iz zgornjega opisa smo dobili nekaj osnovnih idej o konceptu RTOS in zdaj lahko začnemo z izvajanjem projekta FreeRTOS v Arduinu. Začnimo kar z namestitvijo knjižnic FreeRTOS v Arduino IDE.
Namestitev knjižnice FreeRTOS v Arduino IDE
- Odprite Arduino IDE in pojdite na Sketch -> Include Library -> Manage Libraries. Poiščite FreeRTOS in namestite knjižnico, kot je prikazano spodaj.
Knjižnico lahko prenesete iz github in .zip datoteko dodate v Sketch-> Include Library -> Add .zip datoteko.
Zdaj je potrebno Arduino IDE ponovno zagnati. Ta knjižnica vsebuje tudi nekaj primerov programske kode, ki jih lahko najdete tudi v Datoteka -> Primeri -> FreeRTOS, kot je prikazano na sliki 6.
Mi bomo napisali programsko kodo čisto od začetka, da bomo razumeli delovanje, kasneje pa lahko preverite programsko kodo primerov iz knjižnice in jih uporabite v svojih programih.
Shema vezja
Na sliki 7 so prikazane medsebojne povezave za ustvarjanje opravila utripajoče LED-ice s FreeRTOS na Arduinu:
Primer Arduino FreeRTOS – Ustvarjanje nalog FreeRTOS v Arduino IDE
Poglejmo osnovno strukturo za pisanje projekta FreeRTOS.
- Najprej v uvodnem zaglavju vključite Arduino FreeRTOS datoteko kot
#include <Arduino_FreeRTOS.h>
- Potem podajte prototip funkcije vseh vaših funkcij, ki jih pišete za izvajanje, ki so zapisane kot
void Task1( void *pvParameters );
void Task2( void *pvParameters );
..
….
- Zdaj v funkciji void setup () ustvarite opravila in zaženite načrtovalnik opravil.
Za ustvarjanje naloge je v nastavitveni funkciji poklican API xTaskCreate () z določenimi parametri oziroma argumenti.
xTaskCreate( TaskFunction_t pvTaskCode, const char * const pcName, uint16_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask );
Med ustvarjanjem katerega koli opravila je treba posredovati 6 argumentov. Poglejmo, kakšni so ti argumenti
- pvTaskCode: To je preprosto kazalec na funkcijo, ki izvaja nalogo (v resnici samo ime funkcije).
- pcName: opisno ime za nalogo in tega FreeRTOS ne uporablja. Dodan je izključno za odpravljanje napak.
- usStackDepth: Vsaka naloga ima svoj lasten unikatni sklad, ki ga jedro dodeli opravilu, ko je naloga ustvarjena. Vrednost določa število besed, ki jih lahko vsebuje sklad, ne število bajtov. Na primer, če je sklad širok 32 bitov in se vrednost za usStackDepth predstavi kot 100, bo v RAM pomnilniku za sklad tega opravila dodeljenih 400 bajtov prostora (100 * 4 bajtov). Zato je to treba uporabiti pametno, saj ima Arduino Uno samo 2 Kbajta RAM pomnilnika.
- pvParameters: vhodni parameter opravila (lahko je tudi prazna vrednost, NULL).
- uxPriority: Prednost naloge (0 je najnižja prednost).
- pxCreatedTask: Uporablja se lahko za predajo ročice (handle) nalogi, ki jo ustvarjate. Ta ročica se nato lahko uporabi za sklicevanje na nalogo v klicih API, ki na primer spremenijo prednostno nalogo ali jo izbrišejo (lahko je tudi prazna vrednost, NULL).
Primer izdelave naloge
xTaskCreate(task1,”task1″,128,NULL,1,NULL);
xTaskCreate(task2,”task2″,128,NULL,2,NULL);
Tu ima naloga 2 večjo prioriteto in se zato najprej izvede.
- Ko ustvarimo opravilo, zaženemo načrtovalnik v void setup z uporabo vTaskStartScheduler (); API.
- Funkcija Void loop () bo ostala prazna, saj nobenega opravila ne želimo izvajati ročno in neskončno. Ker izvršitev naloge zdaj ureja razporejevalnik.
- Zdaj moramo implementirati funkcije opravil in v te funkcije zapisati logiko, ki jo želimo izvajati. Ime funkcije mora biti enako prvemu argumentu API-ja xTaskCreate ().
void task1(void *pvParameters)
{
while(1) {
..
..//your logic
}
}
- Večina programske kode potrebuje funkcijo zakasnitve, da ustavi tekoče opravilo, vendar v RTOS-u ni priporočljivo uporabljati funkcije Delay (), saj ustavi CPU s tem pa preneha delovati tudi RTOS. V ta namen ima jedro operacijskega sistema FreeRTOS poseben API, ki za določen čas blokira nalogo.
vTaskDelay( const TickType_t xTicksToDelay );
Za zakasnitve zato lahko uporabimo ta API, ki zakasni opravilo za določeno število periodičnih prekinitev. Dejanski čas, za katerega je naloga še vedno blokirana, je odvisen od pogostosti periodičnih prekinitev. Konstanta portTICK_PERIOD_MS se lahko uporablja za izračun realnega časa iz pogostosti periodičnih prekinitev.
Če želimo imeti zamudo 200 ms, samo napišemo to vrstico
vTaskDelay( 200 / portTICK_PERIOD_MS );
Pri tej vaji bomo uporabili naslednje FreeRTOS API-je za izvajanje treh nalog:
- xTaskCreate ();
- vTaskStartScheduler ();
- vTaskDelay ();
Opravila, ki jih je treba ustvariti za to vajo:
- Utripajoča LED-ica na digitalnem priključku 8 s frekvenco 200 ms
- Utripajoča LED-ica na digitalnem priključku 7 s frekvenco 300 ms
- Natisniti številke na monitor prek serijske komunikacije s frekvenco 500 ms.
Izvajanje FreeRTOS opravil v Arduino IDE
- Iz zgornje razlage osnovne strukture zdaj vključimo datoteko FreeRTOS v zaglavje Arduino programske kode. Nato naredimo prototipe funkcij. Ker imamo tri naloge, naredimo tri funkcije in to so ti trije prototipi:
#include <Arduino_FreeRTOS.h>
void TaskBlink1 (void * pvParameters);
void TaskBlink2 (void * pvParameters);
void Taskprint (void * pvParameters);
- V funkciji void setup () inicializiramo serijsko komunikacijo s hitrostjo 9600 bitov na sekundo in ustvarite vse tri naloge z API-jem xTaskCreate (). Sprva določite prednostne naloge vseh nalog kot »1« in zaženite načrtovalnik.
void setup () {
Serial.begin (9600);
xTaskCreate (TaskBlink1, “Task1”, 128, NULL, 1, NULL);
xTaskCreate (TaskBlink2, “Task2”, 128, NULL, 1, NULL);
xTaskCreate (Taskprint, “Task3”, 128, NULL, 1, NULL);
vTaskStartScheduler ();
}
- Zdaj izvedemo vse tri funkcije, kot je prikazano spodaj za LED-ice, to je utripanje task1.
void TaskBlink1(void *pvParameters)
{
pinMode(8, OUTPUT);
while(1)
{
digitalWrite(8, HIGH);
vTaskDelay( 200 / portTICK_PERIOD_MS );
digitalWrite(8, LOW);
vTaskDelay( 200 / portTICK_PERIOD_MS );
}
}
Podobno izvedemo tudi funkcijo TaskBlink2. Funkcija Task3 bo zapisana kot
void Taskprint(void *pvParameters)
{
int counter = 0;
while(1)
{
counter++;
Serial.println(counter);
vTaskDelay( 500 / portTICK_PERIOD_MS );
}
}
In to je to! Uspešno smo zaključili projekt FreeRTOS za Arduino Uno. Celotno programsko kodo lahko najdete skupaj z videoposnetkom na spletni strani, kjer je opisana ta vaja.
Na koncu na digitalna priključka 7 in 8 prek uporov priključite dve LED-ici, naložite programsko kodo na Arduino ploščici in odprite serijski monitor. Videli boste, da se vrednost števca poveča enkrat v 500 ms in izpiše imena opravil, kot je prikazano na sliki 8.
Pri opazovanju delovanja LED-ic boste opazili, da utripata v različnih časovnih intervalih. Poskusite se malo poigrati s prednostnim argumentom v funkciji xTaskCreate. Spremenite številko in opazujte obnašanje na serijskem monitorju in na delovanju LED-ic.
Zdaj lahko razumete prva dva primera programske kode, v katerih se ustvarjajo naloge analognega in digitalnega branja. Na ta način lahko naredite več naprednejših projektov samo z uporabo FreeRTOS API-jev in ploščice Arduino Uno. Začetek celotnega programa vidimo na sliki 10, celotno programsko kodo pa lahko snamete s spletne strani [3], kjer je ta primer objavljen.
Vir:
1: https://circuitdigest.com/article/understanding-rtos-and-how-to-use-it-for-embedded-systems
2: https://www.freertos.org/Documentation/RTOS_book.html
Avtor: Rishabh Jain