Czech English

5. Blikáme LEDkou 3

aneb více úloh současně

V první lekci Blikáme LEDkou jsem zadal úkol napsat program na současné blikání dvou LED různou frekvencí. Abyste to byli schopni zvládnout s dosavadními znalostmi, tak byly periody blikání zvoleny "hezky". Pokud to budou libovolné hodnoty, úloha se podstatně ztíží. Řešení nejen této úlohy je tématem dnešní lekce.



Zapojení

Pro testování potřebujeme 2 LED připojené k arduinu tak, jak jsme je používali v lekci 2. LED budou připojeny na výstupy 2 a 3.

Přímočaré řešení

Vrátíme se tedy k původní úloze a ukážeme si "jednoduché" řešení. Úkolem je blikat současně dvěmi LED. Přičemž jedna bliká s periodu 1s (0,5s svítí a 0,5s je zhasnutá) a druhá s periodou 0,5s (0,25s svítí a 0,25s je zhasnutá).
Požadovaný průběh signálů na výstupech mikrokontroleru si můžeme znázornit v grafu:

Z obrázku vidíme, že:
    v čase 0s: zapneme obě LED
    v čase 0,25s: vypneme LED2
    v čase 0,5s: vypneme LED1 a zapneme LED2
    v čase 0,75s: vypneme LED2
    V čase 1s: zapneme obě LED (jako v čase 0s ... děj se začíná opakovat)
Musíme tedy popsat průběh jednoho cyklu (trvajícího 1s). Program by tedy mohl vypadat takto:
#define LED1  2
#define LED2  3
#define TIME  250

void setup()
{   
  pinMode(LED1, OUTPUT);   // nastaveni pinu 2 jako vystup
  pinMode(LED2, OUTPUT);   // nastaveni pinu 3 jako vystup
}

void loop()
{
  // cas 0      ... zapnout obě LED
  digitalWrite(LED1, HIGH);
  digitalWrite(LED2, HIGH);
  delay(TIME);
  
  // cas 0,25s  ... vypnout LED2
  digitalWrite(LED2, LOW);
  delay(TIME);

  // cas 0,5s   ... vypnout LED1, zapnout LED2
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, HIGH);
  delay(TIME);
  
  // cas 0,75s  ... vypnout LED2
  digitalWrite(LED2, LOW);
  delay(TIME);
}

Mírné vylepšení

Když si požadovaný průběh hodnot výstupů lépe prohlédneme, můžeme si všimnout, že ho lze zkráceně popsat:
    v čase 0s: změníme stav obou LED (rozsvítíme)
    v čase 0,25s: změní stav LED2 (vypneme)
    v čase 0,5s: změníme stav obou LED (vypneme LED1 a zapneme LED2)
    v čase 0,75s: změníme stav LED2 (vypneme)
    V čase 1s: změníme stav obou LED
Z toho vyplývá, že pokud zvládneme realizovat změnu výstupní hodnoty, stačí v programu popsat jen polovinu periody.
Jak tedy realizovat změnu hodnoty na výstupu? Hodnotu na výstupním pinu lze přečíst stejnou funkcí jako čteme hodnotu na vstupech.
  if(digitalRead(LED) == 1)    // sviti-li LED
  { 
    digitalWrite(LED, LOW);    // zhasni LED
  }
  else                         // nesviti-li LED
  { 
    digitalWrite(LED, HIGH);   // rozsvit LED
  }
}
Toto funguje, ale je to trošku "ukecané". Připomeneme si, že druhý parametr ve funkci digitalWrite je hodnota, kterou chceme zapsat na výstup, a to nemusí být nutně jen 0 (LOW) nebo 1 (HIGH), ale i hodnota proměnné nebo výrazu.
S využitím pomocné proměnné můžeme změnu hodnoty výstupu realizovat takto:
  x = !digitalRead(LED);      // nacteni hodnoty stavu LED a jeho negace
  digitalWrite(LED, x);       // zapis nove hodnoty na vystup
a nakonec nejkratší zápis bez použití pomocné proměnné (hodnotu zapisovanou na výstup určíme přímo čtením stavu výstupu a jeho negací):
  digitalWrite(LED, !digitalRead(LED));  // zmena hodnoty vystupu
A nyní zkrácená verze programu využívající změnu hodnoty výstupu:
#define LED1  2
#define LED2  3
#define TIME  250

void setup()
{   
  pinMode(LED1, OUTPUT);   // nastaveni pinu 2 jako vystup
  pinMode(LED2, OUTPUT);   // nastaveni pinu 3 jako vystup
}

void loop()
{
  // cas 0      ... zmenit obe LED
  digitalWrite(LED1, !digitalRead(LED1));
  digitalWrite(LED2, !digitalRead(LED2));
  delay(TIME);
  
  // cas 0,25s  ... zmenit LED2
  digitalWrite(LED2, !digitalRead(LED2));
  delay(TIME);
}
Problém tohoto řešení je v tom, že pokud budeme chtít hodnoty period blikání změnit, budeme muset celý program předělat. A co horší, pokud budou požadované hodnoty period blikání nesoudělná čísla, tak tímto způsobem ani řešení nezískáme.
Ovšem hlavní problém je v tom, že tento program ve své podstatě nic rozumného nedělá. Tedy bliká LED, ale samotná operace rozsvícení nebo zhasnutí LED trvá několik mikrosekund a pak mikrokontroler 250 milisekund čeká (nic užitečného nedělá). Přitom by mohl vykonávat nějakou jinou úlohu. Ale jak to zařídit?

Polling

Jednou z jednoduchých a velmi elegantních metod je tzv. polling (dotazování). Program tak, jak jsme už zvyklí, probíhá ve smyčce. Testuje příznaky událostí (podmínky, kdy má něco nastat) a pokud je podmínka splněna, tak provede nějakou akci. Takto se může "současně" zpracovávat větší počet úloh, které jsou na sobě nezávislé.
Ukážeme si to příkladu našich dvou blikajících LED. Podmínkou pro změnu stavu výstupu řídicího LED je dosažení určitého času. Využijeme toho, že v arduinu "běží" čas (funkce millis() vrací počet ms od "spuštění" programu). Tzn. stačí porovnat aktuální čas s určenou hodnotou a víme, zda se má stav LED změnit.
Nejprve si to ukážeme pro jednu led:
1. vytvoříme si proměnnou (časovou značku), ve které máme čas, při kterém má nastat nějaká akce
2. ve smyčce Loop kontroluji, zda "reálný" čas už dosáhl časové značky
3. pokud ano, provedu akci a posunu časovou značku o požadovanou periodu
#define LED   2
#define TIME  500

unsigned long Time;       // promenna s aktualni hodnotou casove znacky (32b)

void setup() 
{
  pinMode(LED, OUTPUT);   // nastaveni pinu 2 jako vystup
  Time = millis();        // ulozeni aktualniho casu jako casove znacky
}

void loop() 

  if(millis() >= Time)    // je-li aktualni cas >= casova znacka
  { 
    digitalWrite(LED, !digitalRead(LED)); // zmen hodnotu vystupu
    Time = Time + TIME;                   // posun casovou znacku o 500ms
  }
}
Tento program, dělá to samé, co náš úplně první program. S tím rozdílem, že mikrokontroler nikde nečeká a tedy může dělat i jinou činnost.
Pro více současných akcí, uděláme pro každou událost vlastní časovou značku, jinak vše zůstane stejné.
#define LED1  2
#define LED2  3
#define TIME1 500
#define TIME2 250

unsigned long Time1;      // promenna s aktualni hodnotou casove znacky (32b) pro LED1
unsigned long Time2;      // promenna s aktualni hodnotou casove znacky (32b) pro LED2

void setup() 
{
  pinMode(LED1, OUTPUT);  // nastaveni pinu 2 jako vystup
  pinMode(LED2, OUTPUT);  // nastaveni pinu 3 jako vystup
  Time1 = millis();       // ulozeni aktualniho casu jako casove znacky 1
  Time2 = Time1;          // ulozeni aktualniho casu jako casove znacky 2
}

void loop() 

  if(millis() >= Time1)    // je-li aktualni cas >= casova znacka 1
  { 
    digitalWrite(LED1, !digitalRead(LED1)); // zmen stav LED1
    Time1 = Time1 + TIME1;                  // posun casovou znacku o 500ms
  }

  if(millis() >= Time2)    // je-li aktualni cas >= casova znacka 2
  { 
    digitalWrite(LED2, !digitalRead(LED2)); // zmen stav LED2
    Time2 = Time2 + TIME2;                  // posun casovou znacku o 250ms
  }
}
Úpravou hodnot period blikání (TIME1, TIME2) můžeme snadno měnit frekvenci blikání LED zcela nezávisle na sobě.

Blikání LED při stisknutém tlačítku

S využitím této metody můžeme konečně korektně vyřešit úlohu z lekce 3. Sestavit program tak, aby LED blikala s periodou 1s pokud je tlačitko stisknuté. Pokud tlačítko není stisknuté, tak je LED zhasnutá.
Příklad řešení:
#define LED       2
#define TIME      500
#define TLACITKO  5

unsigned long Time;       // promenna s aktualni hodnotou casove znacky (32b)

void setup() 
{
  pinMode(LED, OUTPUT);             // nastaveni pinu 2 jako vystup
  pinMode(TLACITKO, INPUT_PULLUP);  // nastaveni pinu 5 jako vstup s pull-up rezistorem
  Time = millis();                  // ulozeni aktualniho casu jako casove znacky
}

void loop() 

  if(digitalRead(TLACITKO) == 0)    // je-li stisknuto tlacitko
  {
    if(millis() >= Time)                // otestujeme, je-li aktualni cas >= casova znacka
    { 
      digitalWrite(LED, !digitalRead(LED)); // zmen hodnotu vystupu LED
      Time = Time + TIME;                   // posun casovou znacku o 500ms
    }
  }
  else                              // neni-li stisknute tlacitko
  { 
    digitalWrite(LED, LOW);         // zhasni LED
  }
}

Úkoly na samostatnou práci

1. Vyřešte úlohu dvou současně se pohybujících LED v hadovi z minulé lekce s využitím pollingu.
2. Sestavte program pro ovládání více LED (had z minulé lekce), kde se budou všechny LED postupně z jedné strany rozsvěcovat a po rozsvícení všech, zase budou postupně zhasínat od první do poslední (časový úsek mezi rozsvěcování/zhasínáním jednotlivých LED bude 0,2s, celá perioda bude záviset na počtu LED, např. pro 7 LED - 7 x 0,2s pro rozsvícení + 7 x 0,2s pro zhasnutí = 2,8s).