Osaamistavoitteet: Sulautetun ohjelmoinnin yleisiä periaatteita. Sulautetun ohjelman toteutus SensorTag-laitteessa.
Sulautettu reaaliaikakäyttöjärjestelmä¶
Aloitamme sulautettujen laitteiden ohjelmointiin perehtymisen käymällä ensin läpi sulautetun ohjelman rakenteen ja lisäämällä sitten toiminnallisuuksia opettelemalla käyttämään eri oheislaitekirjastoja. Kurssilla käytämme SensorTag-laitetta, mutta tarjoamme lisämateriaalina myös tietoa Raspberry Pi Pico -laitteesta.
Reaaliaikakäyttöjärjestelmä¶
Sensortagin valmistaja (Texas Instruments) tarjoaa laitteen ohjelmointia helpottavan reaaliaikakäyttöjärjestelmän (engl. Real-time operating system, RTOS), jonka kirjastoja ja palveluita käytämme siis harjoitteluun ja lopuksi harjoitustyön toteuttamiseen. Tässä materiaalissa esitetään laitteelle ohjelmointiesimerkkejä, mutta parhaiten asioita oppii tietysti itse kokeilemalla ja tekemällä. Mutta aluksi muutamia aiheeseen liittyviä käsitteitä.
Käyttöjärjestelmä on tietokoneen sovelluksia pyörittävä ohjelmisto, jolla on kaksi pääasiallista tehtävää. Se tarjoaa suoritusympäristön sovelluksille ja hallinnoi tietokoneen laiteresursseja, oheislaitteita sekä käyttöoikeuksia. Käyttöjärjestelmä tarjoaa palveluita rajapintojen ja kirjastojen kautta sovelluksille näiden resussien käyttöön, mm. muistihallintaan, tiedostojärjestelmän, oheislaitteiden käyttöön ja tiedonsiirtoon järjestelmästä ulospäin. Käyttöjärjestelmä myös kontrolloi sovellusten suoritusta, jakaen prosessori(e)n suoritusaikaa ja muistia sovellusten kesken. Tarkemmin näitä asioita käsitellään myöhemmällä Käyttöjärjestelmät-kurssilla, joten ohitamme tässä kattavan esityksen.
Reaaliaikakäyttöjärjestelmä 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 johonkin syötteeseen/tapahtumaan reagoidaan varmasti yhden millisekunnin sisällä ja reagointi saa kestää max 10 millisekuntia. RTOS itsessään voi olla hyvin kevyesti toteutettu, se voi esimerkiksi pelkästään jakaa prosessoriaikaa sovellusten kesken. Kevyt toteutus tukee myös osaltaan sovelluksia, joissa on tiukat reaaliaikavaatimukset, koska käyttöjärjestelmän omien rutiinien suoritukseen ei kulu juurikaan aikaa. RTOS:n palveluiden oma toteutus vaikuttaa siis merkittävästi sulautetun ohjelman suunnitteluun, toteutukseen ja ylläpitoon.
Kuten aiemmin esitettiin, sulautettuja järjestelmiä on toki mahdollista ohjelmoida ilman käyttöjärjestelmää ja/tai laiteohjelmistoa, mutta tällöin ohjelmoija on vastuussa laitteen vastaavien toiminnallisuuksien, kun oheiskomponenttien kytkemisestä mikrokontrolleriin ohjelmallisesti sekä sovelluksen tarvitsemien rajapintojen/palveluiden toteuttamisesta.
Sulautetun ohjelman toteutus¶
Kuten aiemmassa C-kielen materiaalissa todettiin, tietokoneohjelmien toteuttamisessa modulaarisesti on hyvinkin paljon järkeä ja myös järkeä. Sulautettujen
maailmassa myös ohjelman kokonaisuus on järkevää jakaa modulaarisesti eri tehtäviin, joiden toiminnallisuus voidaan loogisesti kapseloida, eli erottaa toisistaan. Nyt ohjelmassa jokaiselle tehtävälle määritellään omat syötteet mihin se reagoi ja omat vasteet.
maailmassa myös ohjelman kokonaisuus on järkevää jakaa modulaarisesti eri tehtäviin, joiden toiminnallisuus voidaan loogisesti kapseloida, eli erottaa toisistaan. Nyt ohjelmassa jokaiselle tehtävälle määritellään omat syötteet mihin se reagoi ja omat vasteet.
Esimerkiksi, kurssilla SensorTag-laitteessa tehtäviä voisi olla sensoridatan lukeminen, näytön päivitys tai kommunikoida toisen laitteen kanssa. Nojoo, nämä eivät ole vain esimerkkejä vain juuri niitä tullaan harjoitustyössä toteuttamaan.
Nyt on tietysti järkevää antaa RTOS huolehtia tehtävien rinnakkaisesta suorittamisesta, jotta vasteisiin voidaan vastata mahdollisimman tehokkaasti. Ohjelmoijan tehtäväksi jää sittensuoritettavan tehtävän toteutus ja sen suoritusparametrien määrittely tehokkuusvaatimukset huomioiden. Näitä suoritusparametrejä ovat esimerkiksi ajoitus, prioriteetti, muistin määrä, erilaiset alkuasetukset, jne. Tässä suhteessa sulautetun ohjelmoinnin opettelu on näiden valmiiden RTOS:n ominaisuuksien käytön opettelua kirjastojen, rajapintojen ja ohjelmistorakenteiden kautta.
Vaihtoehto olisi tietysti ohjelmoida RTOS tai laiteohjelmisto itse, tyyliin Arduinon superloop-rakenne (iso main-funktion sisäinen silmukka jossa on kaikki ohjelman toiminnallisuus). Kuten todettu, niin superloop on ok ratkaisu pienehköjen sulautettujen järjestelmien toteuttamiseksi, joilla ei ole kummoisia resurssi- eikä suoritusvaatimuksia. Mutta tässä menetetään ainakin iso osa modulaarisuuden hyötyjä ja ohjelmoija voi jopa vaikeuttaa ohjelman suoritusta. Ylläpidollisesti tällainen ratkaisu voi olla ns. painajainen, koska pienikin koodimuunnos saattaa muuttaa tarkasti hioittuja ajoituksia pieleen. Tuskin kukaan lähtisi itse toteuttamaan superlooppiin esimerkiksi eri tehtävien prioriteettien mukaista suoritusta, ratkaisu olisi väistämättä kömpelö. No, siellä työelämässä sulautettujen ohjelmointi onkin yleiseen työasemaohjelmointiin nähden oma taiteenlajinsa, kun päästään tekemään tehtäviä kovilla vaatimuksilla, oheislaiteajureita tai jopa omaa käyttöjärjestelmää.
Tehtävien toteutuksen ja suoritusparametrien määrittelyn lisäksi tehtävien toiminnan synkronointi edellyttää ratkaisuja. Esimerkiksi yksittäiseen syötteeseen, kuten napin painallukseen laitteessa, voi yksi tai reagoida useampi tehtävä tai se voi aiheuttaa sarjan peräkkäisiä toimintoja eri tehtävissä. Miten tällainen toteutus voidaan tehdä?
Itseasiassa, nyt aletaan mennä jo sinne Käyttöjärjestelmät-kurssin tontille, joten jätetään asiat sinne. Kuitenkin tähän on olemassa digitaalitekniikasta tuttu helpohko ratkaisu, jota tulemme käyttämään kurssilla, nimittäin tilakoneet. Kurssilla ei ole tarkoitus mennä syvemmälle tilakoneiden teoriaan (todella iso juttu), mutta käytämme niitä toteuttamaan tehtävien välistä synkronointia ohjelmassamme.
Lyhyesti tilakoneen idea on, että ohjelman looginen suoritus etenee tilasta toiseen syötteiden, tehtävien ja vasteiden kautta. Tätä varten ohjelmassa määritellään (ainakin yksi) tilamuuttuja, jonka arvoa muutetaan tapahtumaa ja tehtävää vastaavaksi. Tehtävät ja tapahtumiin reagoivat ohjelmistokomponentit (lue funktiot) saavat tiedon tilamuutoksesta ja toteuttavat sitä vastaan toiminnon. Sen jälkeen tila muuttuu seuraavaan tilaan ja siihen taas ohjelminstokomponentit reagoivat. Esimerkiksi tilamuutokset sulautetussa ohjelmassa voisivat aiheuttaa seuraavaa:
odotustila -> napin painallus -> luetaan sensoridata -> tulos näytölle/viestitys -> odotustila
Tässä esimerkissä ohjelmassa voisi olla kolme/neljä eri tehtävää: napin painallukseen reagoiva keskeytyksen käsittelijä, tehtävä sensorin kanssa kommunikointiin, tehtävä näytönpäivitykseen ja yhtäaikanen tehtävä langattomaan tiedonsiirtoon. Ehkä vielä tehtävä datan esikäsittelyyn, mutta tämä on suunnittelukysymys riippuen mm. siitä kuinka paljon dataa on. Nyt tämän loogisen toiminnallisuuden voisi tietenkin toteuttaa superloopiin perustuen, mutta tällöin kaikissa osatehtävissä pitäisi huomioida se missä tilassa ohjelman muut toiminnallisuudet ovat, esimerkiksi kommunikoidaanko juuri oheislaitteiden kanssa. Esimerkiksi yo. tilakoneessa emme voisi vastaanottaa viestejä samaan aikaan kun luemme sensoridataa.
Esimerkki: Tilakonetoteutus sulautetulle ohjelmalle Arduinolle. Tässä on myös mielenkiintoinen esimerkki. Kannattaa vilkaista näitä sivuja yleiskuvan saamiseksi, koska käytämme kurssilla samantyyppistä ratkaisua.
Esitellään yksinkertainen esimerkki Arduinolla:
enum State {
WAITING,
READ_SENSOR,
PROCESS_DATA,
DISPLAY_RESULT,
TRANSMIT_DATA
};
State currentState = WAITING;
void loop() {
switch (currentState) {
case WAITING:
if (buttonPressed()) {
currentState = READ_SENSOR;
}
break;
case READ_SENSOR:
readSensor();
currentState = PROCESS_DATA;
break;
case PROCESS_DATA:
processData();
currentState = DISPLAY_RESULT;
break;
case DISPLAY_RESULT:
displayResult();
currentState = TRANSMIT_DATA;
break;
case TRANSMIT_DATA:
transmitData();
currentState = WAITING;
break;
}
}
Lisää tästä aiheesta hieman myöhemmin.
TI-RTOS:n esittely¶
Kuvassa lohkokaavio TI-RTOS:n, firmiksen ja laiteajurien tarjoamista valmiista toiminnallisuuksista SensorTag-laitteelle. RTOS tarjoaa ohjelmoijalle rajapintoja (kuvassa punainen alue), jotka piilottavat alemman tason toteutuksen yksityiskohdat, kuten laite-ja komponenttiajurit, reaaliaika-ominaisuudet ja tiedonsiirron detaljit. Tällä kurssilla meitä 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 tarvitse mennä.

Mainitsemme joitakin näistä kirjastoista jo nyt, mutta niiden käyttöön ohjelmissa perehdymme yksityiskohtaisesti myöhemmin luentomateriaalissa.
Palvelut ja kirjastot¶
Seuraavia kirjastoja tulemme käyttämään ohjelmien toteuttamisessa. Ao. 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 yleisiä I/O-pinnejä.
- driverlib Laiterekisterit ja bittimaskit.
- I2C Kirjasto i2c-tiedonsiirtoväylän käyttämiseen. Moniin SensorTagin antureihin päästään käsiksi tällä kirjastolla.
- UART Sarjaliikenneprotokolla oheislaitteiden kanssa kommunikointiin. Esimerkiksi PC:lle voidaan lähettää dataa tai vastaanottaa 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.)
(Kiinnostuneille: Tarkempaa tietoa kirjastosta löytyy TI-RTOS Kernel User Guide:sta ja TI RTOS User's Guide:sta.)
Ohjelman toiminta¶
Otetaan vielä esimerkiksi SensorTagin C-ohjelman main-funktio. SensorTagin RTOS:n ja kirjastojen C-kieli voi alkuun vaikuttaa kryptiseltä, koska käytetään niin paljon valmiiksi määriteltyä tavaraa. Mutta, tässä auttaa koodiin perehtyminen ja miettiminen, että mitä siinä oikeastaan tapahtuu.
Myös SensorTagin ohjelmalla on yksi main-funktio, josta ohjelman suoritus lähtee. Ok, RTOS tekee ensin omiaan piilossa taustalla ennen main-funktion suoritusta, mutta emme murehdi siitä tällä kurssilla.
Alla esimerkki SensorTag-ohjelman
main
-funktiosta:// SensorTagin kirjastot mukaan
#include <xdc/runtime/System.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/drivers/I2C.h>
#include <ti/drivers/PIN.h>
int main(void) {
// Hox! Esimerkin alusta on jätetty ao. muuttujien esittely pois
Board_initGeneral(); // alustetaan laite
Board_initI2C(); // alustetaan i2c-väylä käyttöön
// I/O-määritys jolla otetaan painonappi käyttöön
buttonHandle = PIN_open(&buttonState, buttonConfig);
if(!buttonHandle) {
System_abort("Error initializing button\n");
}
// Asetetaan painonapille sen tapahtuman käsittelijä (keskeytys)
// eli tässä funktio buttonFxn
if (PIN_registerIntCb(buttonHandle, &buttonFxn) != 0) {
System_abort("Error registering button callback function");
}
// Alustetaan sovellukseen yksi tehtävä, totetus taskFn-funktiossa
Task_Params_init(&taskParams);
taskParams.stackSize = STACKSIZE;
taskParams.stack = &taskStack;
taskParams.priority=2;
task = Task_create((Task_FuncPtr)taskFxn, &taskParams, NULL);
if (task == NULL) {
System_abort("Task create failed!");
}
// Kerrotaan debuggerin kautta, että laitteen alustus onnistui
System_printf("Initialization OK!\n");
System_flush();
// Käynnistetään RTOS (laiteohjelmisto BIOS),
// jolloin tehtävä lähtee suoritukseen
BIOS_start();
return 0;
}
Kuten kaikissa C-ohjelmissa, ohjelman alussa esikääntäjäkäskyillä tuodaan kirjastot ohjelman käyttöön. SensorTagin RTOS:n kanssa niitä onkin koko joukko, eikä tässä vielä (läheskään) kaikki.
Sen jälkeen
Lisäksi, esimerkissä haluamme käyttää I2C-kirjastoa kommunikointiin anturien kanssa, joten se alustetaan funktiokutsulla
main
-funktion alussa alustetaan itse laite. Itseasiassa, mitä tässä tehdään niin kerrotaan RTOS:lle että se pyörii SensorTag-laitteessa. Muistetaan että TI:n RTOS on yleinen käyttöjärjestelmä TI:n kaikille sulautetuille laitealustoille, joten siksi tämä tarvitaan ottamaan laitteen resurssit, olemassaolevat oheislaitteet, I/O-määrittelyt, laitekohtaiset kytkennät, jne.Lisäksi, esimerkissä haluamme käyttää I2C-kirjastoa kommunikointiin anturien kanssa, joten se alustetaan funktiokutsulla
Board_initI2C
. Kun laite on alustettu, otetaan ohjelmaan mukaan valitut oheislaitteet, tekemällä niille alustukset ja erilaiset I/O-määrittely. Esimerkissä otetaan ohjelman käyttöön toinen laitteen painonapeista ja kerrotaan napinpainalluksesta syntyvän tapahtuman (keskeytys) käsittelijä, eli tässä funktio
buttonFxn
. Jokainen SensorTagin tehtävä on siis C-kielinen funktio ja niiden kirjoittamiseen ja käyttöön pätee (varauksin) samat säännöt kuin C-kielen funktioiden yleensä. Sitten olisi main-funktiossa 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ä debuggerin avulla 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. Muistetaanpas aiempi puhe siitä standardikirjastojen sulautetut toteutukset vaihtelee toteutettujen ominaisuuksien suhteen. Viimeiseksi, funktiossa käynnistetään "varsinainen" ohjelma eli taskien suoritus funktiokutsulla
BIOS_start
. Tämän jälkeen ohjelma vain ja ainoastaan reagoi tässä main-funktiossa määriteltyihin oheislaitteiden aiheuttamiin tapahtumiin. Huomatkaa, että koodissa käytetään paljon alustuksien jälkeisiä tarkistuksia varmistamaan, että alustukset menivät oikein. Nämä tarvitaan juuri siitä syystä, että sulautettuja ohjelmoidessa meillä on rajoitettu näkymä laitteen sisäiseen toimintaan..
SensorTag ohjelman toteutus¶
Tässä materiaalissa perehdytään siihen, miten SensorTag:lle laaditaan sulautettu ohjelma tehtäväpohjaisesti niin, että useita tehtäviä voidaan ajaa laitteen RTOS-käyttöjärjestelmällä rinnakkain.
Kuten aiemmassa materiaalissa vihjailtiin, TI-RTOS:n kantava ajatus on, että ohjelman toiminnallisuus toteutetaan erillisinä tehtävinä joita ajetaan laitteen suorituskyvyn mukaan rinnakkain. Ohjelmoijalla on muuten vapaat kädet määritellä miten ohjelman jaetaan tehtäviin ja mitä tehtävät sisäisesti tekevät. Yksinkertainen ohjelma voidaan toki toteuttaa yhdellä ainoalla taskilla, mutta heti monimutkaisempi ohjelma, joka käyttää vaikkapa useita eri oheislaitteita, ei helposti luonnistu yhdellä taskilla. Tällainen moniajo oikein toteutettuna yleisesti nopeuttaa ohjelman suoritusta merkittävästi, koska tehtävien ei tarvitse odotella toistensa valmistumista.
Tässä onkin ohjelmoijalla suunnittelun paikka. Ei tietenkään ole mielekästä jokaista pikkuasiaa varten luoda omaa taskia, koska se kuluttaa RTOS:lle varattuja resursseja. Taskin sisäiset toiminnot on nekin järkevää toteuttaa funktiona. Esimerkiksi, ympyrän pinta-alan laskemiseksi ei tarvita taskia, ihan vaan C-kielen funktio riittää, jota kutsumme taskista tarvittaessa.
Jotta taskien rinnakkainen suoritus onnistuisi yhdellä mikrokontrollerilla, on sen reaalista suoritusaikaa jaettava tehtävien (ja RTOS:n) kesken. Suoritusajan jakaminen voi perustua esimerkiksi aikaperusteiseen jakoon, prioriteettien mukaiseen jakoon tai syötteiden / erilaisten signaalien reaktiiviseen käsittelyyn kun ne saapuvat laitteelle. Suoritusajan jaon yhteydessä on myös huolehdittava, ettei suoritukseen päässyt tehtävä rohmua kaikkea suoritusaikaa, jottei laitteen muu toiminta pysähdy pitkäksi aikaa. Tällainen tilanne voisi tulla esimerkiksi kun korkean prioriteetin tehtävä tukahduttaa alemman prioriteetin tehtäviä viemällä niiltä kaiken suoritusajan. Toinen vastaava tilanne voisi tulla eteen kun, tehtävä kommunikoi (suhteellisen) hitaan oheislaitteen kanssa ja itsekkäästi pitää suoritinta odotusaikana hallussaan.
Homma TI-RTOSssä¶
Tarkastellaanpa esimerkkikuvan avulla, miten eri tehtävät ((engl. Task) saadaan toimimaan rinnakkain SensorTagissa. Esimerkissä on toteutettu ohjelmaan:
- Kaksi ohjelmaan liittyvää taskia (engl. Task) eli Taskit 1 ja 2.
- Yksi valmis taski: Viestintätaski, joka oletetaan kirjaston mukana ohjelmaan tulevaksi taskiksi.
- Yksi keskeytyksen käsittelijä, joka toteuttaa napinpainallusten käsittelyn ohjelmassa.
Kuvassa taskeilla on siis jokin suoritusaika ja prioriteetti. Lisäksi huomataan, että joidenkin taskien suorituksen loputtua ne pistetään nukkumaan
sleep
-funktiokutsulla. Huomataan, että ohjelmataskit pyörivät korkeammalla prioriteetillä (2) kuin kirjastotaski (1) ja että keskeytysten prioriteetti on kaikkein korkein ("ääretön"). 
Katsotaanpa seuraavaksi mitä ohjelmassa tapahtuu:
- Ohjelman suoritus lähtee liikkeelle Taskista 1, joka suoritetaan loppuun ja asetetaan nukkumaan.
- Sen jälkeen suoritin vapautuu ja RTOS antaa suorituksen Taskille 2, joka sekin pistetään nukkumaan.
- Tämän jälkeen RTOS:lla on taas valta päättää mitä laitteessa tapahtuu, ja koska ohjelmassa ei ole sillä hetkellä hereillä olevia prioriteetin 2 taskeja, suorittaa RTOS tiedossa prioriteetin 1 taskin (viestintätaski).
- Kun viestintätaskin suoritus loppuu, RTOS huomaa taas hereillä olevan prioriteetin 2 Taski 1:sen ja suorittaa sen.
- Taski 1sen suorituksen aikana mikrokontrollerille tulee keskeytyssignaali, ja koska keskeytyksen prioriteetti on ääretön, siirrytään sen käsittelijä suorittamaan kesken Taski 1sen suorituksen!
- Kun keskeytyksen käsittelijäfunktio on suoritettu, RTOS jatkaa Taski 1sen suorittamista siitä mihin se jäi.
- Ja näin ohjelman suoritus jatkuu kolmen taskin ja yhden keskeytyskäsittelijän välillä hamaan ikuisuuteen..
Hetkinen, mitä keskeytyksessä tapahtuu? Nyt joudutaan menemään hieman asioiden edelle, eli keskeytys on mikrokontrollerille tuleva hardispohjainen-signaali, kun taas muuten taskien ajo on ohjelmiston (RTOS) toimintaa. Nyt (tässä) prioriteetti ääretön tarkoitti sitä, ettei RTOS pysty sanomaan mitään keskeytysten suorittamisesta vaan niiden käsittely hanskataan mikrokontrollerissa. (Tämä ei ollut nyt täydellinen selvitys keskeytyksien käytöstä, mutta palaamme asiaan myöhemmässä materiaalissa.)
Ok, yksi juttu vielä... miksi viestintätaskin perässä ei ole
sleep
-kutsua? Selitys on se, että kyseisen SensorTagin langattoman radion kirjaston toiminta vaatii, että taski pysyy aktiivisena eikä mene nukkumatilaan. Tästä syystä viestintätaskin prioriteetti on 1, kun se ylemmillä taskeilla on korkeampi, prioriteetti 2 jotta ne saisivat suoritusaikaa. Lisäksi meidän tulee tietää vielä yksi asia taskin suorittamisesta. Tästä esityksestä sai kuvan, että jokainen taski ajetaan kerran alusta loppuun liipaisemalla se aina käyntiin, mutta näin ei asia kurssilla ole. Itseasiassa käytetään taskien sisäistä superlooppia eli
while
-silmukkaa, jonka sisällä sleep
-funktiokutsu tehdään! Näin meillä ohjelmassa pysyy kerran luodut taskit käynnissä ja ne myös pitävät ohjelman suorituksen päällä kokoajan. Tämä siis vaatii sen että superloopin jokaisen iteraation lopussa kutsutaan sleep
-kutsua. Taskissa siis käynnistetään ajastin, jonka aikaa se on nukkumassa ja tällöin muut taskit pääsevät väliin ja saavat suoritusaikaa. Sopiva nukkuma-aika on sitten sovelusriippuvainen ja ohjelmoijan määritettävissä.Ok ok, mutta mitä tapahtuisi jos taskien sisällä ei olisi superlooppia? Koska taski on C-kielen funktio, se vain suoritettaisiin kerran ja näin koko sulautetun ohjelman suoritus loppuu, kun kaikki sen taskit olisi kerran suoritettu. Superloop taskeissa on siis oleellinen ratkaisu sille, että ohjelma pysyy laitteessa hengissä.
RTOS:n Task-kirjasto¶
TI-RTOS mahdollistaa kuvatunlaisen ohjelman toteutuksen ja moniajoympäristön SensorTag-laitteelle tarjoamallaan
Task
-kirjastolla. Kirjaston avulla taskeja voidaan luoda funktioina ja RTOS:n kautta ohjata niiden toimintaa. Katsotaan koodiesimerkkiä
Task
-kirjaston toiminnasta. #include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
// Taski tarvitsee oman sisäisen pinomuistin
#define STACKSIZE 512
Char myTaskStack[STACKSIZE];
// Taskin toteutusfunktio
Void myTaskFxn(UArg arg0, UArg arg1) {
// Taskin ikuinen elämä
while (1) {
System_printf("Mun argumentit ovat %ld ja %ld\n", arg0, arg1);
System_flush();
// Kohteliaasti nukkumaan hetkeksi
Task_sleep(1000000L / Clock_tickPeriod);
}
}
int main(void) {
// Taskeihin liittyviä tietorakenteita
Task_Params myTaskParams;
Task_Handle myTaskHandle;
// Laitteen alustus
Board_initGeneral();
// Alustetaan taskin suoritusparametrit
Task_Params_init(&myTaskParams);
// Osoitetaan taskille sen pinomuisti
myTaskParams.stackSize = STACKSIZE;
myTaskParams.stack = &myTaskStack;
// Asetetaan taskin prioriteetti
myTaskParams.priority = 2;
// Argumentit taskille (vain esimerkin vuoksi)
myParams.arg0 = 127; // Argumentti 1
myParams.arg1 = 0xFFFF; // Argumentti 2
// Luodaan taski
myTaskHandle = Task_create((Task_FuncPtr)myTaskFxn, &myTaskParams, NULL);
if (myTaskHandle == NULL) {
System_abort("Task create failed");
}
// Terveisiä konsolille
System_printf("Hello world!\n");
System_flush();
// Ohjelma käynnistyy
BIOS_start();
return (0);
}
Palastellaanpa tämä esimerkki.
Taskin toteutusfunktiossa
myTaskFxn
meillä onkin äsken mainittu ikuinen silmukka ja sen lopussa kiltisti Task_sleep
-funktio, joka toteuttaa nukkumistoiminnon ja antaa vallan takaisin RTOS:lle jokaisen iteraation jälkeen. Sitten
main
-funktiossa uutena asiana taskin suoritusparametrien asetus tietorakenteeseen myTaskParams
joka on tyyppiä Task_Params
). Esimerkissä asetetaan taskille kolme asiaa, jotka on aina oltava erikseen jokaiselle taskille:- Kutsutaan
Task_params_init
-funktiota, joka asettaa kaikki parametrit oletusarvoon. - Asetetaan taskin pinomuisti (sovitaan tässä vaiheessa että pino on taskin oma sisäinen muistialue, josta meidän ei tarvitse välittää) jäseniin
.stack
ja.stackSize
. Jokaiselle taskille on oltava oma pinomuisti, eli samaa muistialuetta ei saa jakaa useille taskeille. - Pinomuistin koko on tässä 512 tavua, joka riittää useimmille taskeille. Poikkeukset on selitetty kurssimateriaalissa.
- Asetetaan taskin prioriteetti jäseneen
.priority
. - Ohjelman taskeille Yleensä prioriteetti 2
- Viestintätaskille prioriteetti 1
- Jos tuntuu, että prioriteetteihin pitäisi koskea harjoitustyössä, kysykää ensin asiaa opettajalta.
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. Argumentteina taskille voidaan välittää kaksi unsigned int-tyyppistä (
typedef unsigned int 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
) (tässä myTaskFxn
sekä parametrit myTask_Params
. Huomataan myös
System_abort
-funktio, jolla ohjelman suoritus voidaan ohjelmallisesti keskeyttää virhetilanteeseen ja tulostaa haluttu virheviesti konsoli-ikkunaan, tässä siis jos taski ei käynnisty, jos vaikka laitteen muisti on lopussa. Kello käy¶
RTOS tarjoaa
Clock.h
-kirjastossa muuttujan Clock_tickPeriod
, joka kertoo meille kuinka monta mikrosekuntia yhteen systeemin kellojaksoon (engl. tick) menee. Oletuksena arvo on 10, eli yksi tick on 10 mikrosekuntia. Nyt siis kellojaksojen määrä saadaan kaavalla n / Clock_tickPeriod
, jossa n on haluttu aika mikrosekunteina.Itseasiassa, hardisnikkareille tiedoksi, tässä RTOS:n kellojakso on ohjelmallinen vakio, eikä vastaa 1:1 laitteen kellokomponenttia.
Lopuksi¶
Näin olemme tutustuneet ohjelmien suorittamisen perusteisiin RTOS:ssa. Opettelemme RTOS:n kirjastojen käyttöä syvemmin kurssin laboratorioharjoituksessa.
Esimerkkikoodi yllä saattaa vaikuttaa kryptiseltä, aiemman selkeän C-kielen jälkeen, mutta kuten perehtymällä nähdään niin SensorTag-koodissa käytetään paljon valmiita kirjastoja, niiden funktiokutsuja, joilla on oma nimeämiskäytäntönsä, valmiita tietorakenteita, osoittimia ja vakioita. Lähemmin tarkastellen kuitenkin kyse on ihan samasta C-kielestä.
Kurssin laboratorioharjoituksessa annetaan opiskelijoille ohjelma-aihio (engl. template), joka sisältää tarvittavia taskien toteutuksia ja parametrien määrityksiä valmiina, joten niitä ei tarvitse itse lueskella materiaalin ulkopuolelta (vapaaehtoista lisämateriaalia tarjotaan kyllä luettavaksi).
Ilmainen vinkki 1: Jos laite menee ohjelmassa tukkoon, syynä on useimmiten liian pitkäkestoinen taski tai unohtunut
Task_sleep
-kutsu toteutusfunktiossa. Tällainen taski taas on syytä suunnitella uusiksi, jakaa se pienempiin osiin. Ilmainen vinkki 2: Toteutusfunktion sisälle ei kannata tehdä useita peräkkäisiä sleep-kutsuja, koska tämä ratkaisu menee usein pieleen silloin kun lisäätte ohjelmaan toiminnallisuuta ja nämä aiemmin säädetyt ajoitukset menevät pieleen. Toteutusfunktioon aina vain yksi
sleep
-kutsu ja toiminnalisuudet ehtorakenteen ja tilakoneen taakse (kts. tuleva materiaali). Ilmainen vinkki 3: Joskus näkee ratkaisuja, joissa on viritetty useita eri prioriteettitasoja. Tästä on todennäköisesti seurauksena laitteen toiminnan sekoaminen, jos viritettyyn ohjelmaan lisätäänkin jälkeenpäin toiminnallisuutta. Mieluummin pysytään prioriteettitasolla 2 ja mietitiin taskien toiminta uusiksi. Assistenteilta voi aina kysyä.
Ilmainen vinkki 4: SensorTagin ohjelmaa ei todella kannata toteuttaa Arduino-tavalla yhdellä taskilla ja superloopilla, teette itsellenne ison karhunpalveluksen kun ohjelman logiikassa toiminnallisuuksien välillä ja ajoituksissa mitä todennäköisimmin ilmenee ongelmia.
Anna palautetta
Kommentteja materiaalista?