- Kursmaterial
- Planering
- Arbete
- Kunskapsdokument
- Andra kurser
- Om Kursolle
3. Moment03 - Arduino PL
Info
Till detta moment finns en sida med lösningsförslag.
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äremot 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.7; 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.70 3 3.00 3 3.00
I Arduino-programmering, liksom i många andra språk, finns två typer av typkonverteringar: implicit och explicit.
- Implicit konvertering: Sker automatiskt när datatyper blandas i en operation eller vid tilldelning. Arduino försöker då anpassa värdena till den förväntade datatypen. Detta kan leda till att precision går förlorad, exempelvis om ett
float
-värde tilldelas enint
-variabel, där decimalerna automatiskt tas bort. - Explicit konvertering (eller casting): Här anger vi tydligt att vi vill ändra datatypen genom att placera den nya typen i parenteser framför värdet, till exempel
(int)f
. Denna metod ger oss mer kontroll och gör koden tydligare, särskilt när vi vet att en konvertering kan påverka värdets precision eller utseende.
I kodexemplet ovan används explicit konvertering för att visa hur float
-värden kan omvandlas till int
och vice versa, vilket påverkar decimalerna. Funktionen toInt()
och toFloat()
hanterar konvertering från String
till int
respektive float
som en implicit konvertering, utan att vi behöver använda direkt casting.
Uppgift: Räkna ut omkretsen och arean på en cirkel
Skriv ett program som beräknar både omkretsen och arean på en cirkel baserat på en given radie. Deklarera radien som en variabel av typen float, och använd sedan variabler för att lagra både omkretsen och arean. Skriv ut resultaten i Serial Monitor och använd konkatenering för att inkludera text som beskriver varje värde.
Utskrift
Radie: 5.0 Omkrets: 31.42 Area: 78.54
Uppgift: Konvertera temperatur från Celsius till Fahrenheit
Skapa ett program som konverterar en temperatur i Celsius till Fahrenheit och skriver ut resultatet i Serial Monitor. Deklarera temperaturen i Celsius som en int och använd typkonvertering för att beräkna den exakta Fahrenheit-värdet med float. Använd konkatenering för att inkludera både Celsius- och Fahrenheit-värdena i samma utskrift.
Formeln för att konvertera Celsius till Fahrenheit: Fahrenheit = Celsius * 9.0 / 5.0 + 32
Utskrift
25 C = 77.0 F
Uppgift: Beräkning av löptid och hastighet
Skriv ett program som beräknar löphastigheten baserat på en given distans (i km) och tid (i minuter och sekunder, tid och minuter skall vara egna variabler). Programmet ska beräkna total tid i timmar för att få hastigheten i km/h. Använd typkonvertering där det behövs och skriv ut resultaten i Serial Monitor.
Utskrift
Distans: 5.00 km Total tid: 0.425 timmar Hastighet: 11.76 km/h
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 ingen 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. Vissa inkluderingsfiler går enkelt att ladda ner via Arduino IDE, LinkedList är en sådan, och det gör du via menyalternativet Tools -> Manage Libraries...
. 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. Skillnaden 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.6 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.7 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 funktioner
void setup() { Serial.begin(9600); // Startar Serial Monitor float length = 5.0; float width = 3.0; float areaResult = calculateArea(length, width); // Funktion med returvärde float perimeterResult = calculatePerimeter(length, width); // Funktion med returvärde displayResults(length, width, areaResult, perimeterResult); // Funktion utan returvärde } void loop() { // Tom eftersom allt sker i setup() } // Funktion: calculateArea() // Beräknar arean av en rektangel // Inparametrar: två float-värden (length och width) // Returvärde: float (area) float calculateArea(float l, float w) { return l * w; } // Funktion: calculatePerimeter() // Beräknar omkretsen av en rektangel // Inparametrar: två float-värden (length och width) // Returvärde: float (perimeter) float calculatePerimeter(float l, float w) { return 2 * (l + w); } // Funktion: displayResults() // Skriver ut resultatet av beräkningarna // Inparametrar: fyra float-värden // Returvärde: inget (void) void displayResults(float l, float w, float area, float perimeter) { Serial.print("Rektangel med längd "); Serial.print(l); Serial.print(" och bredd "); Serial.println(w); Serial.print("Area: "); Serial.println(area); Serial.print("Omkrets: "); Serial.println(perimeter); }
Utskrift
Rectangle with length 5.00 and width 3.00 Area: 15.00 Perimeter: 16.00
Uppgift: Temperaturkonvertering med funktioner
Skriv ett program som innehåller tre funktioner: en för att konvertera Celsius till Fahrenheit, en för att konvertera Fahrenheit till Celsius, och en som skriver ut tabeller för omvandling mellan dessa temperaturer. Testa att köra dessa funktioner och skriv ut resultaten för varje omvandling i Serial Monitor.
I exempelutskriften nedan så ser du att samtliga tal inte skrivs ut, utan endast utvalda tal.
Exempelutskrift
Celsius | Fahrenheit 0 C = 32.00 F 10 C = 50.00 F 20 C = 68.00 F 30 C = 86.00 F 40 C = 104.00 F 50 C = 122.00 F 60 C = 140.00 F 70 C = 158.00 F 80 C = 176.00 F 90 C = 194.00 F 100 C = 212.00 F Fahrenheit | Celsius 32 F = 0.00 C 42 F = 5.56 C 52 F = 11.11 C 62 F = 16.67 C 72 F = 22.22 C 82 F = 27.78 C 92 F = 33.33 C 102 F = 38.89 C 112 F = 44.44 C 122 F = 50.00 C 212 F = 100.00 C
3.8 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 random
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: Tal större än medelvärde
Skriv ett program som genererar en array med tio slumpmässiga heltal mellan 1 och 100. Beräkna sedan medelvärdet av talen och skriv ut det i Serial Monitor. Skapa därefter en LinkedList som endast lagrar värdena över medelvärdet och skriv ut dessa värden.
Uppgift: Yatzy
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.9 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.9.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
int buttonState = 0; // buttonState lagrar avläst värde på knappen void setup() { pinMode(2, INPUT); // Pin 2 för att läsa av knappen pinMode(13, OUTPUT); // Pin 13 för att skicka info till led Serial.begin(9600); // Öppnar kommunikationen till Serial monitor } void loop() { buttonState = digitalRead(2); // Läs av värdet på knappen if (buttonState == HIGH) { // Om knappen är nedtryckt ... digitalWrite(12, HIGH); // ... tänd LED } else { // ... annars ... digitalWrite(12, HIGH); // ... släck LED } Serial.println(buttonState); // Skriv ut värdet på knappen }
Det där är egentligen inget nytt mot vad ni har gjort tidigare. Testa nu så att kretsen fungerar.
3.9.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
int buttonState = 0; // buttonState lagrar avläst värde på knappen int counter = 0; // counter lagrar knapptryck void setup() { pinMode(2, INPUT); // Pin 2 för att läsa av knappen pinMode(13, OUTPUT); // Pin 13 för att skicka info till led Serial.begin(9600); // Öppnar kommunikationen till Serial monitor } void loop() { buttonState = digitalRead(2); // Läs av värdet på knappen if (buttonState == HIGH) { // Om knappen är nedtryckt ... digitalWrite(12, HIGH); // ... tänd LED counter++; // Öka värdet på counter med 1 } else { // ... annars ... digitalWrite(12, HIGH); // ... släck LED } Serial.println(counter); // Skriv ut värdet på räknaren }
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.9.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
int buttonState = 0; // buttonState lagrar avläst värde på knappen int counter = 0; // counter lagrar knapptryck bool isPushed = false; // lagrar om knappen är nedtryckt void setup() { pinMode(2, INPUT); // Pin 2 för att läsa av knappen pinMode(13, OUTPUT); // Pin 13 för att skicka info till led Serial.begin(9600); // Öppnar kommunikationen till Serial monitor } void loop() { buttonState = digitalRead(2); // Läs av värdet på knappen if (buttonState == HIGH && !isPushed) { // Om knappen är nedtryckt ... digitalWrite(13, HIGH); // ... tänd LED counter++; // ... öka värdet på counter med 1 Serial.println(counter); // ... skriv ut värdet på räknaren isPushed = true; // ... "spärra" knapptryck } if (buttonState == LOW) { // Om knappen är uppsläppt ... isPushed = false; // ... sätt isPushed till false, förbered knapp för nytt tryck digitalWrite(13, LOW); // ... släck LED } }
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.
3.10 Hantering av tillstånd (State) i Arduino
I många Arduino-projekt är det användbart att hantera olika "tillstånd" eller "lägen" för att göra systemet mer flexibelt och effektivt. Genom att använda tillstånd kan du låta en knapp eller sensor utföra olika funktioner beroende på systemets aktuella tillstånd. Detta minskar behovet av flera knappar och gör koden mer organiserad.
Varför använda tillstånd?
- Mångsidighet: En knapp kan utföra flera funktioner.
- Effektivitet: Systemet kan ignorera onödig indata när det inte behövs.
- Struktur: Koden blir mer läsbar och lättare att underhålla.
Exempel på tillståndshantering
- Tillstånd 0 – Viloläge: LED är avstängd. Knappen startar systemet.
- Tillstånd 1 – Aktivt läge: LED blinkar. Systemet utför en uppgift.
- Tillstånd 2 – Inställningsläge: LED lyser konstant. Användaren kan ändra inställningar.
Pseudokod för ett enkelt tillståndssystem
Starta programmet Sätt tillstånd till 0 Loop: Läs knappens tillstånd Om knappen trycks: Öka tillstånd med 1 Om tillstånd > 2: Sätt tillstånd till 0 Om tillstånd == 0: LED av Ignorera sensorer Om tillstånd == 1: LED blinkar Läs sensorer Utför uppgifter Om tillstånd == 2: LED på Ändra inställningar
Implementering i kod
const int buttonPin = 2; // Knapp ansluten till digital pin 2 const int ledPin = 13; // LED ansluten till digital pin 13 int state = 0; // Variabel för aktuellt tillstånd int lastButtonState = HIGH; // För att spåra knappens tidigare tillstånd void setup() { pinMode(buttonPin, INPUT_PULLUP); // Ställ in knapp med intern pull-up pinMode(ledPin, OUTPUT); // Ställ in LED som utgång Serial.begin(9600); // Starta seriell kommunikation } void loop() { int buttonState = digitalRead(buttonPin); // Läs knappens tillstånd // Kontrollera om knappen har tryckts ned (fallande flank) if (lastButtonState == HIGH && buttonState == LOW) { state++; // Gå till nästa tillstånd if (state > 2) { // Om tillstånd överstiger 2, återställ till 0 state = 0; } delay(50); // Debounce } lastButtonState = buttonState; // Uppdatera knappens tidigare tillstånd // Hantera tillstånd if (state == 0) { // Tillstånd 0: Viloläge digitalWrite(ledPin, LOW); // LED av // Systemet är inaktivt } else if (state == 1) { // Tillstånd 1: Aktivt läge blinkLED(); // Anropa funktion för att blinka LED // Läs sensorer och utför uppgifter } else if (state == 2) { // Tillstånd 2: Inställningsläge digitalWrite(ledPin, HIGH); // LED på // Tillåt användaren att ändra inställningar } } // Funktion för att blinka LED void blinkLED() { digitalWrite(ledPin, HIGH); delay(250); digitalWrite(ledPin, LOW); delay(250); }
Uppgift: Utöka tillståndssystemet med fler funktioner
Lägg till ett fjärde tillstånd i systemet där du använder en potentiometer ansluten till analog pin A0. I detta nya tillstånd ska du läsa värdet från potentiometern och skriva ut det i Serial Monitor. LED:en ska vara avstängd i detta tillstånd.
Uppgift: Implementera lång och kort knapptryckning
Modifiera programmet så att en kort knapptryckning (mindre än 1 sekund) växlar till nästa tillstånd, medan en lång knapptryckning (mer än 1 sekund) återställer systemet till tillstånd 0.
Uppgift: Större avslutande uppgift
Här förbereds för en större uppgift.