4.1
Připojené přímo
4.1.1
Měření napětí nad 5 V – dělič napětí
Pro měření napětí pomocí Arduina nepotřebujeme žádné nákladné periferie, stačí obyčejný dělič napětí. Rezistory R1 a R2 (viz následující obrázek) volíme tak, aby se celkový odpor R1 + R2 pohyboval v desítkách až stech kΩ. Dělicí poměr volíme s jistou rezervou, abychom na straně Arduina nikdy (!!!) neměli vyšší napětí než 5 V. Rovněž musíme dát pozor na přepólování (samotný dělič polaritu neovlivňuje).
+
9. Dělič napětí – schématické znázornění – vlevo nezatížený, vpravo zatížený
Obr. 9. Dělič napětí – schématické znázornění – vlevo nezatížený, vpravo zatížený
Zátěž Rz ovlivňuje dělicí poměr. Je paralelně připojena k rezistoru R2 a tím snižuje odpor mezi přilehlými uzly A a B. Vnitřní odpor vstupu Arduina představuje zátěž Rz – to jsou řádově sta MΩ a dělič by se choval spíše jako nezatížený. Nicméně není vůbec jisté, že Arduino přiřadí úrovní napětí 5,00 V na analogovém vstupu hodnotu 1023 (maximum z 10 bitového AD převodníku), a proto provádíme kalibraci.
Výpočet dělicího poměru nezatíženého děliče (k odhadu stačí):
U2U1=R2R1+R2
Výpočet dělicího poměru zatíženého děliče:
U2U1=RABR1+RAB, kde RAB=R2RZR2+RZ
Předpoklady a zásady:
  • úroveň vstupního signálu v Arduinu je přímo úměrná napětí na jeho analogovém vstupu,
  • dělicí poměr volíme tak, aby se nejčastěji měřená úroveň napětí pohybovala v horní polovině měřicího rozsahu (našeho Arduino-voltmetru),
  • výstupní napětí děliče před připojením vstupu Arduina přeměříme (nesmí přesáhnout 5,00 V),
  • vstup Arduina nesmíme přepólovat (+ na vstup, − na GND).
Postup kalibrace:
  1. potřebujeme dvě referenční napětí U11U12 (např. 80 % a 30 % z předpokládaného rozsahu),
  1. napětí U11 změříme voltmetrem (referenčním přístrojem) a pak s pomocí děliče přečteme odpovídající úroveň signálu s1 (0 až 1023) na analogovém vstupu Arduina,
  1. stejným způsobem změříme U12s2,
  1. pokud je to možné, měření několikrát opakujeme a pokud se úrovně signálu pro stejná napětí pokusů liší, průměrujeme je,
  1. určíme závislost měřeného napětí U1 na úrovni vstupního signálu Arduina a zabudujeme ji do programu pro Arduino.
Abychom se vyhnuli nepřehledným vzorcům v hlavní smyčce programu, vytvoříme funkci napeti(), která celočíselnou úroveň analogového signálu ze vstupu přepočítá na napětí vyjádřené desetinným číslem. Použijeme lineární interpolaci, tedy stanovíme závislost měřeného napětí na úrovni signálu jako lineární funkci:
U1=as+b
Koeficienty a, b vypočítáme ze soustavy lineárních rovnic z alespoň dvou měření různých napětí U1. Např. pokud naměříme:
U11=19,95 V;  s1=822U12=9,98 V;  s2=409
pak
a=U11-U12s1-s2=19,95-9,98822-409=9,97413b=U11+U12 -a s1+s22=19,95+9,98-9,97413 822+4132=0,21312=0,1066
Neočekávejme závratnou přesnost. Uvědomme si, že voltmetr v roli referenčního přístroje pracuje s chybou, rozlišení analogového vstupu jsou přibližně promile rozsahu a svou roli hraje i teplota čipu Arduina. Pro možnou kontrolu můžeme do programu zabudovat přepínač mezi zobrazením měřeného napětí a úrovně vstupního signálu.
// měření napětí pomocí děliče /* o----+ | R1 | U1 +----o | R2 U2 ==> siaU | o----+----o Vhodné rezistory pro dělič napětí mají odpory v jednotkách až stech kΩ. Běžně prodávaný hotový dělič má R1 = 30 kΩ a R2 = 7.5 kΩ, tedy v poměru 4:1 a měl by na vstup Arduina přivádět 1/5 měřeného napětí U. Vždy ale kalibrujeme vstup pro konkrétní zapojení a Arduino - nespoléháme na teoretický výpočet! Arduino na vstupu klade také nějaký odpor a rozhodně nemusí platit, že napětí 5V dá na vstupu hodnotu 1023. Tlačítkem na pinu D2 se zapíná a vypíná mód, kdy displej zobrazuje místo napětí ve voltech jen číselnou úroveň signálu. */ #include<LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,16,2); // SCL = A5, SDA = A4 uint8_t pidTl = 2; // přepínací tlačítko módu zobrazení uint8_t sidTl[2]; // signály z tlačítka uint8_t zobrazeni; // mód zobrazení: 0 = napětí, 1 = signál uint8_t piaU = A0; // vstupní pin pro měření analogového signálu uint16_t siaU; // hodnota analogového signálu na vstupu uint64_t cas, perioda = 500; // pro synchronizaci času, aby displej tolik neblikal // převodní funkce vstupního analogového signálu na hodnotu měřeného napětí double napeti(uint16_t signal) { /* linearní interpolace ze zkušebního měření: U1 = a*s + b; ------------- U1_1 = 19.95 V, s1 = 822 U1_2 = 9.98 V, s2 = 409 ------------- a = (U1_1 - U1_2) / (s1 - s2) = 9.97 / 413 b = U1_1 + U1_2 - a*(s1 + s2) = 0.1066 diody v obvodu by způsobily b != 0 */ return signal * 9.97 / 413 + 0.1066; //return signal; // pro kalibraci - jen signal } void setup() { pinMode(pidTl,INPUT_PULLUP); pinMode(piaU,INPUT); lcd.init(); lcd.backlight(); lcd.home(); lcd.print("Zmerene napeti:"); } void loop() { cas = millis(); sidTl[0] = !digitalRead(pidTl); // načtení tlačítka siaU = analogRead(piaU); // načtení signálu z děliče napětí if(sidTl[0] && !sidTl[1]) zobrazeni = !zobrazeni; // přepnutí módu zobrazení // příprava textu: buď napětí, nebo signál String text2r = zobrazeni ? "signal: " + String(siaU) : String(napeti(siaU))+" V"; lcd.setCursor(0,1); lcd.print(" "); // smazání řádku lcd.setCursor(0,1); lcd.print(text2r); // tisk napětí nebo signálu sidTl[1] = sidTl[0]; // zapamatování stavu tlačítka while(millis()-cas < perioda) /*čekej*/; // synchronizace }
Dobrovolníci by mohli přidat menu s průvodcem kalibrací, kde by bylo možné přiřadit jednotlivým úrovním vstupního signálu napětí měřené referenčním voltmetrem, program by spočítal koeficienty a, b převodní funkce a uložil by je do EEPROM paměti Arduina.
+
10. Arduino voltmetr
Obr. 10. Arduino voltmetr
Z obrázku je patrné, že náš Arduino voltmetr právě měří napětí vlastního zdroje (12V baterie). Tímto způsobem lze zdroj monitorovat a např. vyslat varování, že je potřeba baterii dobít.
Kalibrace a použití Arduino voltmetru
4.1.2
Servo
O různých druzích servomotorů se můžete dočíst např. v [6]. K Arduinu připojujeme nejčastěji servo modelářské, protože k tomu (díky jeho spotřebě energie) většinou nepotřebujeme přídavné napájení ani další periferie, jen volný pin s PWM výstupem. Modelářské servo je DC motorek s převodovkou, jemuž lze nastavit úhel otočení hřídele (otočí se do zadané polohy, zastaví a drží). Maximální úhel otočení hřídele je omezen nejčastěji do 180°. Dosažení kýženého úhlu otočení zajišťuje zpětnou vazbou potenciometr umístěný přímo na ose rotoru. Krajní polohy bývají zajištěny mechanickými dorazy. Průběžná serva fungují jako pomaloběžné motory – nemají dorazy a úhel otočení je určen rychlostí a dobou otáčení. Konkurencí jsou jim krokové motory.
Řídicí jednotku mívá servo analogovou nebo digitální (více viz [7]), ale na spolupráci s Arduinem to nemá vliv. Přijímá PWM signál a podle šířky pulzu nastavuje úhel otočení u omezeného serva nebo rychlost a směr otáčení u průběžného serva.
V Arduino IDE instalujeme knihovnu Servo. Ta obsahuje konstruktor objektu Servo a několik základních metod:
Podrobnosti najdete v nápovědě knihovny Servo v Arduino IDE nebo na webu [1]. V následujícím příkladu zkusíme kalibrovat servo s omezeným úhlem otočení – nalézt mezní hodnoty šířky pulzu a související rozsah úhlu otočení výstupní hřídele.
Příklad
+
11. Modelářské servo – vlevo MG996R, vpravo SG90
Obr. 11. Modelářské servo – vlevo MG996R, vpravo SG90
Pokusme se najít minimální a maximální úhel otočení hřídele běžně dostupného modelářského servomotoru SG90 a odpovídající šířky pulzu. Připravme vlastní funkci pro nastavení úhlu otočení a ověřme správnost řešení.
K určení úhlu potřebujeme ukazovací ručku (nasadíme vhodný nástavec na osu serva a jeho rameno prodloužíme např. drátem) a úhloměr (např. vytiskněte ten z následujícího obrázku).
+
12. Úhloměr
Obr. 12. Úhloměr
Aby se úhloměr příliš nedeformoval, podlepíme jej nebo připevníme na pevnou podložku. Umístěn musí být středem v ose hřídele motoru, ale s fixací počkáme.
Šířku pulzu budeme zobrazovat LCD znakovým displejem a regulovat ji budeme rotačním potenciometrem (0–10 kΩ). K zapojení ani nepotřebujeme schéma: např. servo D9, potenciometr A0 a LCD A4, A5 (I2C). Takže připravená úloha by mohla vypadat takhle:
+
13. Sestava serva připravená ke kalibraci
Obr. 13. Sestava serva připravená ke kalibraci
Při kalibraci budeme mapovat vstupní signál potenciometru do rozsahu 0 až 3 000. Nastavením hodnoty 0 zjistíme minimální úhel otočení – orientujeme úhloměr 0° do této polohy a zafixujeme (přilepíme, připneme svorkou…). Pomalu otáčíme potenciometrem do druhé krajní polohy (3 000) a odečteme maximální úhel. Těmito hodnotami šířky pulzu jsme určitě překročili možnosti připojeného serva, a získali jsme pouze jeho krajní polohy (úhly). Dále budeme zjišťovat šířky pulzu odpovídající krajním polohám přesně, abychom získali kalibrační měřítko. Pomalým přejížděním rozsahu najdeme hodnoty přibližně (kdy se servo již pohne a již zastaví).
#include <Servo.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,20,4); //objekt displej Servo naseServo; // objekt servo uint8_t poaServo = 9; uint8_t piaPot = A0; uint16_t siaPot; uint16_t soaSirkaPulzu; void setup() { pinMode(piaPot,INPUT); naseServo.attach(poaServo); // aktivujeme servo lcd.init(); lcd.backlight(); delay(3000); } void loop() { siaPot = analogRead(piaPot); soaSirkaPulzu = map(siaPot,0,1023,0,3000); // počáteční nástřel lcd.clear(); lcd.home(); naseServo.writeMicroseconds(soaSirkaPulzu); lcd.print(soaSirkaPulzu); delay(200); // ne moc rychle, nebo servo vytuhne }
Zjistili jsme, že SG90 se chová nespolehlivě a nelze použít pro přesnější práci, proto je opustíme a použijeme kvalitnější MG996R. To má ale trochu větší spotřebu a buď použijeme pro výstup místo displeje sériovou linku, nebo servo přinapájíme 5V z externího zdroje. Otočení o 0° odpovídá přibližně šířce pulzu 550 μs. Takže přemapujeme celý rozsah potenciometru do blízkosti této krajní hodnoty (např. 450–650) a upřesníme ji.
soaSirkaPulzu = map(siaPot,0,1023,450,650); // upřesníme min
Rozběhne se při hodnotě 554, na nulu se vrátí při 548 (to je hledaná minimální šířka pulzu). Obdobně upřesníme druhou krajní polohu, kdy otočení o 180° odpovídá přibližně šířce pulzu 2 400 μs. Mapujeme (např. 2 300–2 500) a upřesníme ji.
soaSirkaPulzu = map(siaPot,0,1023,2300,2500); // upřesníme max
Zastaví se při 2411 na 169° (hledaná maximální šířka pulzu a úhel otočení), rozběhne se (zpět dolů) při 2388. Ověřovací program bude zobrazovat úhel ve stupních a obsluha bude moci porovnávat údaje na displeji s polohou ručky na úhloměru. Řádky pro kalibraci nemusíme nutně mazat, stačí je zakomentovat (mohly by se ještě hodit…);
#include <Servo.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,20,4); //objekt displej Servo naseServo; // objekt servo uint8_t poaServo = 9; uint8_t piaPot = A0; uint16_t siaPot; uint16_t soaSirkaPulzu; uint8_t soaUhel; char znakStupen = B11011111; // kód znaku '°' na displeji void setup() { pinMode(piaPot,INPUT); naseServo.attach(poaServo); // aktivujeme servo lcd.init(); lcd.backlight(); delay(3000); } void loop() { siaPot = analogRead(piaPot); //soaSirkaPulzu = map(siaPot,0,1023,0,3000); // počáteční nástřel //soaSirkaPulzu = map(siaPot,0,1023,450,650); // upřesníme min //soaSirkaPulzu = map(siaPot,0,1023,2300,2500); // upřesníme max soaSirkaPulzu = map(siaPot,0,1023,530,2430); // 548 - 2411 +- nepřesnost okrajů potenciometru soaUhel = map(soaSirkaPulzu,548,2411,0,169); // úhel zobrazený na displeji lcd.clear(); lcd.home(); naseServo.writeMicroseconds(soaSirkaPulzu); //lcd.print(soaSirkaPulzu); // šířku pulzu zobrazuje jen při kalibraci lcd.print("Uhel "+String(soaUhel)+String(znakStupen)); delay(200); // ne moc rychle, nebo servo vytuhne }
Kalibrace byla úspěšná, servo MG996R funguje podle předpokladů, jen jeho rozsah není celých 180°. Servo SG90 pracuje velmi nepřesně, lze použít pro demonstraci základních vlastností a výrobu hraček.
4.1.3
Pole LED (bargraf)
Jednorozměrné pole LED (tzv. bargraf či „teploměr“) je sada nezávislých LED se stejnými parametry (někdy se liší barvou) umístěných v řadě vedle sebe. Nejčastěji je využijeme k vizualizaci analogového signálu, kde např. u 10segmentového provedení odpovídá segment desetině rozsahu (tedy 10 %), což je velmi názorné. Pro vizualizaci binární hodnoty v jednom bajtu postačí 8 segmentů.
+
14. Desetisegmentový bargraf (nahoře) a rezistorová síť A221J s 10+1 vývodem (dole)
Obr. 14. Desetisegmentový bargraf (nahoře) a rezistorová síť A221J s 10+1 vývodem (dole)
Každé LED je potřeba předřadit odpovídající rezistor, což může být při zapojování do kontaktního pole frustrující. Ulehčit práci si lze použitím rezistorové sítě typu A (paralelně řazené rezistory s jedním společným vývodem), která má tvar hřebenu (se standardní roztečí vývodů 2,54 mm).
V následující ukázce připravíme zobrazení digitalizované hodnoty analogového signálu (ř. 8 až 17) a zobrazení jednotlivých bitů v bajtu (ř. 19 až 31). Pokud k pinu A7 přivedeme analogový signál v rozsahu 0–5 V (např. z potenciometru) bargraf jej zobrazuje. Funkce bgZobrazA() zpracuje dva argumenty – hodnotu a rozsah (oba 16bitové celé nezáporné číslo) – kdy poměr rozsvícených segmentů ku jejich celkovému počtu vyjadřuje poměr zobrazované hodnoty ku rozsahu.
// Pole LED - bargraf
uint8_t podBg[] = {4,5,6,7,8,9,10,11,12,13};   // piny jednotlivých LEDuint8_t delkaBg = 10;   // délka bargrafu = počet jeho segmentů (LED)uint8_t piaPot = A7;    // pin potenciometruuint16_t siaPot;        // signál z potenciometru
// zobrazení max 16 bitové nezáporné hodnotyvoid bgZobrazA(uint16_t hodnota, uint16_t rozsah){   uint8_t i;   uint8_t naBg = map(hodnota,0,delkaBg,0,rozsah-1);   // rozsvítíme část odpovídající hodnotě   for(i = 0; i<naBg; i++) digitalWrite(podBg[i], HIGH);   // zbytek zhasneme   for(; i<delkaBg; i++) digitalWrite(podBg[i], LOW);}
// zobrazení bitové skladby jednoho bajtuvoid bgZobrazB(byte bajt){   uint8_t i;   // zapisujeme zprava doleva od posledního bitu   for(i = 0; i<8; i++)   {      digitalWrite(podBg[7-i], bajt & B1);      bajt >>= 1;   // posuneme o bit doprava, poslední bit zmizí   }   // zhasneme nepoužité LED (je-li jich více než 8)   for(; i<delkaBg; i++) digitalWrite(podBg[i], LOW);}
void setup(){   for(uint8_t i = 0; i<delkaBg; i++) pinMode(podBg[i], OUTPUT);   pinMode(piaPot, INPUT);}
void loop(){   siaPot = digitalRead(piaPot);   bgZobrazA(siaPot, 1024);}
Funkce bgZobrazB() je v ukázce jen definována, ale není použita. Vyžaduje, aby bargraf měl alespoň 8 segmentů, protože zobrazuje hodnotu každého bitu v bajtu. Nadpočetné segmenty zhasne.
Počet segmentů je uložen v proměnné delkaBg a jejich mapa v poli podBg[], což sice není úsporné, ale poskytuje to svobodu v připojení, jestliže potřebujeme některý pin (např. PWM) použít k jinému účelu, případně změnit pořadí jejich zapojení.
4.1.4
Posuvný registr
Zjednodušeně je posuvný registr druh paměti s daty uspořádanými ve frontě, která se na povel posune o jedno místo (1. prvek je odstraněn, ostatní se posunou o jednu pozici dopředu a na poslední místo je zapsán prvek nový). Daty rozumíme logické hodnoty 0 nebo 1 v jednotlivých paměťových místech (bitech). Vstup dat je realizován sériově (jedním vstupním bitem postupně; SI – serial input) nebo paralelně (najednou tolika vstupy, kolik má paměťových míst; PI – parallel input). Výstup dat obdobně (SO – serial output, PO – parallel output). Každý registr má ještě řídicí vstupy pro povolení práce, posuv, zápis výstupů a mazání dat. Také se neobejde bez napájení.
Charakteristická činnost – posuv dat (o 1 paměťové místo dále/vedle) je řízen tzv. hodinovým signálem. Slovo „hodinový“ zde neznamená časovou pravidelnost, ale vyčkání na sledovanou změnu úrovně signálu (náběžná hrana / sestupná hrana). Zápis dat na výstup (výstupy) je řízen vlastním hodinovým signálem (a na posuvu není závislý).
SIPO se používají jako převodníky ze sériové komunikace na paralelní, PISO obráceně a PIPO jsou univerzální (není problém degradovat paralelní vstup nebo výstup na sériový). Pojďme si demonstrovat práci SIPO posuvného registru, který lze využít např. k redukci počtu pinů Arduina k obsluze digitálních výstupů.
Příklad
Představme si fungování oblíbeného 8bitového SIPO posuvného registru 74HC595. K ovládání použijeme dvě tlačítka – levé pro nastavení vstupního bitu, pravé pro aktivaci posunu a zároveň zobrazení výstupu, obě současně pro vymazání paměti, vstupu i výstupů. K monitorování použijeme bargraf, kde první segment bude zobrazovat vstup, druhý nebude použit a zbylých osm segmentů bude zobrazovat jednotlivé výstupy (bity paralelního výstupu).
+
15. Posuvný registr 74HC595 – náčrt s mapou vývodů a reálné zapojení
Obr. 15. Posuvný registr 74HC595 – náčrt s mapou vývodů a reálné zapojení
Vývody
  • QAQH označují výstupy,
  • QH‘ duplikovaný QH (např. pro kaskádování nebo cyklení),
  • SER sériový vstup,
  • !OE povoluje (LOW) nebo zakazuje (HIGH) činnost registru,
  • RCLK hodinový signál pro zápis na výstupy,
  • SRCLK hodinový signál pro posuv,
  • !SRCLR maže (LOW) nebo udržuje (HIGH) paměť.
K zajištění náběžné (i sestupné) hrany hodinového signálu na daném pinu slouží funkce puls() (ř. 48–53). Po startu programu proběhne krátké intro, kdy po bargrafu přeběhne světélko zprava doleva (8 bity z výstupů registru). Zdrojový kód je bohatě komentovaný.
// SIPO posuvný registr 74HC595 (Texas Instruments)/*Mapa vývodů:
 QB--+-- --+--VCC     |  U  | QC--+     +--QA     |     | QD--+     +--SER     |     | QE--+     +--!OE     |     | QF--+     +--RCLK     |     | QG--+     +--SRCLK     |     | QH--+     +--!SRCLR     |     |GND--+-----+--QH'
V "SER" nastavíme vstupní bit 0/1 (sériový vstup).Náběžnou hranou "SRCLK" dáme povel k posunu.Náběžnou hranou "RCLK" povel k zápisu.Na výstupy "QA" až "QH" (posun se fyzicky projeví)."!SRCLR" maže nastavením LOW paměť registru => tedy nuluje,proto jej držíme na HIGH, dokud jej nechceme smazat."!OE" nastaveno na LOW povoluje výstupy "QA až QH",na HIGH je zakazuje => pracujeme při hodnotě LOW."QH'" je kopií "QH" určenou pro přenos na vstup dalšíhoposuvného registru připojeného do kaskády.*/
uint8_t pidTlData = 2;      // tlačítko pro nastavení hodnoty 0/1 na vstupuuint8_t pidTlPosun = 3;     // SRCLK - tlačítko pro posunuint8_t podVypnuto = 13;    // !OAuint8_t podData = 4;        // SER (DATA IN)uint8_t podSmazat = 9;      // !SRCLRuint8_t podVystup  = 7;     // RCLK - paměťový hodinový signál (též LATCH)uint8_t podPosun = 8;       // SRCLK - hodinový signál pro posuv výstupů (též CLOCK)uint8_t sidTlData[2];       // stavy tlačítka pro nastavení hodnoty vstupuuint8_t sidTlPosun[2];      // stavy tlačítka pro posun [0] aktuální, [1] minulýuint8_t sodData;            // aktuální hodnota vstupního bituuint8_t posun;              // 0 čekání, 1 posunuint8_t sodSmazat = HIGH;   // nechceme mazatuint16_t cas, perioda = 60; // nejkratší trvání loop() 60 ms
// puls signálu na daném pinu, zaručena náběžná i sestupná hranavoid puls(uint8_t pin){   digitalWrite(pin,LOW);   digitalWrite(pin,HIGH);   digitalWrite(pin,LOW);}
void setup(){   // piny   pinMode(pidTlData, INPUT_PULLUP);   pinMode(pidTlPosun, INPUT_PULLUP);   pinMode(podVypnuto, OUTPUT);   pinMode(podData, OUTPUT);   pinMode(podPosun, OUTPUT);   pinMode(podVystup, OUTPUT);   pinMode(podSmazat, OUTPUT);   // příprava registru   digitalWrite(podVypnuto, LOW);   // zapneme   digitalWrite(podVystup, LOW);    // příprava pro výstup   digitalWrite(podData, LOW);      // vstup LOW   digitalWrite(podSmazat, HIGH);   // nemazat   // intro - běžící bit   digitalWrite(podData, HIGH);     // "rozsvítíme" poslední bit   puls(podPosun);    // potřebujeme oba pulsy v tomto pořadí:   puls(podVystup);   // nejprve posun, pak teprve výstup   for(uint8_t i=0; i<8; i++)   // světélko "běží" doleva (až zmizí ven)   {      digitalWrite(podData, LOW);      puls(podPosun);      puls(podVystup);      delay(250);   // cca 4 skoky za sekundu   }}
void loop(){   cas = millis();   // synchronizace - reset času - měříme délku smyčky   // vstupy z tlačítek   sidTlData[0] = !digitalRead(pidTlData);   sidTlPosun[0] = !digitalRead(pidTlPosun);
   // mazání – stiskem obou tlačítek mažeme celý registr,   // přestaneme mazat a blokovat další akce až po uvolnění obou tlačítek   if(sidTlData[0] == HIGH && sidTlPosun[0] == HIGH) sodSmazat = LOW;   else if(!sodSmazat && !sidTlData[0] && !sidTlPosun[0]) sodSmazat = HIGH;         // nastavení vstupního bitu registru (SER) - tlačítko TlData přepíná bit mezi 0 a 1   if(sidTlData[0] == HIGH && sidTlData[1] == LOW) sodData = !sodData;   // načtení vstupního bitu do registru a posun ostatních tlačítkem TlPosun   if(sidTlPosun[0] == HIGH && sidTlPosun[1] == LOW)   {      /* Tlačítko pidTlPosun musí zařídit náběžnou hranu SRCLK         a následně i do RCLK, aby se posun projevil         na výstupech posuvného registru. */      posun = HIGH;   }
   // příprava výstupu   sodData &= sodSmazat;   // aby mazání zahrnulo i vstupní bit   posun |= !sodSmazat;    // aby se případné smazání ihned projevilo      // výstupy   digitalWrite(podSmazat, sodSmazat);   // maže nebo drží data   digitalWrite(podData, sodData);   // nastaví hodnotu vstupního bitu registru   if(posun)   // jen pokud je aktivován posun tlačítkem nebo mažeme   {      puls(podPosun);    // posune bity v registru      puls(podVystup);   // zapíše na výstup      posun = LOW;       // už je posunuto   }      // posun v bufferu tlačítek   sidTlData[1] = sidTlData[0];   sidTlPosun[1] = sidTlPosun[0];   // synchronizace - garantovaná minimální perioda smyčky (kvůli tlačítkům)   while(cas - millis() < perioda) /* čekej*/;}
Ruční ovládání posuvného registru
Pro pohodlnou práci s posuvným registrem disponuje Arduino vestavěnými funkcemi shiftOut()shiftIn() a pro ty, kteří nemají rádi bitové operace, i funkcemi ze sekce Bits and Bytes.
Dalším zajímavým rozšířením práce s posuvnými registry by mohlo být jejich spojení do kaskády, kdy výstup QH‘ připojíme na vstup SER dalšího registru a zásobovat daty např. 32 bitovou sběrnici. Případně signál z QH‘ volitelně předáme na vstup stejného registru a bity tak můžeme posouvat v uzavřeném okruhu (beze ztráty informace – jen dojde k překódování). Zvládnete to sami?
4.1.5
Sedmisegmentový displej
Sám název napovídá, že znak tohoto displeje je složen ze sedmi segmentů (proužků). Jeho grafické možnosti jsou tím značně omezeny. Používá se hlavně k zobrazení číslic. Někdy mají navíc „osmý segment“ pro desetinnou tečku, dvojtečku…anebo mají úplně jiné uspořádání segmentů (např. pro znaménka + a –). Dnes je segment nejčastěji tvořen (nebo podsvícen) LED.
Vyrábějí se jedno či víceznakové, se společnou anodou nebo katodou. Dále je potřeba znát úbytek napětí a maximální trvalý proud pro segment – to přečteme z dokumentace. LED jsou choulostivé na přepětí i na překročení maximálního proudu.
+
16. Připojení 7S displeje se společnou anodou k Arduinu – vlevo náčrtek, vpravo realita a mapa vývodů
Obr. 16. Připojení 7S displeje se společnou anodou k Arduinu – vlevo náčrtek, vpravo realita a mapa vývodů
S využitím zapojení na obrázku si na následujícím příkladu předvedeme jednoduchou, ale účinnou techniku zobrazování 7S znaku.
Příklad
Na 7S jednoznakovém displeji budeme stále dokola zobrazovat postupně číslice od 0 do 9, desetinnou tečku a symbol chyby (E.).
K dispozici máme model BS-A812RD (např. z https://www.gme.cz/led-display-20mm-green-hd-a812rd). Z popisu a přiložené dokumentace (datasheet ke stažení u prodejce) zjistíme, že LED tvořící segmenty mají společnou anodu (připojíme ji k 5 V, kdežto katodu bychom připojili ke GND), to znamená, že segment rozsvítíme přivedením LOW a zhasneme přivedením hladiny HIGH signálu na příslušný vývod. Budeme tedy pracovat s obrácenou logikou. Dále zjistíme provozní napětí 2,1 V a trvalý proud 30 mA.
K zajištění provozního napětí a proudu provedeme tradiční výpočet odporu předřadných rezistorů k jednotlivým segmentům:
Rp=5-2,13010-3=2,90,0397 
Program Fritzing disponuje rezistorem o odporu 100 Ω (viz náčrtek – levá část obrázku), fyzicky máme k dispozici 120 Ω (foto zapojení – pravá část obrázku). Vždy hledáme nejbližší vyšší dostupnou hodnotu, abychom zařízení nezničili. Segmenty budou svítit nepatrně menší intenzitou, ale to nevadí. Při zapojování buďme obezřetní, protože z teoretických 16 vývodů jich má displej jen 13 a některé ani nepoužijeme (pro lepší orientaci si můžeme vývody 1 a 16 označit z boku čárkou). Dále ze dvou desetinných teček funguje pouze ta pravá (vše lze najít v datasheetu).
Po zapojení přejděme k programu. Algoritmus bude jednoduchý:
  • definujeme vztah mezi výstupními piny Arduina a segmenty displeje
  • definujeme množinu znaků, a ke každému znaku mapu rozsvícených a zhasnutých segmentů
  • při startu displej zhasneme
  • poté cyklicky zobrazujeme definované znaky 0–9, des. tečku a E.
Nyní podrobněji…
Bylo by vhodné, aby mohl být program použit pro oba typy 7S displejů – se společnou anodou i se společnou katodou. Připravíme makra C_CATHODE (nulový bajt) a C_ANODE (bajt samých jedniček), pomocí nichž budeme na ř. 10 nebo 11 nastavovat typ použitého displeje.
Pole uint8_t segmenty[8], zajišťující přiřazení pinů Arduina jednotlivým segmentům, je záměrně uspořádáno pozpátku kvůli jednoduššímu zpracování v cyklu při zapisování ve funkci void tisk7sBajt(…). Tato funkce přijímá bajt, kde každý bit znamená jeden segment (zleva doprava od A po des. tečku), 1 rozsvícený, 0 zhasnutý. Protože nepotřebujeme jiná čísla než 0 a 1, stačí na všech osm segmentů jediný bajt, nemusíme použít pole osmi čísel. Výraz znak & B1 vezme ze znaku (bajtu) pouze poslední bit (0 nebo 1) a funkce digitalWrite(…) tak zapíše 0 (LOW) nebo 1 (HIGH) na příslušný pin. Operace znak >>= 1 pak posune celý bajt o jeden bit doprava (poslední bit zmizí a zleva se na první místo přidá 0). Takto se na posledním bitu postupně zprava doleva vystřídají všechny bity znaku (proto bylo mapování pinů na segmenty pozpátku). Ještě operace znak = znak ^ typ v případě displeje se společnou anodou invertuje celý bajt (XOR každého bitu s 1, tedy zamění 0 a 1). Pro společnou katodu se nestane nic (XOR s nulovými bity neprovede žádnou změnu).
Funkce void tisk7sCislo(…) jen usnadňuje tisk znaků předdefinovaných v poli byte znaky[12].
// sedmisegmentový displej
#define C_CATHODE B0#define C_ANODE B11111111/*typ rozhoduje o zapojení i o úrovni signálu:- společnou katodu připojujeme ke GND a signál HIGH rozsvěcuje segment, LOW zhasíná- společnou anodu připojujeme k 5 V a signál LOW rozsvěcuje segment, HIGH zhasíná*/byte typ = C_ANODE;   // se společnou anodou// byte typ = C_CATHODE;   // se společnou katodouuint8_t cislo;/*značení segmentů:A, B, C, D, E, F, G tvoří znak, o = des. tečka  --A-- |     | F     B |     |  --G-- |     | E     C |     |  --D--  o                { o,G,F,E, D, C,B,A} */uint8_t podSegmenty[8] = {12,9,6,5,11,10,8,7};   // mapování segmentů na piny Arduina
byte znaky[12] =   /* 7+1=8 segmentů => vejdou se do 1 bajtu,   pozice určuje segment (A je 1. zleva, tedy obráceně než při mapování segmentů na piny) */{   B11111100,   // 0   B01100000,   // 1   B11011010,   // 2   B11110010,   // 3   B01100110,   // 4   B10110110,   // 5   B10111110,   // 6   B11100000,   // 7   B11111110,   // 8   B11110110,   // 9   B00000001,   // .   B10011111    // E. = error (chyba)};
void tisk7sBajt(byte znak){   znak = znak ^ typ;         // zohledníme typ displeje, společná anoda prohodí 0 a 1 v bajtu   for(uint8_t i=0;i<8;i++)   // procházíme bajt zprava bit po bitu (segment po segmentu od o po A)   {       digitalWrite(podSegmenty[i], znak & B1);       znak >>= 1;   }}
void tisk7sCislo(int8_t cifra){   if(cifra < 0) cifra = 10;        // des. tečka   else if(cifra > 9) cifra = 11;   // chyba   tisk7sBajt(znaky[cifra]);}
void setup(){   for(uint8_t i=0;i<8;i++) pinMode(podSegmenty[i], OUTPUT);   tisk7sBajt(B0);   // zhasneme = pošleme nulový bajt}
void loop(){   // všechny znaky (včetně des. tečky a chyby) se postupně střídají dokola po 1 sekundě   if(cislo == 11) cislo = -1;  // des. tečka   tisk7sCislo(cislo);   cislo++;   delay(1000);}
Funkce a datové struktury z předešlého příkladu by se mohly stát základem vlastní knihovny. Jas displeje lze řešit potenciometrem nebo připojením společné anody (katody) k PWM pinu.
Rozšíření displeje nutně neznamená, že na každý další znak spotřebujeme stejný počet pinů, jako na ten první. Výrobci totiž vývody odpovídajících si segmentů ve všech znacích slučují do jednoho společného, takže za každý další znak na displeji přidáme pouze jeden pin pro jeho společnou anodu (katodu). I tak spotřebujeme 8 nebo 9 pinů na první znak (až 16 pinů na 8 znaků). Pokud tedy nutně potřebujeme volné piny pro další periferie, můžeme použít např. „obyčejný“ posuvný registr (SIPO) nebo speciální obvod, např. MAX7219 nebo MAX7221 na SPI sběrnici.
Společné vývody segmentů přinášejí další výzvu: jak zobrazit na různých pozicích libovolné, spolu nesouvisející znaky? Jednoduše: oklameme lidský zrak tím, že necháme rychle za sebou cyklicky zobrazovat jednotlivé znaky samostatně. Naznačme si to na dalším příkladu.
Příklad
Vytvořme demonstrační aplikaci, která bude na čtyřznakovém sedmisegmentovém displeji zobrazovat kladná i záporná čísla.
+
17. Čtyřznakový 7s displej se společnými katodami – nahoře náčrtek ve Fritzingu, uprostřed realita, dole vnitřní zapojení a odpovídající vzhled
Obr. 17. Čtyřznakový 7s displej se společnými katodami – nahoře náčrtek ve Fritzingu, uprostřed realita, dole vnitřní zapojení a odpovídající vzhled
Z obrázku je patrná zajímavá skutečnost – desetinná tečka za prvním znakem není zapojena – funkci 8. segmentu k prvnímu znaku tak plní až desetinná tečka za druhým znakem a 8. segment druhého znaku tvoří dvojtečka.
Z úsporných důvodů bude aplikace zobrazovat stále dokola celá čísla od -100 do 200, ale příslušné funkce vytvoříme tak, aby zvládaly plný rozsah celých čísel od –NNN do NNNN, kde N znamená nejvyšší cifru v číselné poziční soustavě od 2 do 16. Necelá čísla s plovoucí tečkou nebo zobrazení času s dvojtečkou můžete dotvořit sami…
Projekt bude vhodnější rozdělit do dvou souborů, např. „7sd.ino“ s výkonnou částí a „7sd.h“ s podpůrnou knihovnou. Tu první část není třeba dlouze vysvětlovat, jen na řádku 5 připravíme pole s čísly pinů jednotlivých segmentů, stejně jako v minulém příkladu, na řádku 6 pole s čísly pinů, kam připojíme společné anody nebo katody jednotlivých znaků (podle obrázku) a na řádku 7 vytvoříme (zkonstruujeme) objekt displeje, kdy nastavíme jeho typ, předáme odkaz na piny segmentů, počet a piny jednotlivých znaků. Na řádku 22 je volána metoda displej.tiskCislo(cislo), která zajišťuje samotné zobrazení čísla na displeji.
// sedmisegmentový displej#include "7sd.h"   // načtení naší knihovny
// použitíuint8_t podSegmenty[8] = {5,6,7,8,9,10,11,12};   // mapa segmentů na piny Arduinauint8_t podZn[] = {2,3,4,13};  // mapa pozic znaků na piny Arduina, max. 8displej7s displej(C_CATHODE,podSegmenty,4,podZn);   // konstrukce displeje
#define MIN -100         // nejmenší zkušební hodnota#define MAX 200          // největší zkušební hodnotaint cislo = MIN;         // počáteční zkušební hodnotauint64_t loCas;          // pomocná proměnná pro synchronizaciuint16_t perioda = 500;  // perioda změny zkušební hodnoty
void setup(){   loCas = millis();     // začátek synchronizace}
void loop(){   displej.tiskCislo(cislo);         // zobrazení čísla na displeji      if(millis() - loCas >= perioda)   // číslo se změní po určité periodě   {      loCas = millis();              // začátek nové periody      cislo++;                       // jednoduše roste po 1      if(cislo > MAX) cislo = MIN;   // po dosažení max. hodnoty začne znovu   }}
Podpůrná knihovna obsahuje definice objektu displeje, metod a interních proměnných. Tvorba znaku je převzata z minulého příkladu a doplněna tak, aby šlo zapisovat záporné znaménko a znaky číslic až šestnáctkové soustavy. Metoda uint8_t tiskCislo(int32_t cislo, uint8_t z = 10) zobrazí na displeji celé číslo v soustavě o základu z. Pokud parametr z vynecháme, volí desítkovou soustavu. Je navržena tak, aby při každém volání buď zobrazila další cifru (cyklicky zprava doleva, pokud je číslo stejné jako minule), nebo poslední cifru (úplně vpravo, pokud se zobrazované číslo od posledního volání změnilo). Objektové pojetí zjednodušuje práci s displejem ve výkonném kódu (v souboru 7sd.ino).
// základní knihovna pro práci se 7 segmentovým displejem o max. 8 znacích
#ifndef _7SD_H_ #define _7SD_H_ 1   // ochrana proti opakovanému načtení
#define C_CATHODE B0        // pro displej se společnou katodou#define C_ANODE B11111111   // pro displej se společnou anodou#define MINUS 17            // znak mínus má index 17#define NIC 18              // prázdný znak má index 18
class displej7s             // vytvoření třídy pro displej{   byte typ;                // typ displeje   uint8_t pocetZn, aktZnak, chyba;   // počet znaků displeje, pořadí aktuálního znaku, druh chyby   int8_t zaporne, i;       // -1/1 záporné/kladné č., i pomocná pro cykly   int32_t loCislo;         // minule zobrazované číslo (celé víceciferné)   uint8_t cifra[8];        // pole cifer, na které se číslo rozloží   /*   značení segmentů:   A, B, C, D, E, F, G tvoří znak, o = des. tečka    --A--   |     |   F     B   |     |    --G--   |     |   E     C   |     |    --D--  o   v pořadí {o,G,F,E,D,C,B,A}   */   uint8_t *segmenty;   // ukazatel na začátek pole pinů pro segmenty   uint8_t *poZn;       // ukazatel na začátek pole pinů pro pozice displeje
   byte znaky[19] =   /* 7+1=8 segmentů => vejdou se do 1 bajtu,      pozice určuje segment (A je 1. zleva, tedy obráceně než při mapování segmentů na piny).      Máme připraveno na 16 soustavu a plovoucí tečku, ale nepoužijeme je. */   {      B11111100,   // 0      B01100000,   // 1      B11011010,   // 2      B11110010,   // 3      B01100110,   // 4      B10110110,   // 5      B10111110,   // 6      B11100000,   // 7      B11111110,   // 8      B11110110,   // 9      B11101110,   // A      B00111110,   // b      B00011010,   // c      B01111010,   // d      B10011110,   // E      B10001110,   // F      B00000001,   // . nebo :      B00000010,   // - (mínus)      B0           // prázdný znak   };
   // konstrukce a inicializace   public:   // konstruktor objektu displeje   displej7s(byte typD, uint8_t pinyS[], uint8_t pocetZ, uint8_t pinyZ[])   {      // inicializace trvalých proměnných      typ = typD;      segmenty = pinyS;      pocetZn = pocetZ;      poZn = pinyZ;      // aktivace pinů      for(i=0; i<8; i++) pinMode(segmenty[i], OUTPUT);      for(i=0; i<pocetZn; i++)      {         pinMode(poZn[i], OUTPUT);         tisk7sBajt(i, B0);   // začneme se zhasnutým displejem      }      aktZnak = 0;            // začneme na levém okraji displeje   }
   void tisk7sBajt(uint8_t pozice, byte znak)   // tiskne na danou pozici jakýkoliv bajt s definicí segmentů   {      uint8_t i;      znak = znak ^ typ;      // zohledníme typ displeje, společná anoda prohodí 0 a 1 v bajtu      for(i=0;i<pocetZn;i++)         if(i == pozice) digitalWrite(poZn[i], typ & B1);   // aktuální pozice         else digitalWrite(poZn[i], !(typ & B1));           // ostatní vypneme (ale může v nich zbýt energie)      for(i=0;i<8;i++)   // procházíme bajt zprava bit po bitu (segment po segmentu od o po A)      {         digitalWrite(segmenty[i], znak & B1);              // rozsvěcíme nebo zhasínáme segment         znak >>= 1;   // posun na další segment (bude na konci bajtu)      }   }
   void tisk7sCislo(uint8_t pozice, int8_t cifra)   // usnadňuje tisk číslice   {      if(cifra < 0) cifra = 16;          // des. tečka      else if(cifra > 18) cifra = 18;    // chyba - nic      tisk7sBajt(pozice, znaky[cifra]);  // tiskne předdefinovaný znak z pole   }
   uint8_t tiskCislo(int32_t cislo, uint8_t z = 10)  // preferujeme desítkovou soustavu, ale umí do 16   {      if(cislo != loCislo)  // se změnou zobrazovaného čísla začínáme znovu      {         chyba = 0;                             // zatím bez chyby         loCislo = cislo;                       // zapamatuje si aktuální číslo         if(cislo <= -pow(z,pocetZn-1) || cislo >= pow(z,pocetZn)) chyba = 1;   // mimo rozsah         zaporne = cislo < 0 ? -1 : 1;          // znaménko se projeví později         cislo *= zaporne;                      // dále číslo zpracováváme bez znaménka
         for(i=pocetZn-1; i>=0 && cislo; i--)   // číslo rozkládáme na cifry odzadu         {            cifra[i] = cislo % z;               // Hornerovo schema / Archimedův algoritmus            cislo /= z;                         // přechod do vyššího řádu         }         if(i == pocetZn-1)                     // samotná nula se vypíše         {            cifra[i] = 0;            i--;         }         else if(zaporne == -1)                 // pokud je číslo záporné, předřadí se znaménko         {            cifra[i] = MINUS;            i--;                     }         for(; i>=0; i--) cifra[i] = NIC;       // směrem doleva případně doplní prázdné znaky         aktZnak = 0;      }
      tisk7sBajt((aktZnak-1)%pocetZn,B0);   // odstraníme "ducha" z minulé pozice (zbytková energie zlobí)
      if(chyba) tisk7sCislo(aktZnak, MINUS);      // chyba se zobrazí jako samé pomlčky      else tisk7sCislo(aktZnak, cifra[aktZnak]);  // jinak zobrazí znak na aktuální pozici
      aktZnak = (aktZnak+1) % pocetZn;            // posune se na další pozici
      return chyba;   // kdybychom chtěli dále zpracovávat chybu   }};   // středník nutný – konec definice objektu
#endif
Pokud by doba trvání jedné smyčky loop() narostla natolik, že by displej nestíhal kvalitně zobrazovat zadané číslo, bylo by nutné použít samostatný řadič, který by práci displeje řídil nezávisle.
4.1.6
Matice LED
Práce s maticí 8x8 LED je stejná jako s osmimístným sedmisegmentovým displejem, kdy jeden řádek matice je ekvivalentem znaku a jednotlivá LED na řádku je ekvivalentem segmentu displeje. Prostorové uspořádání do řádků a sloupců je však přirozené. Výrobci matic používají stejný trik jako u 7S displejů – spojují např. katody všech diod ve stejném řádku a anody všech diod ve stejném sloupci, takže matice nemá 65 vývodů (např. 64 katod + 1 spol. anoda), ale jen 16 (8 spol. katod + 8 spol. anod).
+
18. Matice 8x8 LED – foto a schéma vnitřního zapojení
Obr. 18. Matice 8x8 LED – foto a schéma vnitřního zapojení
Tentokrát využijeme k řízení matice obvod MAX7219, který podporuje (průchozí) připojení až 8 matic 8x8 (nebo osmiznakových 7S displejů) za sebou. V následujícím příkladu Arduino poslouží v tvorbě obrázků (ikon) a jejich případnému využití ve zdrojovém kódu.
Příklad
Vytvořme jednoduchý editor dvoubarevných obrázků o velikosti 8x8 pixelů, které se dají dále využít např. pro tvorbu znaků nebo ikon pro různé typy rastrových displejů. Zapojení provedeme podle následujícího obrázku.
+
19. Zapojení maticového LED displeje s řadičem MAX7219, ovládacích tlačítek a Arduina – náčrt a realita
Obr. 19. Zapojení maticového LED displeje s řadičem MAX7219, ovládacích tlačítek a Arduina – náčrt a realita
Čtveřice tlačítek vpravo ovládá pohyb kurzoru (1 stisk = 1 krok daným směrem), kurzor nemůže překročit okraj. Tlačítko vlevo dole mění barvu pixelu, tlačítko vlevo nahoře ukončí editaci znaku, smaže displej a odešle definici znaku v jazyce C (pole bajtů) po sériové lince, odkud ji lze kopírovat (viz následující obrázek).
+
20. Zdrojový kód obrázku jako pole bajtů v jazyce C
Obr. 20. Zdrojový kód obrázku jako pole bajtů v jazyce C
K úspěšné kompilaci musíme mít instalovanou knihovnu „LedControl“ (nabídka Nástroje / Spravovat knihovny…). Řadič podporuje SPI, pinem D12 posíláme data, D11 hodinový signál a D10 volíme matici pro výstup (nevolíme, protože připojíme jen jednu…ale tu možnost máme). Zdrojový kód je komentovaný, přesto nezaměňujte počet připojených matic za sebou (maximálně 8) a adresu konkrétní matice (její pořadí na SPI od Arduina, číslováno od nuly), tedy máme 1 matici a ta má adresu (číslo) 0. Pokud bychom měli např. 5 matic, měly by adresy od 0 do 4. Ve spoustě webových návodů je to uvedeno nesprávně!
Řadič s maticí potřebuje k renderu (plnému překreslení) čas do 100 ms (jak praví autoři knihovny), podle toho je nastavena minimální délka trvání smyčky loop() (proměnná casPmZ). Perioda blikání kurzoru (dobaKmitu) je empiricky stanovena na 1 200 ms pro prázdný pixel. Obsazený pixel kmitá čtyřnásobnou frekvencí oproti neobsazenému. Obsazenost (barvu) pixelu navíc indikuje dioda na pinu D13 (ta externí vůbec nemusí být připojena, např. pokud by rušila).
// Matice 8x8 LED s řadičem MAX7219/*   Program umožňuje kreslit obrázky 8x8 pixelů. Kurzor je zobrazen blikáním LED   na aktuální pozici. Periodu blikání prázdného pixelu určuje proměnná   "dobaKmitu", obsazený pixel kmitá čtyřikrát rychleji a navíc obsazenost   monitoruje dioda na pinu D13. Pohyb je řízen čtyřmi směrovými tlačítky,   naplnění/smazání pixelu tlačítkem na pinu "pidTlPotvrd". Stiskem tlačítka na   pinu "pidTlZrus" dojde ke smazání displeje, nastavení kurzoru do levého   horního rohu a vypsání C definice obrázku jako pole bajtů do sériové linky.   Ze sériového monitoru ji lze kopírovat do zdrojového kódu...*/#include<LedControl.h>   // knihovna pro ovládání LED matic pomocí obvodů MAX72XX
// přiojení řadiče matic (SPI)uint8_t podData = 12;   // datauint8_t podClk = 11;    // hodinový signáluint8_t podCS = 10;     // výběr matice (je-li jich více)// tlačítkauint8_t pidTlPotvrd = 5, sidTlPotvrd[2];uint8_t pidTlZrus = 4, sidTlZrus[2];uint8_t pidTlLeve = 7, sidTlLeve[2];uint8_t pidTlHorni = 6, sidTlHorni[2];uint8_t pidTlPrave = 2, sidTlPrave[2];uint8_t pidTlDolni = 3, sidTlDolni[2];// stavová LEDuint8_t podLed = 13;// ostatníuint8_t pocetMatic = 1; // počet matic připojených k řadiči (max. 8)uint8_t mAsta = 0;      // jedinou matici pojmenujeme "mAsta", její pořadí = 0uint8_t mBod[2];        // mBod[0] = souřadnice X, mBod[1] = Y aktivního boduuint8_t loBOD[3];       // stav minulého bodubyte mObraz[8];         // binární mapa obrazu na maticiuint8_t casPmZ = 100;   // nutná časová prodleva mezi zobrazenímiuint64_t cas,mCas,dobaKmitu = 1200;
// zpřístupnění připojených maticLedControl matice = LedControl(podData,podClk,podCS,pocetMatic);
void setup(){   // aktivace tlačítek   pinMode(pidTlPotvrd,INPUT_PULLUP);   pinMode(pidTlZrus,INPUT_PULLUP);   pinMode(pidTlLeve,INPUT_PULLUP);   pinMode(pidTlHorni,INPUT_PULLUP);   pinMode(pidTlPrave,INPUT_PULLUP);   pinMode(pidTlDolni,INPUT_PULLUP);   // aktivace stavové LED   pinMode(podLed,OUTPUT);   /* Ze začátku jsou matice v režimu úspory energie (paměť je aktivní,      ale LED jsou zhasnuté). Pro povolení zobrazení tu naši musíme probudit. */   matice.shutdown(mAsta,false);   zobrazLogo(mAsta);               // pomalu se rozsvítí a zhasne logo Arduina   //matice.setIntensity(mAsta,8);  // nastavení jasu v rozmezí 0-15, 8 je napůl   matice.clearDisplay(mAsta);      // smažeme obraz   Serial.begin(9600);              // sériová linka   mCas = millis();                 // reset času pro dodržení min. periody zobrazení}
void loop(){   cas = millis() % dobaKmitu;   // aktualizace času pro blikání kurzoru   // vstupy z tlačítek   sidTlPotvrd[0] = !digitalRead(pidTlPotvrd);   sidTlZrus[0] = !digitalRead(pidTlZrus);   sidTlLeve[0] = !digitalRead(pidTlLeve);   sidTlHorni[0] = !digitalRead(pidTlHorni);   sidTlPrave[0] = !digitalRead(pidTlPrave);   sidTlDolni[0] = !digitalRead(pidTlDolni);
   // aktualizace hodnoty pixelu před případným přesunem   matice.setLed(mAsta,mBod[1],mBod[0],mObraz[mBod[1]] >> 7-mBod[0] & B1);   // reakce na náběžnou hranu tlačítek (jen jednoho - podle pořadí)   if(sidTlPotvrd[0] == HIGH && sidTlPotvrd[1] == LOW) akcePotvrd();   else if(sidTlZrus[0] == HIGH && sidTlZrus[1] == LOW) akceZrus();   else if(sidTlLeve[0] == HIGH && sidTlLeve[1] == LOW) mBod[0] -= mBod[0] ? 1 : 0;   else if(sidTlHorni[0] == HIGH && sidTlHorni[1] == LOW) mBod[1] -= mBod[1] ? 1 : 0;   else if(sidTlPrave[0] == HIGH && sidTlPrave[1] == LOW) mBod[0] += mBod[0] < 7 ? 1 : 0;   else if(sidTlDolni[0] == HIGH && sidTlDolni[1] == LOW) mBod[1] += mBod[1] < 7 ? 1 : 0;
   zobrazBod(mAsta,mBod);   // zobrazení aktuálního pixelu
   // zapamatování tlačítek do další smyčky   sidTlPotvrd[1] = sidTlPotvrd[0];   sidTlZrus[1] = sidTlZrus[0];   sidTlLeve[1] = sidTlLeve[0];   sidTlHorni[1] = sidTlHorni[0];   sidTlPrave[1] = sidTlPrave[0];   sidTlDolni[1] = sidTlDolni[0];
   // zaručíme minimální délku smyčky, aby matice stíhala zobrazovat   while((millis()-mCas) < casPmZ) /*čekej*/;   mCas = millis();}
void akcePotvrd()   // zapsání hodnoty pixelu jako bitu do aktuálního řádku obrazu{   mObraz[mBod[1]] ^= B1 << 7 - mBod[0];}
void akceZrus()   // zápis do sériové linky a smazání obrazu na displeji{   String text = "byte obrazek[] =\n{";   Serial.println(text);   matice.clearDisplay(mAsta);   // smazání matice   mBod[0] = mBod[1] = 0;        // reset pozice   for(uint8_t i=0; i<8; i++)   {       text = "   B" + String(mObraz[i],BIN) + ",";       Serial.println(text);       mObraz[i] = B0;   }   Serial.println("};");}
void zobrazBod(uint8_t cMatice, uint8_t bod[])   // zobrazení bodu na displeji{   uint8_t signal = mObraz[bod[1]] >> 7-bod[0] & B1;   uint64_t uprDobaKmitu = signal ? dobaKmitu/4 : dobaKmitu;   if(cas%uprDobaKmitu > uprDobaKmitu/2) matice.setLed(cMatice,bod[1],bod[0],signal);   else matice.setLed(cMatice,bod[1],bod[0],!signal);   digitalWrite(podLed,signal);}
void zobrazLogo(uint8_t cisloM)   // úvodní intro s logem Arduina{   uint8_t i,radek;   uint16_t prodleva = 50;   byte logo[] =   {      B00001110,      B00010001,      B00010101,      B01110001,      B10001110,      B10101000,      B10001000,      B01110000   };
   // zhasneme   matice.setIntensity(cisloM,0);   // nastavíme obraz loga   for(radek = 0; radek < 8; radek++)      matice.setRow(cisloM,radek,logo[radek]);   delay(prodleva);   // postupně rozjasňujeme   for(i = 1; i < 16; i++)   {      matice.setIntensity(cisloM,i);      delay(prodleva);   }   // postupně zhasínáme   for(i = 15; i > 0; i--)   {      matice.setIntensity(cisloM,i);      delay(prodleva);   }   // pro jistotu smažeme úplně   matice.clearDisplay(cisloM);}
Editor ikon s maticí LED
Jak jste si jistě všimli, program obsahuje malý bonus – úvodní rozjasňující se a pak pohasínající logo Arduina, kde při omezeném rozlišení není možné vykreslit symboly „–„ a „+“.
Sami si vyzkoušejte spojení více matic za sebou a např. běžící text na řádku.
4.1.7
IR senzor, IR LED
IR (infrared, infračervené) záření není lidským okem viditelné, ve spektru se s rozsahem vlnových délek od 760 nm do 1 mm nachází mezi viditelným a mikrovlnným zářením. Používá se pro přenos vzduchem na krátké vzdálenosti (optické závory, dálkové ovladače, měřiče vzdálenosti), pro přenos optickým vláknem (telekomunikace, IT), ve výkonových laserech (svařování, dělení materiálu). Lze jím ohřívat povrchy těles bez toho, aby se ohříval okolní vzduch (topení), nebo lze naopak měřit teplotu těles podle úrovně vyzařování IR záření (bezkontaktní teploměry, PIR detektory).
Jako vysílač slouží IR LED, jako přijímač IR fotodioda, fotorezistor nebo fototranzistor. V následujícím příkladu použijeme IR přijímač VS1838B pro příjem signálu z dálkového ovladače v aplikaci pro ruční regulaci DC motoru.
Příklad
+
21. DC motorek řízený pomocí IR dálkového ovladače
Obr. 21. DC motorek řízený pomocí IR dálkového ovladače
Za pomoci běžného IR dálkového ovladače (dále jen DO) budeme řídit chod DC motorku. Vypínací mechanické tlačítko bude zařízení zapínat i vypínat. V zapnutém stavu bude svítit červená LED, displej a zařízení bude reagovat na povely DO. Ve vypnutém stavu nebude svítit ani červená ani zelená LED, ani displej a zařízení nebude reagovat na DO. Funkce DO budou rozděleny do dvou úrovní:
  • pohotovost – vybrané tlačítko (nejlépe zelené) zapne úroveň provoz, druhé vybrané tlačítko (nejlépe červené) vypne úroveň provoz (návrat do pohotovosti) – motor neběží, zelená LED nesvítí, displej zobrazuje informaci „Zapni zelenym tlacitkem“,
  • provoz – svítí zelená LED, displej zobrazuje otáčky motoru v rozsahu –100% až 100%, vybranými tlačítky lze přidávat/ubírat otáčky o 1%, 10% a zastavit – motor začíná vždy zastavený.
Vypnutí mechanickým tlačítkem je nadřazeno všem ostatním funkcím.
Nejdříve zapojíme součástky podle předešlého obrázku:
  • IR senzor – D2
  • LED červená – D3
  • LED zelená – D4
  • tlačítko – D5
  • motor – D6 a D7 (pořadí ovlivňuje pouze smysl otáčení)
  • LCD displej – I2C (SCL – A5, SDA – A4, VCC – 5V, GND)
Musíme mít nainstalovanou knihovnu IRremote (verzi 3 nebo vyšší, je dostupná ve správci knihoven Arduino IDE). Pak nahrajeme do Arduina program IR analyzátor:
// analyzátor dálkového ovladače #include <IRremote.h> // verze >= 3.0.0 int pidIR = 2; // vstupní pin IR čidla void setup() { Serial.begin(9600); // spustí IR přijímač na daném pinu, zapne vestavěnou LED (na pinu 13) pro signalizaci aktivity IrReceiver.begin(pidIR, ENABLE_LED_FEEDBACK); } void loop() { if (IrReceiver.decode()) // jestliže zachytí signál { IrReceiver.printIRResultShort(&Serial); // podá krátkou zprávu do sériové linky IrReceiver.resume(); // pokračuje v naslouchání } }
Tento program vypisuje do sériové linky informace o stisknutém tlačítku DO, abychom si mohli jednotlivá tlačítka zmapovat – zajímá nás hexadecimální adresa, např. 0x46 při stisku červeného tlačítka CH (nahoře uprostřed). Po zjištění adres všech zamýšlených tlačítek přistoupíme k tvorbě finálního programu:
// motor na dálkové ovládání
#include<IRremote.h>   // verze >= 3.0.0#include<LiquidCrystal_I2C.h>
// tlačítka dálkového ovladače (adresy zjištěny analyzátorem)#define TL_VYP0 0x45    // všechna#define TL_VYP1 0x46    //    červená#define TL_VYP2 0x47    //    tlačítka#define TL_ZAP 0x43     // zelené tlačítko >||#define TL_PLUS 0x15    // +#define TL_PLUS10 0x40  // >>|#define TL_MINUS 0x7    // -#define TL_MINUS10 0x44 // |<<#define TL_0 0x16       // 0
// rozsah otáček motoru v %#define MOTOR_MIN -100#define MOTOR_MAX 100
//////////////// proměnné ////////////////
LiquidCrystal_I2C lcd(0x27,16,2);   // displejuint8_t pidIR = 2;          // vstupní pin IR čidlauint8_t pidTl = 6;          // vypínací tlačítkouint8_t podLed[] = {3,4};   // provozní Ledky - 3 červená (zapnuto), 4 zelená (provoz)uint8_t poaMotor = 5;       // PWM, výkon motoru - 1. póluint8_t podMotorSmer = 7;   // směr otáčení motoru - 2. pól
int8_t soaMotor;  // otáčky motoru MOTOR_MIN až MOTOR_MAXbool sodLed[2];   // signály pro ledky [0] červená, [1] zelenábool sidTl[2];    // [0] aktuální a [1] minulý stav vypínacího tlačítkabool zapnuto;     // zařízení zapnuto = HIGH / vypnuto = LOWbool provoz;      // je/není v úrovni provoz
uint64_t minCasTl = 100;     // minimální prodleva opakovaného čtení tlačítkauint64_t minCasIR = 200;     // minimální prodleva opakovaného čtení IRuint64_t loCasTl, loCasIR;   // časy minulých stisků
////////////// funkce //////////////
void zapVyp()   // zapnutí nebo vypnutí tlačítkem{   zapnuto = !zapnuto;  // přepnutí   provoz = LOW;        // zhasne zelenou LED   soaMotor = 0;        // zastaví motor   lcd.clear();         // smaže displej   if(zapnuto) lcd.backlight();  // rozsvítí displej   else lcd.noBacklight();       // zhasne displej}
void motor(int8_t procenta)   // nastaví směr a otáčky motoru{   uint8_t soaVykon;   // úroveň signálu PWM pro motor   if(procenta < 0)    // záprorný směr   {      soaVykon = map(procenta,0,MOTOR_MIN,255,0);   // mapujeme obráceně!!!      digitalWrite(podMotorSmer,HIGH);   // HIGH --> U1 = 5V --> obrátíme polaritu      analogWrite(poaMotor,soaVykon);    // U2 <= 5V --> U2 - U1 < 0   }   else   // kladný směr   {      soaVykon = map(procenta,0,MOTOR_MAX,0,255);      digitalWrite(podMotorSmer,LOW);   // LOW --> U1 = 0V      analogWrite(poaMotor,soaVykon);   // U2 > 0V --> U2 - U1 > 0   } }
void setup(){   pinMode(pidTl,INPUT_PULLUP);   pinMode(podLed[0],OUTPUT);   pinMode(podLed[1],OUTPUT);   pinMode(poaMotor,OUTPUT);   pinMode(podMotorSmer,OUTPUT);   IrReceiver.begin(pidIR, ENABLE_LED_FEEDBACK);   // aktivace IR přijímače   lcd.init();                     // aktivace displeje   loCasTl = loCasIR = millis();   // časovače pro stisk tlačítka mechanického i z IR}
void loop() {   // bezpečně načte tlačítko, ale nebrzdí čtení IR signálu   if(millis()-loCasTl > minCasTl) sidTl[0] = !digitalRead(pidTl);
   if(sidTl[0] && !sidTl[1])   // zapnutí/vypnutí tlačítkem   {      loCasTl = millis();  // reset časovače      zapVyp();            // zapne nebo vypne   }   sidTl[1] = sidTl[0];    // zapamatování tlačítka do další smyčky     // IR signál   if (zapnuto && IrReceiver.decode())   // něco zachytil   {      // úroveň pohotovost - nadřízená      switch(IrReceiver.decodedIRData.command)      {         // úroveň pohotovost - nadřízená         case TL_VYP0: case TL_VYP1: case TL_VYP2:            provoz = LOW;            soaMotor = 0;            break;         case TL_ZAP:            provoz = HIGH;            soaMotor = 0;            break;         default:            // úroveň provoz - podřízená            // bráníme se rychlému opakování povelu, ale neblokujeme ostatní            if(provoz && millis()-loCasIR > minCasIR)            {               loCasIR = millis();   // reset časovače               switch(IrReceiver.decodedIRData.command)               {                  case TL_0:                     soaMotor = 0;                     break;                  case TL_MINUS:                     soaMotor -= (soaMotor == MOTOR_MIN ? 0 : 1);                     break;                  case TL_MINUS10:                     soaMotor = (soaMotor >= MOTOR_MIN + 10? soaMotor - 10 : MOTOR_MIN);                     break;                  case TL_PLUS:                     soaMotor += (soaMotor == MOTOR_MAX ? 0 : 1);                     break;                  case TL_PLUS10:                     soaMotor = (soaMotor <= MOTOR_MAX -10 ? soaMotor + 10 : MOTOR_MAX);                     break;               }            }      }      IrReceiver.resume();   // IR přijímač pokračuje v naslouchání   }
   // zobrazení na displeji   if(zapnuto)      if(provoz)   // provoz      {          lcd.home();          lcd.print("Motor: "+String(soaMotor)+"     ");   // přepíše otáčky motoru v %          lcd.setCursor(0,1);          lcd.print("+/-/0 otacky    ");      }            else   // pohotovost      {          lcd.home();          lcd.print("Zapni zelenym   ");          lcd.setCursor(0,1);          lcd.print("tlacitkem...    ");      }
   // výstupy   // příprava   sodLed[0] = zapnuto;  // červená LED   sodLed[1] = provoz;   // zelená LED   // zápis   digitalWrite(podLed[0],sodLed[0]);   digitalWrite(podLed[1],sodLed[1]);   motor(zapnuto*provoz*soaMotor);   // motor}
Zdrojový kód je komentovaný, přesto si raději rozeberme vybrané pasáže:
  • ř. 7–15 – tlačítka DO jsou definována na začátku programu pomocí maker kvůli snadné změně,
  • ř. 29 – poaMotor musí vždy být pin s PWM výstupem kvůli plynulé regulací, připojíme k němu jeden z vývodů motoru,
  • ř. 30 – k pinu podMotorSmer připojíme druhý vývod motoru – pokud kladný a záporný smysl otáčení motoru při práci neodpovídá naší představě, prohodíme zapojení obou vývodů mezi sebou,
  • ř. 164 – volání motor(zapnuto*provoz*soaMotor) zajistí nastavené otáčky motoru pouze v případě, že je zařízení v úrovni provoz, tedy proměnné provozzapnuto mají hodnotu 1 (HIGH),
  • ř. 56–71 – funkce void motor(int8_t procenta) zajišťuje zápis signálů na piny motoru tak, aby smysl a rychlost jeho otáčení odpovídaly procentuální hodnotě otáček zobrazených na displeji – parametry chodu motoru určuje rozdíl napětí U1 na pinu poaMotor mínus U2 na pinu podMotorSmer, kdy napětí U1 je úměrné hodnotě signálu soaMotor a U2 je buď 0 nebo 5 V (podle smyslu otáčení) – tím lze dosáhnout na svorkách motoru napětí od −5 do 5 V,
  • ř. 33 – pole bool sodLed[2] je v programu nadbytečné, ale je vhodné oddělit logické proměnné stavu zařízení (zapnuto, provoz) od jejich znázornění pro obsluhu,
  • ř. 38–40, 87 a 114 – pro ošetření zákmitů vypínacího tlačítka a příliš rychlému opakování povelů z DO je nutné jejich opakované čtení nezávisle na sobě zpozdit – časové prodlevy jsou určeny experimentálně,
  • ř. 80 – čtení IR senzoru využívá přerušení a probíhá nezávisle na našem kódu od jeho aktivace,
  • ř. 97 – metoda IrReceiver.decode() vrací hodnotu true, jestliže se jí v aktuálním intervalu sledování podařilo rozpoznat nějaký povel z IR senzoru, jinak vrací false,
  • ř. 100 – přijatý povel (adresa tlačítka DO) je uložen v  IrReceiver.decodedIRData.command (části struktury IrReceiver.decodedIRData se všemi údaji o zachyceném signálu),
  • ř. 137 – metoda IrReceiver.resume() resetuje data a začne nový interval sledování IR senzoru.
Prostřednictvím knihovny IRremote lze signály DO nejen přijímat, ale i vysílat. Můžeme si tak vytvořit vlastní dálkové ovládání různých přístrojů. Vše o aktuální verzi projektu naleznete v [8]. Povedený návod v češtině [9] používá starší verzi knihovny (2.X.X).
4.1.8
Měření vzdálenosti
Uvažujeme-li o bezkontaktním měření vzdálenosti, lze využít principu měření intenzity pole (např. mag. pole v blízkosti permanentního magnetu), úhlové zaměření zdroje signálu, doba šíření signálu ze zdroje, velikost ozářené plochy atd. Představme si tři zástupce různých principů používané s Arduinem.
Laserové měřiče vzdálenosti VL53L0X, VL53L1X apod. pracují na principu ToF (Time-of-Flight), tedy měří dobu, za kterou se vrátí paprsek odražený od překážky zpět k čidlu. Senzor tedy obsahuje vysílač (IR LED laser) i přijímač (IR fotodiodu). Citlivost závisí na barvě překážek (předmětů, jejichž vzdálenost se měří), jejich tvaru, propustnosti a poloze a také na prostředí (sluneční svit apod.). Běžná přesnost měření se pohybuje v rozmezí ±3 %, maximální vzdálenost do 200 cm (výjimečně i více). Oba moduly komunikují po I2C sběrnici na 5 i 3,3 V. Potřebnou knihovnu lze doinstalovat přímo ve správci Arduino IDE.
IR senzor překážek neměří přímo vzdálenost, ale detekuje překážku do určité vzdálenosti (např. do 6 cm), obsahuje IR LED (vysílač) a IR fotodiodu (přijímač) a měří hladinu odraženého světelného signálu. Práh se nastavuje trimrem. Jedná se o obyčejný logický prvek (GVS – připojuje se na GND, VCC 3,3 nebo 5 V a digitální vstup Arduina). Překážku v dosahu navíc signalizuje i rozsvícením integrované LED.
Ultrazvukovému dálkoměru HC-SR04 se věnujme trochu podrobněji – pracuje na principu ToF, měří vzdálenost překážky od 2 do 400 cm v „zorném“ úhlu 15° s přesností okolo 3 mm. Při vzdálenosti přes 2 m občas vygeneruje nesmyslnou hodnotu.
+
22. Měřiče vzdálenosti – zleva ultrazvukový, laserový, IR senzor překážek
Obr. 22. Měřiče vzdálenosti – zleva ultrazvukový, laserový, IR senzor překážek
Jednoduchý ukázkový program by mohl vypadat např. takto:
// Ultrazvukový měřič vzdálenosti HC-SR04 uint8_t podTrig = 3; uint8_t pidEcho = 2; uint64_t odezva; // doba odezvy v µs uint16_t vzd; // vypočtená vzdálenost v mm uint16_t vZvuku = 343; // rychlost šíření zvuku vzduchem při 20 °C v m/s uint16_t perioda = 1000; // perioda čekání na nové měření uint64_t puls(uint8_t pod, uint8_t pid, uint8_t delkaUs) { // puls dané délky v mikrosekundách (rozsah 10µs - 3 min) // před ním i po něm "zameteme" digitalWrite(pod, LOW); delayMicroseconds(delkaUs); digitalWrite(pod, HIGH); delayMicroseconds(delkaUs); digitalWrite(pod, LOW); return pulseIn(pid, HIGH); } void setup() { pinMode(podTrig, OUTPUT); pinMode(pidEcho, INPUT); Serial.begin(9600); } void loop() { odezva = puls(podTrig, pidEcho, 10); // min délka pulsu 10 µs => min vzd 3.5 mm // výpočet vzdálenosti v mm vzd = odezva * vZvuku / 1000; Serial.print("Vzdalenost je "); Serial.print(vzd); Serial.println(" mm."); delay(perioda); // aby výstup nebyl tak horlivý }
Pokud nám stačí určovat vzdálenost překážky v centimetrech, stačí upravit přepočet. Pro měření odezvy je vytvořena samostatná funkce puls(), která před vysláním pulzu HIGH do modulu „zamete“ (nastaví hodnotu LOW, aby bylo co detekovat), pak vyšle 10µs puls a následně vestavěnou funkcí pulseIn() měří, za kolik µs se puls z modulu vrátí na vstupní pin Arduina. V dokumentaci Arduina je specifikována minimální délka pulsu právě 10 µs, což odpovídá vzdálenosti přibližně 3,5 mm. Tím je také dána minimální vzdálenost od překážky.
4.1.9
Čidla teploty
Jako nejjednodušší čidlo teploty může posloužit termistor (termorezistor). Jde o polovodičovou součástku, jejíž elektrický odpor je ovlivněn teplotou (výrazněji než u kovů). Rozlišujeme PTC (pozistory) – jejich odpor s rostoucí teplotou také roste, NTC (negastory) – jejich odpor s rostoucí teplotou klesá. Závislost ale není lineární, navíc se termistor sám zahřívá průchodem proudu. Proto je vhodné takové čidlo kalibrovat (nebo alespoň ověřit vzorec z jeho dokumentace). Připojení je jednoduché (VS – napájení a analogový vstup). V aplikacích s Arduinem se nejčastěji používají NTC, protože mají pracovní rozsah −50 °C až 150 °C (méně často −100 °C až 300 °C, výjimečně do 400 °C) [11]. Najdeme je např. u 3D tiskáren RepRap. Termorezistory a termotranzistory výrobci integrují do modulů s vlastním procesorem a digitálním výstupem hodnoty naměřené teploty.
Dále se hojně využívají IR senzory, jejichž princip je založen na souvislosti teploty povrchu těles s vyzařováním energie v infračervené části spektra. Vyrábějí se ve formě hotových modulů a s Arduinem komunikují většinou po I2C sběrnici a výrobci k nim dodávají obslužné knihovny. Jejich výhodami jsou poměrně vysoká přesnost, bezkontaktní měření a že jsou kalibrovány z výroby.
+
23. Senzory teploty – vlevo NTC termistor, uprostřed digitální čidlo Dallas, vpravo IR (I2C)
Obr. 23. Senzory teploty – vlevo NTC termistor, uprostřed digitální čidlo Dallas, vpravo IR (I2C)
4.1.10
Čidla osvětlení
Proporcionálně lze intenzitu osvětlení měřit pomocí fotorezistoru nebo fototranzistoru. Výrobci tyto součástky často integrují do modulů s digitálním výstupem nebo s připojením k I2C sběrnici. Typickým využitím je konstrukce tzv. soumrakového spínače. Často jsme svědky blikání venkovního osvětlení rodinných domů za soumraku a při rozednění. Důvodem je nedostatečné odstínění senzoru – ten je zmaten zdrojem světla, který sám reguluje. Také by výrazně pomohla změna skokové regulace (zapnuto/vypnuto) na proporcionální (nepřímá úměrnost intenzity okolního světla a výkonu svítidla).
+
24. Fotorezistor – nejjednodušší senzor osvětlení
Obr. 24. Fotorezistor – nejjednodušší senzor osvětlení
4.1.11
Čidla hmotnosti
Většina dostupných hmotnostních senzorů pracuje na principu tenzometrického jevu, při němž deformace senzoru způsobí změnu jeho odporu. Pokud tyto senzory (tenzometry) ve formě tenkých fólií nalepíme na povrch pevného tělesa, můžeme měřit jeho deformaci. Pro omezení vlivu rušení jsou tenzometry spojovány po čtyřech do Wheatstonova můstku. Hmotnostní senzory jsou tedy deformovány úměrně tíze váženého objektu a tenzometrický snímač úměrně deformaci mění svůj odpor. Výstup je potřeba zesílit a případně digitalizovat. [12]
+
25. Tenzometrický snímač hmotnosti YZC-131 a DA převodník HX711
Obr. 25. Tenzometrický snímač hmotnosti YZC-131 a DA převodník HX711
4.1.12
Snímání otáček
V současné době se k měření otáček používají nejčastěji dva druhy snímačů:
  • Hallova sonda [10] – založená na tzv. Hallově jevu, kdy tenkou vrstvou polovodiče protéká konstantní elektrický proud v jednom směru a změna magnetického pole vyvolá napětí ve směru kolmém na směr protékajícího proudu – typicky měří počet průchodů permanentního magnetu v blízkosti sondy,
  • optický snímač – využívá buď principu optické závory (nejčastěji IR), kdy štěrbinou mezi vysílačem a přijímačem prochází otáčející se terčík s otvory, nebo měří vychýlení paprsku odraženého od povrchu terčíku s odrazivou vrstvou.
Zkusme změřit otáčky DC motorku pomocí optického snímače TCST2103 (fotopřerušovače). Otáčky budeme regulovat otočným potenciometrem (0–10 kΩ) a zobrazovat na LCD displeji. Součástky zapojíme podle následujícího obrázku.
+
26. Měření otáček DC motorku
Obr. 26. Měření otáček DC motorku
Terčík a držák motoru s čidlem jsou zhotoveny pomocí 3D tisku. Terčík je pravidelně rozdělen na 4 pole – dvě zakrytá (generují HIGH) a dvě nezakrytá (LOW). Se signálem z čidla nakládáme stejně jako u tlačítka – hledáme buď náběžnou, nebo sestupnou hranu. Časový úsek mezi dvěma stejnými hranami znamená polovinu otáčky terčíku, tedy i osy motoru. Zdrojový kód by mohl vypadat takto:
// Měření otáček motoru optickým senzorem TCST2103
#include <LiquidCrystal_I2C.h>LiquidCrystal_I2C lcd(0x27,16,2);   // displej
uint8_t pidOptS = 2;   // vývod S optického senzoruuint8_t piaPot = A0;   // střední vývod potenciometruuint8_t poaMotor = 3;  // PWM - napájení motoruuint8_t sidOptS[2];    // aktuální a minulý stav senzoru (jako u tlačítka)uint16_t siaPot;       // analogová hodnota z potenciometruuint8_t soaMotor;      // analogový příkon do motoruuint32_t loCas, cas, perioda, otacky[2];  // pro výpočet otáčekuint32_t loLcdCas, lcdProdleva = 200;     // aby se displej nepřepisoval často
void setup(){   pinMode(pidOptS, INPUT);   pinMode(piaPot, INPUT);   pinMode(poaMotor, OUTPUT);   lcd.init();   lcd.backlight();   lcd.print("Otacky DC motoru");   delay(2000);}
void loop(){   //vstup   sidOptS[0] = digitalRead(pidOptS);   cas = millis();   // celá smyčka bude mít konstantní čas   siaPot = analogRead(piaPot);      // logika   if(sidOptS[0] == LOW && sidOptS[1] == HIGH)   {      perioda = 2*(cas - loCas);   // rozdíl sousedních náběžných hran je jen polovinou periody      loCas = cas;                 // zapamatování času      otacky[0] = 60000/perioda;   // minuta má 60 000 ms   }   if(cas-loCas > 2000)  // 2 s žádný kmit => motor stojí   {       otacky[0] = 0;       loCas=cas;   }   sidOptS[1] = sidOptS[0];              // zapamatování stavu opt. senzoru   soaMotor = map(siaPot,0,1023,0,255);  // výkon motoru řídí potenciometr
   // vystup   analogWrite(poaMotor, soaMotor);   if(abs(otacky[0]-otacky[1]) > 0.02*otacky[1])  // pásmo necitlivosti 2%      otacky[1] = otacky[0];                      // zapamatování otáček
   if(cas > loLcdCas + lcdProdleva)   // zápis na displej (ne moc často)   {      loLcdCas = cas;      lcd.setCursor(0,1);      lcd.print(String(otacky[0])+"/min     ");   }}
Na ř. 50 až 53 jsou vidět praktická opatření proti „horlivosti“ – pásmo necitlivosti na malé změny otáček a minimální doba zobrazení údaje na displeji. Otáčky se obvykle uvádějí v jejich počtu za minutu. Nejčastější chybou začátečníků, kteří nedávali ve fyzice pozor, bývá, že vždy po dobu jedné minuty počítají impulzy. Tím získají – navíc až po uplynulé minutě – průměrné otáčky, tedy údaj, který o aktuální situaci motoru neříká nic. Naše metoda začne selhávat až ve chvíli, kdy polovina otáčky motoru trvá kratší dobu než jedno opakování smyčky loop() programu v Arduinu (tu si umíte změřit sami).
4.1.13
Převodník logických úrovní 3,3/5 V
Ne všechny periferie jsou kompatibilní s oběma úrovněmi napětí (3,3 a 5 V) logické úrovně HIGH. Někdy výrobci u 3,3V zařízení uvádějí, že jsou 5 V tolerantní, někdy vysloveně varují, že ne. Pokud nemáme jistotu, že zařízení je schopno pracovat s 5V úrovní signálu, použijeme převodník logických úrovní, ideálně obousměrný. Jeho zapojení naznačuje následující obrázek.
+
27. Obousměrný osmikanálový převodník logických úrovní 3,3/5 V a náčrt zapojení např. pro sériovou komunikaci
Obr. 27. Obousměrný osmikanálový převodník logických úrovní 3,3/5 V a náčrt zapojení např. pro sériovou komunikaci
Na stranu HV (high voltage) připojíme vždy napájení 5 V, na stranu LV (low voltage) napájení 3,3 V. GND převodníku je průchozí a musí být propojen s GND obou zařízení (i zdroje, pokud není součástí jednoho ze zařízení). Pak do odpovídajících pozic proti sobě připojujeme vodiče vstupů/výstupů, jejichž úrovně potřebujeme převést. Hlavně nepoplést strany!
4.1.14
Externí paměť
Arduino Nano nedisponuje příliš velikou operační pamětí. Situaci mění až nové modely s procesory ARM. Navíc je někdy potřeba uložit data, která vydrží i po vypnutí. MCU ATmega328 má integrovanou EEPROM paměť o velikosti 1 kB. Tato paměť má ovšem omezený počet zápisů na jedno paměťové místo, výrobci uvádějí přibližně 100 000 zápisů, ale praxe s různými klony ukazuje i výrazně méně. Návody na její využití najdete např. na https://www.arduinotech.cz/inpage/eeprom/ nebo na https://arduino8.webnode.cz/news/lekce-17-arduino-a-eeprom/. Dále je možné využít externí paměť, např. AT24C256 s kapacitou 32 kB. Připojuje se přes I2C sběrnici, návod k jejímu využití najdete např. na https://navody.dratek.cz/navody-k-produktum/arduino-i2c-eeprom-pamet-at24c256.html. Pokud nestačí ani tato velikost, lze použít SD kartu. Její čtečky se připojují přes SPI rozhraní. Návody a příklady naleznete např. na https://www.hwkitchen.cz/navody-hwkitchen/navod-na-pouziti-modulu-sd-karty-arduino-navody/ nebo na https://www.arduino.cc/en/reference/SD.