- Kursmaterial
- Planering
- Arbete
- Kunskapsdokument
- Andra kurser
- Om Kursolle
3. Moment03 - Arduino PL
I detta moment kommer det vara fokus på programmeringsspråket Arduino PL (Programming Language). Detta är ingen grundkurs i programmering så i detta moment kommer jag fokusera på saker som fungerar på ett annorlunda sätt i Arduino PL än vad det gör i Python. Vissa av sakerna som jag kommer visa är väldigt lika medan annan funktionalitet skiljer sig en hel del. Fokus kommer heller inte vara på funktioner som hänger ihop med olika komponenter även om jag eventuellt kommer använda detta vid något tillfälle för att kunna ge bra exempel på programmeringen.
3.1 Basics
Arduino PL är i grunden ett programmeringsspråk som bygger på C++ vilket är ett väldigt strikt programmeringsspråk jämfört med tex Python. Arduino PL är fokuserat på att programmera just Arduino och Arduinokompatibla kort vilket gör att språket är nedbantat för att lösa de specifika uppgifterna som är intressanta när vi skall bygga våra Arduinoprojekt.
I moment02 så kodade du i Arduinos egen miljö men i detta kapitel kommer jag koda i TinkerCad för att ha en smidig miljö att laborera i. Det går att göra exempelkoderna i Arduinos miljö men då måste du också ladda upp dina koder till Ardunio för att kunna köra dem. TinkerCad når du här. Tänk på att ladda ner och spara dina koder, då detta inte görs automatiskt.
Detta moment är inte heltäckande utan det handlar om att jag visar skillnader/likheter med Python och sedan så laborerar/testar du en hel del på egen hand. Jag kommer också länka till bra guider/exempel som fungerar att utgå ifrån. En bra start är alltid att gå till The Arduino Reference.
3.1.1. Funktionerna setup() och loop()
När du startar ett nytt projekt i Arduino PL så kommer det finnas två funktioner i din kod (eventuellt finns det också lite exempelkod). Dessa två funktioner är setup() och loop().
Kodexempel: Funktionerna setup() och loop()
void setup() { } void loop() { }
Dessa två funktioner är det som bygger upp din kod. Funktionen setup() är den funktionen som kommer anropas först. Det är här som vi bl.a. kommer beskriva vilka kopplingar vi gör och vilka pins som skall användas. Funktionen loop() innehåller den koden som skall köras hela tiden när programmet körs. Funktionen loop() anropas hela tiden direkt efter funktionen är färdig vilket innebär att det inte går att bestämma hur många gånger per sekund som denna funktion körs utan det beror helt på hur lång tid det tar att köra funktionen.
Nästa exempel visar hur dessa två funktioner används i praktiken.
3.2 Utskrifter till Serial Monitor
Serial Monitor är den möjlighet vi har att kunna styra utskrifter så att det blir enklare att testa/felsöka våra koder. Serial Monitor hör ihop med Arduino IDE och kommer inte finnas när våra applikationer sedan skall köras på vårt utvecklingskort. Vi skall nu testa en enkel utskrift till Serial Monitor
.
Kodexempel: Utskrift till Serial Monitor
void setup() { // Öppnar kommunikationen med Arduino, 9600 bps (bits/s) Serial.begin(9600); Serial.println("Print from setup()"); } void loop() { Serial.println("Print from loop()"); delay(1000); // pausar 1000ms }
Programmet kommer nu köras genom att funktionen setup() anropas först och sedan kommer funktionen loop() att anropas gång på gång tills progammet avslutas.
Snabb genomgång av koden visar att först behöver vi öppna en kommunikation med Arduino för att kunna använda Serial Monitor
, vilket görs på rad 4.
På rad 5, och rad 10, så skriver vi ut en rad med text till Serial Monitor
, println()
kommer skriva ut innehåll samt skapa en radbrytning, vill jag inte ha en radbrytning så anger jag print()
.
Testa nu koden för att se hur det fungerar. Testa också skillnaden mellan println()
och print()
.
3.3 Variabler
Inom Arduino PL så måste vi deklarera en variabel innan vi kan använda den. Att deklarera en variabel innebär att vi ger den ett namn och vilken datatyp som variabeln skall lagra. Till skillnad från Python kan detta sedan inte ändras vilket innebär att en variabel som är tänkt att den skall lagra ett heltal alltid måste lagra ett heltal.
Kodexempel: Deklarera variabler
void setup() { // Öppnar kommunikationen med Arduino, 9600 bps (bits/s) Serial.begin(9600); int tal1; // Deklarerar tal1 som en int (heltal) tal1 = 4; // Tilldelar tal1 ett värde int tal2 = 3; // Initierar tal2 då det får både datatyp och värde vid skapandet // Utskrifter Serial.println(tal1); Serial.println(tal2); } void loop() { }
I exemplet ovan så visar jag både hur jag deklarerar en variabel, ger den ett namn och en datatyp, samt hur jag kan initiera en variabel då får den också ett värde i samband med att den skapas.
3.3.1 Synlighet
Synligheten för en variabel är viktig, en variabel som deklareras inne i en funktion finns endast tillgänglig där. Däremort kan vi deklarera en variabel som global
vilket innebär att den är tillgänglig i samtliga funktioner i vårt program. Detta är ibland en smidig lösning, men det kan också skapa problem ifall du vill, eller inte vill, påverka denna variabels värde i olika funktioner.
Kodexempel: En variabels synlighet
// Initierar en global variabel som är synlig i alla funktioner int tal1 = 9; void setup() { Serial.begin(9600); // Öppnar kommunikationen Serial.println(tal1); // Utskrift av global variabel tal1 int tal1 = 4; // Initierar en lokal variabel med samma namn Serial.println(tal1); // Utskrift av lokal variabel } void loop() { Serial.println(tal1); // Skriver ut den globala variabeln delay(1000); // Paus i 1000ms exit(0); // Avbryter programmet }
I denna koden vill jag visa hur man kan, men inte bör, skapa en global och en lokal variabel med samma namn. Risken är stor att du här påverkar en variabels värde på ett annat sätt än vad du har tänkt.
Att skapa globala variabler är inget fel, men gör detta endast om du tänker att variabeln behövs i flera funktioner.
3.3.2 Datatyper
Ardunio har ett antal datatyper som du behöver ha koll på, här kommer jag lista de vanligaste;
- bool är datatypen som lagrar ett av två värden,
true
ellerfalse
. Boolean är endast ett alias för bool och ingen egen datatyp. - int är standarddatatypen för heltal. Det som är lite speciellt för int i Arduinovärden är att den lagrar olika stora tal på olika typer av kort. På en Arduino Uno så är tex intervallet mellan -32768 till 32767, medan det på ett mer kraftfullt kort lagrar värden på +/- drygt 2 miljarder. Vill du vara säker på att alltid kunna lagra så stora tal så skall du istället välja datatypen long och vet du att det lägre intervallet räcker så kan du använda short. Det som är lite intressant/lurigt med heltalsvariablerna är att när de slår i
taket
för vilket värde de kan ha så börjar de om utifrån lägsta värdet. Testa att deklarerashort tal = 32767;
öka sedan värdet med etttal++;
och skriv sedan ut värdet påSerial.println(tal);
.tal++;
är enklaste sättet i Arduino PL att öka värdet på en heltalsvariabel med ett. Du kan flytta intervallet på en heltalsvaribel så att den endast är positiv. Om storleken är rimlig på en short men du vill att den går från 0 till 65535 så kan du deklarera den som enunsigned short tal;
. - double och float är de decimaltalsvariabler som används i Arduino PL. Double fungerar lite olika på olika kort, är det vikltigt med hög precision av decimaler så välj float.
- char är datatypen för ett enskilt tecken. En char representerar både ett tecken, tex 'A', och ett tal, 65 för 'A', vilket lämpar sig väl för att modifiera enskilda tecken. Viktigt här är att tecken markeras med
enkelfnutt
. - String är datatypen som används för att lagra strängar, vilket egentligen är en array (mer om detta senare) av enskilda tecken. Denna datatyp är lite mer komplex än de andra men går att använda relativt enkelt om man vill. Initiera en variabel på följande sätt
String kurskod = "tiatil00s";
. - När det gäller variabelnamn så kommer inte TinkerCad att godkänna namn som innehåller å/ä/ö utöver de vanliga reglerna att det inte får förekomma ett mellanslag i variabelnamnet eller att namnet börjar med en siffra.
3.3.3 Konkatening och utskrifter
Att skriva ut text och variabler kan göras på olika sätt.
Kodexempel: Exempel på utskrifter
String code = "tiatil00s"; String name = "Tillämpad Programmering"; int points = 100; void setup() { // Öppnar kommunikationen med Arduino, 9600 bps (bits/s) Serial.begin(9600); // #1. Skriv ut en sak i taget Serial.print(code); Serial.print(" - "); Serial.print(name); Serial.print(", "); Serial.print(points); Serial.println("p."); // #2. Skriv ut allt på en gång Serial.println(code + " - " + name + ", " + points + "p."); // #3. Skapa variabel för att bygga utskriftssträng String output = code + " - " + name + ", " + points + "p."; Serial.println(output); // #4. Float (decimaltal) kräver lite mer omtanke för att fungera. float f = 1.256812; // Använd String()-konstruktorn för att skapa en formaterad sträng String s = String(f, 3); // 3 decimaler Serial.println(s); // Serial.println(String(f,3)); funkar också... String out = String("Orginaltal: ") + String(f); Serial.println(out); } void loop() { }
3.3.4 Typkonvertera
Att typkonvertera innebär att man gör om data från en datatyp till en annan. Oftast handlar det om att konvertera mellan int och float. I de programmeringsspråk där man matar in saker via en konsoll/GUI så behöver man också kunna konvertera från text till tal men det är ju inget som vi behöver tänka på för tillfället. Vårt data som behandlas är ju av typen int eller float.
Kodexempel: Exempel på utskrifter
void setup(){ Serial.begin(9600); int i; String s = "3"; float f = 3.6; Serial.println(f); // Skrivs ut med 2 decimaler i = (int) f; // När typkonverteringen går från float till int så tas decimaler bort Serial.println(i); // Innehåller inga decimaler float g = (float) i; Serial.println(g); // Två decimaler igen, men nu har talet förändrats // Konvertering från string till int mha hjälpfunktionen toInt() int si = s.toInt(); Serial.println(si); // Konvertering från string till float mha hjälpfunktionen toFloat() float sf = s.toFloat(); Serial.println(sf); } void loop() { }
Utskrift
3.60 3 3.00 3 3.00
3.4 Array vs LinkedList
När vi programmerade i Python så var det enkelt att använda listor. I Arduino PL så är det inte riktigt lika enkelt utan då behöver vi i stället göra ett val. Valet består mellan att skapa en Array
, vilket är en variabel med ett fast antal komponenter som skapas när variabeln skapas, eller en LinkedList
vilket är en lista som kan variera i längd. Denna listan kräver lite mer för att kunna användas.
3.4.1 Array
En array är en variabel som består av ett förutbestämt antal komponenter. När du väl har skapat en array så kan du inte förändra dess storlek, däremot kan du förändra innehållet i varje komponent.
Du deklarerar en array genom att tala om vilken datatyp den skall innehålla, ger den ett namn och talar om vilken storlke den skall ha. Om du tilldelar den värden samtidigt som du skapar den så behöver du inte ge arrayen en storlek utan då kommer den bli så stor som den skall vara så att alla värden får plats.
Att arbeta med specifika komponenter i en array gör du på samma sätt som du gjorde i en lista när vi jobbade med Python, du anger indexet för komponenten som du vill anropa. Tänk på att indexeringen börjar på 0
.
Kodexempel: Exempel på användningen av arrayer
int i[3]; // Deklarera variabeln i int j[] = {1, 2, 3}; // Initiera variabeln j int k[3] = {1, 2, 3}; // Initiera variabeln k String s[3] = {"Anna","Bo","Cilla"}; void setup(){ Serial.begin(9600); i[0] = 1; // Tilldela variabeln i .. i[1] = 2; // .. värden. Siffran inne i .. i[2] = 3; // .. klammern är indexet. Serial.println(i[2]); // Skriver ut sista komponenten Serial.println(j[2]); // Skriver ut sista komponenten Serial.println(k[2]); // Skriver ut sista komponenten Serial.println(s[2]); // Skriver ut sista komponenten } void loop() { }
Utskrift
3 3 3 Cilla
3.4.2 LinkedList
Finns det inte bra lista i Arduino PL? Jo, men ingen som inbyggd. För att kunna använda det som kallas LinkedList
så behöver du först ladda ner och importera en inkluderingsfil
. En inkluderingsfil innehåller Arduinokod som behöver importeras när du skall göra tillägg till de inbyggda funktionerna som redan finns i Arduino PL. En inkluderingsfil har oftast filändelsen .h
. Inkluderingsfilen till LinkedList hittar du här. Du kan inte köra med inkluderingsfiler i TinkerCad utan då får du köra koden på något annat sätt.
Kodexempel: Exempel på användningen av LinkedList
#include <LinkedList.h> LinkedList<int> lista = LinkedList<int>(); void setup(){ Serial.begin(9600); Serial.println("Start"); // Lägg till lista.add(1); lista.add(2); lista.add(3); } void loop() { Serial.print("Storlek på listan: "); Serial.print(lista.size()); }
Utskrift
Start Storlek på listan: 3
Inkluderingsfilen måste läggas i samma mapp som övriga filer i ditt projekt.
3.5 Iterationer
Iterationer, eller loopar som vi känner dem, finns naturligtvis också i Arduino PL. Skillnanden mot vad ni har sett tidigare är främst hur for-loopen skrivs. Vi saknar också en foreach-loop, alltså en enkel loop som itererar över en array/lista.
Kodexempel: Exempel på användningen av loopar
int len = 3; // Initierar len som är längden på arrayen int j[] = {1, 2, 3}; // Initiera variabeln j void setup(){ Serial.begin(9600); Serial.println("Skriv ut med for-loopen"); for(int i = 0; i < len; i++){ // i existerar bara inne i for-loopen Serial.println(j[i]); } Serial.println("Skriv ut med while-loopen"); int i = 0; // Initierar i while (i < len){ Serial.println(j[i]); i++; } } void loop() { }
Utskrift
Skriv ut med for-loopen 1 2 3 Skriv ut med while-loopen 1 2 3
Uppgift: Arrayer och loopar
Skapa en variabel som lagrar antalet komponenter i en array. Fyll sedan arrayen med värden, låt värdet som lagras vara indexet, en räknare eller något värde som är en funktion av index eller räknare. Loopa sedan igenom arrayen och skriv ut dess värden.
Tips: Fundera på om det gör någon skillnad att skapa arrayen innan funktionen setup() eller inne i funktionen setup().
Uppgift: Lista och loopar
Om du vill så testa att göra samma uppgift som ovan fast med en LinkedList istället.
Tips: Detta måste du göra på din egna dator och via Arduino IDE, eftersom LinkedList.h
inte fungerar på TinkerCad.
3.5 exit()
Det finns en funktion som avbryter programmet och det är exit(0);
, siffran i parantesen kan vara olika för att kunna meddela vilken exit()
som har körts.
Viktigt att tänka på är att programmet slutar köras men Arduinon stängs inte av bara för att programmet inte längre körs så detta är en funktion som bäst används när något är problematiskt och bör kombineras med någon form av felmeddelande. Att använda det i utvecklingssyfte är en annan sak då man kan vilja analysera olika delar av koden utan att programmet hela tiden fortsätter att köras.
Kodexempel: Exempel på användningen av exit()
int i = 1; // Initiera variabeln i void setup(){ Serial.begin(9600); // Öppnar kommunikationen med konsollen } void loop() { Serial.println(i++); // Skriv ut i och öka sedan dess värde med 1 delay(500); // Pausar 0.5 s if (i > 9){ exit(0); // Avbryt programmet } }
Utskrift
0 1 2 3 4 5 6 7 8 9
3.6 Funktioner
För att ditt program skall vara lättare att följa så kan du skapa egna funktioner. Vi har i kursen programmering01 pratat om funktioner och samma anledningar som tidigare gäller fortfarande att en funktion bör skapas när samma sak behöver göras flera gånger, då pratar jag inte om att funktionen loop() körs flera gånger utan om att något inne i loop() behöver göras flera gånger, eller att det är kod som av annan orsak blir tydligare av att flyttas ut och separeras. Utgå ifrån att en funktion gör en sak, antingen en beräkning eller en åtgärd. Var inte rädd för att skapa flera funktioner.
Precis som tidigare så byggs en funktion upp av tre delar;
- Vad som returneras från funktionen, här anges datatypen på det som returneras eller
void
om inget returneras. - Funktionens namn, tex
even()
- Inne i parantesen så anges de eventuella inparametrar som skall skickas till funktionen,
even(int i)
Viktigt att tänka på är hur synligheten gäller för våra variabler. I exemplet nedan så använder jag synligheten på olika sätt, ibland skickar jag med en inparameter och ibland så använder jag den globala variabeln som syns i alla programmets funktioner. Att göra som jag visar här är inte att rekommendera då jag är inkonsekvent, men se det mer som ett sätt att se på olika alternativ.
Kika också på hur jag kommenterar mina funktioner.
Kodexempel: Exempel på användningen av exit()
int i = 0; // Initiera variabeln i void setup(){ Serial.begin(9600); // Öppnar kommunikationen med konsollen } void loop() { i++; // Öka värdet med ett delay(500); // Pausar 0.5 s if (i % 2 == 0){ // Om talet är jämnt delbart med två .. Serial.println(even(i)); } else if (i == 9){ // .. annars om talet är 9 end(); } else { // .. annars other(); } } // Funktion: end // Stänger av programmet // Inparametrar: inget/none // Returvärde: inget/none void end(){ Serial.println("Programmet avslutas"); delay(200); // Pausar 0.2 s för att hinna skriva ut texten exit(0); } // Funktion: even() // Returnerar en sträng ifall talet är jämnt // Inparameter: ett heltal // Returvärde: en sträng med text String even(int i){ String s = "Talet " + String(i) + " är jämnt"; return s; } // Funktion: other() // Skriver ut vad talet har för värde // Inparameter: inget/none // Returvärde: inget/none void other(){ Serial.println("Talet är " + String(i)); }
Utskrift
Talet är 1 Talet 2 är jämnt Talet är 3 Talet 4 är jämnt Talet är 5 Talet 6 är jämnt Talet är 7 Talet 8 är jämnt Programmet avslutas
3.7 Slump
Slump är en vanligt förekommande funktionalitet i olika program. I Arduino PL så är slumpen lätt att jobba med då allt finns inbyggt. Det är dock viktigt att anropa randomSeed(analogRead(0));
för att få en unikt slumpad sekvens vid varje körning. Testa att kommentera bort denna koden och ditt program kommer slumpa
samma sekvens varje gång.
Kodexempel: Exempel på användningen av exit()
int tal; void setup(){ Serial.begin(9600); // Öppnar kommunikationen med konsollen randomSeed(analogRead(0)); // Anges för att få unik slumpsekvens } void loop() { tal = random(6) + 1; // Slumpar ett värde mellan 1 - 6 delay(500); // Pausar 0.5 s Serial.println("Slumpat tal: " + String(tal)); if (tal == 1){ Serial.println("Programmet avbryts!"); delay(200); exit(0); } }
Utskrift
Slumpat tal: 6 Slumpat tal: 5 Slumpat tal: 6 Slumpat tal: 3 Slumpat tal: 1 Programmet avbryts!
Uppgift: Slump
Nu gör vi en uppgift baserad på slump, array, loopar och funktioner. Jag gillar ju att använda mig av tärning, och yatzy, när vi har möjlighet.
Din uppgift är att slå fem tärningar och räkna hur många gånger du behöver slå en yatzyhand
innan du får yatzy. Inga tärningar sparas utan hela handen kastas om. För att få yatzy så skall alla fem tärningar ha samma värde.
Uppgift: Vidareutveckling 1
Om du upprepar denna undersökning flera gånger, kan du då skapa en resultatlista med hur många slag det kräver i varje omgång
. Redovisa sedan hela undersökningen samt ett medelvärde på dina resultat.
Uppgift: Vidareutveckling 2
Sannolikheten att få yatzy på ett slag är 1 på 1296. Du borde alltså få ca 15.4 yatzy på 20000 slag, vilket resultat kan du nå i en undersökning? Hur skiljer sig resultatet om du kör undersökningen 1, 3, 7, 20, 50 ggr?
3.8 Knapptryck
Här kommer jag nu köra ett exempel där jag låter programmeringen addera något extra till en enkel koppling. Vi skall leka lite med en knapp och se vilka möjligheter, och problem, den kan skapa med och utan programmering.
Jag kommer visa mina exempel via TinkerCad men det går självklart att göra samma saker med din egna labutrustning.
3.8.1 Tänd en LED med en knapp
Vi börjar med en enkel koppling med en knapp och en LED, två resistorer, 330Ω kopplad till LED och 10kΩ för resistorn som är kopplad till knappen. Den resistorn som kopplas till knappen handlar om att undvika att knappen påverkas av annan spänning så att det verkligen är nedtryckningen som mäts och inget annat.
Bild på krets

Kodexempel: Tänd en LED med knapp

Det där är egentligen inget nytt mot vad ni har gjort tidigare. Testa nu så att kretsen fungerar.
3.8.2 Räkna knapptryck
Nu skall vi testa att räkna antalet knapptryckningar. Vi låter kretsen se likadan ut men vi bygger om koden så att vi skapar en räknare och skriver ut räknarens värde.
Kodexempel: Räkna knapptryck

När jag nu kör programmet och trycker ner knappen så händer följande...
Output

Det som händer är att räknaren ökar med ett för varje gång som funktionen loop()
körs. Detta innebär att om jag håller ner knappen så kommer den att räkna upp i all oändlighet, så kan vi ju inte ha det för att kunna räkna antalet knapptryckningar.
3.8.3 Kontrollera nedtryckningar
Det finns några olika tekniker för att se till att knappen endast läses av ifall det är en nedtryckning och inte en nedhållning som sker. Vi skulle kunna välja att pausa scriptet, delay(100)
, i en viss tid om vi vet hur länge en knapptryckning pågår, men det hjälper oss inte om någon gör en lång nedtryckning, då kommer den ändå att fortsätta räkna nedtryckningen som flera. Vi behöver spärra
avläsningen av knappen på något annat sätt.
Kodexempel: Kontrollera nedtryckningar

Det som jag har förändrat är att jag har skapat en variabel isPushed
som lagrar om knappen är nedtryckt eller inte, egentligen lagrar jag här ifall den har ändrat state
.
Sedan har jag ändrat den stora selektionen så jag kollar ifall knappen är nedtryckt samtidigt som jag kikar att den inte är nedtryckt sedan tidigare. Om detta händer så ökar jag counter-värdet med ett och skriver sedan ut räknarens värde. Genom att skriva ut räknaren inne i denna selektionen så kommer jag skriva ut värdet när det förändras och inte varje gång loop()
körs. Slutligen så sätter jag isPushed
till true
så att koden inte läser av fler nedtryckningar förrän knappen är släppt igen.
I nästa selektion så kollar jag om knappen är släppt, om så är fallet så ger jag variabeln isPushed
värdet false
och släcker LED.
När jag nu kör programmet och trycker ner knappen upprepade gånger så händer följande...
Output

Perfekt, nu fungerar det som det är tänkt.
Uppgift: Nollställ räknaren
Lägg till en knapp till kretsen. När du klickar på denna knappen så skall räknaren nollställas.
Uppgift: Utbyggnad
Lägg till en grön LED och se till att denna lyser en liten stund i samband med att räknaren nollställs.
Uppgift: Slå tärning vid knapptryck
Använd kretsen som du skapade i förra övningen, nu skall du slumpa fram en tärning när du klickar på knappen. Skriv ut värdet på tärningen i Serial Monitor
.
Uppgift: Utbyggnad
Lys upp en, eller flera LED, för att visa värdet på tärningen. Kanske en RGB-LED?
Uppgift: Utbyggnad
Bygg ut programmet så att du kan slumpa flera tärningar. Lagra dem på lämpligt sätt.
Uppgift: Utbyggnad
Sätt dit ytterligare en knapp för att kunna nollställa din lista/array.
Uppgift: Utbyggnad
I en tidigare uppgift fick du i uppdrag att göra en statistisk analays av antalet yatzy du kunde få för ett givet antal slag. Aktivera varje sådan undersökning med ett knapptryck och använd nollställningen för att kunna avbryta/återställa din undersökning.
Skriv ut på lämpligt sätt värdet av din undersökning.