2. Muuttujat ja tietotyypit

Koodi kirjoitetaan lohkoihin

C-kielessä ohjelman lähdekoodi kirjoitetaan lohkoihin, jotka erotetaan toisistaan {} -merkeillä. Lohkon aloittaa { -kaarisulku ja päättää } -kaarisulku. Mikäli lohko ei ole oikein suljettu tai avattu, kääntäjä antaa virheilmoituksen eikä ohjelmaa voida kääntää. Lohkot voidaan tietyissä tapauksissa jättää myös pois, mutta suosittelen ajattelemaan, että jokainen rakenne koodissa pitää kirjoittaa lohkojen sisään. Kaarisulkeiden käytöstä lisää myöhemmin.

Muuttujat ja tietotyypit

Muuttujaa käytetään varastoimaan tietoa väliaikaisesti ohjelman suorituksen aikana. Esimerkiksi lämpötilaa mittaavan sovelluksen muistissa on lämpötila muuttuja, mihin mittauksen aikana otettu arvo tallentuu. Muuttujia käytetäänkin ohjelmoinnissa aina ja siksi on tärkeää ymmärtää mikä muuttuja on ja miten sitä käytetään.

Pelkästään tieto siitä, että meillä on muuttuja käytössään ei riitä tietokoneelle, sen täytyy tietää myös mitä käytössä olevalla tiedolla tarkoitetaan. Ohjelman kääntäjän pitää tietää onko kyseessä kokonaisluku, desimaaliluku vai kirjain. Tämän tiedon perusteella muuttujia voidaan käsitellä kullekin tietotyypille ominaisella tavalla.

Muuttujien määrittely

Muuttujia määritellään varattujen sanojen avulla, eli siis näin:

unsigned int     kokonaisluku_muuttuja;
unsigned char  kirjain_muuttuja;

Näin luotiin kaksi muuttujaa, joista ensimmäinen kuvaa 16 bittistä kokonaislukua ja toinen 8-bittistä kokonaislukua. Mutta mitä eroa näillä sitten on ja miksi sana unsigned? Näihin vastaus alla.

Bitit ja bytet

Tietokonemaailmassa asiat esitetään ykkösinä ja nollina. Nollaa tai ykköstä esittävää lukua puolestaan sanotaan bitiksi. Looginen nolla tarkoittaa yleensä myös nollaa volttia. Looginen ykkönen puolestaan käyttöjännitettä, joka voi olla esimerkiksi 3,3 volttia. Alla olevassa kuvassa on esitetty kyseiset jännitetasot ja bitit.

Mikroprosessori ymmärtää bittejä jännitetasojen perusteella. Yhdellä bitillä ei hyvin paljon pysty tekemään, joten mikrokontrollerit ovat joko 8-, 16- tai 32-bittisiä. Tämä tarkoittaa käytännössä sitä, miten leveä dataväylä prosessorilla on käytössä. 8-bittinen mikroprosessori kykenee käsittelemään 8 bittiä kerrallaan yhden tai useamman suoritusjakson eli kellojakson aikana. 16-bittinen puolestaan kaksinkertaisen määrän ja 32-bittinen nelinkertaisen määrän 8-bittiseen verrattuna. Jako ei välttämättä aivan näin suoraviivaisesti mene suorituskyvyn suhteen, mutta asioiden ymmärtämistä varten tämä tieto riittää tässä vaiheessa.

Kun käsitellään 8 bitin verran tietoa, puhutaan tavusta (byte); yksi tavu on siis 8 bittiä. 16 bittiä on nimeltään sana (word) ja 32 bittistä lukua kutsutaan ympäristöstä riippuen tuplasanaksi (dword), pitkäksi kokonaisluvuksi (long) tai joksikin muuksi. Suurimmat arvot, mitä kyseiset luvut voivat desimaalina olla ovat:

luku unsigned (2bit) signed (2bit - 1)
8-bittinen 256 +/- 127
16-bittinen 65 536 +/- 32768
32-bittinen 4 294 967 296 +/- 2 147 483 648

Kuten huomaat, luvut muodostuvat 2:n potensseista. Signed ja unsigned sanat erottavat sen, käytetäänkö etumerkkibittiä vai ei. Tärkein asia on kuitenkin ymmärtää bitin ja byten ero, nimitykset kyllä jäävät mieleen kunhan ne tulevat tutuksi.

Jos et vielä osaa binäärimuunnoksia, niin voit lukea niistä vaikkapa wikipedian sivuilta.

Tietotyypit

Tietoa käsitellään siis bitteinä, mutta tieto voi kuitenkin tarkoittaa montaa eri asiaa. Esimerkiksi kirjaimet voidaan esittää numeroina (ja niin ne oikeasti ovatkin vain numeroita). Tietomuotoja on siksi hyvä erottaa toisistaan, joten sitä varten on luotu tietotyypit, jotka ovat olennainen asia ohjelmoinnissa. Tietotyyppi määrittää ohjelmassa myöskin sen, mitä sille voidaan tehdä. Esimerkiksi kahta kirjainta ei voi sinänsä laskea yhteen, sillä siinä ei ole järkeä (tottakai tämä voidaan tietokoneella tehdä mutta ymmärtänet tarkoituksen).

Tietotyyppien nimitykset ovat standardeja ja puhuttaessa esimerkiksi kokonaisluvuista (integer) ne käsitetään myös sellaisiksi. Se kuinka monta bittiä kokonaisluku on, riippuu ympäristöstä. Esimerkiksi MSP430 mikrokontrollereilla kokonaisluvut tarkoittavat 16-bittistä lukua (mikä on yleinen kokonaisluvun pituus myös PC-ohjelmoinnissa). Kokonaisluvut voivat olla negatiivisia tai positiivisia, samoin kuin reaaliluvut tai 8-bittiset luvutkin. Tietotyyppien määritys riippuu hieman myös kääntäjästäkin. Alle on koottu tietotyyppien tyypilliset nimitykset ja niiden pituudet.

Tietotyyppi Kuvaus ja pituus
char Lyhin tietotyyppi, joka koostuu 8-bitin pituisesta luvusta.
short Kokonaislukutyyppi, vähintään 16 bittiä.
int Käytännössä sama kuin short, 16-bittinen kokonaisluku.
long 32-bittinen kokonaisluku
float Yhden tarkkuuden liukuluku, 32-bittiä
double Tuplatarkkuuden liukuluku, 64-bittiä

Yllä mainitut tietotyypit voivat olla etumerkiltään positiivisia tai negatiivisia. Jos halutaan käyttää negatiivisia lukuja (esim. lämpötilan ilmaisemiseksi) niin tietotyypin edessä käytetään signed sanaa. Jos taas riittää pelkästään positiiviset luvut, niin käytetään unsigned sanaa. Unsigned sanalla saadaan käyttöön kaikki tietotyypin bitit ja signed sanalla käytöstä vähenee etumerkkiä varten yksi bitti. Esimerkiksi:

unsigned char luku;  // 8-bittinen luku, jonka maksimiarvo 255 = 0xFF = 1111 1111
signed char luku2;    // 7-bittinen luku, jonka maksimiarvo on +/- 127 esim:
// 0x7F = 0111 1111 = -127   TAI    0xFF = 1111 1111 = 127

Kuten huomaat, on tietotyypit kirjoitettu punaisella ja ennen muuttujien määrittelyä. Tämä johtuu siitä, että C-kielessä mm. tietotyypit ovat varattuja sanoja. Varattu sana tarkoittaa sitä, että sitä ei voi käyttää esimerkiksi muuttujan nimenä. Sitä voi kuitenkin käyttää silloin, kun halutaan kertoa tietokoneen kääntäjälle, että "käytetään tätä muuttujaa luku tietotyyppinä unsigned char", aivan kuten yllä on tehty.

MSP430G2231 muistin rakenne

MSP430G2231 mikrokontrollerin ohjelmamuistiin mahtuu 2 kilotavua ohjelmakoodia. Lisäksi käytössä on 128 tavua RAM muistia. Nämä eivät kovin paljoa itse asiassa ole, mutta pieniin harrastetöihin piiri on varsin pätevä. Saatavilla on myös enemmän muistia sisältäviä mikrokontrollereita, mutta ei niistä tämän enempää. Alla olevassa kuvassa on esitetty MSP430G2231:n flash muistin rakenne.

Flash muisti kyseiselle mikrokontrollerille alkaa osoitteesta 0xF800 ja loppuu 0xFFFF osoitteeseen. Kun vähennetään 0xF800 (63488) arvosta 0xFFFF (65535) saadaan 0x7FF, joka on siis 2047 byteä. Kysymys kuuluukin, mihin hävisi yksi byte, jos 2 kilobytea on oikeasti 2048? Yksinkertaisesti siihen, että mikrokontrolleri ja tietokonemaailmassa laskenta lähtee aina nollasta, joka on samalla ensimmäinen luku. Sama laskentatapa pätee myös RAM eli työmuistin kohdalla. RAM muisti on esitetty kuvassa alla.

Luvun etuliite 0x on yleisesti käytetty tapa, kun halutaan esittää heksalukuja. Jos et ole vielä ymmärtänyt heksamuotojen esitystapaa, niin tutustu siihen esim täällä tai täällä.

Muuttujat muistissa

Muistia on helpoin ajatella yhtenä jatkuvana lohkona joka koostuu mikroprosessorin arkkitehtuurin mukaisista alkioista eli muistipaikoista. 8 bittisillä mikrokontrollereilla 8 bittiä vastaa yhtä muistipaikkaa, 16 bittisillä mikrokontrollereilla 16 bittiä on vastaavasti yksi muistipaikka ja 32 bittisillä kuten arvata saattaa 32 bittiä on yksi muistipaikka.

Muistipaikan sijaintia kutsutaan osoitteeksi ja osoiteavaruudeksi sanotaan sitä aluetta, mistä mikrokontrolleri voi tietoja lukea. Osoitteet alkavat aina nollasta ja eri osoitepaikoissa sijaitsee erilaista tietoa tai ohjelman suorituskoodia taikka mikrokontrollerin kalibrointitietoja. Muuttujat ladataan ja alustetaan yleensä mikrokontrollerin työmuistiin eli RAM-muistiin ja ohjelma suoritetaan mikrokontrollerin flash-muistista.

Mikrokontrollerin työmuisti alkaa aina jostain tietystä osoitteesta (muistialueet selviävät mikrokontrollerin datalehdestä). Tässä tapauksessa alkuosoite on 0x200 ja loppuosoite on 0x27F. Jos lasket laskimella mitä on 0x27F (639 des.) - 0x200 (512 des.) saat tulokseksi 0x7F, joka vastaa desimaalina 127. Saatat ehkä ihmetellä mihin yksi byte on kadonnut? Työmuistinhan piti olla 128 byteä pitkä, kuten kuvassa, eikö näin sitten ole?

Työmuisti on kyllä 128 byteä pitkä, vaikka laskutoimitus ei niin näytäkään. Tämä johtuu siitä, että muistipaikkoja (ja ohjelmoinnissa muutenkin) laskiessa lukuun pitää lisätä 1. Tämä johtuu siitä, että myös ensimmäinen (tai viimeinen) muistipaikka pitää laskea mukaan, sillä muistipaikkojen arvot (0x200 ja 0x27F) ovat muistin osoitteita. Alla olevassa kuvassa on asiaa selvennetty yksinkertaisella esimerkillä.

Ensimmäinen muistipaikka alkaa osoitteesta nolla ja viimeinen päättyy osoitteeseen 5. Jos lasket 5-0 saat 5, vaikka muistipaikkoja mihin voidaan tallentaa on oikeasti 6 kpl. Tästä syystä osoitteiden pituuteen pitää siis lisätä 1.

Muuttujan määritys ja sen paikka muistissa

Edellisissä kappaleissa tulikin jo esille kuinka muuttujia määritellään. Seuraavaksi tarkastellaan miten muuttuja sijoittuu muistiin. Varataan siis kahdelle muuttujalle tilaa seuraavasti:

unsigned int     kokonaisluku_muuttuja;
unsigned char  kirjain_muuttuja;

Koska kyseessä on kaksi eri tyyppistä muuttujaa, varautuvat ne muistista hieman eri tavoin. Alla olevassa kuvassa on näytetty, kuinka muuttujille varataan muistista paikka MSP430G2231 mikrokontrollerissa. Edellä mainitut muuttujat eivät todennäköisesti ohjelmassa varaudu alla oleviin osoitteisiin oikeasti, mutta vaikka osoitteet eivät olisi juuri samoja, on periaate kuitenkin sama.

Oletetaan, että kokonaisluku_muuttuja alustetaan arvoon 0xFACE (64206) ja kirjain_muuttuja arvoon 0x00. Koska kokonaisluku_muuttuja on unsigned int tyyppinen, sille varataan muistista kaksi byteä. Varatut kaksi byteä voidaan jakaa enemmän merkitsevään ja vähemmän merkitsevään byteen, jotka on kuvassa väritetty vihreällä ja sinisellä. int tyyppisessä muuttujassa eniten merkitsevä byte on siten FA ja vähiten merkitsevä CE. 0xFACE arvon sijainti alkaa tässä tapauksessa osoitteesta 0x200, missä sijaitsee eniten merkitsevä byte FA. Osoitteessa 0x201 sijaitsee vähiten merkitsevä byte eli CE ja osoitteessa 0x202 sijaitsee yhden tavun pituinen kirjain_muuttuja. Ja näiden välissä on siis käyttämätön muistipaikka, kuvassa valkoisella.

Koska kirjain_muuttuja on vain yhden byten pituinen, sille varataan muistista yhden byten verran tilaa, joka on merkitty kuvaan ruskealla. Sen sijaan sen muistipaikka ei olekaan 0x203, vaan 0x202. Jos ohjelmaan luotaisiin toinen saman tyyppinen yhden tavun pituinen muuttuja nimeltään kirjain_muuttuja_2, niin tällöin sille varattaisiin paikka ruskean ja sinisen lohkon välistä ja sen muistipaikka olisi 0x203.

MSP430 "tykkää" 16-bittisistä luvuista, mistä syystä muistipaikkojen varaus menee yllä mainitulla tavalla. Jos tämä asia ei kuitenkaan täysin auennut vielä, niin ei kannata huolestua. Se selviää sinulle ajan saatossa varmasti.