Ethvert C-program starter med funktionen 'main'. På en microcontroller som AVR, vil det typisk se sådan ud for et 'tomt' program, der gør 'ingenting':
int main()
{
/* call program-initialization from here */
while(1) /* do this forever */
{
/* this is your main-loop; you might want to put some code here */
}
return(0); /* (never reached) */
}
første ting du ser, er 'int'. Dette er en variabel-type. Vi har en funktion vi kalder 'main', denne vil returnere en værdi. Derfor er vi nødt til at fortælle hvilken type værdi den skal returnere.
En int er lidt et vidt begreb. På en microcontroller som AVR vil den nok typisk være 16 bit bred (2 bytes).
Inde i vores funktion har vi et til keyword (nøgleord), der hedder 'while'.
While er en C-kommando og tager et enkelt udtryk. Så længe dette udtryk er sandt, vil de efterfølgende kommandoer (dem der således er inde i næste krølle-parantes blok) blive udført.
Sidst ses keyword'et 'return'. Dette er også en C-kommando. 'return' tager også et enkelt udtryk som argument, og dette udtryk bliver returneret (givet til) den kode som har kaldt rutinen (i dette tilfælde 'main').
Vi kan prøve at gøre en smule mere...
#include <avr/io.h>
#include <util/delay.h>
void initLED()
{
DDRA = 0xff; /* set all pins to become output pins */
PORTA = 0x00; /* turn off all pins (eg. set all pins low) */
}
int main()
{
initLED();
while(1) /* do this forever */
{
PORTA ^= (1 << PA0);
_delay_ms(250);
_delay_ms(250);
}
return(0); /* (never reached) */
}
Vi har nu en hel masse mere.
#include indlæser en 'header-fil', dvs. en fil, der indeholder definitioner og forskellige værdier, program-stumper, funktions-prototyper, mm.
Det er ganske rart at man har include-kommandoen. Dette er ikke direkte en C-kommando, men det er noget pre-processoren håndterer. En C-compiler består nemlig af flere dele. En pre-processor køres først, og gør et stykke arbejde på din sourcekode, og denne kode som pre-processoren så har lavet, videregives til den rigtige C-compiler. Når C-compileren har lavet sin binære fil (en 'objekt-fil'), skal linkeren overtage. Linkeren kan klistre flere binære filer sammen. Dette er praktisk, for så kan man have en masse klargjorte binære filer, så man ikke skal compile en hel masse hver eneste gang.
#include kan bruges på to forskellige slags filer.
1: #include <systemheader.h>
2: #include "userheader.h"
En 'system-header' er en headerfil, som er 'installeret' sammen med din compiler.
En 'userheader' er en headerfil, som du selv har lavet, og den ligger sammen med din source-kode (i samme directory/mappe).
OK, næste ting, er at vi definerer vores egen rutine, den kalder vi 'initLED'.
Da den ikke returnerer nogen værdi, skriver vi keywordet 'void' foran. Void betyder stort set 'ingenting', man kan også sige 'smid væk'.
I vores initLED rutine sætter vi først DDRA til at have alle bits sat. DDRA er defineret i den include-fil der hedder <avr/io.h>.
DDRA er en forkortelse, og står for 'Data Direction Register A'.
PORTA er din microcontroller's første port (hvis den altså er implementeret; for enkelte microcontrollere har ingen PORTA, men måske en PORTB eller PORTC).
PORTA bruges på 2 måder...
Hvis du har sat et port-ben til at være input, kan du sætte bitten i PORTA til 1, for at koble pull-up modstanden til. Sættes bitten til 0, kobles pull-up modstanden fra.
Hvis du har sat portbenet til at være output, og sætter portbenets bit til 1, vil dette portben sende fx. 5V ud, hvis din forsyning til microcontrolleren er 5V.
Er portbenets bit sat til 0, vil portbenet's output være 0V (GND).
Nu kommer vi ned til main...
der kalder vi rutinen 'initLED'. Da denne rutine ingen parametre (argumenter) tager, lader vi parantesen med argumenter være tom.
initLED();
-Linien skal afsluttes med semikolon. Dette er C's måde at opdele kommandoer/instruktioner.
Du kan således godt have flere kommandoer på samme linie, når de bare opdeles med semikolon.
Mellemrum, tabuleringer, Return og Linefeed karakterer behandles helt ens; dvs. alle behandles på samme måde som mellemrum.
Hvis nu initLED havde returneret en værdi, kunne vi have skrevet...
int8_t myvar;
myvar = initLED();
...så kunne vi lave udregninger på denne værdi som initLED returnerede. Men da vi har valgt at initLED ikke skal returnere en værdi, kan vi kun kalde rutinen på den måde, som koden ovenover viser.
Inde i while-løkken har vi følgende linie:
PORTA ^= (1 << PA0);
Den kan også skrives således:
PORTA = PORTA ^ (1 << PA0);
Dette kræver lidt forklaring...
Vi sætter PORTA lig med den gamle værdi af PORTA XOR (1 bitskiftet til venstre PA0 gange).
^ (hat) betyder XOR, eller Exclusive OR.
XOR ligner PLUS lidt i funktion, men XOR er en direkte binær funktion.
Et binært tal består af 0'er og 1'er, fx.
%11001001
XOR'er man 2 binære tal sammen, er resultatet af de bits der er 1 i begge tal = 0.
Er den ene bit 1 og den anden bit 0, bliver resultatet = 1.
Eks:
%1100
XOR %1001
-----
%0101
OK, nu er vi ovre XOR-delen. Vi har stadig bitskiftning.
PA0 er egentlig et konstant tal, som er defineret i avr/io.h
-Værdien for PA0 er sådan set 0, så vores linie vil se sådan ud inde bag ved:
PORTA = PORTA ^ (1 << 0);
Dette bliver regnet ud af compileren og forkortet til:
PORTA = PORTA ^ 1;
-Så alt i alt, hvad der sker, er at den laveste bit i PORTA, dvs. bitten for PA0, bliver ændret fra 0 til 1, hvis den i forvejen er 0, eller fra 1 til 0, hvis den i forvejen er 1.
Næste 2 linier ser således ud:
_delay_ms(250);
_delay_ms er en funktion der er defineret i filen util/delay.h
Den har brug for at man har defineret F_CPU til den clock-frekvens som microcontrolleren kører. Ofte vil denne clock-frekvens være 8MHz, hvis man kører med den interne oscillator. (Personligt kører jeg næsten altid med 20MHz). Indstillingen af F_CPU foregår i den fil, der hedder Makefile (hvis du bruger WinAVR eller anden compiler-distribution med gcc).