4.5
Praxe
Projděme si několik základních stavebních kamenů většiny programů pro Arduino. Dodržování „správných“ zásad nás posouvá od nezávazné zábavy ke skutečné automatizaci.
4.5.1
Základní úlohy (D-out, in, A-out, in)
Analogové vstupy (standardně v rozsahu 0 až 5 V) jsou digitalizovány pomocí A/D převodníku a v programu vystupují jako celočíselné hodnoty v rozsahu 0 až 1023 (10 bitů). Výstup je realizován pomocí PWM (pulzně šířková modulace) a hodnoty musí být také celočíselné, v rozsahu 0 až 255 (8 bitů).
Digitální vstupy a výstupy pracují s hodnotami binárními, tedy 0 (LOW) a 1 (HIGH). Jejich malý přehled, viz [11][12].
Základní úlohy – digitální a analogový vstup a výstup
Pro vstup nebo výstup je třeba příslušný pin aktivovat pomocí funkce pinMode(<pin>,<mód>). Všechny datové piny Arduina Nano podporují digitální vstup i výstup. Analogový vstup je možný na pinech A0 až A7. Analogový výstup na pinech D3, D5, D6, D9, D10 a D11. Módy rozlišujeme: OUTPUT (výstup), INPUT (vstup), a INPUT_PULLUP (invertovaný vstup). Zpravidla provádíme nastavení pinu jednou na začátku programu, tedy ve funkci setup(). Změna nastavení stejného pinu nemá příliš smysl.
Některé piny mohou plnit další funkce, tedy pokud máme dost volných, vyhýbáme se Rx0, Tx1 (sériová linka) a A4, A5 (I2C).
Digitální výstup je použit již v prvním programu (kap.4.3). Funkcí digitalWrite(<pin>,<hodnota>) zapíšeme hodnotu na daný pin. Digitálním výstupem řídíme kontrolky, relé, krokové motory atp.
U digitálního vstupu přečteme hodnotu z příslušného pinu funkcí digitalRead(<pin>). Typicky takto zpracováváme signál z tlačítka. To je základním ovládacím prvkem, je nutné s ním umět precizně nakládat. Začněme zapojením…
+
23. Zapojení tlačítka – vlevo INPUT (použit pull down rezistor 4,7 kΩ), vpravo INPUT_PULLUP
Obr. 23. Zapojení tlačítka – vlevo INPUT (použit pull down rezistor 4,7 kΩ), vpravo INPUT_PULLUP
Pokud tlačítkem spojujeme vstupní pin s +5 V, tedy tak, aby ve stisknuté poloze generovalo HIGH a v nestisknuté LOW, musíme vstupní pin připojit přes rezistor (R > 1 kΩ) ke GND. Bez toho by pin při nestisknutém tlačítku přijímal rušení z okolí a choval by se nahodile. Rezistor nesmíme vynechat, jinak by při stisku tlačítka došlo ke zkratu.
Arduino disponuje ještě jednou metodou vstupu – INPUT_PULLUP, kdy se ke vstupnímu pinu automaticky připojí +5 V přes vnitřní rezistor (R = 20 kΩ). Ušetříme součástku, ale musíme si uvědomit, že se logika obrátila (invertovala) a stisknuté tlačítko generuje LOW, nestisknuté HIGH.
+
24. Vizualizace digitálního signálu – idealizovaná forma – upravený snímek sériové plotteru
Obr. 24. Vizualizace digitálního signálu – idealizovaná forma – upravený snímek sériové plotteru
U tlačítka nás zajímá buď okamžitý stav (signalizace), nebo změna stavu, např. počátek stisku (sepnutí/přepnutí).
4.5.1.1
Signalizační tlačítko
Univerzálním indikátorem signálu je LED. Zjednodušeně řečeno: chtěné hodnoty zobrazujeme zeleným světlem, ty nechtěné červeným. V této úloze bude tedy svítit zelená LED při stisknutém tlačítku. Při nestisknutém tlačítku bude zhasnutá.
Úloha je sice jednoduchá, ale zde začíná automatizace – tam je na prvním místě bezpečnost, na druhém spolehlivost, pak to ostatní. Začněme pořádkem v názvech proměnných, které mají něco společného se vstupem a výstupem – doporučuji, aby jejich název začínal třípísmennou zkratkou, např.:
  1. p = pin, s = signál
  1. i = INPUT nebo INPUT_PULLUP, o = OUTPUT
  1. d = digital, a = analog
A pak vlastní název proměnné související s připojeným zařízením, např. pidTlacitko nebo pidTl, případně pidButton by byl vhodný název pro pin vstupního tlačítka a sodLed pro výstupní signál na LED.
Čísla pinů nejsou nikdy záporná a jejich počet nepřekročí 256, tedy vhodným typem proměnné je pro ně uint8_t. Navíc číslo pinu se nebude během programu měnit, proto ještě lepší je const uint8_t. Digitální signál má pouze dvě hodnoty, použijeme typ bool.
Ukázka zdrojového kódu:
1. // piny 2. const uint8_t podLed = 13; 3. const uint8_t pidTl = 2; 4. // signály 5. bool sidTl = HIGH; 6. bool sodLed = LOW; 7. 8. void setup() 9. { 10. pinMode(podLed, OUTPUT); 11. pinMode(pidTl, INPUT_PULLUP); 12. } 13. 14. void loop() 15. { 16. // vstupy 17. sidTl = digitalRead(pidTl); 18. 19. // logika 20. if(sidTl == LOW) sodLed = HIGH; 21. else sodLed = LOW; 22. 23. //výstupy 24. digitalWrite(podLed, sodLed); 25. }
Hlavní smyčku loop() je vhodné rozdělit alespoň na tři sekce (viz komentáře v ukázce): první (//vstupy) má za úkol načíst všechny vstupní signály, poslední (//výstupy) má za úkol zapsat všechny výstupní signály, uprostřed proběhne logické zpracování, kde na základě vstupních signálů nastavíme ty výstupní.
V úvodní sekci načteme všechny vstupní signály (dále jen vstupy) do proměnných a zásadně tyto originální hodnoty nijak neupravujeme! Raději pro upravenou hodnotu zavedeme jinou (pomocnou) proměnnou. Jedinou výjimkou by mohl být vstup INPUT_PULLUP, který je invertovaný a tuto skutečnost lze „opravit“ tím, že vstup načteme také invertovaně, např.: sidTl = !digitalRead(pidTl), čímž bude stisknuté tlačítko generovat HIGH a nestisknuté LOW.
V sekci logického zpracování naopak pracujeme pouze s proměnnými, nenačítáme znovu žádné vstupy a neměníme hodnotu originálních vstupních proměnných – to by mohlo zmást algoritmus. Rovněž výstupní proměnné pouze nastavujeme – hodnota v nich při logickém zpracování může procházet určitým vývojem…
Hodnoty výstupních proměnných (výstupní signály, dále výstupy) zapíšeme na příslušné piny až v závěrečné sekci, kdy máme jistotu, že se do konce smyčky nezmění.
4.5.1.2
Přepínací tlačítko
Přepínací tlačítko známe např. z dálkového ovládání většiny TV přijímačů – stiskem stejného tlačítka se zařízení zapne i vypne, přičemž doba stisku nerozhoduje. Z hlediska signálu, který čteme z tlačítka, je rozhodující náběžná hrana – signál vzroste z LOW na HIGH (pro INPUT) nebo sestupná hrana – signál klesne z HIGH na LOW (pro INPUT_PULLUP). Zapojení přepínacího i signalizačního tlačítka bude shodné, změníme jen program – zajímají nás vždy dvě po sobě jdoucí iterace hl. smyčky programu, kdy nejprve zachytíme signál HIGH a následně LOW. Ideální bude použít navíc proměnnou, která uchová stav tlačítka z minula i po jeho novém načtení. Proměnným pro přenos hodnot do dalších iterací hlavní smyčky by také slušelo speciální označení, např. „lo“ (jako loop) na začátku názvu. Program by mohl vypadat takto:
1. // piny 2. const uint8_t podLed = 13; 3. const uint8_t pidTl = 2; 4. // signály 5. bool sidTl = HIGH, loMinulyStavTl = HIGH; 6. bool sodLed = LOW; 7. 8. void setup() 9. { 10. pinMode(podLed, OUTPUT); 11. pinMode(pidTl, INPUT_PULLUP); 12. } 13. 14. void loop() 15. { 16. // vstupy 17. sidTl = digitalRead(pidTl); 18. 19. // logika 20. if(sidTl == LOW && loMinulyStavTl == HIGH) sodLed = !sodLed; 21. delay(300); 22. loMinulyStavTl = sidTl; 23. 24. //výstupy 25. digitalWrite(podLed, sodLed); 26. }
V programu se vyskytuje delay(300), tedy zpoždění. Důvodem je přechodový jev při změně stavu tlačítka, kdy se úroveň signálu může po dosažení LOW krátkodobě zvednout na HIGH a opět spadnout na LOW. To by ale tlačítko přepnulo tam a zase zpět a stalo by se nespolehlivým. Zpoždění nechá signál uklidnit. Nicméně delay() zdrží celou smyčku a pokud bychom souběžně řešili další úkoly, potřebujeme najít nějaké citlivější řešení. Jedním z možných je použití přerušení. Přechodový jev může pohltit i RC člen (viz http://ladyada.net/library/rccalc.html), to ale znamená připojit další součástky.
4.5.1.3
Tlačítka start a stop
I bez závazných norem (ČSN EN 60204-1 ed. 3) nám vlastní rozum velí dbát na bezpečnost. Vezměme modelový příklad, kde máme k dispozici jedno tlačítko START, které zapne chod zařízení, jedno tlačítko STOP, které vypne chod a LED, která simuluje chod zařízení (svítí = zapnuto, nesvítí = vypnuto). STOP má přednost přede vším ostatním. Naopak na START reaguje zařízení jen pokud nejsou důvody proti – v našem případě není stisknuto STOP – obecně mohou vstoupit do hry ještě senzory „nežádoucího stavu“. Po výpadku a obnově napájení nesmí zařízení samo pokračovat v činnosti. A dále – pokud současně tiskneme START i STOP a STOP uvolníme, nesmí se zařízení rozběhnout. Leda poté, co START také uvolníme a znovu stiskneme.
V kódu si ukážeme i jiné techniky: makro, invertované čtení, pole a finty s logickou hodnotou:
1. // piny 2. const uint8_t podLed = 4; 3. const uint8_t pidTlStart = 2; 4. const uint8_t pidTlStop = 3; 5. // signály 6. bool sidTlStart[2] = {LOW, LOW}; // [0] – nový, [1] - minulý 7. bool sidTlStop = LOW; 8. #define sodLed Zapnuto // sodLed znamená to samé, co Zapnuto 9. // ostatní 10. bool Zapnuto = LOW; 11. 12. void setup() 13. { 14. pinMode(podLed, OUTPUT); 15. pinMode(pidTlStart, INPUT_PULLUP); 16. pinMode(pidTlStop, INPUT_PULLUP); 17. } 18. 19. void loop() 20. { 21. // vstupy 22. sidTlStop = !digitalRead(pidTlStop); // stisk = HIGH 23. sidTlStart[0] = !digitalRead(pidTlStart); 24. 25. // logika 26. if(sidTlStop) Zapnuto = LOW; 27. else if(sidTlStart[0] && !sidTlStart[1]) Zapnuto = HIGH; 28. delay(10); 29. sidTlStart[1] = sidTlStart[0]; 30. 31. //výstupy 32. digitalWrite(podLed, sodLed); // sodLed = Zapnuto 33. }
Novinky začínají 6. řádkem, kde je pro signál z tlačítka START použito pole – hodnota s indexem [0] bude znamenat aktuální stisk tlačítka, [1] ten z minulé smyčky. Demonstrativně jsou do pole vloženy hodnoty LOW, ačkoliv to není třeba, protože globální celočíselné proměnné se automaticky nulují. Nicméně na ř. 23 čteme signál invertovaně (ale v módu INPUT_PULLUP), takže nestisknuté tlačítko bude přečteno jako LOW. Dále na ř. 29 dojde k posunu ve frontě. Tímto způsobem by šla provozovat i delší fronta, akorát by se muselo posunout o jedno místo postupně více prvků pole (poslední se zapomene, nahradí jej předposlední atd.).
Na ř. 8 je definováno makro sodLed jako falešná proměnná – slovo „sodLed“ bude totiž nahrazeno slovem „Zapnuto“ – a na podLed bude ve skutečnosti zapsána hodnota Zapnuto (předposlední ř.).
Na ř. 26 a 27 je v podmíněných příkazech if(…) použita finta, kdy nenulová hodnota (HIGH) sama o sobě znamená pravdu a nulová (LOW) nepravdu a odpadá potřeba porovnání, nicméně pro lepší přehled by řádky mohly vypadat takto:
if(sidTlStop == HIGH) Zapnuto = LOW; else if(sidTlStart[0] == HIGH && sidTlStart[1] == LOW) Zapnuto = HIGH;
4.5.1.4
Analogový vstup a výstup
Jak již bylo napsáno, digitalizovaná hodnota analogového vstupu (piny A0 až A7) je desetibitová, tedy potřebujeme šestnáctibitovou proměnnou – ideální je uint16_t. Pro výstup stačí osmibitová, tedy uint8_t. V následujícím kódu provedeme zatím jen přepis přečteného vstupu na výstup. Vstupní signál budeme regulovat potenciometrem s odporem 0 až 10 kΩ. Na výstup můžeme připojit malý DC motorek (např. z CD nebo DVD mechaniky) nebo jen LED.
1. // piny 2. const uint8_t piaPot = A7; 3. const uint8_t poaMotor = 3; 4. // signály 5. uint16_t siaPot; 6. uint8_t soaMotor; 7. 8. void setup() 9. { 10. pinMode(piaPot, INPUT); 11. pinMode(poaMotor, OUTPUT); 12. } 13. 14. void loop() 15. { 16. // vstupy 17. siaPot = analogRead(); 18. 19. // logika 20. soaMotor = map(siaPot,0,1024,0,255); 21. 22. //výstupy 23. analogWrite(poaMotor, soaMotor); 24. }
Zapojeno a odzkoušeno? V ukázce vidíme, že funkce pinMode() nerozlišuje analogový a digitální signál. Dále je použita funkce map(), která převede hodnotu siaPot z rozsahu 0–1023 do rozsahu 0–255 (z 10 na 8 bitů). Zkuste prohodit její poslední dva argumenty 0, 255 na 255, 0... Ano, změnil se smysl ovládání (nikoliv směr pohybu motoru). U této úlohy se projevila ještě jedna vlastnost motoru – z klidového stavu se nerozeběhne ihned, ale až po překročení určité úrovně napájení (výstupního signálu z pohledu Arduina), což je způsobeno třením. Hluchý rozsah lze přeskočit jiným mapováním nebo dodatečným ořezáním pomocí funkce constrain(). Hodnotu výstupního signálu můžeme nechat vypisovat např. do sériového monitoru (viz. kap. 4.5.3) a zjistit tak, kdy se skutečně motor rozeběhne. Naopak, při snižování otáček motoru není hodnota signálu pro zastavení stejná jako pro jeho rozběh, a tak statická změna mapování nebude tím správným řešením. Nejlepší volbou by byla zpětná vazba ze senzoru, který bude otáčky motoru měřit a ovlivňovat výstupní signál tak, aby otáčky motoru reagovaly úměrně nastavení potenciometru.
Poznámka
Univerzální náhradou digitálního vstupního zařízení je tlačítko, analogového potenciometr. Pro simulaci analogového i digitálního výstupního zařízení vystačíme s LED. Náhradu použijeme v situaci, kdy potřebujeme připravit pokus (program), ale periferie zatím nemáme k dispozici, nebo je nechceme obětovat. Náhrady sice nemusí fungovat úplně stejně (viz rozběh DC motorku), ale na hrubé odladění základních funkcí programu to stačí.
4.5.2
Časovače, loop() a cykly v ní, přerušení
Prázdná smyčka loop() Arduina Nano netrvá ani jednu celou milisekundu. Každý příkaz, který do hlavní smyčky zařadíme, ji prodlouží. Více než nejrychlejší iterace nás většinou zajímá ta nejpomalejší, tedy z vnějšího pohledu maximální doba reakce na případný podnět. Spolehlivost bývá důležitější než špičkový ale rozkolísaný výkon.
Nejčastějším problémem bývá používání funkce delay() a nadbytečných cyklů uvnitř hlavní smyčky. Během např. delay(1000) program jen sekundu čeká a nic jiného nedělá. Procesor sice trošku podvádí a pracuje na vlastních úkolech navíc (generuje PWM, ošetřuje přerušení…), ale v loop() se v tu dobu jen čeká. Rovněž vložený cyklus (for, while, do) v hlavní smyčce by měl konat pouze to, co zbytečně neprodlužuje dobu reakce Arduina (např. může projít stav všech kláves na klávesnici, ale neměl by opakovaně testovat tutéž klávesu atp.).
Pokud skutečně potřebujeme mezi dvěma následujícími opakováními téže akce zpoždění, ale nechceme prodlužovat hlavní smyčku zbytečně, mohli bychom si zavést vlastní hodiny pomocí funkcí millis() nebo micros() a stopovat, zda již potřebný čas uplynul a buď zdržovat pomocí krátkého delay() nebo delayMicroseconds(), nebo akci vynechávat, dokud není časová prodleva dostatečná. Obě techniky si naznačíme – předpokládáme, že všechny potřebné proměnné a funkce existují a zbytek programu má smysl. První techniku můžeme nazvat např. „minimální délka smyčky“:
uint32_t cas, perioda = 15; //...setup() apod. void loop() { cas = millis(); akce(); //...další příkazy while(millis() - cas < perioda); // nebo delay(1); }
Na předposledním řádku stačilo opakovat prázdný příkaz. Koho to děsí, může použít např. malé delay(1). Druhou techniku nazvěme např. „správný okamžik“
uint32_t loCasMinule, perioda = 15; //...setup() apod. void loop() { //...předchozí příkazy if(millis() > loCasMinule + perioda) { akce(); loCasMinule = millis(); } //...další příkazy }
Nicméně vykonávání akce je sekvenční, to proto, aby byla dodržena minimální doba mezi dokončením akce a začátkem následující. Pokud bychom chtěli pravidelné spouštění v určitý čas (dle budíku), vypadal by kód např. takto:
uint32_t perioda = 2000; bool akceByla = false; //...setup() apod. void loop() { //...předchozí příkazy if(millis() % perioda < perioda/2) { if(!akceByla) { akce(); akceByla = true; } } else akceByla = false; //...další příkazy }
Pozor, vnořený if() musí být obalen {} nebo by musel mít svoje else s prázdným příkazem! Funkci millis() lze nahradit proměnnou, která získá čas dříve nebo jinou cestou, např. z RTC modulu (real time clock). Funkci akce() lze pojmout objektově a pak by pojistka proti opakovanému provedení (akceByla…) mohla zmizet z hlavní smyčky.
Pro ošetření události, která má mít přednost před ostatními, lze využít přerušení (interrupt). Nicméně je-li to možné, využití přerušení se v programu vyhýbáme – je to přeci jen „předbíhání ve frontě“, navíc spojené s „obelháváním“ zbytku programu (např. se zastaví systémový čas apod.). Podrobné seznámení s přerušením je nad rámec tohoto materiálu – musíte hledat jinde, např. v nápovědě Arduino IDE.
4.5.3
Sériová linka (+monitor a plotter)
Arduino Nano má jednu sériovou linku – tedy komunikační kanál umožňující přenos znaků (bajtů) mezi Arduinem a jiným zařízením, které tento způsob komunikace podporuje. Obě zařízení musí mít společnou zem (GND) a křížem propojené piny Rx (D0) a Tx (D1) – tam, kde jedna strana vysílá, ta druhá naslouchá a naopak. Dále je nutné nastavit oběma zařízením stejnou komunikační rychlost, pro jednoduché pokusy nejčastěji 9600 baud (resp. bps, tedy bitů za sekundu). Tuto rychlost je možné měnit, nejčastěji dělíme či násobíme mocninou dvou. např. 4800, 19200, 38400…Je vhodné používat pouze standardní hodnoty (podle ANSI).
+
25. Ukázka sériového monitoru a plotteru – fotomontáž – nelze spustit oba najednou
Obr. 25. Ukázka sériového monitoru a plotteru – fotomontáž – nelze spustit oba najednou
+
26. Detail nastavení bitové rychlosti sériového monitoru
Obr. 26. Detail nastavení bitové rychlosti sériového monitoru
USB připojení k PC obsadí stejnou sériovou linku (viz kap. 3.3.1, některá Arduina mají více sériových linek, pak obsadí tu první). Pokud tedy používáme sériovou linku ke komunikaci např. s dalším Arduinem, měli bychom před nahráváním programu sériovou linku odpojit a připojit zpět až po konci nahrávání. Podobně, pokud používáme piny D0 a D1 pro jiné účely, si musíme uvědomit, že pokud je Arduino připojeno k USB, je signál na D0 rušen z PC a naopak zápisem na D1 obtěžujeme PC. Problému se vyhneme napájením z VIN místo USB.
Chceme-li po sériové lince vysílat, zapneme ji příkazem Serial.begin(<rychlost>). Ukončíme ji Serial.end(), ale pokud to neuděláme, ukončí se sama při vypnutí Arduina. U pokročilých systémů je slušností při jejich řízeném vypínání poslat druhé straně informaci o konci komunikace a pak ji ukončit. Druhá strana tak ví, že nedošlo pouze k výpadku.
K textovému výstupu použijeme funkce Serial.print()Serial.println() (ta navíc odřádkuje). K blokovému výstupu (po bajtech) slouží Serial.write(). Při čtení je situace komplikovanější – příchozí data se shromažďují ve frontě (bufferu) dlouhé 64 bajtů (znaků). Počet obsazených bajtů vrací funkce Serial.available(), samotné čtení provádí Serial.read() a jí podobné funkce – ty vybírají data z bufferu. Serial.peek() také přečte znak, ale v bufferu jej ponechá.
Pomocí sériového monitoru a plotteru (nabídka Nástroje) můžeme výstup sériové linky sledovat. Plotter kreslí graf číselné hodnoty z výstupu funkce Serial.println() v čase, monitor zobrazuje text. Z monitoru lze text i odeslat zpět do Arduina. Jen je třeba nastavit správné parametry komunikace. Rychlost známe, ale ještě je tu konec řádku, tedy znak, který monitor přidá při odeslání. Chybný konec řádku znamená žádný znak, CR (‘\r‘, ASCII 13, návrat vozíku) – používá se v Mac OS, NL (‘\n‘, ASCII 10, nový řádek) – používá se v Linuxu, nebo dvojice CR+NL – používá se v MS Windows. Při sériové komunikaci se raději vyhýbáme vícebajtovým (unicode) znakům, protože komplikují zpracování (počet bajtů neodpovídá počtu znaků, takový znak již nelze zpravovat jako char atd.).
Následuje ukázka funkce, která přečte ze sériové linky povel ukončený středníkem, CR, LF, nebo jejich libovolnou sekvencí.
1. // globální proměnné 2. String vetaSem = "", povel = ""; 3. char znakSem; 4. bool konecSem = false; 5. 6. // funkce 7. int ctiSerial() 8. { 9. if(povel == "") 10. while(Serial.available()) 11. { 12. znakSem = Serial.read(); 13. if(znakSem == '\r' || znakSem == '\n' || znakSem == ';') 14. { 15. if(vetaSem != "") konecSem = true; 16. break; 17. } 18. else vetaSem += znakSem; 19. } 20. 21. if(konecSem) 22. { 23. povel = vetaSem; 24. vetaSem = ""; 25. konecSem = false; 26. return 1; // dočteno - povel je hotov 27. } 28. return 0; // není dočteno 29. }
Funkce bude opakovaně volána v hlavní smyčce a pokud sesbírá postupně celý text povelu, vrátí hodnotu 1, jinak 0. Ukončovací znaky navíc jsou ignorovány. Tato funkce je reakcí na „nevhodné chování“ Serial.readString().
4.5.4
Velké vstupy a výstupy
Pokud chceme Arduinem zpracovávat signály s hodnotami vybočujícími z rozsahu 0 až 5 V, potřebujeme nějakou pomůcku, která hodnotu signálu upraví. Pro stejnosměrný vstup (např. napětí na výstupu solárního panelu) využijeme nejspíše dělič napětí, pro střídavé napětí asi transformátor s usměrňovačem. Pokud vstupní periferii navrhujeme sami, musíme kromě napětí do 5 V řešit také proud, který nesmí překročit 40 mA.
Pro výstupy, tedy přesněji pro spínání velkých zátěží, máme k dispozici např. relé nebo tranzistor. Pro prosté spínání je pohodlným řešením relé (s ovládacím napětím do 5 V). Spíná jak stejnosměrné (nejčastěji do 30 V), tak i střídavé napětí (do 250 V) s proudem do 10 A. Charakteristickou vlastností relé je klapnutí, když elektromagnet přitáhne nebo pustí jazýček kontaktu a galvanické oddělení ovládacího signálu od ovládaného napětí. K monostabilnímu relé lze spínaný obvod připojit dvojím způsobem – vstup vždy COM, výstup buď NO (normaly open, tedy rozpojeno) nebo NC (normaly closed, tedy spojeno). Další vlastnosti je vhodné nastudovat vždy v dokumentaci. Bistabilní relé má pružinkou aretovány obě polohy kontaktu a přepíná se stejným impulzem tam i zpět. Dalším typem je SSR (solid state relay), což je polovodičová součástka, která neklape, je rychlejší a má nižší vlastní spotřebu, avšak spíná pouze signál s dynamickou charakteristikou, tedy ne stejnosměrný (ten propouští trvale!), střídavý a PWM ano. SSR vhodná pro Arduino mívají maximální trvalý spínaný proud do 3 A.
Tranzistor je také rychlejší, úspornější a tišší než klasické relé, jen nesnese změnu polarity (nic pro střídavé napětí). Další jeho výhodou je, že kromě spínání může cílový obvod i regulovat (pomocí PWM). Zapojení se liší podle použitého typu tranzistoru. Např. ohřev trysky a podložky 3D tiskáren v RAMPS (shieldu pro Arduino Mega) je spínán pomocí MOSFET tranzistorů. [13]
+
27. Regulace větší zátěže (např. DC motoru) pomocí tranzistorů – dioda zamezí obrácení polarity při rozběhu motoru, NPN tranzistor (např. BC548) je použitelný pro menší kolektorové proudy (do 0,5 A), FET N-kanál (např. IRF540) pro větší proudy (i přes 30 A), tranzistorové pole (např. ULN2003A) pro více zátěží (Arduino Nano má pouze 6 PWM výstupů), zdroj např. 9 nebo 12 V DC, rezistor 1 kΩ
Obr. 27. Regulace větší zátěže (např. DC motoru) pomocí tranzistorů – dioda zamezí obrácení polarity při rozběhu motoru, NPN tranzistor (např. BC548) je použitelný pro menší kolektorové proudy (do 0,5 A), FET N-kanál (např. IRF540) pro větší proudy (i přes 30 A), tranzistorové pole (např. ULN2003A) pro více zátěží (Arduino Nano má pouze 6 PWM výstupů), zdroj např. 9 nebo 12 V DC, rezistor 1 kΩ
4.5.5
Pokročilé periferie (shieldy)
Shieldem (štítem) rozumíme přídavnou periferii, která se připojí k Arduinu a většinou zakryje všechny jeho vstupní a výstupní kontakty (mimo USB). Toto pojmenování se již rozšířilo prakticky na jakoukoliv periferii, která má nějakou přidanou funkci, ačkoliv obecný název zní modul. Shield/modul zjednodušuje práci nebo umožňuje připojit zařízení, které by Arduino samo řídit nemohlo. Pro Arduino Nano příliš mnoho vlastních shieldů neexistuje, ale je použitelná spousta univerzálních, např. motor shield pro řízení krokových a DC motorů, BlueTooth, WiFi nebo GSM moduly atd.
+
28. Rozšiřující shield pro Arduino Nano – pro připojení periferií s třípinovým konektorem
Obr. 28. Rozšiřující shield pro Arduino Nano – pro připojení periferií s třípinovým konektorem
4.5.6
Pokročilé periferie (I2C sběrnice)
Oblíbeným a jednoduchým způsobem připojení periferií je I2C (I2C, též IIC – Inter-Integrated Circuit nebo TWI – Two-Wire Interface) sběrnice (má i další názvy). Jedná se o sériovou sběrnici, která pomocí dvou pinů A4 (SDA) a A5 (SCL) umožňuje Arduinu Nano (jiná Arduina mohou používat jiné piny) komunikovat s dalšími zařízeními. Celkem jsou k propojení potřeba čtyři vodiče, kromě SCL a SDA ještě GND a +5 V. Arduino funguje jako master, tedy komunikaci řídí, ostatní (slave) zařízení musí mít nastavenou unikátní adresu (0x08 až 0x7f, čili 8 až 127) a pouze odpovídají. Nelze, aby např. nějaké čidlo samo hlásilo naměřené hodnoty, Arduino se na hodnoty musí vždy dotázat. Pokud je na I2C připojeno více slave zařízení, musíme řešit problém pullup rezistoru, kterým jsou linky SDA a SCL připojeny k napájení (+5 V z Arduina). Jednotlivé moduly již bývají pullup rezistory osazeny z výroby a při propojování se jejich odpory řadí paralelně, čímž celkový odpor klesá. Nadbytečné pullup rezistory je třeba odpojit (většinou odpájet). Dvě slave zařízení by ještě neměla působit problémy i bez úpravy.
K práci s I2C potřebujeme knihovnu Wire (#include<Wire.h>). Výrobci většinou ke svým zařízením dodávají vlastní knihovny (a ty samy volají knihovnu Wire). Pokud chceme ověřit adresu připojených slave zařízení, lze použít např. následující program…
1. #include<Wire.h> 2. 3. byte chyba; 4. byte adresa; 5. uint8_t pocet = 0; 6. 7. void setup() 8. { 9. Wire.begin(); 10. Serial.begin(9600); 11. Serial.println("Hledam I2C zarizeni..."); 12. for (adresa = 8; adresa <= 127; adresa++) 13. { 14. Wire.beginTransmission(adresa); 15. chyba = Wire.endTransmission(); 16. if (chyba == 0) 17. { 18. pocet++; 19. Serial.print(pocet); 20. Serial.print(". adresa: 0x"); 21. if(adresa < 16) Serial.print("0"); 22. Serial.println(adresa, HEX); 23. } 24. else if (chyba == 4) 25. { 26. Serial.print("??? adresa: 0x"); 27. if (adresa < 16) Serial.print("0"); 28. Serial.println(adresa, HEX); 29. } 30. } 31. if(pocet == 0) Serial.println("Zadne zarizeni nenalezeno!"); 32. } 33. 34. void loop() 35. {}
Nejčastěji používanými I2C periferiemi bývají LCD displeje a RTC moduly [10][14]. Zkusme si tedy připojit displej i RTC modul: na prvním řádku se bude zobrazovat den v týdnu, datum (dd.mm.) a čas (hh:mm), na druhém se bude po jedné sekundě otáčet ručka („kolotoč“) a zobrazovat „teploměr“ (0 až 15 dílků).
+
29. RTC modul – spodní stranu překrývá baterie CR2032, která jej dokáže napájet až 8 let
Obr. 29. RTC modul – spodní stranu překrývá baterie CR2032, která jej dokáže napájet až 8 let
+
30. LCD znakový displej – ze spodní strany je připájen I2C řadič
Obr. 30. LCD znakový displej – ze spodní strany je připájen I2C řadič
1. // displej 2. #include<LiquidCrystal_I2C.h> 3. LiquidCrystal_I2C lcd(0x27,16,2); // objekt displeje 4. // vlastní znaky 5. byte backslash[8] = 6. { 7. B00000, 8. B10000, 9. B01000, 10. B00100, 11. B00010, 12. B00001, 13. B00000, 14. B00000 15. }; 16. char kolotoc[4] = {'|', '/', '-', 0}; // kód 0 bude mít backslash, zatím v displeji není 17. uint8_t teplomer; // počet dílků na teploměru (0-14) 18. uint8_t i; // pomocná proměnná pro cykly 19. 20. // hodiny 21. #include <RTClib.h> 22. RTC_DS1307 hodiny; // hodiny - RTC modul 23. DateTime datumCas; // struktura s aktuálním datem a časem 24. String strCas; // kompletní textová informace o datu a času 25. char dnyTydne[7][3] = {"Ne", "Po", "Ut", "St", "Ct", "Pa", "So"}; 26. uint16_t loSekunda = -1; 27. 28. //////////// 29. // funkce // 30. //////////// 31. 32. void casNacti() // načte aktuální čas do proměnných datumCas a strCas 33. { 34. datumCas = hodiny.now(); // načte aktuální čas do struktury 35. strCas = String(dnyTydne[datumCas.dayOfTheWeek()]) + " "; // dny v týdnu 36. strCas += (datumCas.day() < 10 ? "0" : "") + String(datumCas.day()) + "."; // formát DD 37. strCas += (datumCas.month() < 10 ? "0" : "") + String(datumCas.month()) + ". "; // MM 38. //strCas += String(datumCas.year()) + " "; // nezobrazujeme, ale takhle se to dělá: RRRR 39. strCas += (datumCas.hour() < 10 ? "0" : "") + String(datumCas.hour()) + ":"; // hh 40. strCas += (datumCas.minute() < 10 ? "0" : "") + String(datumCas.minute()); // mm 41. } 42. 43. void setup() 44. { 45. lcd.init(); // aktivuje displej 46. lcd.backlight(); // zapne podsvícení displeje 47. lcd.createChar(0, backslash); // displej registruje nový znak 48. 49. // připojíme hodiny, pokud neodpovídají, zkoušíme po 10 s znovu 50. while(! hodiny.begin()) 51. { 52. lcd.home(); // kurzor do levého horního rohu 53. lcd.print("Nevidim hodiny"); // tisk textu, kurzor se posouvá sám 54. delay(10000); 55. } 56. 57. // kontrola spuštění obvodu reálného času 58. if (! hodiny.isrunning()) // if (hodiny.lostPower()) 59. { 60. lcd.clear(); // smaže dispej a kurzor nastaví do levého horního rohu 61. lcd.print("Spoustim hodiny"); 63. // nastavení: rok, měsíc, den, hodina, minuta, sekunda 64. // příklad: 26.8.2019 7:31:20 65. // hodiny.adjust(DateTime(2019, 8, 26, 7, 31, 00)); 66. // nebo datum a čas kompilace - náš případ 67. hodiny.adjust(DateTime(F(__DATE__), F(__TIME__))); 68. } 69. 70. // počáteční fáze teploměru 71. datumCas = hodiny.now(); // naplnění struktury aktuálním časem 72. teplomer = datumCas.second()%15; // zjištění aktuální fáze teploměru 73. lcd.setCursor(2,1); 74. for(i=1; i<=teplomer; i++) lcd.write(255); // doplnění počátečních dílků 75. lcd.home(); 76. } 77. 78. void loop() 79. { 80. casNacti(); // přečte čas z hodin a vloží do strCas 81. // správný okamžik pro zobrazení - změna sekundy 82. if(datumCas.second() != loSekunda) 83. { 84. lcd.setCursor(0,0); // začátek 1. řádku 85. lcd.print(strCas); // zobrazí datum a čas 86. lcd.setCursor(0,1); // začátek 2. řádku 87. lcd.write(kolotoc[datumCas.second()%4]); /* perioda kolotoče 4 sekundy 88. pro tisk vlastních definovaných znaků použijeme metodu .write(), 89. která neprovádí automatickou konverzi a bere jen znaky typu char 90. */ 91. teplomer = datumCas.second()%15; // perioda teploměru 15 sekund 92. lcd.setCursor(1+teplomer,1); // nastaví kurzor na aktuální dílek 93. if(teplomer == 0) lcd.print(" "); // smaže teploměr 94. else lcd.write(255); // přidá dílek 95. loSekunda = datumCas.second(); // zapamatuje si aktuální sekundu 96. } 97. }
4.5.7
Obnova/výměna zavaděče (bootloaderu)
Zavaděč neboli bootloader je program, který je trvale uložen ve flash paměti Arduina a zajišťuje nahrávání firmware (např. námi vytvořených programů v IDE) do zbylé části paměti flash. Pokud je zavaděč porušen, nelze přes USB (obecně po sériové lince) do Arduina nic nahrát. Musíme použít jinou cestu – externí programátor nebo další Arduino – jimi lze nejen nahrát firmware (program), ale i obnovit nebo vyměnit bootloader. Ukažme si, jak nahrání nového zavaděče provést pomocí jiného Arduina.
Především potřebujeme samotný binární (.hex) soubor s bootloaderem. Buďto jej najdeme a stáhneme z internetu (např. optiboot), nebo použijeme originální z instalace Arduina – ten by se měl nacházet např. ve složce „/usr/share/arduino/hardware/arduino/avr/bootloaders/atmega“ (GNU/Linux), „C:\Program Files (x86)\Arduino\hardware\arduino\avr\bootloaders\atmega“ (MS Windows) nebo obdobné – záleží na konkrétní instalaci a modelu Arduina, jehož zavaděč chcete vypálit (nahrát).
Dále musíme do Arduina-programátoru nahrát v IDE program z nabídky Soubor/Příklady/ArduinoISP/ArduinoISP.
+
31. Obnova bootloaderu pomocí jiného Arduina – to vlevo slouží jako programátor, to vpravo jako příjemce. Propojíme až před samotným vypálením zavaděče.
Obr. 31. Obnova bootloaderu pomocí jiného Arduina – to vlevo slouží jako programátor, to vpravo jako příjemce. Propojíme až před samotným vypálením zavaděče.
Potom teprve připojíme cílové Arduino k programátoru podle výše uvedeného obrázku, zvolíme v nabídce Nástroje/Programátor/Arduino as ISP a dále Nástroje/Vypálit zavaděč.
Pokud máme k dispozici specializovaný programátor (např. Arduino ISP, AVR ISP apod.), nic do něho nehráváme, nasuneme jej konektorem rovnou na ICSP konektor Arduina, zvolíme jen správný typ programátoru v nabídce Nástroje a pálíme.