Tilakoneet¶
Osaamistavoitteet: Opiskelija ymmärtää miten tilakoneita voidaan käyttää sulautetun ohjelman suunnittelussa ja toteutuksessa.
Tilakoneita (engl. finite-state machine, FSM) käytetään yleisesti digitaalitekniikassa sekvenssilogiikan toteuttamiseen. Tämä lisäksi tilakoneita käytetään monipuolisesti tietojenkäsittelytieteessä mallintamisen työkaluna. Sulautetuissa järjestelmissä tilakone on oiva keino mallintaa ohjelmiston toimintaa jo suunnitteluvaiheessa sekä ohjata ja hallita tapahtumapohjaisen ohjelman toimintalogiikaa. Kurssilla tilakoneista on erityisesti hyötyä kun toteutamme tapahtumiin reagointia SensorTagin RTOS:n useiden eri taskien kesken.
Tässä materiaalissa emme mene tilakoneiden salaisuuksiin (formaleihin esityksiin ym.) sen syvemmälle, vaan esitämme miten ne on helppo ottaa osaksi ohjelmamme suunnittelua ja toteutusta. Ohjelmoinnin ammattilaisilla on käytössä ohjelmointiympäristöjä ja työkaluja, joilla esimerkiksi laatia tilakoneita osaksi ohjelmakoodia. Jotkut työkalut jopa automatisoivat itse ohjelmointia tuottamalla valmista koodia tilakonekuvauksesta.
Tilakone¶
Tilakone on järjestelmän tai prosessin malli, jonka toiminnallisuus kuvataan tiloina ja niiden muutoksina. Moore-tyyppisessä tilakoneessa seuraava tila määräytyy nykyisen tilan ja syötteiden perusteella. Ulostulo määräytyy kuitenkin pelkästään nykyisen tilan mukaan, ei siirtymien tai syötteiden. Tämäntyyppinen tilakone on deterministinen, eli jokaisessa tilassa ulostulo on kiinteä riippumatta siirtymistä. Tilasiirtymäkaavio kuvaa järjestelmän mahdolliset tilat, siirtymät niiden välillä sekä tapahtumat (syötteet), jotka laukaisevat siirtymän tilasta toiseen.
Kuvassa alla tilakoneessa on kaksi tilaa
Tila a
ja Tila b
. Nuolilla kuvattuihin tilasiirtymiin on liitetty myös niiden input
it. Kukin tila voi tuottaa yhden tai useampia output
teja.
Tämä yksinkertaistettu tilakoneen esitys riittää meille tällä kurssilla.
Esimerkki tilakoneesta¶
Alla sulautetun ohjelman toiminta kuvattuna tilakoneena. Tilakoneeseen on määritelty toiminnallisuudet, siirtymät ja globaali tilamuuttuja, jonka arvo (=tila) muuttuu tapahtumien seurauksena. Ohjelman toimintalogiikka perustuu tilamuuttujaan niin, että suoritus etenee tapahtumien tilasiirtymien kautta vaiheesta toiseen.
- Tilassa IDLE vain odotetaan tapahtumia ikuisessa silmukassa.
- Kerran sekunnissa saadaan tapahtuma (=ajastinkeskeytys), joka aiheuttaa tilasiirtymän tilasta IDLE tilaan READ_SENSOR.
- Tilasta READ_SENSOR, jossa luetaan uusi anturidata, siirtymä tilaan UPDATE, jossa päivitetään anturidata näkyville laitteen ruudulle.
- Tilasta UPDATE siirtymä takaisin odotustilaan IDLE.
- Ikuisessa silmukassa tarkistetaan onko laitteelle tullut viestejä (tapahtuma). Jos on, tilasiirtymä tilaan NEW_MSG.
- Tilassa käsitellään vastaanotettu viesti
handle_msg
ja lähetetään vastaussend_reply
. - Jonka jälkeen odotuspalataan tilaan IDLE.

Jos haluat korostaa käytöstä syötteen sijaan, joka pakottaa tilamuutoksen, voit käyttää toisenlaista lähestymistapaa. Tässä tapauksessa nuolet osoittavat tilat, ja laatikoissa on kuvattu kussakin tilassa suoritettavat funktiot.

Toteutus C-kielellä¶
Tilakoneen ohjelmoinnissa on neljä periaatetta:
- Tilan toiminnallisuus funktioihin.
- Tilamuutokset sijoitusoperaatioilla tilamuuttujaan. Tästä syystä tilamuuttuja on globaali muuttuja, jotta sen arvoa voidaan käsitellä kaikkialla ohjelmassa!
- Ohjelman ikuisessa silmukassa tarkistetaan tilamuuttujan arvo ja suoritetaan sen mukainen tilasiirtymä.
- Ohjelmassa voi olla useita sisäkkäisiä tilakoneita. Esimerkiksi RTOS:n taskin suorituksessa voi olla oma tilakone, samalla kun koko järjestelmää pyörittää toinen tilakone.
Näiden periaatteiden pohjalta, katsotaanpa eräs tapahtumapohjainen toteutus yo. tilakoneelle SensorTagin taski-ajatteluun pohjautuen.
// Hox! Ao. esimerkki ei toimi suoraan koska IDLE on varattu sana..
// Käytä siis tälle tilalle jotain muuta nimeä.
// Tilaesittelyt
enum state { IDLE=1, READ_SENSOR, UPDATE, NEW_MSG };
// Globaali tilamuuttuja, alustetaan odotustilaan
enum state myState = IDLE;
// Ajastinkeskeytys kerran sekunnissa
Void clkFxn(UArg arg0) {
// Muutetaan tilaa halutuksi
// Hox! If-lauseella tarkistetaan, että tilasiirto on mahdollinen!!
// Nyt sallitaan vain tilasiirtymä IDLE -> READ_SENSOR
if (myState == IDLE) {
// Tilasiirtymä IDLE -> READ_SENSOR
myState = READ_SENSOR;
}
}
// Tiedonsiirtotaski
Void commTask(UArg arg0, UArg arg1) {
while (1) {
// Funktiolla is_message_waiting tarkistetaan
// onko puskurissa viestejä
// Lisäksi sallitaan vain tilasiirtymä IDLE -> NEW_MSG
if (is_message_waiting() == TRUE && myState == IDLE) {
// Tilasiirtymä IDLE -> NEW_MSG
myState = NEW_MSG;
// Tilan toiminnallisuus
handle_message();
send_reply();
// Tilasiirtymä NEW_MSG -> IDLE
myState = IDLE;
}
}
}
// Anturien käsittely
Void sensorTask(UArg arg0, UArg arg1) {
while (1) {
if (myState == READ_SENSOR) {
// Tilan toiminnallisuus
read_sensor_values();
// Tilasiirtymä READ_SENSOR -> UPDATE
myState = UPDATE;
}
Task_sleep(..);
}
}
// Anturien käsittely
Void displayTask(UArg arg0, UArg arg1) {
while (1) {
if (myState == UPDATE) {
// Tilan toiminnallisuus
update_screen();
// Tilasiirtymä UPDATE -> IDLE
myState = IDLE;
}
Task_sleep(..);
}
}
Hox! Ylläoleva tilakonetoteutus ei toimi (ihan tarkoituksella) ilman muokkausta kurssin SensorTag-laitteessa!!
Ohjelmassa on asetettu ajastinkeskeytys kerran sekunnissa, jonka käsittelijä on
clkFxn
, joka muuttaa tilan READ_SENSOR-tilaan. Näin tilakoneen avulla luemme sensorin arvon kerran sekunnissa. Samoiten commTask
-tehtävässä muutamme tilan NEW_MSG, jos puskurissa on odottamassa viestejä. Huomatkaa oleellinen asia. Nyt
if
-lauseella tarkistamme onko tilamuutos luvallinen. Ilman tarkastuksia saattaisi koodissa tulla tilasiirtymiä, jotka eivät ole tarkoituksenmukaisia ja sotkevat ohjelman toiminnan. Yleisesti tilasiirymän luvallisuuden tarkistus on oleellinen osa niiden käyttöä ohjelmissa!!Tilakonetta suunnitellessa tilat kannattaa järjestää esim. kasvavassa numerojärjestyksessä niin, että tilasiirtymien tarkistus voidaan tehdä myös vertailu-operaattorilla (pienempi kuin, jne) suorivaaivaisesti. Tietysti tässä kannattaa välttää monimutkaisia
if-else
-rakenteita! Toinen oleellinen asia koodin rakenteen parantamiseksi on tilojen määrän minimointi. Mutta siihen emme mene tarkemmin tällä kurssilla, muuten kuin että mietimme onko jokin nyt varmasti tarpeen ohjelmassamme. Esimerkissä UPDATE-tila ei ole välttämätön, mutta se tarvitaan, koska teemme ruudun päivityksen omassa taskissaan. Näin tilamuuttujan avulla hallitsemme ohjelman toimintalogiikkaa eri taskien välillä!Toinen käyttötapaus tilakoneelle sulautetuissa ohjelmissa on käyttöliitttymän toimintalogiikan toteuttaminen. Tilakoneen avulla voimme määritellä (rajatulle resurssille) kuten SensorTagin kahdelle painonapille ohjelman tilasta riippuvaa erilaista toiminnallisuutta. Esimerkiksi MENU-tilassa yksi nappi on omistettu virtanapiksi, mutta PELI-tilassa sitä voidaan käyttää johonkin muuhun. Kätevää!
Muut FSM-tyypit: Mealy-tilakone¶
Moore-tilakoneessa ulostulo riippuu ainoastaan tilasta. On kuitenkin olemassa muita FSM-tyyppejä erilaisilla lähestymistavoilla. Mealy-tyyppisessä tilakoneessa tilasiirtymä määräytyy sekä nykyisen tilan että syötteiden perusteella. Tilasiirtymien aikana voidaan myös suorittaa toimintoja, jotka tuottavat ulostulon. Tämä tilakone on myös deterministinen, mutta tässä tapauksessa yhdestä tilasta ja yhdestä syötteestä voi tapahtua vain yksi tilasiirtymä. Alla oleva kuva esittää Mealy-tilakonetta. Yleisesti ottaen Mealy-koneen toteuttaminen tuottaa nopeamman vasteen syötteille, mikä mahdollistaa joustavamman toiminnan tietyissä siirtymissä. Tämä voi kuitenkin tehdä järjestelmien käsittelyn RTOS:ssa hieman monimutkaisemmaksi. Seuraava kaavio esittää Mealy-tilakoneen. Huomaa, että ulostulo sisältyy tilasiirtymään.

Tässä tapauksessa toteutus olisi hieman erilainen. Huomaa, että tässä tapauksessa tila ei ole sidottu tehtävään, vaan siirtymä kohdetilaan on. Huomaa myös, että tilan muutos tapahtuu, kun ulostulo on annettu.
// Tilaesittelyt
enum state { IDLE=1, READ_SENSOR, UPDATE, NEW_MSG };
// Globaali tilamuuttuja, alustetaan odotustilaan
enum state myState = IDLE;
bool sensor_ready = FALSE; // Lippu sensorin lukemisen tarkistamiseksi
// Ajastinkeskeytys kerran sekunnissa
Void clkFxn(UArg arg0) {
// Ajastin käynnistyy kerran sekunnissa, asetetaan sensor_ready-lippu
sensor_ready = TRUE; // Ilmaisee, että sensori tulisi lukea
}
// Tiedonsiirtotaski
Void commTask(UArg arg0, UArg arg1) {
while (1) {
if (is_message_waiting() == TRUE && myState == IDLE) {
// Toiminto siirtymän aikana: viestin käsittely ja vastaaminen
handle_message();
send_reply();
// Siirtymä IDLE -> NEW_MSG
myState = NEW_MSG;
}
}
}
Void idleTask(UArg arg0, UArg arg1){
if (myState == NEW_MSG || myState == UPDATE){
myState = IDLE;
}
}
// Anturien käsittelytaski
Void sensorTask(UArg arg0, UArg arg1) {
while (1) {
if (sensor_ready && myState == IDLE) {
// Nollataan sensor_ready-lippu
sensor_ready = FALSE;
// Toiminto siirtymän aikana: anturien lukeminen
read_sensor_values();
// Siirtymä IDLE -> READ_SENSOR. sensor_ready-lippu merkitsee, kun ajastin käskee lukemaan anturin
myState = READ_SENSOR;
}
Task_sleep(..);
}
}
// Näytön käsittelytaski
Void displayTask(UArg arg0, UArg arg1) {
while (1) {
if (myState == READ_SENSOR) {
// Toiminto näytön päivittämiseksi tapahtuu tämän siirtymän aikana
update_screen();
// Siirtymä READ_SENSOR -> UPDATE
myState = UPDATE;
}
Task_sleep(..);
}
}
Tämän vuoden kurssiin UML-keskustelua ei sisällytetä.
Lopuksi¶
Tilakoneita tullaan käyttämään kurssin harjoitustyössä.
Anna palautetta
Kommentteja materiaalista?