You are on page 1of 30

5.2.

Sulautetun järjestelmän C-kielen perusteet 2/8, käsitteet


7.1.2008 pva

”Hän joka ei ota riskejä,


ei myöskään juo shampanjaa.”
- venäläinen sanalasku

Tässä osiossa tärkeää:


lisää ohjelmoinnissa tarvittavia peruskäsitteitä, kuten
- muuttujat
- tietotyypit
- perusoperaattorit

Sisältö

Muuttuja, variable
Ohjeita muuttujan nimeämiseen:
Miksi muuttuja pitää esitellä?

Tieto- eli datatyypit


C-kielessä on neljä perustietotyyppiä:
Muuttujan tyypin valinta
User types

Talletusluokat eli muistimääritteet


Talletusluokka automaattinen, automatic
Talletusluokka Static
Talletusluokka Extern
Rekisterimuuttujat, Register Variables
Volatile
Yleinen eli globaali muuttuja, Global Variable
Tilde
Vakio, constant
Miksi käytetään vakioita?
1. Numeeriset vakiot
2. Merkkivakiot
3. Symboliset vakiot
4. Varattu sana const

Operaattorit, operators
Sijoitusoperaattorit
Sijoitus- ja matemaattisten operaattoreiden yhdistäminen
Matemaattisten operaattoreiden käyttö-harjoitus
Vertailuoperaattorit
Unaarioperaattori
Lisäys- ja vähennysoperaattorit
Varatut sanat

1
Yleistä

Tämä luku on hyvin laaja ja perusteellinen. Se sisältää ”kaiken olennaisen ja käytän-


nössä tarvittavan” perustiedon. Eli eräänlaiset C-kielen aakkoset, joiden avulla myö-
hempi työskentely tapahtuu. Käytä aikaa asioiden omaksumiseen ja harjoitteluun jotta
saat ”tartuntapintaa” varsinaisia laiteläheisen ohjelmoinnin opintoja varten.

Siispä töihin.
Luo uusi projekti, nimi eka_2.c. Kirjoita seuraava koodi.

eka_2.c, esimerkkikoodi
/**********************************************************
Project : eka_2.c
Hardware: PV-M32 + PV-LEDIT on PORTB
Software: WinAVR 20070525
Date : 03.07.2007
Author : pva
Comments: ensimmäiset opetuskoodit
**********************************************************/
#include <avr/io.h>
#include <util/delay.h>

// prototyyppi
void wait(uint16_t time);

int main(void)
{
DDRB = 0xFF; // B-portin suunta ulos

unsigned char kuvio = 0x55; // kuvio bitteinä 0101 0101

while(1) // ikuinen silmukka


{ // silmukan alku
PORTB = 0xF0; // B-porttiin 1111 0000
wait(500); // kulutetaan aikaa

PORTB = kuvio; // B-porttiin kuvio-muuttujan arvo


wait(500);

PORTB = 0x0F; // B-porttiin 0000 1111


wait(500);
} // silmukan loppu
} // main-funktion loppu

// *** Primitive wait() ***


void wait(uint16_t time)
{
volatile uint16_t i;

for(i=0;i<2000;i++)
_delay_loop_2(time);
}

Käännä ja ohjelmoi AVR.

Jatketaan analysointia. Uutta on siis vain rivi:

2
unsigned char kuvio = 0x55; // kuvio bitteinä 0101 0101

Mikä on ”unsigned char kuvio”? Se on

Muuttuja, variable
Tietokone tekee monenlaisia asioita ohjelman ohjaamana; esim. se lukee lämpötila-
anturin arvoja, muuttaa (laskemalla) sen arvon Celsius-asteiksi, vertaa sitä ennalta
annettuun raja-arvoon ja jos arvo ylittää sen, kytkee transistorikytkimen kautta
tuulettimen moottorin pyörimään. Näiden tehtävien suorittamiseen mikro-ohjain
tarvitsee ohjelmakoodin lisäksi tietoa, dataa eli numeroita ja merkkejä.
Osa tiedosta on asetettu pysymään muuttumattomana ohjelman ajon ajan. Ne ovat
vakioita, esim. raja-arvo, josta tuuletin käynnistetään. Toiset tiedot muuttuvat ja niitä
on voitava muuttaa milloin tahansa ohjelman aikana. Luetaan vaikkapa lämpötilatieto
kerran minuutissa. Muutettavat tiedot tallennetaan muuttujiin.

Muuttujan
arvo muuttuu, tai saattaa muuttua, ohjelman ajon aikana.

Vakion
arvo ei muutu ohjelman aikana.

Muuttujat ovat olennaisia asioita kaikissa ohjelmointikielissä. Käsiteltävälle tiedolle


pitää olla jokin varastopaikka, oli se sitten vakio tai muuttuja. Muuttujat ovat tiedon
tallennukseen varattuja käyttömuistin (SRAM) lokeroita, joihin voi tallettaa
ohjelmassa tarvittavia lukuarvoja, numeroita, merkkejä jne. Jokaisella muuttujalla on
oma nimi ja tyyppi.
Jotta C-käännin tietäisi miten muuttujaa käytetään, se on ensin esiteltävä. Esittely
tapahtuu ilmoittamalla funktion alussa muuttujan tyyppi ja muuttujan nimi. Esittely
varaa tilaa muuttujalle RAM-muistista ja yhdistää nimen varattuun tilaan.
Muistipaikan koko määräytyy tyypin perusteella.

Muuttuja
on RAM-muistiin nimetty muistipaikka,
jonne voi tallentaa luvun tai merkin.

Oikeasti muistipaikassa on vain


ykkösiä (sähkövaraus)
ja/tai nollia (ei varausta).

Muuttujalla tulee olla


- nimi, jolla muuttuja tunnistetaan
- tyyppi, joka määrää kuinka suuri muistialue varataan, millaista tietoa sinne
voidaan tallettaa ja mitä toimenpiteitä, operaatioita, voidaan muuttujaan
kohdistaa
- arvo, joka on muistipaikan sisältö, annetaan heti tai myöhemmin. Muuttujan
alkuarvo kannattaa antaa heti määrittelyn yhteydessä. Jos et muuta keksi, anna
vaikka nolla.
Muuttujan määrittelyn formaatti:

3
muuttujan_tyyppi muuttujan_nimi;

int x = 0;
char merkki = 0;
float lampo;

Määrittelyrivi päätetään puolipisteellä (;)

Ohjeita muuttujan nimeämiseen:


Nimen kelvolliset merkit ovat a - z, numerot ja alaviivat. Et saa käyttää ’öökkösiä’,
eli skandinaavisia ä, ö eikä å. Ensimmäinen merkki ei saa olla numero, eikä myöskään
alaviiva (sillä se on varattu järjestelmän käyttöön).
C-kieli ymmärtää isot ja pienet kirjaimet eri merkeiksi. Ole niissä tarkkana. Käytä
muuttujien nimissä vain pieniä kirjaimia. Myöhemmin esiteltävät varatut sanat eivät
voi olla muuttujan nimiä.
Kokonaislukumuuttujan määrittelyssä kirjoitetaan varattu sana int (integer) ja sitten
muuttujan nimi. Voit esitellä useita samaa tyyppiä olevia muuttujia samalla rivillä
erottamalla ne toisistaan pilkulla.
int virta_1, virta_2, jannite;

Muuttujan nimenä kannattaa käyttää jotain sen merkitystä tai tehtävää kuvaavaa
nimeä. Jos esim. muuttujaan tallennetaan virta-arvoja, kannattaa antaa muuttujalle
nimi virta. Vältä käyttämästä muuttujan nimenä pelkkää kirjainta.
Koska globaalin (yleisen) muuttujan nimen tulee olla erityisen kuvaava, (se näkyy
kaikille funktioille ja siten käytetään monessa paikassa) se voi toisinaan muodostua
kahdesta tai useammasta sanasta.
Käytä silloin sanojen välissä alaviivaa, esimerkiksi näin:
char ovi_kytkin;
int lampo_anturi;

Lokaalille eli paikalliselle muuttujalle, joka on vain väliaikainen, käy lyhyempikin


nimi.
i ja j sallitaan silmukoissa,
p ja q osoittimissa ja
s ja t merkkijonoissa,
koska ne ovat niin vakiintuneita käytäntöjä.

Hyvin valittu nimi on kuin ylimääräinen kommenttirivi, se kertoo heti lukijalle mistä
on kysymys, mitä ko. ohjelmakohteessa tapahtuu.

Miksi muuttuja pitää esitellä?

Aina kun kirjoitetaan ohjelmakoodia tarvitaan muuttujia. Käännin tarkistaa joka kerta
muuttujan kohdatessaan, onko muuttuja esitelty. Täten säilyy oikea kirjoitusmuoto.
Jos näin ei tehtäisi, käännin loisi aina uuden muuttujan virheellisenkin nimen
yhteydessä. Tästä seuraisi ongelmia. Isoista ohjelmista ohjelmointivirheiden
etsiminen on tosi työlästä.

4
Huom!
Muuttujalle varataan tilaa SRAM-muistista, koska sen arvoa on voitava muuttaa
ohjelman kuluessa. SRAM-muisti on rakennettu kiikuista, joilla on sähkön
kytkemisen jälkeen satunnainen arvo, siis yksi tai nolla.
Kiikku voi olla ’kiikun tai kaakun’.

Muuttujan esittely varaa vain muistista tilaa, mutta ei ota kantaa muistin sisältöön.
Siksi muuttuja on alustettava, initialisoitava eli sille on annettava arvo.
Alustamattoman muuttujan käyttö on vaarallista, koska muistipaikka saattaa sisältää
mitä tahansa satunnaista dataa.

Käytä ennalta määriteltyjä vakioita, ei lukuja


- sillä ne ovat helpommin muutettavissa
esim.
#define JANNITE 9

Miksi tarvitaan muuttujaa?

Sulautetun järjestelmän työmuistin (SRAM) koko on yleensä vaatimaton, joten


kääntimen tulee tietää:
- kuinka iso muistipaikka datalle varataan, varataan vain minimitarve
- ja mitä toimenpiteitä ko. datalle voidaan tehdä

Tieto- eli datatyypit


C-kieli on 'tyypitetty kieli'. Se tarkoittaa sitä, että kaikilla muuttujilla ja funktioilla on
oltava jokin tyyppi. Muuttujan tyyppi määrää, millaisia arvoja muuttuja voi saada ja
millaisia operaatioita muuttujaan voidaan kohdistaa, sekä paljonko muistia muuttuja
tarvitsee.

C-kielessä on neljä perustietotyyppiä:

char, character, merkkityyppi, esim. 'a', 'B'. Teknisesti char on 8-bittinen


kokonaislukutyyppi, joten sulautetuissa järjestelmissä on tyypillistä käyttää 8-bittisiä
heksalukuja tässä yhteydessä, esim. 0xF0. Muuttuja sisältää numeroita, siis vain
ykkösiä ja nollia, ei kirjaimia, ne vain tulkitaan sopivassa yhteydessä kirjaimiksi.

int, integer, 16-bittinen kokonaislukutyyppi esim. 6, -49, 32736

float, floating point,


32-bittinen reaali- eli liuku- eli desimaalilukutyyppi, esim. 2.46, 3.12E3, Kun liuku-
luku tallennetaan muistiin, se jaetaan kahteen osaan, toiseen osaan tallennetaan
mantissa ja toiseen eksponentti. Tilaa varataan tietylle määrälle numeroita.
Eksponentti ja mantissa voidaan tallettaa muuttujaan eri tavoilla. ANSI-normi ei
määrää miten, joten kääntimet tekevät sen ’omalla tavallaan’.

5
Pienissä mikro-ohjaimissa ei ole laitteistotukea liukuluvuille (laskentayksikköä, saati
matematiikkaprosessoria), joten laskenta on tehtävä ohjelmallisesti. Ongelmaksi tulee
silloin suuri muistintarve. Onneksi suurille liukuluvuille on harvoin tarvetta pienissä
sulautetuissa järjestelmissä.
Liukulukutyypin käytön etuna on se, että sillä voidaan esittää paljon suurempi määrä
lukuja, kuin kokonaislukutyypillä. Haittana taas se, että liukulukuoperaatiot ovat
paljon hitaampia kuin kokonaislukulaskenta ja pikku systeemeissä laskettaessa luvun
tarkkuus vähenee.

double, double precision, ’pidempi desimaaliluku’, liukulukumuuttuja, jossa


kaksinkertainen tarkkuus.

Näitä perustyyppejä voi täsmentää etuliitteellä:


unsigned int, etumerkitön (positiivinen) kokonaislukumuuttuja
unsigned char, etumerkitön merkkimuuttuja

Kokonaisluvut ovat oletuksena etumerkillisiä, ne voivat olla siis joko positiivisia tai
negatiivisia. Yleisin tapa esittää negatiivinen numero on kahden komplementti, two's
complement.
Unsigned on etumerkitön, siis vain positiivisia lukuja sisältävä muuttujatyyppi. Käytä
unsigned-tyyppiä, aina jos suinkin voit. Negatiivisten lukujen käsittelyyn liittyy
etumerkin testaus ja siihen tarvitaan koodia ja muistitilaa ja siten myös aikaa.
Etumerkki varaa yhden bitin paikan rekisteristä, joten lukualue jää pienemmäksi.
(void merkitsee tyhjä, ilman tyyppiä)

Nimi Koko Lukualue Kommentti


Bit 1 bitti 0 tai 1 yksittäinen bitti
char 8 bittiä -128…+127 merkki tai etumerkillinen
kokonaisluku
unsigned char 8 bittiä 0…255 merkki tai etumerkitön
kokonaisluku
int 16 bittiä -32768…+32737 etumerkillinen kokonaisluku
short int 16 bittiä -32768…+32737 etumerkillinen kokonaisluku
signed int 16 bittiä -32768…+32737 etumerkillinen kokonaisluku
unsigned int 16 bittiä 0…65535 etumerkitön kokonaisluku
long int 32 bittiä -2147483648…+2147483647 etumerkillinen kokonaisluku
unsigned long 32 bittiä 0…4294967295 etumerkitön kokonaisluku
int
signed long 32 bittiä -2147483648…+2147483647 etumerkillinen kokonaisluku
int
float 32 bittiä +/-1.175e-38…+/-3.402e38
double 32 +/-1.175e-38…+/-3.402e38

Taulukko 5.2.1. C-kielen tietotyypit.


Siinä on esitelty C-kielen tavallisimmat tietotyypit, niiden muistista varaama tila ja
lukualue.

6
Ei ole järkevää käyttää liukulukuja AVR:n yhteydessä, koska AVR-ytimessä
ei ole liukulukulaskentaa suorittavaa ”rautaa”.
Liukuluvut on käsiteltävä ohjelmallisesti ja se syö valtavasti resursseja.

avr-gcc:n kanssa ei voi käyttää double-tarkkuuden muuttujia.


Parasta mitä voi tehdä
on käyttää 32-bittisiä single precision liukulukuja float.

Huom.
Muuttujien koot vaihtelevat kääntimittäin ja varsinkin sulautettujen järjestelmien C-
kääntimet eivät välttämättä sisällä kaikki niitä tietotyyppejä, mitä ANSI-normi
edellyttää.

Käytä näitä:
ANSI C99 määrittelee muuttujien koot, esim. int16_t, int8_t, uint8_t, jne.
Numeroarvo kertoo muuttujan koon, bittimäärän.
C99 tietotyyppejä kannattaa käyttää porttaamisen (koodin siirto toiseen käännin- tai
MCU-ympäristöön) helpottamiseksi.
Siis silloin, jos tietotyypin koolla on merkitystä ohjelman toimintaan.

int8_t (signed char)


int16_t (signed int)
int32_t (signed long int)
int64_t (signed long long int)

Vastaavasti etumerkittömät muuttujat:


uint8_t (unsigned char)
uint16_t (unsigned int)
uint32_t (unsigned long int)
uint64_t (unsigned long long int)

esim.
typedef signed char int8_t;
se ja muut määritykset löytyvät stdint.h-headerista
ja polku on C:\WinAVR-20070525\avr\include

Mitä tarkoittaa UL numeron perässä?


esim. 100000UL. Se on Unsigned long.
Merkintä ei ole case sensitive, joten voit käyttää u tai U ja l tai L.

C-kielen boolean-muuttuja varaa yhden tavun. Se on joko


0 (== false), or non-zero (== true).
Voit käyttää Boolean-muuttujaa lippuna, flag, se selviää myöhemmin.

7
Muuttujan tyypin valinta

Oikean datatyypin valinta on tärkeää (säästyy muistia ja koodi nopeutuu). 8-bittisissä


sulautetuissa järjestelmissä manipuloimme tavallisimmin 8-bittisiä rekistereitä, tai
I/O-liitäntöinä ulkoiseen maailmaan toimivia portteja. Silloin luonnollinen valinta on
8-bittinen tyyppi eli char.
Valitse etumerkitön char, koska todennäköisemmin käytämme positiivisia lukuja ja
voimme käsitellä suurempia lukuja (2 potenssiin 8 = 256) ja etumerkin testi jää pois.
Jos tarvitaan suurempia lukuja kuin 255, niin silloin luonnollinen valinta on int ja taas
etumerkitön, ellei toisin tarvita.
Palataan koodiin.

unsigned char kuvio;


kuvio = 0x55;

Määritimme siis funktion alussa kuvio-nimisen muuttujan, jonka tyyppi on unsigned


char eli etumerkitön merkkimuuttuja. Seuraavalla rivillä sijoitamme kuvio-muuttujaan
heksa-luvun 0x55.

Muistanet, että = -merkki on C-kielessä sijoitusoperaattori.


PORTB = kuvio; // B-porttiin sijoitetaan kuvio-muuttujan arvo

Seuraavaksi B-porttiin sijoitetaan kuvio-muuttujan sisältö eli siis 0x55. Se on


binäärisenä 0101 0101, joten joka toinen LED loistaa ja joka toinen ei. Viive kuten
ennenkin, jotta ehdimme nähdä mitä tapahtuu. Käännä tämä koodi, samalla nimellä ja
ohjelmoi mikro-ohjain saman tien. Nyt kuvioita on kaksi erilaista, tai oikeastaan
kolme, jos ’pimeät’ lasketaan.

TIETOTYYPIT

Yksinkertaiset määrittelyt perustyypit


- char, unsigned char, void, int, float, double

Yksinkertaiset käyttäjän määrittämät tyypit


- typedef, enum

Rakenteiset, perustyypeistä johdetut


- taulukko-vector,
- tietue-structure,
- yhdiste-union,
- osoitin-pointer,
- tiedosto, typedef struct,

Vinkki:
Miten kokonaislukumuuttujasta liukulukumuuttuja?
int i;
double d;
d = i;
parempi tapa: d = (double)i;

8
Käyttäjän itse määrittämät tyypit
C-kielessä koodaaja voi luoda omia tyyppejä. Se tehdään typedef-määreellä ja apuna
käytetään kirjaston standardityyppejä.
Formaatti on:
typedef standardi_tyyppi omatekema_tyyppi;

typedef unsigned char tavu; // luo tavu-tyypin, etumerkitön char


typedef unsigned int sana; // luo sana-tyypin, etumerkitön int

Näistä myöhemmin lisää.

Talletusluokat eli muistimääritteet


Talletusluokalla määritetään mitkä funktiot pääsevät käsittelemään muuttujaa. Puhu-
taan ns. vaikutusalueesta. Toiseksi talletusluokka määrää, miten kauan muuttuja
pysyy muistissa. Talletusluokat määräytyvät sen perusteella, missä muuttuja määri-
tellään ja mitä avainsanaa käytetään.

Talletusluokka automaattinen, automatic


Kaikki funktion sisällä esitellyt muuttujat, paikalliset muuttujat, kuten myös funktion
parametrit kuuluvat oletusarvoisesti auto-luokkaan, ellei ole toisin esitelty.
Automaattiset muuttujat ovat voimassa vain siinä funktiossa, missä se on määritelty.
Automaattinen muuttuja ’syntyy automaattisesti’ aina kun funktiota kutsutaan ja kun
funktiosta poistutaan, se myös automaattisesti häviää. SRAM-muistipaikka voidaan
ottaa muuhun käyttöön.
Teknisesti ajatellen; järjestelmä varaa auto-tyyppiselle muuttujalle tilaa ajonaikaisesta
pinosta, run time stack, silloin, kun funktion suoritus alkaa ja vapauttaa niiden
tilanvarauksen, kun funktion suoritus loppuu.

Tässä on nyt ’fundeeraamisen’ paikka!


Asiaan vihkiytymätön ihmettelee usein, miten mikro-ohjaimessa voi olla vain 128
tavua SRAM-muistia, siis työmuistia? (Joissakin ohjaimissa ei ole laisinkaan RAM-
muistia).
Edellisen perusteella ymmärrät, miksi sulautetun järjestelmän ohjelma tulee jakaa
pieniin funktioihin ja käyttää vain paikallisia muuttujia. Funktio muuttujineen ’elää’
vain oman aikansa ja ’kuolee’ pois. Sitten uusi funktio omine muuttujineen ’elää’
samassa muistitilassa ja käyttää samaa pinomuistia. Teemme samalla, hyvin pienellä,
pöydällä kaikki työt. Jos entiset työvälineet poistetaan aina uuden työn tullessa ja
uusitaan vain työkalut, ei tarvita isoa pöytätilaa.

Auto-muuttujalla on määrittämätön (’mitä sattuu’) alkuarvo.


Siksi se on aina ensiksi alustettava, eli sille on annettava jokin
arvo.

Jos hieman saivarrellaan, niin oikeasti muuttujat pitäisi


esitellä näin:
auto int luku;
Mutta kun kaikki muuttujat ovat oletusarvoisesti
automaattisia, niin tyyppi auto voidaan jättää pois.

9
Talletusluokka Static

Jo haluamme käyttää funktion laskemaa muuttujan arvoa seuraavan kerran funktioon


tullessamme, on meidän tehtävä muuttujasta pysyvä. Se tapahtuu lisäämällä
avainsana, varattu sana, static, muuttujan tietotyypin eteen:

static unsigned char kuvio;


static uint8_t alku = 0x01;

Static-määrityksellä kuvattu muuttuja alustetaan kerran (ensimmäisellä käyttökerralla)


ja se varaa SRAM-muistia pysyvästi eli tuhlaa muistia). Ellei sitä tehdä, käännin
alustaa muuttujan nollalla.
Uudelleen alustamista ei seuraavalla käyttökerralla tehdä, kuten tapahtuu muilla
automaattisilla muuttujilla. Static-muuttuja säilyttää arvonsa funktion kutsukertojen
välillä. Tämä on tarpeellinen ominaisuus esim. kierroslaskuria käytettäessä.
Mutta huomaa, static muuttuja ei ole näkyvä muissa funktioissa, joten sitä ei voi
niissä käyttää. Static-määre muuttaa muuttujan elinikää (jatkuva) ja tallennuspaikkaa
(pinosta tav. käyttömuistiin).
Tästä on ohjelmaesimerkki merkkitaulukoiden esittelyn yhteydessä.

Miksi ei main-funktiossa käytetä static-määrittelyä?


Koska
main-funktiossa määritelty muuttuja on voimassa koko ohjelman ajan,
mutta se näkyy vain main-funktiolle.

Static-muuttuja on ikäänkuin ”private global variable”, omassa käytössä oleva


globaali muuttuja. Vain se funktio jossa se on määritetty, ”näkee” sen ja voi sitä
manipuloida. Static tyyppinen muuttuja alustetaan AINA käännösvaiheessa, eli jos
sille ei anneta arvoa, c-käännin laittaa arvon nollaksi.

Globaalit ja staattiset muuttujat


alustetaan systeemin puolesta nollaksi
ellei ohjelmoija anna niille alkuarvoja.

Automaattisia ja rekisterimuuttujia ei alusteta


automaattisesti.

Talletusluokka Extern

Funktiot ja globaalit muuttujat ovat ohjelmassa ’kaikkien käytössä’. Jos haluamme


funktion käyttävän muuttujia, jotka on määritetty jossain toisessa tiedostossa, on
kääntimelle kerrottava tämä varatulla sanalla extern. (Isoissa ohjelmissa on
kymmenittäin erilaisia tiedostoja).

Extern-määrittely kertoo kääntimelle, että tämän muuttujan määrittely löytyy


tiedoston ulkopuolelta. Se siis viittaa olemassa olevaan ulkoiseen määrittelyyn. Extern
antaa käyttää ulkoista muuttujaa, vaikka se olisi määritelty myöhemmin samassa tai
eri tiedostossa. Ellei muuttujalle ole tyyppimääritystä, käännin tulkitse muuttujan
tyypiksi extern int.

10
Extern esimerkki
Kirjoita seuraava koodi ja käännä se normaalisti.

/**********************************************************
Project : extern_1.c
Hardware: PV-M32 + PV-LEDIT on PORTB
Software: WinAVR-20081221
Date : 05.01.2008
Author : pva
Comments: extern-määritys, demo
**********************************************************/
#include <avr/io.h>
#include <util/delay.h>
#include "apu.h"
// kerrotaan kääntimelle, että tämä tiedosto on käytettävissä

// prototyyppi
void wait(uint16_t time);

int main(void)
{
DDRB = 0xFF;
// extern unsigned char luku, luku_2;
// esittely, ei ole määrittely, vaan käsky etsiä muuttujaa muualta

unsigned char luku, luku_2;


// aloita tällä, extern rivi kommentoitu = ei toimi
// toinen ajo, kommentoi unsigned rivi
// ja poista kommenttimerkit extern-rivin alusta

while(1)
{
PORTB = 0x00;
wait(200);
PORTB = luku;
wait(200);
PORTB = 0x00;
PORTB = luku_2;
wait(200);
}
}

// *** Primitive wait() ***


void wait(uint16_t time)
{
volatile uint16_t i;

for(i=0;i<2000;i++)
_delay_loop_2(time);
}

Sitten, avaa New File = valkoinen tyhjä ”sheetti” ja kirjoita siihen seuraavat rivit:
uint8_t luku = 0x01;
uint8_t luku_2 = 0x80;
// määrittelyt, muistia on varattu muuttujia varten

ja talleta se projektikansioon (se on sama, missä on käännettävä lähdekoodi)


nimellä apu.h

11
Analysointi

1.) Käännä koodi ensin siten, että luku-muuttujat ovat esitelty ilman asetettua arvoa.
Ohjelma kääntyy (pari varoitusta) ja kun ajat sen, niin tulostus on ”mitä sattuu” =
LEDit ovat pimeitä, koska muuttujat ovat määrittämättömiä (0).

2.) Lisää sitten muuttujamääritykseen varattu sana extern poistamalla


kommenttimerkit rivin alusta. Se kertoo kääntimelle, että muuttujat on määritetty
jossain muualla. Käännin löytää määritykset
uint8_t luku = 0x01;
uint8_t luku_2 = 0x80;

apu.h-tiedostosta, joka on liitetty mukaan käännösympäristöön direktiivillä


#include "apu.h"

Kun nyt käännät ja ajat koodin, tulostus toimii oikein.

Extern-määritystä käytetään aina, jos ohjelma koostuu useasta eri tiedostosta. Se


on hyvin yleistä sulautetuissa järjestelmissä.

Rekisterimuuttujat, Register Variables

Kuten AVR-mikro-ohjaimien tekniikkaa käsittelevässä luvussa kerrottiin, SRAM-


muistialueeseen kuuluu erityinen rekisterialue, jossa ovat I/O-rekisterit, ajastimet ja
muita erityisrekistereitä. Osa näistä rekistereistä on yleiskäyttöisiä, General Purposes
Registers.
CPU:n sisäisen rekisterin käyttö on huomattavasti nopeampaa kuin (ulkoisen) muistin.
Rekisterin käyttö tapahtuu laittamalla avainsana register muuttujan määrittelyn eteen,
esim.
register char virta;

Tämä on vain ehdotus kääntimelle, sillä se saattaa tehdä sen itsekin. Käännin joko
huomioi ohjeen eli register-määreen, tai jättää sen huomiotta. Riippuu tilanteesta.
Sulautetuissa järjestelmissä on toisinaan tärkeää, että käännin ei käytä CPU:n
rekisteriä muuttujan tallentamiseen, vaan käyttää siihen SRAM-muistipaikkaa, mutta
siitä tarkemmin rekisteritason ohjelmoinnin yhteydessä.

Huomaa!
register-esittelyä voidaan käyttää vain automaattisiin
muuttujiin ja funktioiden muodollisiin parametreihin.

Huom!
Älä käytä register-määrittelyä c-koodin yhteydessä!!!
avr-gcc-käännin osaa itse käyttää yleisrekistereitä parhaiten.
Ainoastaan jos opiskelet assembly-koodin käyttöä, silloin voit itse
määrätä mitä yleisrekistereitä käytät.

12
Volatile
Jos sulautetun systeemin koodin toiminta muuttuu oudoksi kun kytket kääntimen
optimoinnin päälle tai otat keskeytyksen käyttöön, on syytä opetella tuntemaan
volatile.

Kun käännin saa sijoittaa haluamiaan muuttujia rekistereihin, nopeuttaa se ohjelmaa


(optimointi). Mutta kun käännin on siirtänyt jonkun muuttujan CPU:n rekisteriin ja
kesken kaiken tulee keskeytys joka käsittelee juuri tuota muuttujaa - siis sen muistissa
olevaa versiota - on tuloksena ongelmia.
Volatile on muuttujan lisämääre joka kieltää käännintä muokkaamasta ohjelmoijan
tarkoittamaa koodin toimintaa. Siis käännin ei suorita tälle muuttujalle minkäänlaista
optimointia tai muuta järjestelyä.

Käyttö:
Muuttuja on määritettävä volatileksi, jos sen arvo voi muuttua koodista huolimatta.
- kun muuttujan arvo voi muuttua asynkronisesti, siis esim. kun globaalia
muuttujaa muokataan keskeytysfunktiossa ja jota arvoa testataan pääkoodissa
- samoin jos käytät muistiavaruudessa olevaa oheislaitteen rekisteriä jonka arvo
voi muuttua koodista huolimatta
- odotat loopissa jossa luetaan I/O-pinnin muuttumista
- tai luet timeria
- jos yleensä odotat, että jotain tapahtuu, kuten while(!jotain);

Syntaksi:
volatile int arvo;
int volatile arvo;
Ihan miten päin vaan. Tutki tarkemmin mallikoodia.

Tässä mallikoodissa käytetään timeria, johon tuskin olet vielä perehtynyt.


Mutta älä välitä siitä
vaan ota koodi käyttöön, niin havaiset volatile-määrityksen merkityksen.
1. kokeile ensin ilman volatile-määritystä, ledit ova pimeitä.
2. sitten ota volatile-määre käyttöön, laskuri toimii ja ledit tulostaa.
/**********************************************************
Project : volatile.c
Hardware: PV-M322 + PV-LEDIT
Software: WinAVR-20071221
Date : 7.01.2008
Author : pva
Comments: volatile-demo, käyttää Timer1 overflow interrupt

Ellet määritä laskuri-muuttujaa volatileksi,


käännin ei "ymmärrä" että laskuri inkrementoidaan
keskeytysrutiinissa,
vaan se "luulee", että muuttujan arvo pysyy vakiona.
ja LEDit pysyvät pimeänä
Kun määritys on volatile, koodi toimii
**********************************************************/

13
#include <avr/io.h>
#include <avr/interrupt.h>

// volatile uint16_t laskuri = 0; // globaali muuttuja


uint16_t laskuri = 0;
// poista volatile-määritys ja kokeile toiminta

// timer_1 overflow interrupt routine


ISR(TIMER1_OVF_vect)
{
laskuri++; // inkrementoidaan laskuria timerin ylivuodon
tapahtuessa
}

int main(void)
{
DDRB = 0xFF;
TCCR1B |= 1<<CS11; // kellon jakoluku; clk/8, muuta tätä
TIMSK |= 1<<TOIE1; // T/C1 Overflow interrupt enable
sei(); // globaali keskeytysten sallinta

for(;;) // ikuinen silmukka


{
PORTB = laskuri;
}
}

Volatile sanoo C-kääntimelle:


”Älä päästä optimoijaa muuttamaan tätä muuttujaa pitämällä sitä pelkästään
AVR:n rekisterissä,
koska sen arvo voi muuttua (esim. keskeytysfunktiossa).
Jos pidät muuttujaa pelkästään rekisterissä, et huomaa sen muuttumista.

Joten aina kun tätä muuttujaa käytetään,


se on haettava SRAMmista ja palautettava sinne takaisin.

Yleinen eli globaali muuttuja, Global Variable

Muuttuja voi olla myös ns. yleinen eli globaali. Globaalit muuttujat määritetään
lähdekoodin alussa ennen funktioita, siis kaikkien funktioiden ulkopuolella. Huomaa,
myös main-funktion ulkopuolella. Siksi globaalit muuttujat ovat voimassa (näkyviä)
kaikkialla ohjelmassa. Siis käytettävissä koko koodin alueella.
Jos globaalin muuttujan arvoa muutetaan, niin sen uusi arvo on siitä lähtien voimassa
kaikissa ohjelman funktioissa. Ellet alusta globaalia muuttujaa, käännin tekee sen itse
ja asettaa arvoksi nollan.
Isoissa ohjelmissa globaalit muuttujat määritetään yhdessä erityisessä header-
tiedostossa ja esitellään siellä missä niitä käytetään määreellä extern.
extern int jannite;

Kun määre on extern, sillä kerrotaan kääntimelle, että muuttuja on määritetty jossain
muussa moduulissa.

14
Lisää ’fundeeraamista’!
Globaali muuttuja asetetaan datamuistiin pysyvästi. Se siis varaa muistia koko
ohjelman ajan. Tämä on tärkeää tietää, koska mikro-ohjaimissa on pieni SRAM-
muistitila.
Yleensä on syytä välttää globaalien muuttujien käyttöä.
Iso ongelma saattaa syntyä siitä, että muuttujan arvoa voidaan muuttaa monesta
paikkaa. Isoissa ohjelmissa ne saattavat aiheuttaa hyvinkin vaikeasti selvitettäviä
virheitä. Paha kauneusvirhe on se, että funktiot eivät tällöin enää ole itsenäisiä,
riippumattomia. Pienissä sovelluksissa, kuten meidän 8-bittisen mikro-ohjaimen
yhteydessä, pieni SRAM-muistitila rajoittaa globaalien muuttujien käyttöä. Siksi
kannattaa opetella heti alkuun oikea ohjelmointityyli ja välttää globaaleja. Jos se
suinkin on mahdollista.

Globaali muuttuja
- on voimassa koko ohjelman ajan ja siten se varaa
muistia kaikenaikaa
- sen arvoa voi mikä tahansa funktio muuttaa,
milloin tahansa.

Muuttujan ominaisuuksia

Muuttujan määrittelypaikka määrää muuttujan ominaisuudet, ellei sitä ole


lisämääreillä toisin kerrottu.

Elinikä
- aika minkä aikana muuttujan tilanvaraus on voimassa, eli muuttuja on
käytettävissä. Funktion (aliohjelma) sisällä määritelty muuttuja ”elää” vain sen
ajan kuin funktion käsittely kestää. Lohkojen ulkopuolella määritetty muuttuja
on globaali ja se on pysyvä (varaa muistia koko ohjelman ajan).

näkyvyys
- koodialue mistä muuttujaan voidaan viitata. Funktion sisällä määritetty
muuttuja näkyy vain ko. funktion sisällä. Se on paikallinen, lokaali.
- Funktioiden ulkopuolella määritetty muuttuja on käytettävissä kaikkialla, se
näkyy jokaiselle funktiolle. Se on globaali muuttuja.
- kukin funktio eli aliohjelma muodostaa oman näkyvyysalueensa. Kun
kutsutaan funktiota, hypätään tämän aliohjelman näkyvyysalueelle.

tallennuspaikka
- datasegmentti SRAM-muistissa
- pinomuisti (SRAM-muistissa)
- rekisteri

tyyppi
- C-kielessä valmiina primitiivisiä tyyppejä, kuten int, char, float
- joista voi rakentaa uusia tyyppejä, kuten taulukko, tietue

15
Määrite Tarkoitus
auto(matic) muuttujat ovat oletusarvoisesti tällaisia
määritys on voimassa vain siinä funktiossa missä se on määritelty,
se on siis paikallinen - local
const muuttujan arvo on vakio
register määrittelee muuttujan paikalliseksi ja määritys kehottaa käännintä
pitämään muuttujan rekisterissä.
Tämä on lähinnä isojen koneitten ominaisuus,
pienissä sulautetuissa järjestelmissä tätä ei tarvita, (rekisterien käyttö
on oletuksena). Älä käytä sulautetuissa.
extern muuttuja tai funktio on määritelty toisessa tiedostossa
static muuttuja on olemassa ohjelman suorituksen ajan
muuttuja alustetaan funktiossa kerran ja sen arvo säilyy vaikka
funktiosta välillä poistutaankin
käyttö: laskurit yms.
typedef tyyppimäärittely
volatile arvo voi muuttua ulkoisista syistä

Seuraavassa harjaannutaan edelleen käänninympäristön käyttöön ja opitaan lisää C-


kielen käsitteitä, kuten
* vakion määritys
* operaattori
* operandi

Vakio, constant
Vakio on myös ohjelman käyttämä muistipaikka, mutta sen sisältöä ei voi muuttaa
ohjelman ajon aikana. Vakion sisältö on, kuten nimikin sanoo, muuttumaton,
staattinen. Se täytyy alustaa esittelyn yhteydessä.

Miksi käytetään vakioita?


Kun ryhdyt kirjoittamaan isoja ohjelmia, joissa voi olla satoja (tuhansia?) koodirivejä,
niin silloin huomaat vakion määrityksen merkityksen. Sama lukuarvo saattaa esiintyä
useita kertoja koodin sisällä. Jos jostain syystä em. lukuarvoa on myöhemmin
muutettava, jokaisen ilmenemispaikan etsintä on työlästä. Käyttämällä vakion
määritystä kerran ohjelman alussa ja sen jälkeen käytetään koodissa vain sitä. Jos
myöhemmin vakion arvo täytyy jostain syystä muuttaa, riittää arvon korjaus vain
yhdessä kohdassa ohjelmaa.

16
1. Numeeriset vakiot
Numeerinen vakio on arvo, joka kirjoitetaan suoraan lähdekoodiin aina tarvittaessa.
Se voidaan tarpeen mukaan kirjoittaa eri muodossa. Kokonaislukuvakiot voidaan
kirjoittaa:
desimaaliluku, ilman etuliitettä esim. 1234
heksaluku, etuliite on 0x esim. 0xF0
oktaaliluku, etuliite on 0 esim. 0567

int kierrosluku = 12;


PORTB = 0xA6;
float alv_vero = 0.22;
Kierrosluku ja alv_vero ovat muuttujia, porttimääritys on ns. makro. (selviää
myöhemmin). Sijoitusoperaattorin (=-merkin) oikealla puolella olevat arvot ovat
numeerisia vakioita. Desimaalipiste erottaa liukulukuvakion kokonaislukuvakiosta.

2. Merkkivakiot
Merkkivakio voi olla tulostettava (kuten aakkoset), tai ei-tulostettava (kuten rivin
vaihto new line, tabulaattori tab). Tulostettavat merkkivakiot kirjoitetaan
ympäröimällä merkki heittomerkeillä, esim. ’A’, joka on heksamuodossa 0x41. Ei-
tulostettavat merkkivakiot ovat ns. ohjausmerkkejä, kuten ’\n’, new line, ’\x0a’
heksalukuna, jolla siirretään kursori seuraavalle riville, tai tabulointimerkki ’\t’,
heksana ’\x09’.

3. Symboliset vakiot
on vakioita, joita ohjelmassa esittää nimi, symboli. Sitä ei voi ohjelman ajon aikana
muuttaa. Käytön etuna on koodin selkeys. Ne otetaan esikääntimen käsittelyyn
samoin kuin header-tiedostot, eli risuaitamerkillä #.

#define VAKIONIMI arvo


- #define, määritä, on, kuten muistanet, esikääntimen komento
- käännin korvaa VAKIONIMI-tekstin arvolla arvo
- define-rivi ei pääty puolipisteeseen

#define JANNITE 10
- esikäännin suorittaa yksinkertaisen tekstikorvauksen, aina kun se näkee sanan
JANNITE, se korvaa sen luvulla 10. Itse käännin ei näe koskaan JANNITE nimeä,
vaan ainoastaan arvon 10.

Makro on esikääntimen komento.


Makron nimi korvataan koodilla, joka suorittaa varsinaisen
toiminnon:
#define NIMI korvaus_teksti

Makro kirjoitetaan tavallisesti isoilla kirjaimilla, jotta se


erottuisi muista määrityksistä ja sen loppuun ei tule
puolipistettä. Tässä esitetty makro on ns. yksinkertainen
makro, siis pelkkä tekstikorvaus. Se voi olla myös
monimutkaisempi, jolloin makro sisältää parametreja.

17
4. Varattu sana const
Varattu sana const on muunnin, jolla muuttuja määritellään const-tyyppiseksi
vakioksi, sitä ei voi enää sen jälkeen ohjelmassa muuttaa. Vakiolla on myös tyyppi ja
käännin tarkistaa, että sitä käytetään oikein.

C-kielessä vakioita on kolme perustyyppiä: int, float ja char


const int VIRTA = 9;
const int KIERROSLUKU = 100;
const char MERKKI = ‘U’;

Const
ei talleta oliota flash-muistiin vaan SRAMmiin. Se on oikeasti muuttuja jonka arvoa
ei voi ohjelmassa muuttaa. Se on eräänlainen suojamekanismi.
Const
on hyödyllinen määre funktioiden parametrien määrityksessä, se estää funktiota
muuttamasta arvoa.

Globaalit vakiot tulisi tallettaa flash-muistiin käyttämällä PROGMEM-määritystä.

Globaalit vakiot tulee tallettaa ohjelmamuistiin (flash).


Se tehdään komennolla:
const char luku PROGMEM = 3; /* Store variable in FLASH */

Mutta näistä myöhemmin oman esimerkkikoodin avulla lisää.

Palataan takaisin koodiin.

Kirjoita seuraava lähdekoodi ja ennen kääntämistä analysoidaan se rivi riviltä eli


selvitetään mitä ohjelma tekee.

vakio.c esimerkki
/**********************************************************
Project : vakio.c
HW: PV-M32 + PV-LEDIT
SW: WinAVR-20070525
Date : 02.12.2007
Author : pva
Comments: vakion käyttö-demo
**********************************************************/
#include <avr/io.h>
#include <util/delay.h>
#define VAKIO 0x81 // vakion määritys

// *** Primitive wait() ***


void wait(uint16_t time)
{
volatile uint16_t i;

for(i=0;i<2000;i++)
_delay_loop_2(time);
}

18
int main(void)
{
uint8_t kuvio;
const uint8_t LUKU = 3; // vakion määritys

DDRB = 0xFF; // B-portin suunta ulos

while(1) // ikuinen silmukka


{
kuvio = 0x02;
PORTB = kuvio; // B-porttiin 0000 0010 bin
wait(3000); // kulutetaan aikaa

kuvio = kuvio + 5; // kuvio on 2 + 5 eli 7


PORTB = kuvio; // B-porttiin 0000 0111, eli 7 des
wait(3000);

kuvio = LUKU * 2; // kuvio on 3 * 2 eli 6 des


PORTB = kuvio; // B-porttiin 0000 0110
wait(3000);

PORTB = VAKIO; // B-porttiin 1000 0001


wait(2000);
}
}

Main-funktiossa määritetään muuttuja, jonka nimi on kuvio ja tyyppi unsigned char.


Char on lyhenne sanasta character ja se tarkoittaa merkkimuuttujaa. Char-muuttujalle
käännin varaa 8 bittiä muistitilaa, jossa voi esittää 256 eri merkkiä tai lukua.

Jos määritetään muuttuja pelkästään tyypiksi char kuvio, niin merkkitavun eniten
merkitsevä bitti, vasemmanpuoleisin, on tällöin ns. merkkibitti. Se kertoo, onko luku
positiivinen tai negatiivinen. Silloin varsinaista merkkiä varten jää vain 7 bittiä, siis
128 eri dataa, jotka voivat olla negatiivisia tai positiivisia.

Rivillä
const unsigned int LUKU = 3;
määritetään vakio LUKU ja sille arvoksi sijoitetaan 3.

Määritetään char-muuttuja kuvio


unsigned char kuvio;
ja while-silmukassa sijoitetaan sille arvo: kuvio = 0x02;

Niin kauan kuin olemme main-funktion sisällä, kuvio-muuttujalla on määritetty arvo,


ellemme sitä ohjelmassa muuta. Palataan koodiin.
While-silmukassa ensiksi kuvio-muuttuja saa arvon 0x02, sitten se kirjoitetaan B-
porttiin. Kolmen sekunnin viiveen jälkeen kuvio-muuttujaan lisätään luku 5 ja summa
sijoitetaan kuvio-muuttujan uudeksi arvoksi. Seuraavalla rivillä kuvio-muuttujan uusi
arvo sijoitetaan B-porttiin ja se näkyy loistavina LEDeinä.
kuvio = LUKU * 2; // kuvio on 3 * 2 eli 6 des
PORTB = kuvio; // B-porttiin 0000 0110

19
Sitten luku-vakion arvo, joka on 3, kerrotaan luvulla 2 ja tulo sijoitetaan kuvio-
muuttujan uudeksi arvoksi. Välittömästi tämän jälkeen kuvio sijoitetaan B-porttiin.

VAKIOT
Arvoa ei voi muuttaa.
Kirjoitusasu määrittää vakioiden tyypin:

kokonaisluku on int tai long int, esim 1, 1234


desimaaliluku on float (esim 5.67)
’a’ merkit ovat char tyyppiä, hipsut
”a” ja ”abc” ovat merkkijonoja, sitaatit

const int Portti=23;

Bitti ja tavu
Pienin tiedon yksikkö on bitti. Se arvo voi olla nolla tai yksi, 0 tai 1.
Tavu on kahdeksan bittiä.
Jokainen tavun bitti voi olla 0 tai 1,
joten erilaisia tavuja voi olla kaikkiaan 2 potenssin 8 eli 256 kappaletta.

Operaattorit, operators
Mikro-ohjaimessa tietoa voidaan käsitellä monella eri tavalla. Ensin data pitää
sijoittaa SRAM-muistiin muuttujan arvoksi ja siirtää sitten itse prosessorin tiedon
käsittely-yksikköön, akkuun, tms. rekisteriin. Se tehdään sijoitusoperaattorilla.

Oikeastaan sijoitus on tiedon kopiointia paikasta toiseen. Tiedon työstämiseen


tarvitaan siihen tarkoitetut toimijat, operaattorit. Koska prosessori käsittelee lukuja,
luonnollisesti operaattorit, jotka osaavat perusmatemaattiset toiminnot, ovat tarpeen.
Kun vertaillaan kahta muuttujaa keskenään, sitäkin varten tarvitaan oma operaattori.
Operaattori on nimi tai symboli toiminnolle, joka panee ohjelman tekemään jotain
operandille, esimerkiksi muuttujalle. Operaattoreita on hyvin monenlaisia. Niiden
tunteminen on yhtä tärkeää kuin muuttuja-käsitteen hahmottaminen. Operandi on se
olio, jota operaattori operoi, käsittelee.

Kaikki C-kielen operaatiot ovat lausekkeita.


- sijoitusoperaattorit
- matemaattiset operaattorit
- vertailuoperaattorit
- loogiset operaattorit

20
Sijoitusoperaattorit, Assignment Operators
Sijoitusoperaattorilla annetaan muuttujalle arvo. C-kielen sijoitusoperaattori on
matematiikan yhtäsuuruusmerkki (=)-merkki. C-kielen yhtäsuuruus on kaksi on-
merkkiä yhdessä eli ( ==).

Sijoitusoperaattorin formaatti:
tunniste = arvo;

HUOM!
Yllä oleva lauseke ei tarkoita sitä, että tunniste ja arvo olisivat yhtä suuria, vaan,
että arvo sijoitetaan tunniste-muuttujan arvoksi.

Sijoitusoperaattorin oikealla puolella oleva arvo, (rvalue, right value) sijoitetaan sen
vasemmalla puolella olevan muuttujan arvoksi (lvalue, left value).

Operaattori, operator Merkitys, Meaning Esimerkki, Example


= sijoitus x = 3;

Taulukko 5.2.2. Sijoitusoperaattori

Esimerkki sijoitusoperaattorin käytöstä

unsigned int muuttuja;


/* esitellään etumerkitön kokonaislukumuuttuja nimeltä muuttuja, sen arvo on tässä
vaiheessa epämääräinen, ’mitä sattuu’ */
muuttuja = 5; // sijoitus, nyt muuttujassa on arvo 5

Aritmeettiset operaattorit, Arithmetic Operators


Aritmeettisilla operaattoreilla suoritetaan laskutoimituksia. Jos osaa peruslasku-
toimitukset, siis yhteen-, vähennys-, jako- ja kertolaskut, niin osaa käyttää C-kielen
aritmeettisia operaattoreitakin. Operaattorien suoritusjärjestys on sama kuin
matematiikassa. Aritmeettiset operaattorit on esitelty taulukossa 5.2.3

Operaattori, Operator Merkitys, Meaning Esimerkki, Example


+ yhteenlasku, addition a = x + y;
- vähennyslasku, subtraction a = x – y;
* kertominen, multiplication a = x * y;
/ jakaminen, division a = x / y;
% jakojäännös, modulus a = x % y;

Taulukko 5.2.3. Aritmeettiset operaattorit

Operandien (taulukon esimerkin x ja y), joita aritmeettiset operaattorit käsittelevät,


täytyy olla numeerisia arvoja. Operandi on siis joko kokonaisluku, liukuluku tai
merkki.

21
Modulus- eli jakojäännösoperaattori.
Jakojäännös jää, kun kokonaisluku jaetaan toisella kokonaisluvulla.
Modulusoperaattori palauttaa kokonaislukujakolaskun jakojäännöksen.

unsigned int jako, jaettava = 8, jakaja = 3;


jako = jaettava % jakaja;
// jako = 8 % 3, eli jako saa arvon 2, joka on jakojäännös

Sijoitus- ja matemaattisten operaattoreiden yhdistäminen


Ohjelmoitaessa tulee usein eteen tapaus, jossa muuttujaan lisätään jokin arvo ja tulos
sijoitetaan takaisin muuttujaan. esim.
virta = virta + 2;

Lisätään luku 2 muuttujaan virta ja sijoitetaan summa virta-muuttujan uudeksi


arvoksi.
Toinen tapa esittää sama asia:
virta += 2;

Näin koodi yksinkertaistuu, mutta samalla se tulee vaikealukuisemmaksi.

Operaattori Merkitys Esim. yhtä kuin


+= summaus ja sijoitus, addition update x += 2; x = x + 2;
-= erotus ja sijoitus, subtration update x -= 2 x = x - 2;
*= kertolasku ja sijoitus, multiplication x *= 2 x= x * 2;
update
/= jakolasku ja sijoitus, division update x /= 2 x = x / 2;

%= muistinumerojako ja sijoitus, modulus x %= 2 x = x % 2;


update
<<= siirto vasemmalle ja sijoitus, left shift x <<= 2 kaikki bitit 2 askelta
update vasemmalle
>>= siirto oikealle ja sijoitus, rigth shift x >>= 3 kaikki bitit 3 askelta
update oikealle
&= bitti bitiltä AND ja sijoitus, bitwise x &= 2
AND update
|= bitti bitiltä OR ja sijoitus, bitwise OR x |= 2
update
^= bitti bitiltä XOR ja sijoitus, bitwise x ^= 2
XOR update

Taulukko. 5.2.4. Yhdistetyt sijoitusoperaattorit.

Aluksi kannattaa käyttää vain yksinkertaista sijoitusoperaattoria (=). Käytä


yhdistettyjä sijoitusoperaattoreita vasta, kun olet opinnoissa alkeita pidemmällä.

22
Matemaattisten operaattoreiden käyttö

Harjoituksia
1. Anna muuttujille uusia, pieniä arvoja (jotta voit seurata koodia) ja totea
ohjelman eteneminen ja aritmeettisten operaattorien toiminta. Viive
suuremmaksi?
2. Lisää kaksi laskutoimitusta peräkkäin samalle käskyriville.
- esim. luku = luku + 3 – 2;

Vertailuoperaattorit, Relational Operators


Vertailuoperaattorit vertaavat kahta arvoa keskenään, kuten ”Onko luku suurempi
kuin 10?” , ”Ovatko luku_1 ja luku_2 yhtä suuria?”, ”Onko a pienempi kuin b?”. Jos
vertailun tulos on tosi, lausekkeen arvo on 1, muussa tapauksessa lausekkeen arvo on
nolla eli epätosi. C-kielen vertailuoperaattorit on esitelty taulukossa 5.2.5.

Operaattori Merkitys Esimerkki


< pienempi kuin, less than x<y
<= pienempi tai yhtä suuri kuin, less than or equal to x <= y
> suurempi kuin, greater than x>y
>= suurempi tai yhtä suuri kuin, greater than or equal to x >= y
== yhtä suuri kuin, equals x == y
!= erisuuri kuin, not equals x != y

Taulukko 5.2.5. Vertailuoperaattorit.

esim.
x<y on tosi, true, jos x on pienempi kuin y, muuten se on epätosi, false.
5<6 on tosi.
6>8 on epätosi.

Harjoitukset tehdään ohjausrakenteiden yhteydessä.

23
Unaarioperaattori, Single Operand
C-kieleen kuuluu ns. unaarioperaattori, se operoi vain yhtä arvoa. Niitä ovat jo edellä
opittu tilde (invertoi bitit) ja tässä esiteltävät lisäys- ja vähennysoperaattorit.

Lisäys- ja vähennysoperaattorit, Increment and Decrement Operators

Yleisin sulautetun systeemin muuttujaan lisättävä tai siitä vähennettävä arvo on 1.


C-kielessä on tähän tarkoitukseen omat operaattorit; lisäys- eli inkrementointi-
operaattori (++) ja vähennys- eli dekrementointioperaattori (--). Lisäysoperaattori
lisää ykkösen operandiinsa ja vastaavasti vähennysoperaattori vähentää ykkösen.

Operaattori Merkitys Esimerkki


++ etuoperaattori, inkrementointi, pre increment ++x;
jälkioperaattori, inkrementointi, post x++;
increment
-- etuoperaattori, dekrementointi, pre --x;
decrement
jälkioperaattori, dekrementointi, post x--;
decrement

Taulukko 5.2.6. Lisäys- ja vähennysoperaattorit.

Esim.
unsigned int i = 2;
i++; // tarkoittaa: i = i + 1; eli i saa arvon 2 + 1 eli on 3
toinen tapa esittää sama toiminto: i += 1;

i--; // tarkoittaa: i = i - 1; eli jos i on 3, niin nyt saa arvon 3-1 eli 2
toinen tapa esittää sama toiminto: i -= 1;

Lisäys - ja vähennysoperaattoreita voidaan käyttää kahdella eri tavalla, ennen tai


jälkeen operandin. Voidaan kirjoittaa siis joko ++i, tai i++.

Etuoperaattori
Jos operaattori on ennen operandia, siis kuten ++i, puhutaan etuoperaattorista. Käsky
toimii niin, että ensin i-muuttujaan lisätään yksi ja vasta sitten sitä käytetään.
int a, b, x = 5;
a = ++x;
- ensin x inkrementoidaan eli x on yhtä kuin 5+1 eli 6 ja sitten tämä 6 sijoitetaan a:n
arvoksi.

toinen esimerkki:
kierrosluku = 2* ++i;
- ensin i = i + 1
- sitten i = i * 2
- ja sitten kierrosluku = i

24
Jälkioperaattori
Jos operaattori on operandin jälkeen, siis kuten i++, puhutaan jälkioperaattorista.
Käsky toimii niin, että ensin operandia käytetään ja sitten vasta kasvatetaan sitä.
int b = 5, x = 6;
b = x++;
- ensin arvo x sijoitetaan b:n arvoksi eli b on 6 ja sitten x inkrementoidaan eli x on 7.

toinen esimerkki:
kierrosluku = 2 * i++;
- ensin kierrosluku = 2 * i
- sitten i = i + 1

* etuoperaattori suoritetaan ennen sijoitusta


ja
* jälkioperaattori suoritetaan sijoituksen jälkeen.

Yksinkertaisissa lauseissa, joissa halutaan vain muuttaa muuttujan arvoa, ei


merkintätavoilla ole eroa.
Mutta monimutkaisissa lauseissa järjestyksellä on merkitystä. Jos esim. yhteenlasku
ja sijoitus suoritetaan samassa lauseessa, toimintojen suoritusjärjestyksellä on todella
merkitystä. Joten ole tarkkana.

Tässä esimerkkikoodi incrementoinnista, olen jättänyt ylimääräiset rivit pois.


Täydennä ja aja koodi.

int main(void)
{
DDRB = 0xFF;
uint8_t luku = 0;

while (1)
{
luku++;
PORTB = luku;
wait(50);
}
}

Analyysi:
1. Mitä tapahtuu kun muuttuja saa arvon 0xFF?

25
Otetaan seuraavaksi siirto-operaattori, koska sillä saadaan edelliseen liittyviä
opettavaisia pikku harjoituksia. Ja toisaalta, ’pienuudestaan’ huolimatta, siirto-
operaattorit tekevät sulautetuissa järjestelmissä useasti hyvinkin tarpeellisia
toimintoja.

Bittikohtaiset siirto-operaattorit
Siirto-operaattorikäskyt siirtävät rekisterin bittejä vasemmalle tai oikealle, niin monta
askelta kuin ohjausnumero edellyttää. Jos esim. siirretään bittejä yhden askeleen
vasemmalle, niin äärimmäisenä vasemmalla oleva bitti 'putoaa roskakoriin' ja
oikeanpuolimaiseksi bitiksi nousee 'hatusta’ nolla.

Kuva 5.2.2. Bittien siirto.

Bittikohtainen siirto vasemmalle

x << 1;
Rekisterin bitit siirtyvät yhden askeleen vasemmalle.

x << 2;
Rekisterin bitit siirtyvät kahden askeleen verran vasemmalle.

x = x << 1;
Muuttujan x bitit siirtyvät yhden pykälän vasemmalle, oikeanpuolimaiseksi bitiksi
tulee 0.
Tämä on käytännössä sama kuin kertominen kahdella.

Huom!
siirto-operaatio ei muuta muuttujan arvoa, ellet käytä sijoitusoperaatiota
x = x >> 3;

26
Bittikohtainen siirto oikealle

x >> 1;
Rekisterin bitit siirtyvät yhden askeleen oikealle.

x >> 3;
Rekisterin bitit siirtyvät kolme askelta oikealle.

x = x >> 1;
Muuttujan x bitit siirtyvät yhden pykälän oikealle, vasemmanpuolimaiseksi bitiksi
tulee 0. Tämä on käytännössä sama kuin jakaminen kahdella.

Käskyn toiminta selviää parhaiten tutkimalla ja ajamalla ohjelmia.

Bittien siirto oikealle, esimerkki


/**********************************************************
Project : siirto_1.c
Hardware: PV-M32 + PV-LEDIT on PORTB
Software: WinAVR-20071221
Date : 05.01.2008
Author : pva
Comments: bittien siirto oikealle
**********************************************************/
#include <avr/io.h>
#include <util/delay.h>
#define WAIT(time) for(uint16_t i=0;i<2000;i++)_delay_loop_2(time);

int main(void)
{
DDRB = 0xFF;
uint8_t bitti = 0x80; // 1000 0000 bin

PORTB = bitti;
WAIT(1000);

while (1)
{
bitti = bitti >> 1; // siirretään bittejä 1 askel oikealle
// tulos asetetaan bitti-muuttujan uudeksi arvoksi
PORTB = bitti;
WAIT(300);
}
}

Analysointi

Kommentit ohjelmassa kertovat kaiken muun, paitsi ei vastausta kysymykseen:

Miksi ensimmäisen kierroksen jälkeen LEDit pysyvät pimeinä?


Muuta koodia niin, että loistava LEDi siirtyy vasemmalta oikealle uudestaan ja
uudestaan.

27
Operaattoreiden suoritusjärjestys
Operaattoreilla on evaluointijärjestys, precedence, joka määrää operaattoreiden
kutsujärjestyksen lausekkeessa.
Jos lausekkeessa on useita samanarvoisia operaattoreita, käytetään ns.
assosioituvuutta, associativity:
- yksioperandiset ja sijoitusoperaattorit evaluoidaan oikealta vasemmalle
- muut vasemmalta oikealle
- järjestystä voidaan muuttaa käyttämällä sulkeita

Operaattori Order of evaluation Remarks


[] () -> vasemmalta oikealle Array subscript, function call
- + sizeof() ! ++ --
& * ~ (cast) oikealta vasemmalle unary
*/% vasemmalta oikealle binary multiplicative
+- vasemmalta oikealle binary additive
>> << vasemmalta oikealle siirto-operaatiot
< <= > >= vasemmalta oikealle relational operators
== != vasemmalta oikealle yhtäsuuruus, erisuuruus
& vasemmalta oikealle bitwise AND operator
^ vasemmalta oikealle bitwise XOR
| vasemmalta oikealle bitwise OR
&& vasemmalta oikealle Logical And operator
|| vasemmalta oikealle Logical Or
?: vasemmalta oikealle conditional operator
= += -= *= /= %=
&= -= |= <<= >>= oikealta vasemmalle assignment
, oikealta vasemmalle comma, pilkku

Taulukko 5.2.7. Operaattoreiden suoritusjärjestys.

28
Sulautettujen systeemien operaattorit, yhteenveto

Useimmin käytetyt operaatiot ovat:


- summaus
- vähentäminen
- kertominen
- jakaminen

Sijoitus
= PORTB = 0x55;
käytetään lauseissa, joissa muuttujaan sijoitetaan lukuarvo taikka suoritetaan muu lauseke

Aritmetiikka
summa + Rtot = R1 + R1;
erotus - Rtot = R1 – R2;
tulo * Rtot = 2*R1;
osamäärä / Rtot = R1/2;
jakojäännös % taajuus_ero = f%50;
* operaatiota käytetään tavallisimmissa matemaattisissa lauseissa
Vertailu
yhtäsuuri ==
pienempi < if (jann < vert)

suurempi > if (virta1 > virta2)


yhtä suuri tai suurempi >=
yhtä suuri tai pienempi <= if (x <= y)

eri suuri != if (virta1 != virta2)

vertailuoperaatioita käytetään yleensä ns haarautuvissa ohjausrakenteissa, joissa ohjelma etenee


vertailun tuloksen perusteella, kuten if-ehtolause ja while-toistosilmukka

Lisäys ja vähennys
lisäys, inkrementointi ++ tavu++;
vähennys, dekrementointi -- teho--;
askeltaa yhdellä ylös- tai alaspäin esim. rekisterin sisältöä tai muuttujan sisältöä

Loogiset operaatiot
JA, AND &
TAI, OR |
ehdoton tai, XOR ^
invertointi ~
siirto vasemmalle <<
siirto oikealle >>
Esim. 8 bit tavujen , rekisterien tai muuttujien sisällön muokkaaminen tavallisilla digitaalitekniikan
operaatioilla. Näitä operaatioita käytetään sulautettujen järjestelmien ohjelmoinnissa.

Loogiset
JA-operaatio &&
TAI-operaatio ||
EI, NOT !
operaatioita käytetään yhdistämään vertailuoperaatioita. Älä sekoita näitä edellisiin operaattoreihin.

29
Varatut sanat
Varattuja sanoja C-kielessä on 32 kpl. Joku niistä määrää CPU:n tekemään jotain
erikoista, toisia yhdessä muiden kanssa kertomaan ohjelmalle mitä tehdä, joitakin et
tule käyttämään milloinkaan. Varatut sanat kirjoitetaan pienellä. Niitä ei saa käyttää
muuhun kuin varattuun tarkoitukseen. Vältä varattuja sanoja muistuttavia
muuttujanimiä.

auto break case char const continue default


do double else enum extern float for
goto if int long register return short
signed sizeof static struct switch typedef union
unsigned void volatile while

30

You might also like