Põhimõtteliselt võib C-keele programmi kirjutada ükskõik mis kujul, kas või üherealisena, sest kompilaator eeldab vaid süntaksireeglite järgimist. Samas on selguse ja lihtsuse huvides ikkagi soovitatav tähelepanu pöörata ka programmikoodi stiilile. Tüüpiline C-keele programmi ülesehitus:
/* Päisefailide kaasamine */ #include <avr/io.h> #include <stdio.h> /* Makro-deklaratsioonid */ #define PI 3.141 /* Andmetüübid */ typedef struct { int a, b; } element; /* Globaalsed muutujad */ element e; /* Funktsioonid */ int main(void) { // Lokaalsed muutujad int x; // Programm printf("Tere maailm!\n"); }
Programmi saab kirjutada teksti, mida ei kompileerita ja mis on programmeerijale abiks selgitamisel või märkmete tegemisel. Samuti saab kommentaare kasutada programmilõikude ajutiseks täitmisest kõrvaldamiseks. Näide kahte liiki kommentaaridest:
// Üherealine kommentaar. // Kommentaariks loetakse kahe kaldkriipsu järel olevat teksti /* Mitmerealine kommentaar Kommentaari algus ja lõpp määratakse kaldkriipsude ja tärnidega */
C-keele baasandmetüübid:
Tüüp | Miinimum | Maksimum | Bitte | Baite |
---|---|---|---|---|
(signed) char | -128 | 127 | 8 | 1 |
unsigned char | 0 | 255 | 8 | 1 |
(signed) short | -32768 | 32767 | 16 | 2 |
unsigned short | 0 | 65535 | 16 | 2 |
(signed) long | -2147483648 | 2147483647 | 32 | 4 |
unsigned long | 0 | 4294967295 | 32 | 4 |
float | -3.438 | 3.438 | 32 | 4 |
double | -1.7308 | 1.7308 | 64 | 8 |
Sulgudes olevat sõna “signed” ei pea kasutama, kuna vaikimisi ongi andmetüübid bipolaarsed.
AVR mikrokontrolleril int = short
PC arvutil int = long
C-keeles puudub spetsiaalne teksti-andmetüüp. Selle asemel kasutatakse char tüüpi massiive (nendest edaspidi) ja ASCII “tähestikku”, kus igal tähel ja märgil on oma järjekorranumber.
Programmis saab kasutada kindlat andmetüüpi mälupesasid - muutujaid. Muutujate nimed võivad sisaldada ladina tähestiku tähti, numbreid ja alakriipsu. Nimi ei tohi alata numbriga. Muutuja deklareerimisel kirjutatakse selle ette andmetüüp. Väärtuse omistamiseks muutujale kasutatakse võrdusmärki (. Näide muutujate kasutamisest:
char c; // char tüüpi muutuja c deklareerimine c = 65; // Muutujale c väärtuse omistamine. c = 'A'; // A on ASCII märgisüsteemis samuti väärtusega 65 // int tüüpi muutuja i20 deklareerimine ja algväärtustamine int i20 = 55; // Mitme unsigned short tüüpi muutuja deklareerimine unsigned short x, y, test_variable;
Konstante deklareeritakse samamoodi nagu muutujaid, kuid ette lisatakse const võtmesõna. Konstantide väärtust ei saa programmi käigus muuta. Näide kasutamisest:
const int x_factor = 100; // int tüüpi konstandi määramine
Baasandmetüüpidest saab struct võtmesõnaga struktuure koostada. Struktuur on justkui kombineeritud andmetüüp. Tüüpi deklareeritakse typedef võtmesõnaga. Näide struktuurist andmetüübi loomise ja kasutamise kohta:
// Uue punkti andmetüübi deklareerimine typedef struct { // x ja y koordinaat ning värvikood int x, y; char color; } point; // Punkti muutuja deklareerimine point p; // Punkti koordinaatide määramine p.x = 3; p.y = 14;
Andmetüüpidest võib koostada massiive (jadasid). Massiiv võib olla ka mitmemõõtmeline (tabel, kuup, jne). Näide ühe- ja kahemõõtmelise massiivi kasutamisest:
// Ühe- ja kahemõõtmelise massiivi deklareerimine char text[3]; int table[10][10]; // Teksti moodustamine märgimassiivist text[0] = 'H'; // Täht text[1] = 'i'; // Täht text[2] = 0; // Teksti lõpetamise tunnus (null-bait) // Tabeli ühe elemendi muutmine table[4][3] = 1;
Muutujate, konstantide ja väärtust tagastavate funktsioonidega saab koostada avaldisi (tehteid). Avaldise väärtusi saab omistada muutujatele, neid saab kasutada funktsiooni parameetritena ja erinevates tingimuslausetes.
C-keeles toetatud aritmeetilised tehted on liitmine (+), lahutamine (-), korrutamine (*), jagamine (/) ja mooduli võtmine (%). Näited aritmeetiliste tehete kasutamisest:
int x, y; // Mooduli võtmine, korrutamine ja väärtuse omistamine, x saab väärtuse 9 x = (13 % 5) * 3; // Liitev-omistav operaator, x väärtuseks saab 14 x += 5; // Ühe lahutamise kiirmeetod, x väärtuseks saab 13 x--;
Loogilised tehted on eitus (!), loogiline korrutamine (&&) ja loogiline liitmine (||). Näide tehete kasutamisest:
bool a, b, c; // Algväärtustamine a = true; b = false; // Eitus // c väärtus tuleb väär sest tõest eitati c = !a; // Loogiline korrutamine // c väärtuseks tuleb väär, sest üks operandidest on väär c = a && b; // Loogiline liitmine // c väärtus tuleb tõene, sest üks operandidest on tõene c = a || b;
NB! bool andmetüüp on pärit C++ keelest ja C-keeles see tegelikult puudub, sest selle asemel on kasutusel täisarvud, kus 0 tähistab väära ja iga muu arv tõest väärtust. Kuid mugavuse pärast on bool Kodulabori teegis siiski kasutusel ja see on defineeritud kui unsigned char. Konstant true tähistab seal väärtust 1 ja false väärtust 0.
Arvude väärtuste võrdlemisel saadakse loogilised väärtused. Võrdlustehted on samaväärsus (=, erinevus (!, suurem (>), suurem-võrdne (> , väiksem (<) ja väiksem-võrdne (< . Näide kasutamisest:
int x = 10, y = 1; // Suurem-kui võrdlustehe, mis on tõene // Tehte ümber on sulud vaid selguse pärast bool b = (5 > 4); // Erinevuse tehe // Tehte tulemus on väär b = (4 != 4); // Aritmeetiline, võrdlus ja loogiline tehe üheskoos // b tuleb väär, sest esimene loogilise korrutise operand on väär b = (x + 4 > 15) && (y < 4);
Bititehted on andmetega binaarses arvusüsteemis tegelemiseks. Neid saab rakendada kõigi täisarv-tüüpi andmetega. Bititehted sarnanevad loogiliste tehetega, kuid erinevad selle poolest, et tehe teostatakse iga bitiga eraldi, mitte kogu arvuga. C keeles on bititeheteks inversioon (~), konjunktsioon (&), disjunktsioon (|), antivalentsus (^), nihe vasakule («) ja nihe paremale (»).
// Märgita 8-bitise char-tüüpi muutuja deklareerimine // Muutuja väärtus on kümnendsüsteemis 5, kahendsüsteemis 101 unsigned char c = 5; // Muutuja c disjunktsioon arvuga 2 (kahendsüsteemis 010) // c väärtuseks saab 7 (kahendsüsteemis 111) c = c | 2; // Bitinihe vasakule 2 võrra // c väärtuseks saab 28 (kahendsüsteemis 11100) c = c << 2;
Bititehted on hädavajalikud mikrokontrollerite registrite kasutamisel. Täpsemalt tutvustab neid AVR registrite peatükk.
Funktsioon on programmilõik, mida saab selle nime järgi täitmiseks välja kutsuda. Funktsioonil võivad olla parameetrid ja funktsioon võib tagastada ühe väärtuse. Kui funktsioon ei tagasta väärtust, on selle tüüp void. Kui funktsioonil pole parameetreid, tuleb vanemat C-keele kompilaatorit kasutades void kirjutada ka parameetrite deklaratsiooni asemele. Näide liitmisfunktsioonist ja tagastamisväärtuseta funktsioonist:
// Kahe int tüüpi parameetriga funktsioon, mis tagastab int-tüüpi väärtuse int sum(int a, int b) { // Kahe arvu liitmine ja summa tagastamine return a + b; } // Ilma parameetrite ja tagastatava väärtuseta funktsioon void power_off(void) { }
Funktsiooni kasutamiseks tuleb see välja kutsuda. Funktsioon peab programmikoodis olema deklareeritud enne välja kutsumise kohta. Näide liitmisfunktsiooni väljakutsumisest.
int x; int y = 3; // Liitmisfunktsiooni väljakutsumine, kus parameetriteks on muutuja ja konstandi väärtus x = sum(y, 5); // Väljalülitamise funktsiooni väljakutsumine, parameetrid puuduvad power_off();
C-keele programmi täitmist alustatakse main nimelisest funktsioonist, mis teeb selle kohustuslikuks funktsiooniks.
Tingimuslause võimaldab vastavalt sulgudes oleva avaldise tõesusele täita või mitte täita tingimusele järgnevat lauset või programmilõiku. Tingimuslause võtmesõna on if. Näide kasutamisest:
// Avaldis on tõene ja lause täidetakse, // sest 2 + 1 on suurem kui 2 if ((2 + 1) > 2) x = 5; // Kui x on 5 ja y on 3, siis täidetakse järgnev programmilõik if ((x == 5) && (y == 3)) { // Suvaline tegevus y = 4; my_function(); }
Tingimuslause võib olla pikem ja sisaldada ka lauset või programmilõiku, mis täidetakse avaldise mittetõesuse korral. Selleks tuleb if tingimuslause järel kasutada else võtmesõna.
// Kas x on 5 ? if (x == 5) { // Suvaline tegevus z = 3; } // Kui x ei olnud 5, kas siis x on 6 ? else if (x == 6) { // Suvaline tegevus q = 3; } // Kui x ei olnud 5 ega 6... else { // Suvaline tegevus y = 0; }
Kui on vaja võrrelda avaldist mitme erineva väärtusega, on mõistlik kasutada valikulauset switch võtmesõnaga. Näide kasutamisest:
int y; // Tingimuslause y võrdlemiseks switch (y) { // y on 1 ? case 1: // Suvaline tegevus function1(); break; // y on 2 ? case 2: // Suvaline tegevus function2(); break; // Kõik muud juhtumid default: // Suvaline tegevus functionX(); // break lauset pole vaja, // kuna võrdlemine lõpeb nagunii }
Tsüklitega saab programmilõiku täita mitmeid kordi.
while võtmesõnaga tähistatud programmilõiku täidetakse seni, kuni sulgudes olev avaldis on tõene
int x = 0; // Tsükkel kestab seni, kuni x on väiksem kui 5 while (x < 5) { // x suurendamine ühe võrra x++; }
for võtmesõnaga tsükkel sarnaneb while tsüklile, kuid lisaks on sulgudes ära määratud enne tsüklit täidetav lause ja iga tsükli ajal täidetav lause
Näide:
int i, x = 0; // Algul määratakse i nulliks. Tsüklit täidetaks seni, kuni // i on vähem kui 5. Iga tsükli lõpus suurendatakse i ühe võrra for (i = 0; i < 5; i++) { // x suurendamine 2 võrra x += 2; } // Siinkohal tuleb x väärtuseks 10
while ja for tsüklitest saab erandkorras väljuda break võtmesõnaga. continue võtmesõnaga saab alustada järgmist tsüklit ilma järgnevat koodi täitmata
int x = 0, y = 0; // Lõputu tsükkel, kuna 1 on loogiline tõesus while (1) { // Tsüklist väljutakse, kui x on saavutanud väärtuse 100 if (x >= 100) break; // x suurendamine, et tsükkel kunagi lõppeks ka x++; // Kui x on 10 või vähem, siis alustatakse järgmist tsüklit if (x <= 10) continue; // y suurendamine y++; } // Siinkohal on y väärtus 90
Tekstitöötlusfunktsioone on mikrokontrolleri puhul vaja eelkõige teksti kuvamiseks LCD ekraanile ja selle saatmiseks/vastuvõtmiseks kommunikatsiooniliideste kaudu.
sprintf funktsioon toimib sarnaselt C-keeles üldlevinud printf funktsiooniga. Erinevuseks on funktsiooni tulemuse väljastamine puhvrisse (märgimassiivi), mitte standardväljundisse (mis on mikrokontrolleritel enamasti jadaliides, PC peal konsooli aken). Funktsioonil on muutuv arv argumente, mida vormindatakse vastavalt formaadile.
Näide 1:
#include <stdio.h> char buffer[20]; int a = 1, b = 2; int len = sprintf(buffer, "%d pluss %d on %d", a, b, a + b); // Tulemuseks on tekstistring "1 pluss 2 on 3"
Näiteks kirjutab sprintf funktsioon märgimassiivi buffer teksti, mis on koostatud kolmest argumendist ja on vormindatud vastavalt formaadi tekstistringile. Formaadi tekstistringis tähistab marker (inglise keeles specifier) %d täisarvu tüüpi argumenti, mis tuleb tekstis asendada. Koostatud teksti lõppu lisatakse automaatselt null-bait, mis tähistab tekstistringi lõppu. Kasutaja peab kindlustama, et koostatava teksti ja null-baidi pikkus ei ületaks märgimassiivi pikkust. Sprintf funktsioon lihtsustab fraaside ja lausete koostamist erinevat tüüpi muutujatest. Funktsioon tagastab märgimassiivi salvestatud teksti pikkuse (pikkus ei sisalda null-baiti). Vea korral tagastatakse negatiivne arv.
Näide 2:
#include <stdio.h> char x[20]; sprintf(x, "%s on %d aastat vana", "Juku", 10); // Sama tulemuse saaksime ka konstantselt defineerida: char x[] = "Juku on 10 aastat vana";
Teise näite formaadi tekstistringis asendatakse marker %s esimese teksti-tüüpi argumendiga ja %d teise täisarv-tüüpi argumendiga. Niipalju, kui on markereid, peab olema ka argumente ning nende tüübid peavad klappima. Samuti peavad argumendid esitatud samas järjekorras nagu markerid. Erinevat tüüpi argumentide jaoks on olemas vastavad formaadi markerid:
Marker | Kirjeldus | Näide |
---|---|---|
%c | Tähemärk | 'a' |
%i või %d | Täisarv | 123 |
%f | Murdarv | 3.14f |
%s | Tekst | “näide” |
%X | Heksadetsimaalarv | 0x3F |
Standardteegi päisefailides stdio.h, stdlib.h ja string.h on veel terve hulk erinevaid funktsioone erinevate teksti-operatsioonide teostamiseks. Näiteks on võimalik nendega teksti muundada arvudeks, tekste liita ja võrrelda, tekstist märke otsida jpm.
Juhuarvude genereerimine ei olegi AVR kontrolleril väga lihtne. Esmalt tuleb juhunumbrigeneraator seemendada arvuga, mille alusel genereeritakse suvaliste numbrite jada. Sama numbri järgi genereeritakse alati sama jada, seega suurema juhuslikkuse saavutamiseks on kasulik seemendus teha arvuga, mis ei ole alati sama - näiteks kasutada selleks kellaaega või lahti ühendatud ADC sisendist väärtust.
Näide, juhuarvu genereerimiseks vahemikus 1 kuni 16 (kaasa arvatud).
#include <stdlib.h> srand(100); // Suvaline seemendusarv int x = 1 + (rand() % 16);