void loop()
{
delay(1000); // pockaj dany pocet milisekund
.... urob nieco ....
}
Nevýhodou na ktorú za chvíľu všetci prídu je to, že funkcia delay() je takzvane blokujúca.
To znamená, že pokým nenastane čas zadaný ako vstupný parameter, tak sa program zacyklí vo funkcii delay().
A nič iného sa nemôže vykonávať (okrem obsluhy prerušení).
A prečo je to problém? Ak by naozaj išlo len o úlohu urob niečo každú sekundu, tak uvedený spôsob je funkčný robí presne to čo požadujeme. Problém je však ten, že zvyčajne chceme aby sa niečo robilo stále a každú sekundu chceme urobiť niečo špeciálne. S delay() sa takéto niečo nikdy nepodarí naprogramovať.
unsigned long actionTime = 0;
void loop()
{
unsigned long now;
unsigned long elapsedTime;
now = millis(); //zisti kolko uplynulo milisekund od zapnutia
elapsedTime = now - actionTime; //vypocitaj kolko uplynulo ms od poslednej akcie
if (elapsedTime >= 1000){ //ak uplynuty cas je dlhsi ako jedna sekunda
actionTime=now; //zapametaj si novy cas akcie
.... urob nieco ... //urob akciu
}
... rob nieco stale ...
}
Toto je správny spôsob ako urobiť požadovaný model správania. Nepoužíva sa žiadne blokujúce
volanie a preto program v slučke loop beží neustále znova a znova.
unsigned long actionTime = 0;
void loop()
{
unsigned long now;
unsigned long elapsedTime;
now = millis(); //zisti kolko uplynulo milisekund od zapnutia
elapsedTime = now - actionTime; //vypocitaj kolko uplynulo ms od poslednej akcie
if (elapsedTime == 1000){ //ak uplynuty cas je jedna sekunda
actionTime=now; //zapametaj si novy cas akcie
.... urob nieco ... //urob akciu
}
... rob nieco stale ...
}
Tento program nebude pracovať spoľahlivo ak "rob niečo stále" bude trvať okolo jednej milisekundy.
V takomto prípade sa môže ľahko stať že elapsedTime nebude 1000,
ale 1001 a preto k vykonaniu už nikdy nedôjde (nie je celkom pravda). Nebezpečné hlavne vtedy ak "rob niečo stále" nemá pevnú
dobu vykonávania a za istých (zvyčajne chybových stavov) sa vykonanie pretiahne nad jednu milisekundu.
unsigned long actionTime = 0;
void loop()
{
unsigned long now;
unsigned long elapsedTime;
now = millis(); //zisti kolko uplynulo milisekund od zapnutia
elapsedTime = now - actionTime; //vypocitaj kolko uplynulo ms od poslednej akcie
if (elapsedTime >= 120000){ //ak uplynuty cas je dlhsi ako dve minúty
actionTime=now; //zapametaj si novy cas akcie
.... urob nieco ... //urob akciu
}
... rob nieco stale ...
}
S prekvapením zistí že urob niečo sa opakuje ani nie raz za minútu. Keď skúsi číslo 60000,
tak to naozaj funguje každú minútu. Tu je problém v tom že prekladač bežne predpokladá
že ide o int. Ten je pre 8 bitové AVR definovaný s dĺžkou 16 bit. Maximálne číslo ktoré
takto sa dá zapísať je 65535. Väčšie čísla sa odrežú bez varovania na dĺžku 16 bitov.
Aby sa to nestalo, treba pridať k číslu suffix L ako long. Teda správne má test vyzerať:
if (elapsedTime >= 120000L){
unsigned long actionTime = 0;
void loop()
{
unsigned long now;
now = millis(); //zisti kolko uplynulo milisekund od zapnutia
if (now >= actionTime + 1000){ //ak uplynuty cas je vacsi ako cas poslednej akcie plus sekunda
actionTime=now; //zapametaj si novy cas akcie
.... urob nieco ... //urob akciu
}
... rob nieco stale ...
}
Táto chyba je poriadne záludná. Chyba sa môže prejaviť iba počas jednej sekundy za 50 dní.
Spočíva v tom že v intervale jedna sekundu pred pretečením počítadla vo funkcii millis() sa "urob niečo" môže robiť
stále až do kým počítadlo nepretečie.
K pochopeniu chyby si stačí uvedomiť že počet miest počítadla nie je nekonečný.
Predstavte si tachometer auta ktorý má 6 miest. To znamená že vie zobraziť maximálne
číslo 999 999. A teraz si predstavte že by ten tachometer vedel aj spočítať
dve čísla. Koľko bude 999 000 + 1000? Malo by to byť 1 000 000.
Lenže je tam iba 6 miest takže výsledok bude 0. No a ak teraz porovnáme
999 000 s výsledkom spočítania, teda s nulou tak dostaneme pravdivý výraz, a preto
vykonáme "urob niečo" stále a zas a znova až dokým hodnota now nepretečie tiež.
Premenná typu unsigned long nie je síce tachometer, ale je to niečo veľmi podobné. Je to 32 číslic vedľa seba. A pretože sme v digitálnom svete tak tie číslice môžu byť iba 0 a 1. Nasledovná vizualizácia ukazuje ako sa to bude správať. Východzí stav ktorý je zobrazený v registroch je práve ten okamih, kedy sa chyba spustí. Je to presne vtedy ak uplynie 4294967 sekund od zapnutia zariadenia (to je 49.71 dňa). V tomto okamihu sa normálne spustí kód v príkaze if. Výsledkom toho je že do premenej actionTime sa vloží číslo 4294967000. To by bolo zatiaľ v poriadku. Takže sa "urobí niečo" a potom sa urobí aj "niečo stále". Potom sa loop znova opakuje.
Lenže súčet čísla v premennej actionTime a hodnoty 1000 pretiekol (výsledok je 704), ale now je stále 4294967000 a test je zase pravdivý. Takže sa znova vykoná niečo v príkaze if. Toto sa bude neustále opakovať dokiaľ now nepretečie na hodnotu 0. Teda po dobu 296ms. Či to bude presne takto ešte záleží od toho koľko trvá vykonanie oboch akcií. Tento rozbor predpokladá že obe akcie sa vykonajú za čas menší ako je jedna milisekunda. Ak to neplatí, uvedené správanie tiež neplatí úplne. Chyba nezmizne, ale jej výskyt a opakovanie bude ešte náhodnejšie.
Či to vadí alebo nie zaleží od toho čo sa vykonáva a v akom časovom intervale.
Napríklad majme mechanické hodiny ktoré sa posúvajú každú sekundu (alebo minútu).
Potom raz za 50 dní pôjdu nejaký čas ako divé.
Alebo to bude polievanie, ktoré je treba robiť raz za 24 hodín.
Potom takýto program bude polievať celých 24 hodín raz za 50 dní.
Obsah premenej actionTime a aj konštanta sa dá vo vizualizácii meniť buď kliknutím a zadaním hodnoty
alebo rolovaním kolečkom na niektorom bite. Tak si môžete interaktívne odskúšať ako
a kedy to pretečie.
A takto to funguje ak je to naprogramované správne s odčítaním. V tomto prípade je zaujímavý okamih kedy dochádza k pretečeniu hodnoty now. Toho sa niektorí obávajú že sa stane niečo hrozného. V skutočnosti sa nestane vôbec nič a rozdiel sa vypočíta spravne aj keď sa bude vyhodnocovať výraz 0 - 4294967000. Toho sa dá docieliť vo vizualizácii pokrútením kolečkom myši na nultom bite premennej now smerom nahor. Je pekne vidno že tá celočíselná aritmetika nemá žiadny problém vypočítať správne uplynutý čas v milisekundách aj v takýchto prípadoch.
Hodne dobrý znalec číslicovej techniky si tu tiež môže všimnúť, že v tomto prípade je použitie premenných dĺžky long na premenné actionTime, elapsedTime (a vlastne aj now) plytvaním miestom v pamäti. Bezproblémovo to bude fungovať aj s premennými typu int (dĺžka 16 bit v AVR svete).