John Val/ Informatica/ Physical computing/sketch 1a

Aanpassing sketch 1, timing

delay probleem ingrijpen voorwaardelijk ingrijpen
Delay

Na deze les hoor je te weten:

  1. Dat je met de Arduino een digitaal signaal uitleest met behulp van digitalRead
  2. millis()
  3. Dat je data kunt schrijven van de Arduino naar de computer via het object Serial .
  4. En het belangrijkste hoe toestanden onafhankelijk van elkaar kan wijzigen.
  5. Wat een struct is .

In het vorige hoofdstuk hebben we de sketch Blink gebruikt. Deze sketch is hieronder verkort weergegeven met een langere wachttijd. In deze sketch gaat het lampje ieder 5 seconden aan dan wel uit. De timing wordt hier gedaan met de delay functie. We hebben voor het aan en uitzetten een functie schakel ingezet.

#define LED1 13     
int wachttijd=5000; // de wachttijd wordt ingesteld op 5 seconden
bool ledstatus= false; // toestand van led1 false=uit true=aan
void setup()
{                
  // Initialiseer de digitale pin als uitvoer.
  pinMode(LED1, OUTPUT);     
}

void loop() 
{
  schakel();          // schakel licht AAN→UIT   UIT→AAN
  delay(wachttijd);   // wacht wachttijd miliseconden
}

void schakel()
{  
  ledstatus = ! ledstatus;
  digitalWrite(LED1, ledstatus);   // schakel de LED aan (HIGH is the voltage level)
}
Opdracht
  1. Leg in eigen woorden uit hoe de functie schakel werkt
  2. Voeg een tweede lampje toe en laat die in tegengestelde fase knipperen.
Probleem

Probleem

Het probleem met delay als timing is tweeledig. Het belangrijkste probleem is dat de Arduino niets anders kan doen tijdens een delay. Stel je wilt tijdens de aan en uit cyclus hierboven snel kunnen reageren op de status van een knop. Als de knop wordt ingedrukt moet er een tweede lamp gaan branden terwijl het knipperen van de eerste lamp gewoon doorgaat. Voor we hiervoor een sketch ontwikkelen vermelden we nog het tweede probleem. In de Blink sketch hierboven lijkt het erop of onze cyclus 10 seconden duurt. Omdat digitalWrite een heel snelle instructie is zal dit ook wel bijna 10 seconden duren. Als echter een functie wordt uitgevoerd die veel meer tijd kost (zeg 1 seconde) dan is de tijd van de cyclus langer dan 10 seconden (10 + 2 × 1 = 12).

We beginnen met een eerste poging waarin we reageren op een knop. Voeg een led en een knop toe. De knop sluit je aan volgens het schema hier onder.

//Een slechte poging met ingrijpen
#define LED1 13
#define LED2 12
#define KNOP 3         
int wachttijd=5000; // de wachttijd wordt ingesteld op 5 seconden
bool ledstatus= false; // toestand van led1 false=uit true=aan
void setup()
{                
  // Initialiseer de digitale pin als uitvoer.
  pinMode(LED1, OUTPUT);     
  pinMode(LED2, OUTPUT);     
  pinMode(KNOP, INPUT);
}

void loop() 
{
  schakel();   // schakel licht AAN→UIT   UIT→AAN
  schakel2();   // schakel licht2 AAN→UIT   UIT→AAN
  delay(wachttijd);          // wacht wachttijd miliseconden
}

void schakel()
{  
  ledstatus = ! ledstatus;
  digitalWrite(LED1, ledstatus); 
}

void schakel2()
{  
  int status=digitalRead(KNOP);
  digitalWrite(LED2, status); 
}
Opdracht
  1. Leg in eigen woorden uit wat digitalRead in schakel2 doet.
  2. Run the sketch en druk direct nadat het eerste lampje aangaat op de knop en houd die ingedrukt tot het tweede lampje aangaat. Hoelang moet je wachten en wat gebeurd er dan tegelijkertijd?

Terug naar de sketch waar slechts 1 lamp knippert, maar nu met een andere manier van wachten. Lees de code aandachtig door. Run de code en observeer.

//
// Timing met een andere manier van wachten
#define LED1 13
bool ledstatus= false; // toestand van led1 false=uit true=aan
int wachttijd=5000; // de wachttijd wordt ingesteld op 5 seconden
unsigned long volgend_moment;  // wanneer moet er wat gebeuren
void setup()
{                
  // Initialiseer de digitale pin als uitvoer.
  pinMode(LED1, OUTPUT);
  // De functie millis() geeft het aantal miliseconden aan sinds het opstarten van
  // de arduino. Het volgende moment dat er iets moet gebeuren is eigenlijk nu
  volgend_moment = millis();  
}

void loop() 
{
  schakel();   // schakel licht AAN→UIT   UIT→AAN
}

void schakel()
{  
  // controleer of er iets moet gaan gebeuren
  if( millis() >= volgend_moment)
  {
    ledstatus = ! ledstatus;
    digitalWrite(LED1, ledstatus);   // schakel de LED aan (HIGH is the voltage level)
    // zet de tijd tot de volgende gebeurtenis deze is nu precies wachttijd miliseconden later
    volgend_moment += wachttijd;
  }
}
Opdracht
  1. Leg in eigen woorden uit wat millis() in setup en schakel doet.
  2. Wanneer komen we met millis in de problemen?
  3. Leg in eigen woorden uit waarom deze sketch hetzelfde lijkt de doen als de eerste sketch in dit hoofdstuk.
Ingrijpen

Ingrijpen

In de de twee knipper voorbeelden zien we het zelfde. Het lampje wisselt iedere 5 seconden van status. In de Arduino is er dan wel wat anders aan de gang. Vergelijk het met de situatie van een wekker versus alleen een klok. Met een wekker kun je je ogen even toe doen terwijl je eitje aan het koken is. Heb je alleen een klok dan moet je de tijd zelf constant in de gaten houden. Maar ja als je eenmaal aan het kijken bent kun je net zo goed andere wijzertjes in de gaten houden en daar op reageren.

Hier onder vind je een code met een timer en een knop waarbij de Arduino direct reageert op de knop. Behalve constant de tijd bijhouden gaat de Arduino nu ook constant kijken of er iets aan de toestand van een knop is veranderd.

#define LED1 13
#define LED2 12
#define KNOP 3         
bool ledstatus= false; // 
int wachttijd=5000; // de wachttijd wordt ingesteld op 5 seconden
unsigned long volgend_moment;  // wanneer moet er wat gebeuren
void setup()
{                
  // Initialiseer de digitale pin als uitvoer.
  pinMode(LED1, OUTPUT);     
  pinMode(LED2, OUTPUT);     
  pinMode(KNOP, INPUT);
  // Met het Serial object
  // kun je communicatie met de computer regelen. Met Serial.begin zet je de snelheid van communicatie.
  // In je sketch venster selecteer je hulpmiddelen → Serial Monitor om uitvoer te zien.
  Serial.begin(115200);     
}

void loop() 
{
  schakel();   // schakel licht AAN→UIT   UIT→AAN
  schakel2();   // schakel licht2 AAN→UIT   UIT→AAN
}

void schakel()
{  
  // controleer of er iets moet gaan gebeuren
  if( millis() >= volgend_moment)
  {
    ledstatus = ! ledstatus;
    digitalWrite(LED1, ledstatus);   // schakel de LED aan (HIGH is the voltage level)
    // zet de tijd tot de volgende gebeurtenis deze is nu precies wachttijd miliseconden later
    volgend_moment += wachttijd;
  }
}

void schakel2()
{  
  int status=digitalRead(KNOP);
  // Met Serial print en println stuur je gegevens naar de computer.
  Serial.print("Status:");
  Serial.println(status?"aan":"uit");
  digitalWrite(LED2, status);   // schakel de LED aan (HIGH is the voltage level)
}
Opdracht
  1. Leg in eigen woorden uit wat het Serial object voor mogelijkheden geeft.
  2. Laad de code op de Arduino en start de seriële monitor. Bekijk de uitvoer als je op de knop indrukt en later weer loslaat. Hoe vaak zie je "status: aan"?
  3. Leg in eigen woorden uit wat
    status?"aan":"uit"
    betekend. (NB: Javascript en PHP hebben de ? operator ook, evenals vele andere talen)

Je ziet wel heel vaak dezelfde uitvoer. We willen dit eigenlijk alleen zien als er wat verandert in de toestand van de knop. We gaan een toestand voor de knop toevoegen.

#define LED1 13
#define LED2 12
#define KNOP 3         
bool ledstatus = false;  // toestand van led1 false=uit true=aan
bool knopstatus = false; // toestand van de knop false=niet true=wel ingedrukt
int wachttijd=5000; // de wachttijd wordt ingesteld op 5 seconden
unsigned long volgend_moment;  // wanneer moet er wat gebeuren
void setup()
{                
  // Initialiseer de digitale pin als uitvoer.
  pinMode(LED1, OUTPUT);     
  pinMode(LED2, OUTPUT);     
  pinMode(KNOP, INPUT);
  // Met het Serial object
  // kun je communicatie met de computer regelen. Met Serial.begin zet je de snelheid van communicatie.
  // In je sketch venster selecteer je hulpmiddelen → Serial Monitor om uitvoer te zien.

  Serial.begin(115200);     
}

void loop() 
{
  schakel();   // schakel licht AAN→UIT   UIT→AAN
  schakel2();   // schakel licht2 AAN→UIT   UIT→AAN
}

void schakel()
{  
  // controleer of er iets moet gaan gebeuren
  if( millis() >= volgend_moment)
  {
    ledstatus = ! ledstatus;
    digitalWrite(LED1, ledstatus);
    // zet de tijd tot de volgende gebeurtenis deze is nu precies wachttijd miliseconden later
    volgend_moment += wachttijd;
  }
}

void schakel2()
{  
  int status=digitalRead(KNOP);
  if(status != knopstatus) // controleer of de status van de knop is veranderd
  {
    knopstatus = status;  // zet de nieuwe status
    Serial.print("Status:");
    Serial.println(status?"aan":"uit");
    digitalWrite(LED2, status);
  }
}
Opdracht
  1. Laad de code op de Arduino en start de seriële monitor. Bekijk de uitvoer als je op de knop indrukt en later weer loslaat. Wanneer zie je nu "status: aan"?
  2. Voeg een derde lampje toe en een tweede knop. Met de tweede knop moet je dit derde lampje aan en uit kunnen zetten. Wat gebeurt er als je beide knopjes onafhankelijk van elkaar indrukt. (dus alleen, niet of samen).
Voorwaardelijk ingrijpen

Voorwaardelijk ingrijpen

In de sketch hierboven hebben we tijdens het knipperen een ander lampje bediend. Wat als we met de knop juist de status van de eerste lamp willen beïnvloeden?

In het vorige hoofdstuk heb je als opdracht twee verkeerslichten moeten aansturen met de Arduino voor een hoofdweg met een zijweg. Als de zijweg nu heel weinig wordt gebruikt en de hoofdweg juist heel, is het slimmer om de zijweg alleen op groen te zetten als er verkeer komt. We nemen nu even aan dat de zijweg een fietspad is met een drukknop bij het verkeerslicht.

Het hoofdweg - zijweg probleem uit het vorige hoofdstuk heb je opgelost met delay instructies tussen de verschillende toestanden. De wachttijden tussen die toestanden hebben we nog steeds nodig. Een fietser moet nu door het indrukken van de knop groen licht gaan krijgen. Dat kan natuurlijk niet gelijk. Eerst moet de hoofdweg oranje krijgen voor dat het verkeerslicht op rood staat om het verkeer op de hoofdweg veilig te kunnen laten stoppen. Er is dus een opeenvolging van toestanden waarvan de meeste niet onderbroken mogen worden en in dit geval slechts één wel (groen op de hoofdweg met rood op het fietspad)

We moeten dus een aantal dingen voor een toestand vast gaan leggen: Hoelang duurt de toestand, wat moet er gebeuren, en mag de toestand worden onderbroken. Er zijn verschillende mogelijkheden om dit te doen. Wij gebruiken hier een datastructuur in c++ struct genoemd, waarmee je op eenvoudige manier gegevens groepeert. Het begrip struct is de voorloper van het begrip class dat gebruikt wordt in object georiënteerd programmeren. Wat we nu gaan doen hadden we iets ingewikkelder met classes kunnen doen. De struct die we gaan maken noemen we toestand die we een duur, een actie en een vlag bevat om aan te geven of de toestand te verstoren is:

struct toestand
{
  unsigned long duur;   // duur in miliseconden
  int  actie;			// nummer van de actie
  bool te_storen;       // toestaan (true) of niet toestaan false
};

Hiermee maken we een array met alle gewenste toestanden:

#define TOESTANDEN 6
toestand toestand_reeks[TOESTANDEN]={
    {10000,1,true}, // groen rood
    {500,2,false},  // geel rood
    {500,3,false},  // rood rood
    {5000,4,false}, // rood groen
    {500,5,false},  // rood geel
    {500,3,false}   // rood rood
};

We moeten bijhouden welke toestand er gaat komen, of de knop is ingedrukt of niet en of we in de reeks moeten ingrijpen.

unsigned long volgend_moment;  // wanneer moet de volgende toestandsovergang plaatsvinden
int volgende_toestand=0;   // welke toestand wordt dit 
bool interupted = false;   // moet in de toestandreeks ingegrepen worden
bool knopstatus = false;   // is de knop ingedrukt of niet

Hieronder vind je de volledige schets. Jouw taak is om het geheel verder te begrijpen aan de hand van de opdrachten er onder.

#define HoofdwegRood 13 
#define HoofdwegGeel 12
#define HoofdwegGroen 11
#define ZijwegRood 10
#define ZijwegGeel 9
#define ZijwegGroen 8
#define AAN true
#define UIT false
#define TOESTANDEN 6
#define KNOP 3
struct toestand
{
  unsigned long duur;
  int  actie;
  bool te_storen;         
};
toestand toestand_reeks[TOESTANDEN]={
    {10000,1,true}, // groen rood
    {500,2,false},  // geel rood
    {500,3,false},  // rood rood
    {5000,4,false}, // rood groen
    {500,5,false},  // rood geel
    {500,3,false}   // rood rood
};
unsigned long volgend_moment;  // wanneer moet de volgende toestandsovergang plaatsvinden
int volgende_toestand=0;   // welke toestand wordt dit 
bool interupted = false;   // moet in de toestandreeks ingegrepen worden
bool knopstatus = false;   // is de knop ingedrukt of niet

void setup()
{                
  // Initialiseer de digitale pin als uitvoer.
  pinMode(HoofdwegRood, OUTPUT);     
  pinMode(HoofdwegGeel, OUTPUT);     
  pinMode(HoofdwegGroen, OUTPUT);     
  pinMode(ZijwegRood, OUTPUT);     
  pinMode(ZijwegGeel, OUTPUT);     
  pinMode(ZijwegGroen, OUTPUT);     
  pinMode(KNOP, INPUT);
  Serial.begin(115200);
  volgend_moment=millis();
}

void loop() 
{
  controleerToestand();   
  controleerKnop();   // verstoor eventueel de reeks toestanden
}

void controleerToestand()
{  
  // controleer of de sequentie moet worden onderbroken
  if(interupted)
  {
    // is de huidige toestand te verstoren? Zo niet dan gaat de huidige toestand
    // gewoon door
    Serial.println("Inbreken  gewenst");
    if(toestand_reeks[(volgende_toestand-1)%TOESTANDEN].te_storen)
    { // wel te verstoren
      // zet de vlag voor verstoring weer uit.
      Serial.println("Ingebroken");
	  interupted=false;
      // vervroeg het moment naar de volgende toestand
      volgend_moment=millis();
    }
  }
  // controleer of er iets moet gaan gebeuren
  if( millis() >= volgend_moment)
  {
    doeActie(toestand_reeks[volgende_toestand].actie);
    volgend_moment += toestand_reeks[volgende_toestand].duur;
    volgende_toestand+=1;
    volgende_toestand%=TOESTANDEN;
  }
}

void controleerKnop()
{  
  int status=digitalRead(KNOP);
  if(status != knopstatus)
  {
    knopstatus = status;
    interupted |= true;
    Serial.print("Status:");
    Serial.println(status?"aan":"uit");
  }
}

void doeActie(int actie)
{
  Serial.print("Actie: ");
  Serial.println(actie);
  switch(actie)
  {
      case 1: lichten(UIT,UIT,AAN,AAN,UIT,UIT); break; // groen rood
      case 2: lichten(UIT,AAN,UIT,AAN,UIT,UIT); break; // geel rood
      default:
      case 3: lichten(AAN,UIT,UIT,AAN,UIT,UIT); break; // rood rood
      case 4: lichten(AAN,UIT,UIT,UIT,UIT,AAN); break; // rood groen
      case 5: lichten(AAN,UIT,UIT,UIT,AAN,UIT); break; // rood geel
  }
}

void lichten(bool hr,bool hg, bool hgr, bool zr, bool zg, bool zgr)
{
  digitalWrite(HoofdwegRood, hr);
  digitalWrite(HoofdwegGeel, hg);
  digitalWrite(HoofdwegGroen, hgr);
  digitalWrite(ZijwegRood, zr);
  digitalWrite(ZijwegGeel, zg);
  digitalWrite(ZijwegGroen, zgr);
}
Opdracht
  1. Laad de code op de Arduino en start de seriële monitor. Bekijk de uitvoer als je op de knop indrukt wanneer het hoofdweg licht op groen staat.
  2. Leg uit wat de functie controleerKnop doet
  3. Leg uit wat de functie lichten doet
  4. Leg uit wat de functie doeActie doet
  5. Leg uit wat de functie controleerToestand doet
  6. Beschouw de situatie van een gevaarlijke kruising van twee gelijkwaardige wegen. Overdag volgt die een normale cyclus waarbij beide wegen even lang groen licht krijgen. In de nachtelijke situatie blijft het licht van een weg groen (de ander natuurlijk rood) totdat er op de andere weg verkeer aankomt. Dan wisselt de situatie. Gebruik twee knopjes om deze situatie na te bootsen en maak een sketch voor deze nachtelijke situatie.