Užitečné funkce
V tomto díle si ukážeme, jak může Arduino vnímat čas. Podrobněji se podíváme na funkce čekání (delay) a další. Poté si ukážeme možnosti matematických operací a na závěr si představíme funkce pro generování náhodných čísel.
Obsah článku:
Čas
V základní výbavě mají desky Arduino čtyři funkce pro práci s časem. Jsou to funkce delay(), delayMicroseconds(), millis() a micros(). První dvě a druhé dvě fungují na stejném principu, jenom pracují s jinými jednotkami. Jsou to milisekundy a mikrosekundy. Důležité je si připomenout převodní vztah mezi jednotkami času kdy: 1 sekunda = 1 000 milisekund = 1 000 000 mikrosekund.
delay()
S touto funkcí jsme se již setkali. Má jediný parametr, a to čas čekání v milisekundách. Rozsah parametru je od 0 do 4,294,967,295. Velkou nevýhodou funkce delay i následující funkce delayMicroseconds() je fakt, že dojde k zastavení téměř veškeré činnosti (pozastavení čtení hodnot ze senzorů, nemožnost ovládat logické hodnoty na pinech atd.). Nedojde však k zastavení těch funkcí, které nejsou přímo závislé na procesoru. Jedná se zejména o příjem informací z Rx linky, kdy se přijatými byty naplňuje buffer a ke zpracování dojde až po skončení funkce delay a také o funkci analogWrite(). Generování PWM signálu totiž probíhá mimo hlavní blok procesoru.
int cekejSekund = 1; long cekejMilisekund = cekejSekund*1000; void setup() { pinMode(13, OUTPUT); } void loop() { delay(cekejMilisekund); digitalWrite(13, HIGH); delay(cekejMilisekund); digitalWrite(13, LOW); }
delayMicroseconds()
Funkce je obdobná, jako delay(), jenom s tím rozdílem, že parametr je zde čas v mikrosekundách. Rozsah parametru je od 0 do 65,535.
millis()
Pomocí funkce millis() se dá zjistit hodnota uložená ve vnitřním časovači procesoru. Zde je uchována informace o délce běhu programu od jeho spuštění. Tato funkce tedy nepotřebuje žádný parametr a vrací počet milisekund od začátku programu. Tento počet však není nekonečný. Maximální vrácená hodnota je 4,294,967,295. Po překročení dojde k takzvanému přetečení časovače, který poté znovu začne počítat od nuly. Funkce millis() se využívá například tam, kde je třeba čekat, ale není žádoucí, aby byl přerušen chod programu.
//program, který pošle každou sekundu zprávu o počtu ms po sériové lince unsigned long cas = 0; void setup() { Serial.begin(9600); } void loop() { if(millis() >= cas + 1000){ cas = millis(); Serial.println(cas); } }
Poznámka: K přetečení časovače dojde přibližně jednou za 50 dní. (4 294 967 295 ms = 4 294 967 s = 71 582 min = 1193 h = 49,7 dní)
micros()
Funkce micros() je stejná jako millis(), pouze vrací hodnotu v mikrosekundách. Rozsah hodnot je stejný, ale jelikož platí, že 1 milisekunda = 1000 mikrosekund, doba přetečení bude tisíckrát menší, tedy asi 71,5 minuty. Nutno dodat, že rozlišení funkce je u 16 MHz procesorů 4 mikrosekundy a u 8 MHz 8 mikrosekund. Výstupem funkce tedy bude násobek čtyř, nebo osmi.
Poznámka: Možná se ptáte, proč jsou maximální hodnoty parametrů, nebo vrácených čísel takové, jaké jsou. Je tomu tak, protože funkce pro práci s časem používají dva datové typy, které jsem ve článku Základní struktury jazyka Wiring neuvedl. Jsou to unsigned int a unsigned long. Datový typ unsigned int má stejný rozsah hodnot jako int, jenom je tento rozsah posunut směrem do kladných čísel. Typ int může uchovat čísla od -32,768 do 32,767. U typu unsigned int se nepracuje se zápornými čísly. Rozsah je u něj tedy od 0 do 65,535. Stejná situace je i u unsigned long, jen s větším rozsahem.
Matematické funkce
Nyní si pojďme ukázat, jaké matematické operace Arduino podporuje. Než ale začneme, připomeňme si základní operátory.
Matematické operátory
Většina těchto operátorů je nám dobře známá z hodin matematiky. Jedinou výjimkou je operátor % (modulo), který vrací zbytek po celočíselném dělení.
1 + 2 = 3 //sčítání 2 - 1 = 1 //odčítání 2 * 3 = 6 //násobení 9 / 3 = 3 //dělení //modulo 9 % 6 = 3 //na první pohled je tato operace poměrně zvláštní //slovy se dá ale jednoduše vyjadřit jako: 9 děleno 6 je 1 zbytek 3 //operace modulo nám vrátí právě tento zbytek
Praktické využití nachází operace modulo mimo jiné i v určování dělitelnosti. Pokud je zbytek po dělení a číslem b nula, potom je a dělitelné b.
int a = 20; int b = 5; if(a % b == 0){ //a je dělitelné b } else{ //a není dělitelné b }
min()
Funkce min slouží k výběru menšího z čísel. Vstupními parametry jsou dvě čísla a výstupem hodnota menšího z nich. Používá se například při hledání nejmenšího čísla v poli, nebo k omezení hodnot ze senzoru (aby nedošlo k překročení určité meze).
//hledání nejmenšího čísla v poli void setup() { int delka = 10; int pole[] = {2, 4, -8, 3, 2, 100, 200, 50, 99, 358}; int nejm = pole[0]; //nejmenší číslo Serial.begin(9600); for(int i = 1; i < delka; i++){ nejm = min(nejm, pole[i]); } Serial.println(nejm); } void loop() { }
max()
Syntaxe této funkce je shodná s min(). Jejími parametry jsou také dvě čísla a vrácenou hodnotou je větší z nich. Může být použita například při hledání největšího čísla v poli.
//hledání největšího čísla v poli void setup() { int delka = 10; int pole[] = {2, 4, -8, 3, 2, 100, 200, 50, 99, 358}; int nejm = pole[0]; //největší číslo Serial.begin(9600); for(int i = 1; i < delka; i++){ nejm = max(nejm, pole[i]); } Serial.println(nejm); } void loop() { }
abs()
Tato funkce vrátí absolutní hodnotu čísla. Vstupem je tedy číslo a výstupem jeho absolutní hodnota.
Poznámka: Absolutní hodnota čísla x je nezáporné reálné číslo. Pokud je x >= 0, abs(x) = x. Pokud je x < 0, potom abs(x) = -x. Laicky řečeno je absolutní hodnota vzdálenost čísla od nuly na číselné ose.
x = abs(150) //x = 150 x = abs(-150) //x = 150
constrain()
Tuto funkci si můžeme představit jako kombinaci min() a max() s vhodnými parametry. Slouží totiž k omezení rozsahu proměnné jak shora, tak zdola. Má tři parametry: upravovanou hodnotu, dolní mez a horní mez. Pokud vstupní číslo klesne pod spodní hranici, výstupem je hodnota spodní hranice. Překročí-li horní hranici, výslednou hodnotou je hodnota horní hranice. Pokud je hodnota v mezích, výstupem funkce bude stejná hodnota, jako na vstupním parametru.
x = constrain(upravovaná hodnota, dolní mez, horní mez); x = constrain(1,10,100); //x = 10 x = constrain(150,10,100); //x = 100 x = constrain(50,10,100); //x = 50
map()
Stejně jako funkce constrain(), slouží i funkce map() k úpravě rozsahu proměnné. Na rozdíl od předchozí funkce však nedochází k oříznutí hodnot, ale k rovnoměrnému „roztažení“, nebo „zmáčknutí“ celé stupnice. Dá se použít například k úpravě hodnoty získané při čtení analogového vstupu (0 – 1023) a jejich použití ve funkci analogWrite, která pracuje s hodnotami od 0 do 255. Syntaxe je následující: x = map(hodnota, minimumPůvodníStupnice, maximumPůvodníStupnice, minimumNovéStupnice, maximumNovéStupnice);
//úprava jasu LED pomocí potenciometru int analog, pwm; void setup() { Serial.begin(9600); } void loop() { analog = analogRead(A0); pwm = map(analog, 0, 1023, 0, 255); Serial.print("Analog: "); Serial.print(analog); Serial.print(" PWM: "); Serial.println(pwm); analogWrite(11, pwm); }
pow()
Funkce pro mocnění čísla na jiné číslo. Vstupními parametry jsou číslo a mocnina.
pow(10,3) = 1000; pow(10,4) = 10000; pow(2,5) = 32;
//ukázka použití int a = 10; int b = 3; float c; void setup() { Serial.begin(9600); } void loop() { c = pow(a,b); Serial.println(c); delay(1000); }
sqrt()
Funkce sqrt() vrátí druhou odmocninu vstupního čísla.
sqrt(25) = 5; sqrt(256) = 16; sqrt(10000) = 100;
Goniometrické funkce
Než si představíme jednotlivé funkce, povězme si něco o jednotkách úhlů. My jsme totiž většinou zvyklí měřit úhel ve stupních. Plný úhel je zde 360°. Matematicky přesnější je ale použití radiánů. Tyto jednotky vycházejí z jednotkové kružnice. Plný úhel (360°) je roven 2*PI radiánů, 180° = PI radiánů atd. Platí mezi nimi převodní vztah: radiány = (stupně * PI)/180. Goniometrické funkce nacházejí uplatnění při výpočtu stran a úhlů v trojúhelnících a dalších rovinných i prostorových útvarech. Všechny goniometrické funkce jsou periodické – po určitém intervalu se jejich hodnoty opakují.
sin()
Funkční hodnota funkce sinus je dána jako poměr strany protilehlé ku přeponě v pravoúhlém trojúhelníku. Amplituda funkce je 1 a perioda 360°, čili 2PI radiánů. Grafem funkce je tzv. sinusoida. Parametrem funkce je úhel v radiánech a výstupní hodnotou je hodnota sinu pro daný úhel. Následující příklad vypíše část sinusoidy otočenou o 90° pomocí pomlček přes sériovou linku.
float pi = 3.14159265359; int amplituda = 10; //aby byla sinusoida viditelná, zvětšíme její amplitudu 10x float perioda = 2*pi; float hodnota; void setup() { Serial.begin(9600); //tento cyklus nalezne hodnotu funkce sinus po desetinnách PI for(float i = 0; i <= perioda; i+=(pi/10)){ //počet vygenerovaných pomlček hodnota = sin(i)*amplituda + amplituda; for(int j = 0; j < hodnota; j++){ Serial.print('-'); } Serial.println(' '); } } void loop() { }
cos()
Funkce cos() slouží k určení kosinu dané hodnoty. Je definovaná jako délka přilehlé ku přeponě v pravoúhlém trojúhelníku. Jejím grafem je kosinusoida. Sinusoida a kosinusoida jsou si podobné. Kosinusoida je vlastně sinusoida posunutá o PI/2 radiánů doprava. Mezi funkcemi sin() a cos() platí převodní vztah sin(x) = cos(x-PI/2). Perioda = 2PI, amplituda = 1. Na příkladu můžete vidět výpis kosinusoidy.
float pi = 3.14159265359; int amplituda = 10; float perioda = 2*pi; float hodnota; void setup() { Serial.begin(9600); for(float i = 0; i <= perioda; i+=(pi/10)){ hodnota = cos(i)*amplituda + amplituda; for(int j = 0; j < hodnota; j++){ Serial.print('-'); } Serial.println(' '); } } void loop() { }
tan()
Poslední goniometrickou funkcí, se kterou umí Arduino pracovat je funkce tangents. Ta je dána jako poměr protilehlé strany ku přilehlé v pravoúhlém trojúhelníku. Má periodu jednoho PI. Rozsah funkčních hodnot je od plus do minus nekonečna.
Náhodná čísla
Stroje na rozdíl od člověka neumí jednoduše vytvořit náhodné číslo. Za „náhodným“ číslem totiž většinou stojí složitá série algoritmů, která je však statisticky předpovidatelná. Takovýmto číslům se říká pseudo-náhodná. Na první pohled jako náhodná opravdu vypadají, ale ve skutečnosti nejsou. Principy generování opravdu náhodných čísel jsou většinou založeny na měření fyzikální veličiny, která je považována za náhodnou (pohyby plynů a kapalin, fázový šum v laseru…). To je ale pro Arduino poměrně složité.
random() a randomSeed()
Tato funkce slouží ke generování pseudo-náhodných čísel. Může mít jeden, nebo dva parametry.
random(max); //vygeneruje "náhodné" číslo mezi 0 a max-1 random(min, max); //vygeneruje "náhodné" číslo mezi min a max-1
Ke správné funkci generátoru je ještě potřeba použít funkci randomSeed(). Ta slouží k nastavení výchozí hodnoty pro generátor. Má pouze jeden číselný parametr. Jako hodnota parametru se používá funkce analogRead() u pinu ke kterému není nic připojeno. Dochází kolem něj totiž k zachytávání elektromagnetického šumu, který může sloužit jako náhodná vstupní hodnota. Celý program by tedy mohl vypadat takto:
void setup() { Serial.begin(9600); randomSeed(analogRead(A0)); } void loop() { delay(500); Serial.println(random(256)); }
Příklad
Pomocí funkce random() si vytvoříme jednoduchou hrací kostku. Výsledek budeme zobrazovat pomocí LED diod uspořádaných stejně, jako černé body na hrací kostce. Využijeme také tlačítko. Vždy po jeho zmáčknutí se vygeneruje nové číslo.
Budeme potřebovat:
- Nepájivé kontaktní pole a vodiče. Třeba ty z našeho Starter kitu, ve kterém najdete i další komponenty potřebné pro tento příklad.
- 7x LED dioda
- 7x 330 ohm resistor
- tlačítko
- 10 kohm resistor
Vše zapojíme podle obrázku. Není důležité, na jaký pin připojíme jakou LED diodu. Vše se dá jednoduše upravit v programu.
Před uploadem programu do Arduina musíme upravit pole s informacemi o pinech LED diod a o tlačítku. Jedná se o pole leds[] a proměnnou tlacitko. Led diody na kostce jsou očíslovány následovně (číslo LED odpovídá jejímu indexu v poli):
Zdrojový kód programu by poté mohl vypadat například takto.
//čísla LED s odpovídajícím indexem byte leds[7] = {2,3,4,5,6,7,8}; //logické stavy LED použitých u jednotlivých čísel byte cisla[7][7] = {{}, /*prázdné pole - 0 se nezobrazuje*/ {0,0,0,1,0,0,0}, /*1*/ {1,0,0,0,0,0,1}, /*2*/ {1,0,0,1,0,0,1}, /*3*/ {1,0,1,0,1,0,1}, /*4*/ {1,0,1,1,1,0,1}, /*5*/ {1,1,1,0,1,1,1}}; /*6*/ byte tlacitko = 9; //pin s tlačítkem byte randn; //proměnná pro náhodnou hodnotu void setup() { Serial.begin(9600); randomSeed(analogRead(A0)); //inicializace generátoru for(int i = 0; i <= 7; i++){ pinMode(leds[i], OUTPUT); digitalWrite(leds[i], HIGH); //kontrola funkce LED } pinMode(tlacitko, INPUT); delay(1000); for(int i = 0; i <= 7; i++){ digitalWrite(leds[i], LOW); //vypnutí všech LED } } void loop() { if(digitalRead(tlacitko) == 1){ for(int i = 0; i <= 7; i++){ digitalWrite(leds[i], LOW); //vypnutí všech LED } randn = random(1,7); for(int i = 0; i <= 6; i++){ if(cisla[randn][i] == 1){ digitalWrite(leds[i],HIGH); } } delay(1000); } }
Pokud vše proběhlo bez chyby, měla by se hodnota na kostce změnit vždy po zmáčknutí tlačítka.
Zdroje obrázků
V případě jakýchkoliv dotazů či nejasností se na mě neváhejte obrátit v komentářích.
- Sledovač slunce s Arduinem - 23.3.2022
- Programovatelný kytarový pedál s Arduinem - 26.2.2020
- Arduino infračervený teploměr vytištěný na 3D tiskárně - 11.2.2020
8 Comments on “Užitečné funkce”
Napsat komentář
Pro přidávání komentářů se musíte nejdříve přihlásit.
Sachlj
22.12.2020 at 19:05Má cenu se ptát když je příspěvek tak starý?
Potřebuji arduinu vysvětlit dělení beze zbytku. Jedna hodnota stále narůstá. Já potřebuji zjišťovat aby v pravidelných hodnotách se něco stalo. Tj aby když hodnota nabude určeného stále se opakující přírůstku tak aby se něco stalo. Jde o měření průtoku vody a měření hladiny vody v nádrži, tj.aby při každém půllitru se změřila hladina vody a to se poslalo do souboru na SD kartě. Jde to?
Zbyšek Voda
22.12.2020 at 19:38Dobrý den,
myslím, že hledáte operátor modulo, tedy zbytek po dělení.
Ten se zapisuje ‚%‘.
x % n … vrací zbytek po celočíselném dělení x/n
tedy např:
0 % 3 = 0
1 % 3 = 1
2 % 3 = 2
3 % 3 = 0
4 % 3 = 1
5 % 3 = 2
…
Vy tedy například otestujete, jestli je zbytek po dělení roven nule.
Stačí to takto? 🙂
marcino
27.5.2018 at 0:12Dobrý den pane Zbyšku,
v příkladu funkce millis() je asi překlep, interval výpisu nebude 1 sekunda, ale 100 milisekund. A také mám pocit, že proměnná cas přeteče asi za 25 dní, protože proměnná je sice long, ale singned (rozsah v kladných číslech je poloviční oproti unsigned). Po přetečení rozsahu proměnné se sice nic strašného nestane, jen výpis času do přetečení časovače nebude odpovídat realitě. Po přetečení časovače ta zase bude normálně načítat čas.
Nebo jsem to špatně pochopil?
Martin
Zbyšek Voda
27.5.2018 at 10:12Dobrý den, děkuji za upozornění.
S tou 100 máte pravdu, utekla mi nula.
U přetečení popisuji návratovou hodnotu té funkce millis(), ne proměnnou cas, ale je pravda, že je to matoucí, když pak následuje příklad se signed long.
Nedostatky jsem upravil, díky.
Chosé
8.9.2016 at 22:36Jde proměnná millis() nějak jednoduše vynulovat?
Zbyšek Voda
11.9.2016 at 17:39Dobrý den,
nijak jednoduše to nejde a navíc to asi ani nechcete. Některé knihovny totiž jsou na této funkci závislé, takže by se mohly vynulováním rozbít.
Spíš je dobré se naučit, jak ošetřit přetečení – viz například https://www.baldengineer.com/arduino-how-do-you-reset-millis.html.
Jan
26.10.2015 at 13:27Zdravim pane Zbysku.
Rad bych se Vas zeptal, zda by jste mi pomohl s kodem pro Arduino Uno.
Rad bych si udelal generator funkci (SQR, Sin, Tri) a mam k tomu AD9058. modul.
Krome vyse zminenych wave forms potrebuji plynule ladit frequenci a plynule ladit duty cycle.
K tomu jeste jedna funkce -trigger- ktera by mi dala moznost jen jednoho impulsu (monostable) a nebo nekolika impulsu (volba delky trvani nebo pocet pulsu) pri dodani exteniho trigger impulsu.
Ja jsem naprosty zacatecnik, nicmene jsem sto to po delsi dobe narogramovat. Problem vidim s16ti pin LCD displayem, U nej bych se urcite zasekl.
Rad bych se Vaszeptal, zda by jste mi umel v teto zalezitosti pomoci pripadne, za nejakou rozumnou castku napogramovat.
Za odpoved predem dekuji a jsem s pozdravem.
Jan
Zbyšek Voda
26.10.2015 at 14:10Dobrý den Jane,
u toho LCD ani nemusíte tolik pinů zapojovat 🙂
V tomto článku: http://bastlirna.hwkitchen.cz/arduino-a-displeje-ii/ se problematice LCD displejů věnuji, tak tam zkuste mrknout. Určitě na to přijdete 🙂
Kdyby se něco nedařilo, nebo byste potřeboval cokoliv zkonzultovat, napište na naše fórum, nebo se mi ozvěte na zbysekvoda@gmail.com.
Ať se daří!
Zbyšek