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);
}
#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
}
}
{
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
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);
}
#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
}
}
#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
}
}
#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
}
}
#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).