Sulautettu reaaliaikakäyttöjärjestelmä¶
Osaamistavoitteet: Sulautetun ohjelman rakenne SensorTag-laitteessa.
Aloitamme sulauttujen laitteiden ohjelmointiin perehtymisen käymällä ensin läpi ohjelman rakenteen SensorTag-laitteessa. Valmistaja tarjoaa laitteen ohjelmointia helpottavan käyttöjärjestelmän (TI-RTOS), jonka kirjastoja ja palveluita käytämme harjoitteluun ja lopuksi harjoitustyön toteuttamiseen. Tässä materiaalissa esitetään laitteelle ohjelmointiesimerkkejä, mutta parhaiten asioita oppii tietysti itse kokeilemalla ja tekemällä. Aluksi muutama aiheeseen liittyviä käsite..
Käyttöjärjestelmä on tietokoneen toimintaa pyörittävä ohjelmisto. Se tarjoaa suoritusympäristön sovelluksille ja hallinnoi tietokoneen resursseja sekä käyttöoikeuksia. Käyttöjärjestelmä tarjoaa palveluita yhtenäisen rajapinnan ja kirjastojen kautta sovelluksille resussien käyttöön, mm. muistihallintaan, tiedostojärjestelmän käyttämiseen, oheislaitteiden käyttöön ja ulkoiseen tiedonsiirtoon. Käyttöjärjestelmä myös kontrolloi sovellusten suoritusta modernissa tietokoneessa, jakaen prosessorin suoritusaikaa ja muistia sovellusten kesken. (Yleisesti käyttöjärjestelmiä käsitellään myöhemmin Käyttöjärjestelmät-kurssilla, joten ohitamme tässä osiossa asioiden kattavan esityksen.)
Reaaliaikakäyttöjärjestelmä (engl. Real-time Operating System, RTOS) taas tarjoaa suoritusympäristön sulautettujen järjestelmien sovelluksille. Reaaliaikaisuus tässä yhteydessä tarkoittaa sitä, että käyttöjärjestelmä takaa toiminnallisuuksille tietyn vasteajan, esimerkiksi yksi millisekunti. RTOS itsessään voi olla hyvin kevyesti toteutettu, se voi esimerkiksi vain jakaa prosessoriaikaa sovellusten kesken. Kevyt toteutus tukee myös osaltaan sovelluksia, joissa on tiukat reaaliaikavaatimukset. Kuten aiemmin esitettiin, sulautettuja järjestelmiä on toki mahdollista ohjelmoida ilman käyttöjärjestelmää, mutta tällöin ohjelmoija on vastuussa laitteen komponenttien kytkemisestä MCU:hun ohjelmallisesti ja sovelluksen tarvitsemien palveluiden toteuttamisesta laitteelle. Reaaliaikakäyttöjärjestelmään on usein toteutettu valmista toiminnallisuutta optimoida kallisarvoisten laiteresurssien käyttöä. RTOS:n palvelut ja toiminnallisuus vaikuttaa merkittävästi sulautetun ohjelman suunnitteluun, toteutukseen ja ylläpitoon.
Yleisesti, sulautetun ohjelman kokonaisuus on järkevää jakaa modulaarisesti erillisiin tehtäviin, joiden toiminnallisuus voidaan loogisesti erottaa toisistaan. Näin tehtävät voidaan toteuttaa omina funktioinaan vaikkapa nyt C-kielellä. Esimerkiksi SensorTag-laitteessa tehtäviä voisi olla sensoridatan lukeminen, näytön päivitys tai langaton tiedonsiirto. Nyt RTOS:n osaa huolehtia niiden rinnakkaisesta ajosta laitteessa, ilman että ohjelmoida esimerkiksi toteuttaisi eri tehtävien prioriteettien mukaisen suorituksen itse.
TI-RTOS:n esittely¶
Kuvassa lohkokaavio TI-RTOS:n, firmiksen ja laiteajurien tarjoamista eri toiminnallisuuksista SensorTag-laitteessa. RTOS tarjoaa ohjelmoijalle rajapintoja (kuvassa punainen alue) ja piilottaa alemman tason toteutuksen yksityiskohdat, kuten laite-ja komponenttiajurit, reaaliaika-ominaisuudet ja tiedonsiirron detaljit. Meitä tällä kurssilla kiinnostaa sovellustaso, jossa käytämme erilaisia kirjastoja rajapintojen kautta (engl. application programming interface, API). Kurssilla pysymme punaisen alueen sisällä, jota syvemmälle laitteen toiminnassa ei mennä.
Mainitsemme joitakin näistä kirjastoista jo nyt, mutta niiden käyttöön ohjelmissa perehdymme yksityiskohtaisesti myöhemmin luentomateriaalissa.
(Kiinnostuneille: Osa RTOS:n lähdekoodista tulee CCS:n asennuspaketin mukana. Niistä voi löytyä paljon muutakin mielenkiintoista, johon ei tässä materiaalissa perehdytä).
Palvelut ja kirjastot¶
Seuraavia kirjastoja tulemme käyttämään ohjelmien toteuttamisessa. (Terminologia käydään tulevassa materiaalissa kyllä läpi.)
- Pin Kirjasto mikrokontrollerin I/O-pinnien hallintaan. Sensortagin painonappeja ja ledejä ohjataan tätä kautta. Myös GPIO-kirjastolla voidaan ohjata I/O-pinnejä.
- driverlib Laiterekisterit ja bittimaskit.
- I2C Kirjasto i2c-tiedonsiirtoväylän käyttämiseen. SensorTagin antureihin päästään käsiksi tällä kirjastolla.
- UART Sarjaliikenneprotokolla oheislaitteiden kanssa kommunikointiin. Esimerkiksi PC:lle voidaan lähettää dataa tai laitteelle lähettää komentoja PC:ltä tällä kirjastolla.
- (PWM Kirjasto Pulssinleveysmodulaation (PWM) tuottamiseen SensorTagin I/O-pinneillä. PWM:llä voidaan digitaalipinnistä syöttää analogista jännitettä tietyllä taajuudella vaikkapa ledin kirkkauden säätämiseen.)
- Display Kirjasto tekstin ja grafiikan tuottamiseen LCD-näytölle.
- wireless_lib Kurssin henkilökunnan toteuttama kirjasto langattomaan tiedonsiirtoon 6LoWPAN-teknologialla.
(Kiinnostuneille: Tarkempaa tietoa kirjastosta löytyy TI-RTOS Kernel User Guide:sta ja TI RTOS User's Guide:sta.)
Ohjelman toiminta¶
SensorTag:lle RTOS:ia käyttävät C-kieliset ohjelmat kirjoitetaan lähdekoodi-moduleina ihan samoja periaatteita noudattaen kuin mitkä tahansa C-kieliset ohjelmat.
Jokaisella ohjelmalla on yksi main-funktio, josta ohjelman suoritus lähtee. (Ok, RTOS tekee ensin omiaan piilossa taustalla ennen main-funktion suoritusta, mutat emme murehdi siitä tällä kurssilla).
Alla esimerkki
main
-funktiosta:// SensorTagin kirjastot mukaan
#include <xdc/runtime/System.h>
#include <ti/sysbios/BIOS.h>
#include <ti/drivers/I2C.h>
int main(void) {
// Alustetaan laite
Board_initGeneral();
Board_initI2C();
// Mahdolliset I/O-määritykset,
// joilla otetaan oheislaitteita käyttöön
...
// Alustetaan sovelluksen eri tehtävät,
// jotka toteutamme itse funktioina
...
// Kerrotaan ohjelmoijalle, että alustus onnistui
System_printf("Hello world!\n");
System_flush();
// Käynnistetään BIOS/RTOS, jolloin tehtävät lähtevät
// suoritukseen
BIOS_start();
return (0);
}
Pureudutaanpas main-funktion toimintaan hiukan. Kuten huomataan, alussa ensin alustetaan itse laite
Board_initGeneral
-funktiolla. Tämä tarkoittaa sitä, että RTOS:lle ja ohjelmallemme kerrotaan, että se ajaa nyt itseään SensorTag-laitteessa ja osaa ottaa huomioon laitteen resurssit, sähköiset kytkennät ja laitekohtaiset I/O-määrittelyt.Tarvittavat kirjastot tulee tietenkin esitellä ohjelmaamme esikääntäjän
#include
-lauseella. Sen, mitä ja missä olevia kirjastoja meidän tulee koodissa käyttää, selviää yleensä laitteen ja ohjelmointiympäristön manuaaleista. Lisäksi käytettävät oheislaitteiden ajurikirjastot tulee alustaa. Tässä tapauksessa haluamme käyttää I2C-kirjastoa kommunikointiin oheislaitteen kanssa, joten se alustetaan kutsulla Board_initI2C
. Näiden alustusfunktioiden sisäisestä maailmasta meidän ei tarvitse tällä kurssilla huolehtia. Ajurien alustamisen jälkeen alustetaan ohjelmoijalle näkyvä I/O, eli käytettävät I/O-pinnit ja -portit, kuten painonapit tai ledit, ja muut I/O-liitännät oheislaitteisiin. Näistä lisää myöhemmin. Seuraavaksi alustetaan varsinaisen ohjelman toiminta. RTOS:n kantava ajatus on, että ohjelma jaetaan modulaarisesti tehtäviin (eng. task) joita suoritetaan rinnakkain (no, oikeasti peräkkäin yksi kerrallaan käyttöjärjestelmän päättämässä järjestyksessä). Jokainen tehtävä on itseasiassa funktio ja niihin pätee (varauksin) samat säännöt kuin funktioiden käyttöön yleensäkin. Taskeilla kannattaa toteuttaa ohjelman korkean tason toiminta. Ohjelmoijalla on muuten vapaat kädet määritellä taskit ja niiden sisäinen looginen toiminta. Yksinkertainen ohjelma voidaan toteuttaa yhdellä ainoalla taskilla ja tässä onkin suunnittelun paikka. Tietenkään ei ole mielekästä jokaista pikkuasiaa varten luoda omaa taskia, mutta silti taskin sisäinen toiminto on järkevää toteuttaa funktiona. Esimerkiksi, ympyrän pinta-alan laskemiseksi ei tarvita taskia, ihan vaan funktio riittää. Taskin sisältä voimme (ja pitääkin) kutsua omia tai API:n funktiota tarpeen mukaan.
Sitten olisi tilaa tehdä mahdollisia muita alustuksia ohjelman toiminnan ohjaamiseen. Esimerkiksi, jos käytämme ajastimia ohjaamaan taskien suoritusta, ne voidaan alustaa tässä.
Tämän jälkeen on hyvä lähettää terveisiä kehitysympäristön konsoli-ikkunalle, jotta näemme, että laitteen alustus on onnistunut. Konsoli-ikkunaan voidaan kirjoittaa
System_printf
-funktiolla, joka toimii (pienin poikkeuksin) samoin kuin C:n standardikirjaston printf-funktio. Itseasiassa System_printf kerää tulostukset puskuriin, joka tyhjennetään vasta System_flush
-kutsulla, joten käytämme sitä jokaisen tulostuksen yhteydessä. Syy tähän käyttäytymiseen on se, että itseasiassa kirjoitus konsolille on laitteemme kannalta erittäin hidas operaatio, joten näin ohjelmoija voi valita sopivan paikan ohjelmasta aikaavievää viestien tulostusta varten!Viimeiseksi, funktiossa käynnistetään taskien suoritus kutsulla
BIOS_start
. Kun kaikki taskit on suoritettu, palataan main-funktioon ja suoritetaan se loppuun. Tietenkin nyt sulautettujen tapauksessa tällä arvolla ei ole juuri merkitystä, koska ohjelma pyörii aiemmin mainitussa ikuisessa silmukassa odottaen jotain herätessä johon reagoida. Tässä tulikin nyt lyhyesti kuvattua SensorTagin ohjelman ja sen main-funktion perusrakenne mille tahansa tälle laitteelle kirjoitettavalle C-kieliselle ohjelmalle.
Tehtävää riittää¶
Tyypillisesti siis sulatetut ohjelmat reagoivat syötteeseen esim. oheislaitteelta tuleva signaali, jolloin tehtävässä syötteen odotteluun kuluvaa aikaa voidaan jakaa muille tehtäville. Myös vaikkapa oheislaitteiden kanssa kommunikointi on hidasta verrattuna laskennan suorittamiseen CPU:ssa. Tällöin on edullista käyttää kommunikoinnin viiveaikaa jonkin toisen tehtävän suorittamiseen rinnakkain jakamalla CPU:n suoritusaikaa. Esimerkiksi, SensorTagin sovelluksen yksi tehtävä voi lukea anturidataa vaikka sekunnin välein, toinen tehtävä näyttää datan näytöllä, jos siinä tapahtuu merkittävä muutos, ja kolmas tehtävä lähettää datan langattomasti verkon yli palvelimelle. Näillä tehtävillä on ihan erilaiset suorituksen viiveajat, joten on edullista lomittaa niiden suoritusta.
Tällainen moniajo oikein toteutettuna nopeuttaa ohjelman suoritusta merkittävästi, kun tehtävän ei tarvitse odotella toisen valmistumista. Esimerkiksi, näytölle voidaan piirtää siinä välissä, kun odotellaan vastausta lähetettyyn viestiin. Suoritusajan jakaminen voi perustua esimerkiksi tehtäville asetettuihin prioriteetteihin tai niiden saamiin ulkoisiin signaaleihin (ts. keskeytys, muistetaan aiemmin esitelty tietokoneen toiminnan kuvaus!). Tällaisessa järjestelmässä RTOS suorittaa tehtäviä niiden tai keskeytysten prioriteetin mukaisessa järjestykessä. Toki nyt RTOS tarvitsisi mekanismejä varmistamaan sovelluksen toimintaa, eli esimerkiksi korkean prioriteetin tehtävä ei saisi tukahduttaa alemman prioriteein tehtäviä. No, näistä lisää tulevilla kursseilla.
Task-kirjasto¶
Nyt, TI-RTOS mahdollistaa kuvatun moniajoympäristön SensorTag-laitteella ja tarjoaa
Task
-kirjaston käyttöömme, jolla taskeja voidaan luoda funktioina ja ohjata niiden toimintaa. Esittelemme
Task
-kirjaston peruskäytön koodiesimerkkien avulla. // Taskin toteuttava funktio
// prototyyppi on aina tämä: Void taskin_funktio(UArg arg0, UArg arg1)
Void myTask(UArg arg0, UArg arg1) {
// Tässä taskissa ei paljoa tehdä..
System_printf("Mun argumentit ovat %ld ja %ld\n", arg0, arg1);
System_flush();
}
int main(void) {
// Tietorakenne-muuttujia, joihin taskien tiedot tallennetaan
Task_Params myParams;
Task_Handle myHandle;
// Laitteen alustus
Board_initGeneral();
// Alustetaan taskin suoritusparametrit
Task_Params_init(&myParams);
myParams.priority = 1;
// Nämä tässä vain esimerkin vuoksi
myParams.arg0 = 127; // Argumentti 1
myParams.arg1 = 0xFFFF; // Argumentti 2
// Luodaan taski ja RTOS:lle taskin käsittellyyn kahva
myHandle = Task_create((Task_FuncPtr)myTask, &myParams, NULL);
if (myHandle == NULL) {
System_abort("Task create failed");
}
// Ohjelma käynnistyy
BIOS_start();
return (0);
}
Palastellaanpa tämä esimerkki.
Ensiksi, taskia varten meidän tulee määritellä sen suoritusparametrit (rakenne
Task_Params
), joista meidän ohjelmaa kiinnostaa prioriteetti ja argumenttien välittäminen taskille. Parametrit tulee ensin alustaa (Task_Params_init
-kutsu), joka asettaa parametreille oletusarvot, jonka jälkeen voimme asettaa omat halutut arvot halutuille parametrille. Lisäksi asetamme taskille kahvan (eng. handle), jolla taskiin voidaan viitata ohjelmassamme. Kahva siis on vain tunnus, joka yksilöi taskin. Tässä esimerkissä kahvaa (Task_Handle
) käytetään vain tarkistamaan, että taskin luonti onnistui. (Huomatkaa System_abort
-funktio, jolla ohjelman suoritus voidaan ohjelmallisesti keskeyttää virhetilanteeseen ja tulostaa haluttu virheviesti konsoli-ikkunaan.)Taskin prioriteetti, jäsenessä
Huomataan, että ohjelman jossa on vain yksi taski, suoritus ei nopeudu prioriteettiä nostamalla.
priority
, määritellään suhteessa toisiin taskeihin. Jos käytössä on useita taskeja, voimme prioriteeteillä antaa enemmän suoritusaikaa raskaammille taskeille. Meidän kurssilla prioriteettitasoja ei tarvitse säätää, taso 1 käy kaikkeen. Vain jos teette monimutkaisempia ohjelmia, joissa on paljon I/O-liikennettä eri komponenttien välillä tms, niin useampia tasoja (1 ja 2) voi tarvittaessa käyttää. Huomataan, että ohjelman jossa on vain yksi taski, suoritus ei nopeudu prioriteettiä nostamalla.
Argumentteina taskille voidaan välittää kaksi unsigned int-tyyppistä (
typedef unsigned in UArg
) arvoa rakenteen jäsenissä arg0
ja arg1
. Tässä on RTOS siis käyttänyt typedef:iä omien muuttujatyyppien luomiseen. Noh, käytämme niitä kiltisti kuten RTOS haluaa, tosin argumenttien välittämistä taskeille ei välttämättä kurssilla tarvita, vaan käytämme esimerkiksi globaaleja muuttujia. Taski luodaan
Task_create
-funktiolla, jolle annetaan parametreiksi taskin suoritusfunktio (tyyppiä Task_FuncPtr
) sekä parametrirakenne (Task_Params). Taskin parametrit voidaan myös jättää määrittelemättä, jolloin argumentiksi annetaan NULL ja taski käyttää oletusparametreja.Kurssin laboratorioharjoituksessa annetaan koodiesimerkit Taskien asetusparametreistä, joten niitä ei tarvitse itse lueskella RTOS:n manuaaleista. tms.
Sulautetun laitteen ikuinen elämä¶
Usein sulautetun laitteen toiminta perustuu syötteen odottamiseen käyttäjältä, jonka ohjelmisto pyörii tyhjäkäynnillä ("idlenä") kunnes syöte/heräte saadaan, ja sitten siihen reagoidaan ohjelmassa. Esimerkiksi syöte voi olla käynnistyskomento mikroaaltouunin ohjauspaneelista.
Tyypillinen ratkaisu tähän on luoda ikuinen toistorakenne ohjelman
main
-funktioon, joka pitää ohjelmamme käynnissä. Ohjelmassa voidaan sitten joko odotella herätettä saapuvaksi (keskeytys) tai pollata (kysytään tilaa, engl. poll) oheislaitteilta yksi kerrallaan. Siis niiltä oheislaitteilta, jotka eivät pysty lähettämään keskeytystä. Keskeytyksistä SensorTag-laitteessa opimme lisää tulevissa kappaleissa. Toistorakennetta main-funktiossa ei kuitenkaan RTOS:n tapauksessa ole hyvä käyttää ikuisen silmukan ajoon, koska main-funktiossa vain alustetaan laitteen toiminta. Joten siirretään toistorakenne taskiin! Tällöin jokin tehtävä on aina hengissä ja RTOS ja laite pyörii taustalla sitä kautta.
...
Void myTask(UArg arg0, UArg arg1) {
// Tämä taski ei lopu
while (1) {
if (onko_nappia_painettu()) {
tee_jotain();
}
Task_sleep( n / Clock_tickPeriod); // Vapautetaan MCU muille taskeille joksikin aikaa!
}
}
Nyt
myTask
käynnistetään main-funktiossa, mutta sen suoritus ei lopu ennenkuin laitteesta katkaistaan virrat johtuen aina totta olevasta while-toistorakenteesta. Lisäksi, olemme kohteliaita ja keskeytämme taskin suorituksen hetkeksi
Task_sleep
-kutsulla, joka vapauttaa mikrokontrollerin hetkeksi muihin hommiin. Tällä funktiolla siis jaetaan MCU:n prosessoriaikaa taskien kesken ja se pitäisi löytyä jokaisen taskin lopusta. RTOS sitten huolehtii taskimme seuraavasta suorituksesta ajallaan. RTOS tarjoaa muuttujan
Clock_tickPeriod
, joka kertoo meille kuinka monta mikrosekuntia yhteen tikitykseen (kellojaksoon, engl. tick) menee. Oletuksena tämä arvo on 10, eli yksi tikitys on 10 mikrosekuntia. Nyt siis kellojaksojen määrä saadaan kaavalla n / Clock_tickPeriod
, jossa n on haluttu aikaviive mikrosekunteina.(Tässä kellojakso on RTOS määrittelemä ohjelmallinen vakio, eikä vastaa 1:1 laitteen kellokomponentin tikitystä.)
Lopuksi¶
Näin olemme tutustuneet Task-kirjastoon ja ohjelmien suorittamisen perusteisiin RTOS:ssa. Opettelemme RTOS:n käyttöä kurssin laboratorioharjoituksessa.
Anna palautetta
Kommentteja materiaalista?