3. Perusohjelman runko

Alunperin julkaistu: 11.2.2017

Viimeksi muokattu: sunnuntai 12.2.2017

Tarkastellaan yksinkertaista esimerkkiä MSP430:n C-ohjelmarungosta:

1  #include <msp430g2231.h>
2  void main(void)
3  {
4           WDTCTL = WDTPW + WDTHOLD; // Pysäytetään vahtikoira
5
6           while(1) // pääohjelman pääsilmukka
7           {
8
9           }
10 }

Vasemmassa reunassa on rivinumerot, joita tarvitaan kun selitän mitä yllä tapahtuu.

Rivillä 1 kääntäjälle kerrotaan #include komennolla, että halutaan ottaa käyttöön msp430g2231.h -tiedosto. Kyseessä on header- eli otsikkotiedosto. Kyseinen tiedosto sisältää kaikki tarvittavat käskyt ja rekisterit, joilla MSP430G2231 mikrokontrolleria ohjataan. Tietysti näiden header-tiedostojen taakse kätkeytyy muutakin, mutta muuta emme tässä vaiheessa tarvitse tietää. Mikäli haluat kurkata .h -tiedoston sisään, niin voit painaa näppäimistöltä CTRL-napin pohjaan ja klikata .h -tiedostoa. Tällöin tiedoston sisältö avautuu uuteen editorin ikkunaan. Voit myös asettaa kursorin .h-tiedoston päälle ja painaa F3 tai hiiren toisella napilla valita 'Open Declaration'.

Riviltä 2 alkaa varsinainen pääohjelma. Ohjelma käsittelee tietoja aina sen pääohjelmassa tai -funktiossa, joka on nimeltään aina main. void tarkoittaa mainin edessä tyhjiötä, eli pääohjelma ei palauta mitään arvoa. Sulkujen sisällä oleva void puolestaan kertoo, että pääohjelmalle ei välitetä mitään parametreja, eli muuttujia.

Riviltä 3 alkaa pääohjelman lohko, jota merkitään { -merkillä. Kaarisulkeet erottelevat ohjelman eri osia toisistaan, mutta niitä ei voi käyttää miten sattuu. Kaksi eri kontrollirakennetta erotellaan toisistaan kaarisulkeilla, joiden sisään koodi kirjoitetaan. Näistä rakenteista myös lisää myöhemmin.

Rivillä 4 asetetaan WDTCTL rekisterin arvoksi WDTPW + WDTHOLD, jotka pysäyttävät mikrokontrollerin watchdog kytkennän. Mikrokontrollerin lisälaitteiden toiminta perustuu kirjoittamalla arvoja mikrokontrollerin rekistereihin (tästä lisää heti osassa 4). Huomaa myös puolipiste lauseen lopussa!

Rivillä 4 on myös kaksi kenoviivaa "//" ja teksti "Pysäytetään vahtikoira". Tätä osaa sanotaan kommentiksi. Koodia voidaan kommentoida joko kenoviivoilla, tai tähdellä ja kenoviivalla, mistä on alla myöskin esimerkki:

// Voit kommentoida riviä näin
koodia;
/* Tai voit kirjoittaa kommentin tällaisen lohkon sisään rivejä ja merkkejä voi lisätä kunhan päättää kommenttilohkon alla olevaan tähteen ja kenoviivaan.
*/

Rivi 5 on kuten näyttääkin, eli tyhjä.

Rivillä 6 alkaa pääohjelman silmukka, eli 'luuppi', missä ohjelman suoritus kiertää "ympyrää". Tällaista rakennetta kutsutaan ikuiseksi silmukaksi.

Riveillä 7 ja 9 on pääohjelman silmukan kaarisulkeet, jotka erottavat silmukkalohkon pääohjelmalohkosta.

Rivi 8 on tyhjä ja rivillä 10 on pääohjelman lohkon päättävä kaarisulku.

Ohjelman kääntäminen ja virheilmoitukset

Kun ohjelma on kirjoitettu, voidaan se kääntää ja linkata. Tämä tapahtuu Code Composer Studiossa esimerkiksi siten, että painetaan pikanäppäintä CTRL + B tai valitaan ylävalikosta Project -> Build All. Jos olet tehnyt kaiken oikein, pitäisi CCS:n Console-ikkunassa (alhaalla) lukea Build complete for project nnn, missä nnn on tietenkin projektisi nimi.

Virheitä kuitenkin sattuu ja kun niin käy, ei kannata hätääntyä. CCS:n Console-ikkunaan ilmestyy punaista tekstiä kun virhe on havaittu. Esimerkiksi puolipisteen unohtaminen yllä olevasta esimerkkikoodista rivillä 4 antaa seuraavan virheen:

"../main.c", line 6: error: expected a ";"

Tämä virhe on selvä ja kääntäjä kertookin suoraan, että se odottaa puolipistettä ennen riviä 6, lähdetiedostossa main.c. Tämä virheilmoitus ei suoraan ilmoita millä rivillä virhe on vaan se kertoo mille riville tullessa kääntäjä huomasi virheen. Joskus on hyvä etsiä virhettä siis hieman ylempää koodista.

Hieman kinkkisempi virhe on silloin, jos ylläolevasta esimerkkikoodista jättää vaikkapa riviltä 7 kaarisulkeen { pois. Tällöin kääntäjän virheilmoitus on:

"../main.c", line 9: error: expected a statement

Tämä virhe ei puolestaan suoraan kerro mitä on tapahtunut ja missä, mutta se kertoo sen, että rivillä 9 oleva suljettu kaarisulku odottaa jotakin lausetta eli merkkiä. Tästä voidaan nopeasti huomata, että ohjelmassa on liian vähän kaarisulkeita.

Jos taas ohjelman silmukan ehto puuttuu riviltä 6, eli while() - lauseesta jää numero uupumaan, tulee virhe:

"../main.c", line 6: error: expected an expression

Tämä taas kerto suoraan sen, että ohjelma odottaa jotain ilmaisua/lausetta/toteamusta rivillä 6 (ei oikein hyvää suomennosta löydy ko. sanalle). Ja kun riviä tarkastelee, voi pian huomata että rakenteesta puuttuu ehto.

Kääntimen virheilmoituksia ei siis pidä aliarvioida. Console-ikkunassa on usein hyödyllistä tietoa, joka auttaa virheiden jäljityksessä. Tosin loogisten virheiden jäljitykseen se ei auta, mutta niistä lisää myöhemmin.

Esimerkkiohjelma 2 - Muuttujat muistissa ja debuggerin käyttö

Edellä oli siis perusohjelman runko selityksineen. Seuraavaksi luodaan ohjelmaan pari muuttujaa ja tutustutaan debuggeriin ja sen käyttöön, joten tuumasta toimeen.

Kirjoitetaan edelliseen esimerkkiohjelmaan heti pääohjelman kaarisulkeen jälkeen uudelle riville parit muuttujat ja alustetaan ne arvoihin 500 ja 10. Tämän jälkeen kirjoitetaan pääsilmukan sisään koodi, joka kasvattaa lukujen arvoa aina yhdellä. Koodi alla (rivinumerot on jätetty käytännön syistä pois).

#include <msp430g2231.h>
void main(void)
{
    WDTCTL = WDTPW + WDTHOLD; // Pysäytetään vahtikoira
    while(1) 
    // pääohjelman pääsilmukka
    {
       kokonaisluku_muuttuja = kokonaisluku_muuttuja + 1;
       kirjain_muuttuja = kirjain_muuttuja + 1;
    }
}

Ennen käännöstä säädetään vielä debug-asetuksia niin, ettei meillä ole käytössä minkäänlaista optimointia (ilman tätä vaihetta edellä oleva ohjelma ei toimi). Tämä tapahtuu valikosta Project -> Properties. Avautuvasta ikkunasta valitaan vasemmalta ensiksi C/C++ BuildConfiguration Settings laatikosta Tool Settings -välilehti ja MSP430 Compiler -otsikon alta Basic Options. Tämän jälkeen Optimization level alasvetolaatikosta valitaan tyhjä asetus (0 oletuksena). Lopuksi painetaan OK.

Kun koodi on kirjoitettu, käännetään se (Project -> Build All tai CTRL+B). Console-ikkunaan pitäisi ilmestyä teksti sujuneesta käännöksestä ja linkkauksesta.

Debuggerin ikkunat

Seuraavaksi kiinnitetään Launchpad tietokoneeseen jolloin vihreän LEDin pitäisi syttyä Launchpadissa osoittaen, että virrat ovat päällä. Ohjelma latautuu mikrokontrollerille, kun painetaan debug pikanäppäintä CCS:n komentorivin valikosta (vihreä koppakuoriainen) tai valitaan Target -> Debug Active project. Nyt Debug perspektiivi pitäisi näkyä ja voimme tarkastella tekemäämme ohjelmaa. Ikkunan pitäisi suurinpiirtein olla alla olevan kuvan mukainen (koko ruutua ei aivan ole kaapattu). Numeroita ei tietenkään näy, sillä lisäsin ne itse asioita selventämään.

Ensimmäisessä ruudussa, siis numerolla 1, nähdään tekemämme ohjelman lähdekoodi. Sininen nuoli osoittaa sen rivin, millä ohjelman suoritus on tällä hetkellä.

Toisessa ruudussa numerolla 2, nähdään ohjelman kokonaismuistin (pinon/stack) käyttö, tähän ei kuitenkaan nyt kiinnitetä enempää huomiota.

Kolmannessa ruudussa numerolla 3, nähdään erilaisia välilehtiä, jotka ovat hyödyllisiä ohjelman toiminnan tutkimisessa. Tästä lisää hetken päästä.

Muuttujien tarkastelu debuggerilla

Lisätään uusi ikkuna valikosta View -> Memory, jolloin oikeaan reunaan avautuu muisti-ikkuna eli Memory, mistä näemme mikrokontrollerin flash- ja RAM-muistin sisällön.

Seuraavaksi valitaan ruudusta 3 välilehti Watch ja kirjoitetaan Name-sarakkeeseen muuttujiemme nimet. Tällä tavalla näemme muuttujien sisällön ja muistipaikan missä ne sijaitsevat. Jos nimessä on virhe, Value kentässä lukee "identifier not found: <nimi>". Muuttujien näyttämä formaatti voidaan muuttaa hiiren toisella napilla avautuvasta valikosta Format. Valitaan tästä valikosta desimaaliformaatti, jolloin näemme lukujen arvot tavallisina lukuina. Voit myös huomata, että muuttujien muistipaikat (0x278 ja 0x27A) näkyvät kentässä Address ja Type kentässä on alustamamme muuttujan tyyppi.

Kun muuttujat on lisätty Watch -ikkunaan, kirjoitetaan edellä mainitun kokonaisluku_muuttujan osoite Memory -ikkunan tekstikenttään, osoitteen voit katsoa Watch -ikkunasta (edellisessä kappaleessa ohje, huom!). Tässä tapauksessa kyseinen muuttuja sijaitsee osoitteessa 0x0278, joten kirjoitetaan se Memory -ikkunaan. Arvo näkyy oletuksena heksalukuna, mutta sen näyttötapa voidaan muuttaa sellaiseksi kuin haluamme Memory -ikkunan alasvetovalikosta. Valitaan siis 16 Bit Unsigned Integer, jolloin näemme, että kokonaisluku_muuttujan arvo todellakin on 500 (kuva alla).

Tässä myös huomaat, että tietokone tulkitsee edellä tarkastetun kokonaisluku_muuttujan arvon nyt täysin erilaiseksi. Tämän vuoksi on tärkeää, että käytetään oikeita tyyppejä muuttujissa. Jos vaihdat asetuksen takaisin kohtaan 16 Bit Unsigned Integer, huomaat että luku 500 ilmestyy jälleen, mutta luvun 10 tilalle tulee taas jotain muuta. Oikeasti siis arvot eivät muutu muistissa, ainoastaan tulkintatapa muuttui, siis se miten debuggeri ja tietokone meille arvot näyttää.

Ohjelman suorittaminen debuggerin avulla

Nyt kun voimme seurata muuttujien elämää mikrokontrollerin muistissa, voimme alkaa suorittamaan ohjelmaa. Jotta näkisimme mitä ohjelmassa tapahtuu suorituksen aikana, täytyy ohjelmaa suorittaa debuggerin avulla rivi riviltä. Selitetään aluksi muutama painike debuggerin toimintoihin liittyen. Alla olevasta kuvasta löytyvät numeroituna tärkeimmät debuggerin käytössä olevat painikkeet.

Painikkeella 1 ohjelman suoritus käynnistetään (Run). Mikäli ohjelmaan ei ole asetettu breakpointteja, ei debuggeri pysähdy koodissa millekään riville, eikä ohjelman suoritusta voi tietokoneen ruudulta seurata. Ohjelma tosin pyörii mikrokontrollerilla normaalisti. Tällä painikkeellä ohjelman suoritusta voidaan myös jatkaa, kun ohjelma on pysäytettynä breakpointtiin.

Painike 2 pysäyttää debuggerin toiminnan ja palaa editointiruutuun.

Painikkeella 3 ja 4 voidaan suorittaa ohjelmaa rivi riviltä. Näiden painikkeiden erona on tosin se, että painiketta 3 (Step Into, pikanäppäin F5) painamalla ohjelman suoritus siirtyy aina seuraavaan suoritettavaan kohtaan. Painikkeella 4 voidaan ns. "hyppiä" funktiokutsujen yli (Step Over, pikanäppäin F6), jolloin esimerkiksi pitkät rutiinit voidaan hypätä ylitse ilman rivi riviltä läpi käymistä.

Painike 5 resetoi mikrokontrollerin CPU:n. Painikkeella 6 ohjelman suoritus aloitetaan aivan alusta. Molemmat painikkeet aiheuttavat ohjelman suorituksen hyppäämisen main lohkon alkuun, mutta muuttujien alustus saattaa vaihdella tilanteen mukaan.

Koodin steppaus

Takaisin koodiimme. Painetaan debuggerin painiketta Step Into (yllä olevassa kuvassa painike 3). Huomaat kuinka vihertävä rivi ja sininen nuoli hyppäävät seuraavalle suoritettavalle riville. Kun painetaan uudestaan F5 (jatkossa käytän vain pikanäppäimiä), huomataan että suoritus siirtyy kokonaisluku_muuttujan alustus komentoon. Kun nyt tarkkailet Watch- ja Memory -ikkunoista muuttujien arvoja, voit huomata kuinka ne muistiin alustuvat.

Rivillä 6 kirjoitetaan vahtikoiran pysäyttämiseksi WDTCTL-rekisteriin kaksi arvoa WDTPW ja WDTHOLD.

Myös rekisterin arvot voidaan nähdä debuggerin ikkunasta. Oletuksena rekisteri-ikkuna ei näy, joten se pitää asettaa näkyville valikosta View -> Registers. Kun tarkastelet rekisteriä Watchdog_Timer, huomaat että komennon suorittamisen jälkeen WDTHOLD arvo on 1.

Kun ohjelma hyppää silmukan while(1) sisälle, muuttujien arvoa kasvatetaan jokaisella suorituskerralla. Muuttujien kasvamisen voit todeta Watch -ikkunaa tarkastelemalla tai tutkimalla Memory -ikkunasta RAM muistipaikkojen arvoja samalla kun painelet F5 tai F6 näppäintä.

Breakpointtien käyttö

Ohjelmaa voidaan testata myös siten, että asetetaan sille johonkin kohtaa ohjelman suoritusta ns. "pysäytyspiste" eli breakpoint (suomennos ei virallinen). Edellä olevaan esimerkkikoodiin asetetaan breakpoint siten, että tuplaklikataan debugger tilassa ollessa koodin vasemmasta reunasta rivinumeroiden viereltä (kuva alla). Kyseiselle riville pitäisi ilmestyä sininen pallukka. Breakpointin saa pois sitä tuplaklikkaamalla.

Kun breakpoint on lisätty, voidaan ohjelman suoritus käynnistää Run napista (vihreä nuoli). Ohjelman suoritus pysähtyy (taukoaa) breakpointin sisältävälle riville, jolloin käyttäjän on joko painettava F5/F6 tai Run, että ohjelman suoritus jälleen alkaa.

Breakpointteja voidaan virheenjäljityksessä hyödyntää esimerkiksi siten, että asetetaan breakpoint suoritettavan ohjelmalohkon sisään, esimerkiksi ehtolauseeseen. Näin silloin, jos halutaan tarkastella kyseisen lohkon toimintaa tai jos epäilee vian löytyvän kyseisestä paikasta. Tietenkin breakpointteja voidaan käyttää missä kohtaa ohjelman suoritusta vain, mutta tämä olikin vain esimerkki.