Klasser och bibliotek

Klasser och bibliotek

I detta kapitel ska vi skapa en egen klass som vi sedan kan skapa flera objekt utav. Vi ska göra det i form av ett bibliotek, så att även andra programmerare kan dra nytta av vårt jobb. Detta exempel visar hur bra objektorienterad programmering är ur ett samarbetsperspektiv. Genom att dokumentera sina klasser väl (och skapa många öppna/flexibla metoder) kan flera programmerare jobba parallellt med varsin del av ett större program. 

Morse-bibliotek

I While-loop byggde vi en SOS-blinkare som kunde blinka Morsebokstäverna S och O. I detta exempel vill vi bygga en mer flexibel lösning som fungerar för alla Morsebokstäver. När vår lösning är klar kan vi ersätta den långa koden i While-loop med en enda kodrad!

blinker.pulseText("sos");

Som koden ovanför antyder ska vi skapa ett objekt som heter blinker. Det objektet har en metod som heter pulseText och används för att skicka Morsemeddelanden. Översättningen från textsträngar till Morsekommandon sker alltså inte i sketchen utan i klassen som blinker är en instans utav. 

För att kunna skapa blinker-objektet måste vi först skapa dess klass. Vi vill att den klassen ska vara lätt att använda och ha många funktioner. Klassen ska kunna användas för både enskilda tecken och hela textsträngar. Den ska också kunna användas för olika GPIO-stift och för olika Morsepulslängder (hastigheter). 

När klassen är klar kommer vi att paketera den i ett bibliotek, så att andra programmerare kan dra nytta av vårt jobb (precis som vi har dragit nytta av en annan programmerares Button-bibliotek). 

Ett bibliotek består av minst fyra filer:

  • klassens header-fil som beskriver själva klassen
  • klassens källkodsfil som innehåller själva programkoden
  • en exempelfil som visar hur biblioteket kan användas
  • en nyckelordsfil som ger utvecklingsmiljön kännedom om bibliotekets funktioner.

Header- och källkodsfil

Vi vill att vår klass ska heta MorsePulse. Det innebär att header-filen ska heta MorsePulse.h och källkodsfilen ska heta MorsePulse.cpp. Vi inleder med att skapa header-filen genom att klicka på pilen längst till höger, välja Ny flik och namnge filen MorsePulse.h.

Vi skapar en ny fil vid namn MorsePulse.h.

Header-filer skrivs enligt följande mall.

#ifndef MorsePulse_h
#define MorsePulse_h

#include "Arduino.h"

class MorsePulse {
  public:

  private:
};

#endif

Ofärdig version av MorsePulse.h

Lägg märke till att vi inleder med #ifndef MorsePulse_h (och avslutar med #endif). Detta är en säkerhetsåtgärd som förhindrar att någon råkar inkludera samma bibliotek flera gånger.  

Lägg även märke till att #include "Arduino.h" står i inledningen. Denna instruktion behövs för att ge klassen tillgång till alla standardfunktioner som Arduino har (vanliga sketcher får tillgång till dem per automatik). 

Vi kommer att återvända till vad som ska stå under public respektive private, men först måste vi skriva själva källkodsfilen. Det gör vi genom att klicka på pilen längst till höger, välja Ny flik och namnge filen MorsePulse.cpp.

Tips! Arduino IDE sparar automatiskt MorsePulse.h- och MorsePulse.cpp-filerna i samma mapp som sketchen du arbetar med. Om du har stängt MorsePulse.h- och MorsePulse.cpp-filerna och vill öppna dem igen, gör du det genom att öppna sketchen som delar mapp med de aktuella filerna. Det går inte att öppna enbart MorsePulse.h- eller MorsePulse.cpp-filen i Arduino IDE. Det går däremot att öppna dessa filer enskilt med andra utvecklingsmiljöer, till exempel med Sublime Text (se Sublime Text).

MorsePulse.h- och MorsePulse.cpp-filerna öppnas tillsammans med en sketch.

I källkodsfilen börjar vi med att inkludera Arduinos standardfunktioner och header-filen.

Del 1/6 av MorsePulse.cpp

Vi skapar sedan klassens så kallade konstruktor. Konstruktorn är den funktion som körs när någon skapar ett nytt objekt av klassen. Konstruktorn ska ha samma namn som klassen, och alla funktioner som hör till klassen ska namnges med klassens namn följt av dubbelkolon och funktionens namn. Konstruktorn ska således heta MorsePulse::MorsePulse

Vår konstruktor ska ha två argument (d.v.s. två attribut som ska anges när ett nytt objekt skapas):

  • vilket GPIO-stift som ska användas
  • vilken Morsepulslängd som ska användas.

GPIO-stiftet lagrar vi i en bytevariabel vid namn ledPin och hastigheten lagrar vi i en osignerad long-variabel vid namn morseSpeed. Vi flyttar sedan innehållet i ledPin- och morseSpeed-variablerna till två nya variabler vid namn _ledPin och _morseSpeed. Anledningen till detta är att vi vill kunna komma åt GPIO-stiftnumret och Morsepulslängden utanför konstruktorn. Vi hade kunnat namnge de nya variablerna med helt andra namn, men namngivningskonventionen inom APL säger att variablerna ska ha samma namn med inledande understreck. 

Del 2/6 av MorsePulse.cpp

Lägg märke till att vi inte har deklarerat variablerna än (och vi kan inte initiera variabler som inte har deklarerats). Vi hoppar därför tillbaka till header-filen (MorsePulse.h) och deklarerar variablerna där. Vi kan antingen deklarera dem som publika variabler eller som privata variabler. Dessa två variabler ska endast användas inuti MorsePulse-klassen och vi sätter dem därför som privata variabler. MorsePulse-konstruktorn ska däremot vara publik så att den går att anropa från andra sketcher. 

#ifndef MorsePulse_h
#define MorsePulse_h

#include "Arduino.h"

class MorsePulse {
  public:
    MorsePulse(byte ledPin, unsigned long morseSpeed);

  private:
    byte _ledPin;
    unsigned long _morseSpeed; 
};

#endif

Ofärdig version av MorsePulse.h

Tillbaka i källkodsfilen (MorsePulse.cpp) är det dags att skapa två nya funktioner. Den ena heter shortPulse och används för att skapa en kort blinkning. Den andra heter longPulse och används för att skapa en lång blinkning. Båda funktionerna har ett argument, pulses, som anger hur många gånger en kort eller lång blinkning ska göras.

Vi använder variabeln  _morseSpeed som referens för hur länge en kort respektive lång puls ska vara. Om _morseSpeed sätts till 200 ms kommer en kort blinkning att vara 200 ms lång och en lång blinkning att vara 600 ms lång (_morseSpeed * 3).

Del 3/6 av MorsePulse.cpp

Enligt Morsespråket ska bokstäver skiljas med en paus som motsvarar tre korta pulser. Hela ord ska skiljas med en paus som motsvarar sex korta pulser. Vi skapar därför två nya funktioner som ger oss just dessa pauslängder: nextLetter och nextWord.

Del 4/6 av MorsePulse.cpp

Klassens näst sista funktion namnger vi pulseLetter. Det är denna funktion som ska översätta alla bokstäver till Morsekommandon. Funktionen tar emot en bokstav som ett argument. Sedan går bokstaven igenom en switchsats för att hitta vilken kombination av Morsepulser som den motsvarar. Avslutningsvis anropas kombinationer av shortPulse och longPulse för att åstadkomma blinkningarna. 

Obs! Du kan ladda ned översättningen mellan bokstäver och Morsekoder från github.com/kjellcompany. Om du skriver av boken (och inte vill skriva av allt) räcker det med att skriva av bokstäverna d, e, h, l, o, r, w och mellanrum. 

Del 5/6 av MorsePulse.cpp

Klassens sista funktion namnger vi pulseText. Den funktionen ska dela upp strängar i separata bokstäver och loopa bokstäverna efter varandra genom pulseLetter-funktionen. Eftersom Morse inte gör skillnad på versaler och gemener, börjar vi med att göra om alla bokstäver till gemener. Det gör vi genom att anropa metoden toLowerCase. Sedan skriver vi en for-loop som loopar igenom bokstäverna i text-strängen.

Del 6/6 av MorsePulse.cpp

Obs! Lägg märke till att text beter sig mer som ett objekt än som en variabel. Det är för att String i själva verket är en klass och text är ett String-objekt. Text-objektet har bland annat metoderna toLowerCase (för att omvandla till små bokstäver), length (för att avgöra hur lång strängen är) och charAt (för att få ut tecknet vid en specifik position).  

Alla nya funktioner måste också finnas med i header-filen. Vi växlar därför tillbaka till MorsePulse.h-fliken och lägger in funktionerna. PulseLetter och pulseText är två funktioner som ska kunna anropas utanför själva klassen (publikt). Det finns däremot ingen anledning till att anropa shortPulse, longPulse, nextWord eller nextLetter utanför klassen. 

Färdig version av MorsePulse.h

Nyckelordsfil

En nyckelordsfil innehåller en lista över namn på klasser och funktioner som biblioteket har. Den behövs för att utvecklingsmiljön ska kunna färgindikera de nya klasserna och funktionerna. Ett bibliotek fungerar även utan en nyckelordsfil, men nyckelordsfilen underlättar mycket för den som ska använda biblioteket.

Utan en nyckelordsfil kan utvecklingsmiljön inte färgindikera korrekt.
Med en nyckelordsfil kan utvecklingsmiljön färgindikera korrekt.

Vi skapar en ny nyckelordsfil genom att öppna valfri textredigerare (till exempel ­Anteckningar eller Sublime Text) och skapar en ny fil vid namn keywords.txt. Sedan skriver vi in nyckelorden följt av en tabb och antingen texten KEYWORD1 eller KEYWORD2. Alla klasser ska vara KEYWORD1 och alla funktioner ska vara KEYWORD2.

MorsePulse     KEYWORD1
pulseText      KEYWORD2
pulseLetter    KEYWORD2

keywords.txt

Exempelfil

För att andra programmerare ska kunna använda vårt bibliotek vill vi skicka med en exempelfil. Den visar kort och gott hur biblioteket kan användas. Vi skriver därför en sketch som heter BlinkHelloWorld.ino och gör precis det som namnet antyder: blinkar ”Hello World” med Morsekod. 

Vi inleder sketchen med att inkludera vårt MorsePulse-bibliotek (vår MorsePulse-klass) och skapa ett nytt MorsePulse-objekt vid namn blinker. Blinker får två attribut: GPIO-stift 13 och 200 ms. I loop-funktionen skriver vi sedan att blinker ska blinka texten ”Hello World”. Observera att sketchen inte kommer att fungera förrän vi har färdigställt biblioteket. 

// Include the MorsePulse library
#include <MorsePulse.h>

// Create a MorsePulse object on GPIO pin 13 with 200 ms Morse pulses
MorsePulse blinker(13, 200);

void setup() {
  
}

void loop() {

  // Blink "Hello World"
  blinker.pulseText("Hello World");
}

BlinkHelloWorld.ino

Färdigställ och installera biblioteket

Innan biblioteket kan användas måste det färdigställas och installeras. Själva färdigställandet består av att lägga alla nya filer på sina rätta platser. Vi börjar med att skapa en ny mapp som delar namn med biblioteket. I vårt fall vill vi att biblioteket ska heta MorsePulse precis som klassen. Vi kopierar över MorsePulse.h, MorsePulse.cpp och keywords.txt till den mappen. Sedan skapar vi en undermapp vid namn examples. Till den kopierar vi hela BlinkHelloWorld-mappen (inkl. BlinkHelloWorld.ino som ligger i BlinkHelloWorld-mappen). 

För att installera biblioteket följer vi instruktionerna i Manuell biblioteksinstallation (d.v.s. att vi flyttar hela MorsePulse-mappen till libraries-mappen). 

Vi flyttar hela MorsePulse-mappen till libraries-mappen.

Här följer en sammanställning över de olika filerna, deras filändelser och deras placeringar.

FilFilnamnFilplats
Header-fil klassnamn.h .../klassnamn/
Källkodsfil klassnamn.cpp .../klassnamn/
Nyckelordsfil keywords.txt .../klassnamn/
Exempelfil exempelnamn.ino .../klassnamn/examples/exempelnamn/

Vi kan testa att allt fungerar som det ska genom att starta om utvecklingsmiljön och leta upp vår exempelfil (BlinkHelloWorld.ino) som ligger bland alla andra exempelfiler på Fil-menyn. Vi kan ladda upp sketchen direkt till utvecklingskortet (utan att koppla något annat till det) eftersom exempelfilen endast använder utvecklingskortets inbyggda lysdiod.

Förenkla SOS-blinkaren

När vi nu har ett färdigt Morse-bibliotek kan vi med några få rader kod bygga en SOS-blinkare. Vi kopplar samman knappen med utvecklingskortet på samma sätt som i While-loop. Sedan skriver vi en sketch där vi inkluderar både Button- och MorsePulse-biblioteket (Button- och MorsePulse-klasserna). Vi skapar ett nytt Button-objekt (på GPIO-stift 2) och ett nytt MorsePulse-objekt (på GPIO-stift 13 med 200 ms i Morsepulslängd). 

Del 1/2 av Sos2.ino

Setup- och loop-funktionerna ser ut som i förra SOS-exemplet, men koden i while-loopen kan vi förkorta till en enda rad.

Del 2/2 av Sos2.ino

Förbättra SOS-blinkaren

En enskild lysdiod åkallar inte speciellt mycket uppmärksamhet. Vi vill därför förbättra vår SOS-blinkare med en digital piezosummer som piper samma budskap som lysdioden blinkar. I bokens tillhörande komponentkit finns både en analog och en digital piezosummer (läs mer i Analog och digital piezosummer). Den digitala piezosummern är den med text ovanpå. Vi kopplar in den enligt ritningen.

Illustration gjord med komponenter från Fritzing (fritzing.org). CC BY-SA 3.0

Tack vare vårt MorsePulse-bibliotek är det mycket enkelt att lägga till en digital piezosummer i vår sketch. Det enda vi behöver göra är att skapa ett nytt MorsePulse-objekt, vilket vi denna gång kallar beeper. Som attribut sätter vi GPIO-stift 10 och 200 ms i Morsepulslängd. I while-loopen ber vi sedan beeper att göra precis samma sak som blinker.

Sos3.ino

Obs! Testa gärna med ett annat budskap än SOS så att ingen person i omgivningen reagerar på ett falskt nödrop.

Senast ändrad: 2017-01-24