Artikel top billede

(Foto: Computerworld)

Arduino, programmering af hjemmeelektronik - del 2

Fra at installere udviklingsværktøjet og lave en simpel opstilling vil vi denne gang fokusere på faciliteterne i programmeringen af microprocessoren.

Af Kenneth Geisshirt, Alt om Data

Denne artikel er oprindeligt bragt på Alt om Data. Computerworld overtog i november 2022 Alt om Data. Du kan læse mere om overtagelsen her.

Programmeringen af Arduino er baseret på sproget Wiring, og du kan bruge et udviklingsmiljø som bygger på Processing. Alt dette kan du downloade fra Arduinos hjemmeside, og installationen af dette miljø med tilhørende oversættere blev gennemgået i første afsnit i serien.

Med Wiring får du et programmeringssprog, som mest minder om C. Det betyder, at hvis du tidligere har programmeret i C, vil du hurtigt føle dig hjemme.

Eftersom C er et proceduralt sprog, finder du ikke avancerede begreber som klasser (som i C++ eller Java) eller højere ordensfunktioner (som i Javascript). I Arduino-verdenen omtales et program som ’sketch’, men i denne artikel vil du kun læse ordet ’program’.

Sproget byder på et lille udvalg af typer. Du finder en lille oversigt i infoboksen »Datatyper«. Endvidere er det mulighed for at erklære arrays på samme måde som i C, for eksempel ved at skrive »int a[7]«. Det giver et array (navnet er ’a’) med syv elementer, alle af typen ’int’. Strenge erklæres som arrays af ’char’ − du skal dog huske at gøre plads til et NULL-tegn i slutningen af strengen.

Første element i et array har indeks 0. Som programmør skal du selv holde styr på antallet af elementer i dine arrays, men du kan bruge funktionen ’sizeof’ til at hjælpe dig.

Funktionen returnerer, hvor mange bytes en variabel fylder. Antal elementer i et array ’a’ kan derved udregnes som ’sizeof(a)/sizeof(a[0])’. Er der tale om en streng, kan du nøjes med ’sizeof(a)’, idet nævneren altid vil være 1.

Der findes et par typer af forgreninger, som du sikkert kender fra andre sprog. Med ’if’ og eventuelt ’else’ kan du lade dit program grene sig ud. Har du brug for at tjekke flere muligheder, er du nødt til at bruge en ’if’ inde i en ’else’, det vil sige noget i retning af ’if ... else if ... else’.

Med mange tjek falder læsbarheden, og med en ’switch’-konstruktion kan du lade dit program grene sig ud over mange muligheder. Hver gren begynder med en ’case’ og skal afsluttes med en ’break’.

Løkker er naturligvis også mulige. Du finder den klassiske ’for’-konstruktion. Løkke-variablen kan du erklære ved løkkens begyndelsen, hvilket giver dig et ekstra niveau af virkefelt. Du kan også opbygge løkker ved hjælp af ’while’.

Nedenfor ser du eksempler på to løkker. Der er ikke den store forskel på dem, men med ’while’ angiver du ikke nødvendigvis et bestemt antal iterationer på forhånd, mens du med ’for’ netop bruger den til det.

Løkker

void setup() {
Serial.begin(9600);
}

void loop() {
Serial.print(”for:”);
for(int i=0; i<10; i++) {
Serial.print(i);
delay(1000);
}
Serial.println(””);

Serial.print(”while:”);
int j = 0;
while (j<10) {
Serial.print(j);
j++;
delay(1000);
}
Serial.println(””);
}

Infoboks – Funktioner
void setup() {
Serial.begin(9600);
}

void forloop(int c) {
Serial.print(”for:”);
for(int i=0; i<c; i++) {
Serial.print(i);
delay(1000);
}
}

void loop() {
forloop(10);
Serial.println(””);
}
Infoboks – Lysdæmper



int ledPin = 9;

void setup() {
}

void loop() {
for(int v=0; v<=255; v+=5) {
analogWrite(ledPin, v);
delay(30);
}

for(int v=255 ; v>=0; v-=5) {
analogWrite(ledPin, v);
delay(30);
}
}

Infoboks – Interrupts
int pin = 13;
volatile int state = LOW;
int i = 0;

void setup() {
pinMode(pin, OUTPUT);
Serial.begin(9600);
attachInterrupt(0, blink, CHANGE);
}

void loop() {
noInterrupts();
digitalWrite(pin, state);
interrupts();
}

void blink() {
Serial.print(”Skifter: ”);
Serial.println(i);
i++;
state = !state;
}

Funktionerne ’Serial.begin’, ’Serial.print’ samt ’Serial.println’ henholdsvis sætter kommunikation og sender data over den serielle (usb-) port. Du kan ved hjælp af disse funktioner sende data tilbage til din computer.

Det er muligt at erklære funktioner i dine programmer. Med C’s begrænsninger, er det ikke muligt at erklære funktioner inden i funktioner.

Du finder et eksempel på erklæring af en funktion i infoboks »Funktioner«. Funktionen ’forloop’ udfører en ’for’-løkke. Det er værd at bemærke, at det er muligt at angive argumenter til funktioner.

To vigtige funktioner

Ethvert gyldigt program til dit Arduino-board kræver, at du erklærer to funktioner. Den første er ’setup’, og den anden er ’loop’.

Funktionen ’setup’ kaldes af Arduino-boardets bootloader, når programmet startes op. Du bruger ’setup’ til at initialisere globale variable og sætte boardets porte op.

Eftersom der er flere porte, som enten kan bruges til input eller output, er ’setup’ et godt sted at specificere tilstanden, inden selve hovedprogrammet går i gang.

Hovedprogrammet finder du i funktionen ’loop’. Funktionen kaldes hver gang, funktionen ’loop’ afsluttes. Det er med andre ord tale om en uendelig løkke. Det er i ’loop’, at du skal skrive den funktionalitet, som dit program sammen med boardet skal give brugeren. Du slipper med andre ord for at skrive en uendelig løkke, selv om det i det C-lignende sprog ikke ville være svært.

Seriel kommunikation

Som allerede omtalt, findes der en række funktioner, som gør det muligt at sende til, og modtage data fra, den computer, som Arduino-boardet er sat til. Det er også muligt at kommunikere med et andet instrument end din computer, hvis dette instrument har en seriel-port. Sker kommunikationen med din computer, sker den over usb-porten, men set fra Arduino-programmøreren er det en god gammeldags RS-232 port.

Sender du data tilbage til din computer, kan du se data via den serielle monitor. Du finder den i »Tools | Serial Monitor« (eller med Shift+Control+M). Det er også muligt at sende data til Arduino-boardet, men det kræver naturligvis, at dit program kan håndtere at modtage data over den serielle port.

Med funktionen ’Serial.begin’ sætter du den serielle port op. Som argument til funktionen, kan du angive den hastighed, som data skal sendes med. Den typiske værdi er 9600 (i enheden bit/sekund). Denne funktion vil du typisk kalde i funktionen ’setup’.

Med funktionerne ’Serial.print’ og ’Serial.println’ kan du sende data fra dit Arduino-board til din computer. Der sker en automatisk konvertering, således at computeren vil modtage data, som umiddelbart kan læses af mennesker.

Det betyder, at hvis du skriver ’Serial.print(42)’, vil du modtage teksten 42 – altså to tegn. Forskellen på ’Serial.print’ og ’Serial.println’ er, at den sidste lige smider et linjeskift med over til computeren. Ønsker du at sende de rå data over til computeren, er funktionen ’Serial.write’ lige, hvad du ønsker. Med ’Serial.write(42)’ får du tallet 42 sendt.

Skal du modtage data fra computeren, kan du først tjekke, hvor mange bytes, computeren har sendt over til Arduino-boardet med funktionen ’Serial.available’. Du kan læse en byte ad gangen med ’Serial.read’.

Digitalt I/O

Der findes en del digitale ben på et Arduino-board. Antallet af ben afhænger af hvilken model, der er tale om. Hvert ben kan bruges til enten input eller output. Du styrer, om et ben er et input- eller et output-ben ved hjælpe af funktionen ’pinMode’. Kaldet ’pinMode(2, OUTPUT)’ sætter ben 2 til at være et output-ben.

Er et ben et output-ben, kan du bruge funktionen ’digitalWrite(p, v)’ til at sætte en lav eller en høj spænding eller værdi (v) på ben ’p’. Tilsvarende kan du læse værdien på et input-ben med funktionen ’digitalRead(p)’.

Hvad en høj eller lav spænding på et ben betyder, afhænger af hvilken elektronik, du har forbundet til benet. Sætter du en lyddiode på benet og GND, vil en høj spænding betyde, at dioden lyser, mens en lav spænding vil lade dioden være slukket.

Analogt I/O

Alle Arduino-boards har også analoge ben – seks ben er ikke ualmindeligt. Input-benene er forbundet med en A/D converter, som omsætter spændingen på benet til en værdi mellem 0 og 1023.

Funktionen ’analogRead(p)’ returnerer værdien på ben ’p’. Du kan købe forskellige sensorer (lys, temperatur, og så videre) og sætte dem på dit board’s analoge ben og på den måde bygge kompakte måleinstrumenter.

Der findes en række porte, som kan bruges som analogt output - eller næsten analogt output. Et Arduino-board har en række PWM-porte. PWM er en forkortelse for Pulse Width Modulation.

Ideen er at skiftevis sætte en høj og en lav spænding i forskellige tidsperioder. Du kan sige, at spændingen pulser med forskellig bredde (i tid). Pulser din microprocessor hurtigt nok, vil komponenterne se det, som om spændingen er et sted mellem lav og høj.

Med en Arduino kan du bruge funktionen ’analogWrite(p, v)’ til at sætte ben ’p’ til at pulsere. Værdien af ’v’ bestemmer, hvordan komponenterne vil se spændingen, 0 svarer til lav, mens 255 svarer til høj. Programmet i infoboks »Lysdæmper« viser dig, hvordan en Arduino kan skrue op og ned for lysstyrken i en lysdiode, som er sat til ben 9 (sammen med en modstand).

Interrupts

En række Arduino-boards har muligthed for at lade eksterne hændelse give anledning til et interrupt. En interrupt-handler i Arduino er en funktion, som kaldes når et interrupt indtræffer.

Som navnet antyder (interrupt er engelsk for afbrydelse), vil interrupt-handleren afbryde, hvad programmet ellers er i gang med og hoppe direkte til funktionen.

Det betyder, at du kan programmere dit Arduino-board til at reagere på eksterne hændelser. Ofte er det de digitale ben to og tre, som kan bruges til at hændelserne.

Funktionen ’attachInterrupt(i, f, m)’ sætter en interrupt-handler op for interrupt ’i’ (0 er ben 2, 1 er ben 3) ved hjælpe af funktionen ’f’. Med ’m’ kan du styre, hvad der skal udløse et interrupt (LOW, når benet har lav spænding, CHANGE, når spændingen skifter, RISING ,når det går fra lav til høj og endelig FALLING, når benets spænding går fra høj til lav). Med funktionen ’detachInterrupt(i)’ kan du fjerne en interrupt-handler for interrupt ’i’.

Funktionerne ’noInterrupts’ og ’interrupts’ sikrer en kritisk region i dit program. En kritisk region er en række programlinjer, som du ønsker ikke må afbrydes af et interrupt.

I infoboks »Interrupts« kan du se et eksempel på, hvordan en interrupt-handling kan bruges. Til Arduino-boardet er sat en trykknap på det digitale ben 2 – og trykknappen får også strøm fra boardet (5V + GND). En global variabel tælles op for hver gang, du trykker på knappen og sender data over den serielle port.

Biblioteker

Der findes en utal af software-bibliotekter til udvikling af Arduino-programmer. Det betyder, at du ikke behøver at skrive alting fra grunden. Du har allerede set, hvordan du kan bruge ’Serial’-bibliotektet til at sende og modtage data over den serielle port.

En del biblioteker er bundet til særlig hardware, for eksempel et krystal-display, en stepper-motor eller en ethernet-controller. Der findes også en række generiske biblioteker til generering af tilfældige tal og håndtering af strenge.

Du gør klogt i at foretage et par søgninger på internettet, inden du kaster dig ud i at implementere en bestemt funktion, måske findes den allerede under en open source licens.

Næste gang I næste og sidste afsnit i serien om åben hardware og Arduino, vil vi bygge en større opstilling og udvikle software til den. Det vil give dig et større overblik i, hvordan de forskellige elementer i Arduino-verdenen hænger sammen.