Aké pomalé je Arduino?
Jednočipové mikropočítače sú rýchle, o tom niet pochybností. Ich reálna rýchlosť však závisí jednak na použitej taktovacej frekvencii a jednak na programe, ktorý v nich beží. Tak som sa rozhodol zistiť aké rýchle (resp. pomalé) sú funkcie knižnice Arduino.
Obsah článku
Atmega328 je rýchla
Ak ste už niekedy programovali akýkoľvek mikroradič (jednočipový mikropočítač), určite ste skúšali blikať LED. Ono, taký program je vlastne obdobou programov typu „Ahoj svet!”, ktoré sú prvou kapitolou väčšiny programovacích jazykov. A možno ste hneď pri prvom pokuse narazili na to, že LED nebliká, ale trvalo svieti, len nižším jasom, ako ste čakali. Áno, aj toto býva súčasťou prvých lekcií, a to že mikroradič treba spomaliť, pretože pracuje rýchlejšie ako človek dokáže zaregistrovať. Na osvieženie, takto vyzerá klasický ukážkový príklad pre Arduino:
void setup(){
pinMode(LED_BUILTIN, OUTPUT);
}
void loop(){
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
V tomto kóde iste vidíte čakaciu slučku na spomalenie práce, v knižnici Arduino
reprezentovaná funkciou delay()
, ktorá tu má za úlohu pozastaviť vykonávanie
programu na 500 ms a vlastne demonštruje aký rýchly mikroradič je, lebo ho treba
spomaliť, a je to pravda – mikroradič je rýchly. Doska Arduino UNO používa mikroradič
ATmega328, ktorý používa taktovaciu frekvenciu 16 MHz, takže jeden cyklus hodín
trvá 0,0625 µs, alebo 62,5 ns. Pretože pri ATmega328 trvá vykonanie jednej
(jednoduchej) inštrukcie práve jeden cyklus hodín, dokáže vykonať jednu takú inštrukciu
každých 62,5 nanosekúnd (samozrejme, niektoré inštrukcie vyžadujú viac cyklov).
A to je naozaj rýchle!
Záleží na tom?
Pokiaľ všetko, čo od svojho Arduina očakávate je občas rozsvietiť, resp. zhasnúť LED, zopnúť relé, prípadne vypísať niečo na LCD a okrem toho raz za niekoľko minút prečítať hodnoty senzorov, je vyššie popísaná rýchlosť viac ako postačujúca. Veď nie je to jedno, či sa LED rozsvieti o milisekundu skôr alebo neskôr? Nie je jedno, či bude senzor čítaný presne v 300-tej sekunde alebo „až” v 300,001-ej? Áno, je to jedno! (teda aspoň väčšinou)
V okamihu, keď prejdete od blikania LED, či spínania relé a občasného čítania senzorov k aplikácii, kde začnete používať prerušenia alebo kde činnosť závisí na načasovaní z externého zdroja udalostí, začína ot byť zaujímavé.
K týmto úvahám ma vlastne priviedol projekt môjho známeho, s ktorým ma požiadal o pomoc, a keďže som sa s niečim podobným pohrával už dávnejšie, ochotne som sa do toho pustil. Konkrétne išlo o stmievanie divadelných reflektorov. Známy našiel na eBay dosku (mimochodom šialene drahú), ktorá obsahovala osem kanálov, z ktorých bol každý samostatne riadený z Arduina a stmievanie realizoval pomocou fázového riadenia triaku. Pri tejto doske bol aj odkaz na ukážkové vide a vzorové zdrojové kódy pre Arduino.
Program v Arduino bol prostý, každému kanálu bolo možné nastaviť jas od 0 do 100 (%), vonkajší obvod poskytoval detekciu prechodu sieťového napätia nulou a pomocou prerušenia zabezpečoval synchronizáciu programu s frekvenciou sieťového napätia. Pretože napätie v našej sieti má frekvenciu 50 Hz a počas jednej vlny prechádza napätie nulou dva krát, je prerušenie vyvolané každých 10 ms. 10 sekúnd je 10 000 µs, takže to nie je nijako krátky čas, ale aby sa dal riadiť jas v stupnici 0 – 100 %, je potrebné tento časový úsek rozdeliť na 100 častí, čo bolo zaistené časovačom, ktorý vyvolával prerušenie každých 100 µs, a tu to začína byť zaujímavé.
Keď som pozrel do obsluhy prerušenia tohoto časovača, videl som tam takéto niečo (skrátené):
void timerIsr()
{
clock_tick++;
if (CH1==clock_tick)
{
digitalWrite(channel_1, HIGH); // triac firing
delayMicroseconds(5); // triac On propogation delay
digitalWrite(channel_1, LOW); // triac Off
}
// at až po kanál 8
}
Čiže v obsluhe prerušenia je najprv zvýšená hodnota čítača (clock_tick
)
a potom je hodnota jasu každého kanála porovnávaná s touto hodnotou, a keď sa rovná,
tak je „prebliknutý” riadiaci vývod, ktorý zopne triak. V závislosti na hodnote
tohoto čítača môže byť triak zopnutý v rôznej časti polvny, čím je riadená efektívna
hodnota napätia (a prúdu), čo sa vo výsledku prejaví znížením/zvýšením jasu žiarovky.
Jednoduché, že? Niečo sa mi na tom ale nezdalo, a tak ma napadlo spočítať, koľko to
celé trvá. Ak neviete, jedno vykonanie funkcie digitalWrite()
trvá niečo cez 6 µs,
pretože pri každom kanáli je vykonané dva krát, zaberie to približne 13 µs. K tomu
čakacia slučka 5 µs, to dáva dohromady cca 18 µs (podmienka tiež zaberie tiež nejaký
čas, ale ten v tomto hrubom výpočte možno pokojne zanedbať).
Pretože je ovládaných dokopy 8 kanálov, bude to v najhoršom prípade trvať 18 x 8 µs, tzn.
144 µs. Na zopakovanie, obsluha prerušenia je spúšťaná každých 100 µs, takže nestihne
dokončiť svoj beh ešte predtým, kým by mala byť vyvolaná znova. Samozrejme, toto nastane
maximálne raz za 10 ms (teda raz za polvlnu), ale i tak … správne napísaný program
sa takto nespráva. Najmä pretože počas týchto 144 µs sú zakázané všetky ostatné
prerušenia, a tak stojí počítadlo millis()
(prípadne môže celkom zblbnúť a ukazovať
hlúposti), stojí obsluha prerušenia UART, čo môže mať za následok stratu znakov, ale
môže nastať aj strata synchronizácie s nulou a žiarovky môžu bliknúť.
Pretože môj známy chcel toto použiť v ich amatérskom divadle, bliknutie reflektorov bolo neprijateľné, ako i strata znakov komunikácie UART, pretože takto chcel riadiť jas z počítača.
Nie, nebudem tu popisovať ako to riešiť, chcel som len ukázať, že ľahko môže nastať situácia, kedy už Arduino nie je až také rýchle ako sa zdá, a nemusí sa jednať ani o riadenie vesmírneho letu.
Aké pomalé je Arduino
Ako to, že v jednej kapitole ppíšem, že vykonanie inštrukcie trvá 62,5 ns, ale že
vykonanie digitalWrite()
trvá cca 6 ms? Jednoducho, digitalWrite()
nie je
inštrukcia, ale funkcia knižnice Arduino. Je to perfektná funkcia, pretože práve
vďaka nej (a jej podobným, ako pinMode()
, digitalRead()
, či analogWrite()
)
je Arduino také populárne. I ten najväčší začiatočník totiž rýchlo pochopí, že má k
dispozícii štrnásť vývodov, ktoré sú očíslované číslami 0 – 13, a že všetko čo
potrebuje je poznať toto číslo (je napísané aj na doske) a zapamätať si, že nastavuje
HIGH
alebo LOW
. Nádherné, úžasne jednoduché, ale – pomalé, hoci vo väčšine
prípadov postačujúce.
Nie, nechcem znosiť knižnicu Arduino pod čiernu zem za to, že je pomalá. V reálnom svete každá sranda niečo stojí, a v tomto prípade je jednoduchosť použitia vyvážená práve časom vykonania a myslím si, že je to prijateľné. No pri ďalšom svojom projekte, kde záleží najmä na spotrebe (lebo to chcem prevádzkovať na batérie a s minimálnymi rozmermi), ma zaujímalo ako dlho vlastne bude trvať vykonanie akcie po zobudení s úsporného režimu – aby som mohol zvoliť tie správne batérie, ktoré zaistia dlhý chod, ale nezaberú príliš miesta. A tak som pátral a pátral, čítal články, strácal sa vo výpočtoch, až som narazil na hotový program ShowInfo na Arduino Playground. Stiahol som ho, nahral do Arduino Leonardo a tu chcem s výsledkom oboznámiť aj anglicky nečítajúcu časť národa.
V/V operácie
Najprv vstupno/výstupné operácie. Všimnite si ten rozdiel v trvaní V/V operácie priamo pomocou knižnice AVR (0,126 µs) a V/V operáciami knižnice Arduino. Ak teda potrebujete niečo vykonať naozaj rýchlo, použite knižnicu avr-gcc, ak pohodlne, použite knižnicu Arduino:
Operácia | [µs] |
---|---|
nop | 0,063 |
avr gcc I/O | 0,126 |
y |= (1<<x) | 0,573 |
pinMode() | 4,506 |
digitalRead() | 6,284 |
digitalWrite() | 7,744 |
analogWrite() | 9,196 |
analogRead() | 112,236 |

Trvanie V/V operácií
V grafe je vynechaná hodnota inštrukcie nop
a funkcie analogRead()
.
Prvá preto, že v tabuľke je pre zaujímavosť, no a druhá preto, že jej trvanie
tak znehodnotilo graf, že rozdiely medzi ostatných operácií nevynikli.
Operácie s číslami
Ďalším zastavením sú operácie s číslami. V mnohých návodoch vidím, že bez rozmyslu
používajú celočíselný typ int
alebo dokonca long int
i tam, kde to vyslovene
potrebné nie je. Je to jednak plytvanie pamäťou (ktorej je i tak málo), ale, ako
môžete vidieť, je to aj plytvanie časom. V neposlednom rade, všimnite si aké časovo
náročné sú operácie násobenia a (najmä) delenia v porovnaní so sčítaním (odčítanie
zaberie rovnako ako sčítanie):
Operácia | byte | integer | long | float |
---|---|---|---|---|
sčítanie | 0,573 | 0,891 | 1,779 | 9,306 |
násobenie | 0,637 | 1,398 | 6,156 | 7,174 |
delenie | 5,461 | 14,396 | 38,986 | 80,686 |
na reťazec | – | 13,066 | 127,086 | 79,711 |

Operácie s číslami
Pomerne zaujímavé výsledky poskytuje meranie času prevodu čísel na reťazec,
kde prevedenie float
trvá kratšie ako long
, ale i tak je to šialene dlhé,
tak to používajte naozaj len ak je to nutné.
Čakacie slučky
No a na záver presnosť časových slučiek. Pre mňa to bolo prekvapujúce, že vlastne
presné je len čakanie (delay()
) 100 ms, ale i tak si myslím, že sú to hodnoty
veľmi pekné a uvádzam ich tu len aby ste sa bezhlavo na presnosť nespoliehali. No
treba spomenúť aj to, že (okrem delayMicroseconds(2)
) je chyba len okolo 1 %,
a to vôbec nie je zlé:
Operácia | [µs] | rozdiel |
---|---|---|
delay(1) | 1007,486 | 7,486 |
delay(100) | 99999,984 | -0,016 |
delayMicroseconds(2) | 1,905 | -0,095 |
delayMicroseconds(5) | 4,95 | -0,05 |
delayMicroseconds(100) | 101,336 | 1,336 |

Presnosť čakacích slučiek
Záver
Čo napísať na záver? Chcel som poukázať na to, že jednoduchosť použitia funkcií z knižnice Arduino neprichádza zadarmo a stojí čas procesora. Sú aplikácie kde na tom nezáleží, no sú aj aplikácie kde to jedno nie je. Ak teda pracujete na niečom, kde už čas nerátate na milisekundy, ale na kratšie intervaly, zvážte opustiť jednoduchosť Arduino a ponoriť sa hlbšie do tajov programovania mikroradičov, so všetkou komplexnosťou použitia ich registrov. Verte mi, nakoniec zistíte, že to vôbec nie je také zložité, ako to na prvý pohľad vyzerá.