Kategória: Elektro návody

Zmenené: 23. júl 2016

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.

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:

V/V operácie
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í

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ácie s číslami
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

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é:

Čakacie slučky
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

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á.