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ä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 eller false. 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 deklarera short tal = 32767; öka sedan värdet med ett tal++; 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 en unsigned 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

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. 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;

  1. Vad som returneras från funktionen, här anges datatypen på det som returneras eller void om inget returneras.
  2. Funktionens namn, tex even()
  3. 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.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 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.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

Bild på kretsen

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

Utskriften

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(12, 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

Utskriften

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 State

Material håller på att tas fram

Det finns tillfällen då vi vill kunna välja vilken typ av sensor som skall läsas av eller att en knapptryckning betyder olika saker. Genom att sätta applikationen i olika state ger oss möjlighet att styra detta.

Detta avsnitt håller jag på att bygga ut.

I avsnitt 3.9 så började vi styra våra system med olika kontroller skrivna mha kod. Vi skall i detta avsnitt kika vidare på hur vi kan låta olika sensorer få olika betydelser beroende på i vilket state vårt system befinner sig. Vi börjar med att bygga om en tidigare skiss och leker vidare med den.

Bild på krets

Bild på kretsen

Jag har byggt vidare på den tidigare skissen och lagt till en knapp och en led, denna gången en grön. Om du kikar på mina knappar så har jag skrivit två färger och der beror på att mina knappar har olika färger, en är gul och en röd.

I första steget så vill jag skriva om koden så att mitt system skall kunna sättas i två lägen; aktivt och ej aktivt och sedan skall jag kunna göra olika saker beroende på vilket state systemet befinner sig i.

Kodexempel: Sätt systemet i aktivt / inaktivt läge

int btnRedState = 0;     // btnRedState lagrar avläst värde på knappen
int btnYellowState = 0;  // btnYellowState lagrar avläst värde på knappen
int counter = 0;         // counter lagrar knapptryck
bool isPushed = false;   // lagrar om knappen är nedtryckt
bool isActive = false;   // lagrar om applikationen är "av" eller "på"

void setup() {
  pinMode(2, INPUT);    // Pin 2 för att läsa av knappen (grön)
  pinMode(3, INPUT);    // Pin 3 för att läsa av knappen (vit)
  pinMode(12, OUTPUT);  // Pin 12 för att skicka info till led (blå) -> grön LED
  pinMode(13, OUTPUT);  // Pin 13 för att skicka info till led (gul) -> röd LED
  Serial.begin(9600);   // Öppnar kommunikationen till Serial monitor
}

void loop() {
  btnRedState = digitalRead(2);     // Läs av värdet på knappen (röd)
  btnYellowState = digitalRead(3);  // Läs av värdet på knappen (gul)

  // Läser av en knapptryckning och ser till att det inte läses av flera gånger.
  if (btnRedState == HIGH && !isPushed) {  // Om knappen är nedtryckt och inte tidigare är nedtryckt
    isActive = !isActive;                  // Vänd status på isActive. (true -> false och tvärt om..)

    // Visa att applikationen är "on" genom att tända grön lampa
    // Koden läggs här för att det är händelsen som styr om lampan skall tändas/släckas,
    // annars kommer den instruktion köras hela tiden (onödigt att be en lysande LED tändas.)
    if (isActive) {            // Om systemet är "aktivt"
      digitalWrite(12, HIGH);  // ... tänd LED
      Serial.println("ON");    // ... och skriv ut i Serial Monitor (om vi vill)
    } else {
      digitalWrite(12, LOW);  // ... släck LED
      Serial.println("OFF");  // ... och skriv ut i Serial Monitor (om vi vill)
    }
    isPushed = true;  // .. spärra knapptryck
  }

  // Om knappen släpps upp så görs den möjlig att klickas ner igen
  if (btnRedState == LOW) {  // Om knappen är uppsläppt ..
    isPushed = false;        // .. gör det möjligt att trycka ner den igen
  }
}

Om du nu testar koden så ser du förhoppningsvis att du kan tända och släcka den gröna lampan, samtidigt som det skrivs ut ON/OFF i Serial Monitor. Det innebär att vi nu kan låta systemet fungera på olika sätt beroende på om det är aktivt eller inaktivt.

3.10.1 Jobba med aktivt system

Nu vill jag skapa en räknare. Men denna räknaren skall bara fungera när systemet är aktiverat. Det innebär att när den gröna lampan lyser så kan jag räkna genom att trycka ner den gula knappen (då skall också den röda lampan lysa). När jag stänger av systemet så skall jag inte längre kunna öka värdet på räknaren men när jag sedan slår på systemet igen så skall räknaren fortsätta på det värde som den hade tidigare.

Vi behöver inte göra om bygga om vår krets på något sätt utan det är redan förberett. Så allt vi behöver göra är att skriva mer kod. För att få ordning på exakt vad som skall göras och att vi förändrar koden på rätt sätt så kan det vara bra att använda någon teknik för att planera vårt arbete. Vi skriver en enkel pseudokod för det som skall göras. Den kommer inte bli komplett då vi inte har pseudokod för hela systemet utan endast för den nya funktionaliteten vi skall implementera i koden. Exakt var vår kod skall placeras löser sig troligtvis på ett relativt enkelt sätt då vi har en välkommenterad kod.

Pseudokod: Implementera räknaren

  • Skapa en räknare (det finns redan en variabel till detta)
  • Om systemet är aktivt (grön lampa lyser)
    • Om gul knapp trycks ner
      • Röd lampa blinkar till
      • Öka räknaren med ett
      • Skriv ut räknarens värde

Troligtvis behöver det inte vara svårare än så.