Arduino
Slovo Arduino označuje niekoľko vecí:
- je to počítač (malá doska plošného spoja osadená väčšinou jednočipovým mikropočítačom Atmel AVR ATMega328)
- je to programovací jazyk v ktorom sa tento počítač dá programovať
- je to program - programovacie vývojové prostredie, pomocou ktorého sa program zapisuje a zároveň cez USB kábel prenáša a spúšťa na Arduine
- sú to všetky tieto veci dokopy.
Arduino sa vyrába v rozličných verziách.
Arduino Uno - najrozšírenejší model Arduina
Arduino Nano - funguje rovnako ako Uno, ale je oveľa menšie - takéto budeme používať aj my
Robot Acrob - používa dosku, ktorá sa podobá na Arduino, ale funguje rovnako, mali sme ich požičané od združenia Robotika.SK na krúžku
Jednočipový mikropočítač ATMega328, ktorý je základom Arduina
Funkcie jednotlivých vývodov jednočipového mikropočítača ATMega328
Podrobné informácie o jednočipovom mikropočítači ATMega328 sa dajú nájsť v jeho datasheete.
Každý počítač sa skladá z týchto základných častí:
Pamäť uchováva všetky údaje (obrázky, zvuky, texty, čísla, a podobne) a programy, ktoré sa vykonávajú príkaz za príkazom v procesore. Pamäť s procesorom sú navzájom prepojené zbernicou, ktorá medzi nimi prenáša údaje. Aby s počítač mohol komunikovať so svojím okolím - napr. s človekom, alebo s vonkajšími pamäťami, na ktorých ukladá rozsiahle údaje (pevné disky, DVD, Internet), tak sú na zbernici pripojené aj vstupné a výstupné zariadenia.
To znamená, programy pozostávajú z postupnosti príkazov:
príkaz1 príkaz2 ... príkazN
V každom okamihu procesor naraz vykonáva iba jeden príkaz, postupne od začiatku programu do konca. V programoch sa môžu vyskytovať cykly:
príkaz príkaz ... opakuj: príkaz príkaz koniec cyklu príkaz príkaz ...
- ak treba nejakú skupinu príkazov niekoľkokrát zopakovať, alebo podmienky, ktoré dovoľujú vykonať rozličné skupiny príkazov, podľa toho, ktorá z nich je práve vhodná (väčšinou na základe otestovania nejakej podmienky):
príkaz príkaz ... platí podmienka? ak áno, vykonaj túto skupinu príkazov: príkaz ... príkaz ak nie, vykonaj inú skupinu príkazov príkaz ... príkaz a potom pokračuj v každom prípade ďalej: príkaz príkaz ...
Arduino je teda počítač, ktorý má svoju pamäť, procesor a nahrávame do neho cez kábel program, ktorý najskôr naprogramujeme pomocou prostredia Arduino:
Nato, aby sme mohli pracovať s prostredím Arduino si ho potrebujeme downloadnut a nainštalovať zo stránky arduino.cc - nainštalujte si "Windows Installer" - to by malo nainštalovať aj ovládače (drivers).
Program sa do Arduina nahráva cez prevodník USB-Serial (ktorý je v bežných arduinach už zabudovaný - ale Acroby ich potrebujú):
Arduinové výstupy sú označené D0-D13 a A0-A5. Napájanie sa privádza na piny VCC a AVCC (+5V) a GND (zem, 0V). RXD, TXD označujú sériový port na komunikáciu s inými počítačmi alebo zariadeniami (napr. s PC), motory sa väčšinou pripájajú na piny D9 a D10.
Na väčšine Arduin je pin D13 pripojený na zabudovanú svietivú diódu (LED). Prvý program, ktorý napíšeme túto LEDku rozbliká.
Každý arduinový program obsahuje aspoň tieto dve funkcie:
void setup() {
// put your setup code here, to run once:
//...
}
void loop() {
// put your main code here, to run repeatedly:
//...
}
Funkcia setup() sa spustí vždy po zapnutí počítača - hneď po spustení programu. Jej úlohou je ponastavovať všetky zariadenia, ktoré sú k Arduinu pripojené. Funkcia loop() sa spustí, keď setup() skončí a ak loop() skončí (čo nemusí...), tak sa spúšťa znovu a znovu, stále dookola.
Pre rozblikanie LED musíme najskôr počítaču nastaviť pin D13 ako výstupný. Každý digitálny pin sa dá používať buď ako vstupný (vtedy Arduino z nožičky integrovaného obvodu vie prečítať, či tam je logická 0 (nulové napätie), alebo logická 1 (napätie 5V). Pin nemôže byť naraz aj vstupný aj výstupný, musíme sa rozhodnúť ako ho chceme používať a podľa toho ho vo fukcii setup() nastaviť. Robí sa to príkazom:
pinMode(číslo_pinu, režim); // pričom režim je buď INPUT, alebo OUTPUT (je možná aj tretia možnosť, ale o tom až niekedy neskôr)
Keď je pin nastavený ako výstupný, tak vo funkcii loop() môžeme LED kedykoľvek zasvietiť zapísaním logickej jednotky:
digitalWrite(číslo_pinu, HIGH); // alebo
digitalWrite(číslo_pinu, 1) // čo je to isté.
podobne, LED zhasneme takto:
digitalWrite(číslo_pinu, LOW); // alebo
digitalWrite(číslo_pinu, 0) // čo je to isté.
Arduino však stihne za sekundu vykonať 16 miliónov inštrukcií (taktovacia frekvencia procesora je 16 MHz) a tak, aby sme si blikanie LED stihli všimnúť, musíme po každej zmene pinu nejaký čas počkať - na to slúži funkcia delay(čas_v_milisekundách). Celý program na blikanie LED teda vyzerá takto:
void setup() {
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
}
Aby sa nám Arduino programovalo jednoduchšie, ak ho máme pripojené káblom, môžeme z neho cez kábel (čiže cez sériový port) do PC poslať nejakú textovú správu:
Vo funkcii setup() najskôr nastavíme rýchlosť prenosu (počet prenesených bitov za sekundu), napr.
Serial.begin(115200);
a potom môžeme používať funkcie na vstup a výstup cez sériový port, napr.
Serial.println("Ahoj");
Celý takýto text sa odvysiela cez jediný pin TXD mikropočítača ATMega328 (pozri obrázok hore). Ako sa to udeje? Každý znak (písmeno, číslica, znamienko a pod.) má nejaký číselný kód - väčšinou sa na to používa medzinárodný štandard ASCII. Vidíme, že napríklad znak 'A' má kód 65, znak 'o' má kód 111, atď.
Tieto kódy sú v Arduine uložené v dvojkovej sústave, tak ako všetky ostatné čísla! Počítače totiž všetky čísla ukladajú v dvojkovej sústave, keďže na to stačí, aby na príslušnom pine bolo kladné napätie (logická jednotka) alebo nulové napätie (logická nula).
Ľudia na výpočty používajú desiatkovú sústavu, keďže majú 10 prstov, je im prirodzenejšia: obsahuje 10 číslic: 0, 1, 2, ..., 9. Za poslednou číslicou nasleduje číslo 10 zložené z dvoch číslic, za posledným dvojciferným číslo 99 nasleduje číslo 100, atď. V čísle, ktoré má viac číslic stojí každá číslica na nejakom mieste, napr. v čísle 2016: je číslica 6 na mieste jednotiek (má hodnotu 6 x 1), číslica 1 na mieste desiatok (má hodnotu 1 x 10), číslica 0 na mieste stoviek (hodnota 0 x 100) a číslica 2 na mieste tisícok (hodnota 2 x 1000). Podobne to funguje aj v dvojkovej sústave, ale namiesto 10 číslic máme k dispozícii iba číslice dve: 0 a 1. Čísla tam nasledujú za sebou takto:
0 - zodpovedá 0 v desiatkovej sústave) 1 - 1 10 - 2 11 - 3 100 - 4 101 - 5 110 - 6 111 - 7 1000 - 8 1001 - 9 1010 - 10 1011 - 11 1100 - 12 1101 - 13 1110 - 14 1111 - 15 10000 - 16 10001 - 17 atď.
ľahko si všimneme, že aj tu majú číslice svoje "rády", napr. v čísle 10110 (čiže 22 v desiatkovej sústave) máme:
0 - rád jednotiek 1 - rád dvojek (1 x 2) 1 - rád štvoriek (1 x 4) 0 - rád osmičiek 1 - ráď šestnástok (1 x 16)
1 x 2 + 1 x 4 + 1 x 16 = 22
Takže na odvysielanie čísla 8-bitového čísla 65 cez pin TXD sa tam postupne odvysielajú číslice 01000001 - a pri rýchlosti 115200 číslic za sekundu tam každá bude iba 1/115200 s, čo je približne 8,68 mikrosekúnd.
Predchádzajúci program upravme tak, aby okrem blikania LEDkou aj vypisoval texty ON a OFF:
void setup() {
pinMode(13, OUTPUT);
Serial.begin(115200);
}
void loop() {
digitalWrite(13, HIGH);
Serial.println("ON");
delay(500);
digitalWrite(13, LOW);
Serial.println("OFF");
delay(500);
}
Dobrý robot by ale iba s výstupmi nevystačil, musí nejakým spôsobom získavať informácie z prostredia - pomocou senzorov, ktoré tvoria jeho vstupné zariadenia. Napríklad ak nárazník narazí do prekážky, stlačí sa mikrospínač a hodnota na výstupe z vypínača sa zmení z logickej nuly na logickú jednotku. Potrebujeme napísať program, ktorý hodnotu na svojom pine dokáže prečítať.
Pin 2 nastavíme na vstupný:
pinMode(2, INPUT);
a funkcia
digitalRead(2)
nám vráti buď 0 alebo 1 - podľa toho, či je na pine 2 logická nula, alebo jednotka. Túto hodnotu môžeme použiť napríklad v podmienke, ktorá sa v jazyku C++ (na ktorom je Arduino založené) používa takto:
if (podmienka) {
// príkazy, ktoré sa majú vykonať, ak je podmienka splnená
}
else {
// príkazy, ktoré sa majú vykonať, ak podmienka nie je splnená, čiže neplatí
}
časť else je nepovinná - môže a nemusí tam byť. V našom prípade môžeme program naprogramovať tak, že pri stlačení tlačidla LED zasvieti:
void setup()
{
pinMode(2,INPUT);
pinMode(13, OUTPUT);
}
void loop()
{
if (digitalRead(2)) {
digitalWrite(13, HIGH);
}
else {
digitalWrite(13,LOW);
}
}
Ako to zapojiť?
Mikrospínač, ktorý je v stavebnici Acroba funguje tak, že bez stlačenia sú prepojené len kontakty cez dlhšie strany obdĺžnika a po stlačení sú prepojené všetky 4 kontakty.
Preto do pinu D2 pripojíme jeden výstup z tlačidla. Druhý výstup tlačidla spojíme s napájaním (VCC), ktoré má hodnotu napätia ako logická 1. Tým zabezpečíme, že pri stlačení tlačidla bude na D2 logická jednotka. Lenže, ak tlačidlo nestlačíme - logická nula tam nebude, bude tam len pripojený "vo vzduchu visiaci" káblik a to nie je dobre. Preto ho treba prepojiť so zemou. Ale pozor - ak by sme ho so zemou prepojili len tak - priamo, tak by sme pri stlačení tlačidla spôsobili skrat - priamymi vodičmi by bolo spojené VCC (plus na baterke) so zemou (mínus na baterke) a takúto situáciu nesmieme dopustiť ani na krátky okamih. Preto pin D2 spojíme so zemou cez rezistor s dostatočne veľkým odporom, napr. 2 kOhm:
Robot Acrob je poháňaný dvoma servomotormi. Servomotor je zaujímavá súčiastka - je to motor s prevodovkou a riadiacou elektronikou. Vnútri vyzerá nejak takto:
používajú sa väčšinou v robotických ramenách, pričom sa môžu otáčať v rozsahu 0-180 stupňov (90 je stredná poloha). Viac otočiť im nedovolí zabudovaná zarážka. Potenciometer je v skutočnosti rezistor s premenlivou hodnotou a používa sa ako spätná väzba, ktorá riadiacu elektroniku serva informuje o tom, ako je práve servo otočené. Cez signálny káblik (biely, alebo žltý) elektronika dostáva pokyn z počítača (napr. z Arduina), do akej polohy sa má servo nastaviť. Elektronika porovná aktuálnu hodnotu s požadovanou a podľa toho rozbehne motor správnym smerom a vhodnou rýchlosťou.
Servá, ktoré sú zabudované do Acrobov, sú tzv. modifikované servá - zarážka, ktorá bráni súvislému otáčaniu je odstránená a potenciometer je nahradený rezistorom so zafixovanou - pevnou hodnotou. Elektronika serva si preto "myslí", že servo je stále v strede (na hodnote 90) a pri pokyne nastavenia do polohy napr. 120 sa roztočí jedným smerom a bude sa tak točiť donekonečna. Pri pokyne napr. 30 sa bude točiť donekonečna opačným smerom. Pri požadovenej polohe 90 sa zastaví a pri polohách blízko 90 sa bude otáčať iba pomaly, pretože už je skoro "v cieli".
Ako servo riadiť - ako mu vysvetliť, že sa má nastaviť do požadovanej polohy napr. 30 stupňov?
Riadiaci signál do serva (biely alebo žltý káblik) by mal každých 20 ms obsahovať pulz (logickú jednotku na krátky okamih). Dĺžka tohto okamihu určuje do akej polohy sa servo má nastaviť. Typicky to býva od 0.5 - 1.5 ms. Nasledujúci program teda roztočí servo jedným smerom:
void setup()
{
pinMode(10, OUTPUT);
}
void loop()
{
digitalWrite(10, LOW);
delay(20);
digitalWrite(10, HIGH);
delayMicroseconds(500);
}
program vyšle na pin D10 krátky pulz 0.5 ms každých 20 ms. Toľko teória. Arduino je však vymyslené tak, aby sme sa nemuseli starať o každý bit sami a stačí použiť knižnicu Servo, ktorá sa o generovanie riadiaceho signálu stará sama v spolupráci so zabudovanými časovačmi počítača. Program, ktorý pohne robotom teda vyzerá takto:
#include <Servo.h>
Servo lavy, pravy;
void setup()
{
lavy.attach(9); // lave servo je na pine 9
pravy.attach(10); // prave servo je na pine 10
}
void loop()
{
// rozbehni sa
lavy.write(120);
pravy.write(30);
// tri sekundy bez
delay(3000);
// zastan
lavy.write(90);
pravy.write(90);
// tri sekundy cakaj
delay(3000);
}
K Acrobu môžeme pripojiť malú sirénku, ktorá funguje ako reproduktor:
Sirénku si môžeme predstaviť ako taký malý reproduktor. Reproduktor vytvára zvuk tak, že po dvoch drôtoch doň prichádza premenlivé napätie pripojené na jeho cievku elektromagnetu. Elektromagnet je pevne spojený s kuželovitou kmitajúcou membránou a vytvorené magnetické pole ho striedavo priťahuje a odpudzuje k pevému magnetu, ktorý je tiež súčasťou reproduktora.
Membrána reproduktora naráža na častice vzduchu, rozkmitá ich a zvuk sa v prostredí šíri ako pozdĺžne kmitajúca vlna mechanického vlnenia ďalej. Na vytvorenie čistého zvuku by sme mali napätie privádzané do reproduktora meniť plynule
ale pri využití digitálnej elektroniky, ktoré vie na výstupe generovať iba 0 a 1 si to môžeme zjednodušiť a pomocou pravidelného striedania digitálneho signálu medzi 0 a 1 môžeme vydať počuteľný tón. Dôležité je hlavne to, aby frekvencia kmitania sedela s frekvenciou požadovaného tónu.
Treba dať pozor na pripojenie - jedna strana sirénky je ozačená ako "+" - tú môžeme pripojiť priamo na niektorý digitálny výstupný pin a riadiť ho rovnako ako pri blikaní LEDkou, len trochu rýchlejšie, druhú stranu sirénky pripojíme priamo na zem.
Hudobné tóny rozličných výšok sa líšia svojimi frekvenciami. Tón s veľkou frekvenciou (za sekundu membrána kmitne veľakrát) je vysoký tón, naopak tón s nízkou frekvenciou kmitania membrány je nízky tón. Človek typicky počuje frekvencie okolo 20-15000 Hz. Nasledujúci program teda vydá tón malé 'A', ktorý má frekvenciu 440 Hz:
void setup()
{
pinMode(11,OUTPUT);
}
void loop()
{
digitalWrite(11, HIGH);
delayMicroseconds(1136); // 1136 usec je dlzka trvania jednej pol-periody
digitalWrite(11,LOW); // ak kmitame 440-krat za sekundu, tak dlzka jedneho
delayMicroseconds(1136); // kmitnutia je 1/440 s, co je 0.0022727 s, polovica
} // z toho je 0.0011363 s = 1.1363 ms = 1136.3 usec
Ak chceme, aby robot zahral nejakú zaujímavejšiu pesničku, tak by sa nám hodila funkcia, ktorá vie zahrať tón s ľubovoľnou frekvenciou a bolo by fajn, keby sme tej funkcii vedeli povedať ako dlho ten tón znie. Funkcia? a to je čo? Funkciu si môžeme predstaviť ako "vlastný blok" v programovacom jazyku pre LEGO MINDSTORMS. Je to kus programu, ktorý môžeme vyvolať opakovane z rôznych miest nášho programu - a môžeme mu zadať nejaké parametre, ktoré určujú, čo presne má urobiť. Každý náš arduinovský program už pozostával z dvoch funkcií - setup() a loop(). Takýchto funkcií môžeme pridať koľko chceme a vyvolať ich z hociktorého miesta programu. Funkcii môžeme predpísať ľubovoľný počet parametrov - každý z nich musí mať stanovený "typ" - čiže napr. či je to číslo, logická hodnota, znak, alebo reťazec znakov. Napríklad:
void scitaj(int a, int b)
{
Serial.print("Sucet je: ");
Serial.println(a + b);
}
je funkcia, ktorá dostane dva parametre a, b, a cez sériový port vypíše text "Sucet je XY", kde XY bude skutočný súčet hodnôt a, b. Takúto funkciu môžeme z programu vyvolať napr. takto:
scitaj(3, 4);
ale za parametre môžeme dosadiť aj hodnoty z premenných, napr.
int premenna = 12;
int ina_premenna;
ina_premenna = 10;
scitaj(premenna, ina_premenna);
Takže teraz napíšme funkciu, ktorá bude zadanou frekvenciou (určenou v Hz) kmitať stanovenú dobu (určenú v ms). Namiesto typu int použijeme typ long, lebo do premenných typu long sa vojdú oveľa väčšie čísla - čo potrebujeme, lebo budeme počítať v mikrosekundách (miliontinách sekundy).
// f - frekvencia v Hz, d - dlzka trvania tonu v ms
void ton(long f, long d)
{
long t = 500000 / f; // ako dlho trva polovica jednej periody tonu s frekvenciou f?
long p = d * f / 1000; // kolko celych period sa vojde do casoveho useku dlzky d?
for (int i = 0; i < p; i++)
{
digitalWrite(11, HIGH);
delayMicroseconds(t); // namiesto 1136 usec ako v pripade tonu s frekvenciou 440 Hz
digitalWrite(11,LOW); // budeme cakat t usec, ktore sme vypocitali z frekvencie f
delayMicroseconds(t); // 1 / f - v sekundach, takze este krat milion (na mikrosekundy) a deleno 2
} // co je to iste ako 500000 / f
delay(50); // na konci kratka prestavka, aby boli tony zretelne oddelene a nezlievali sa
}
V tomto programe sú dva zaujímavé výpočty. Prvý z nich využíva fakt, že vzťah medzi frekvenciou a periódou je T = 1 / f a zároveň f = 1 / T. V prvom riadku funkcie teda vypočítame koľko mikrosekúnd trvá polovica periódy, ak sa má za jednu sekundu zopakovať f kmitnutí signálu nahor a nadol. Druhý výpočet slúži na to, aby sme vedeli koľkokrát sa má kmitnutie nahor a nadol zopakovať (to je tá podmienka i < p v cykle for). Zistíme to tak, že vynásobíme dĺžku trvania tónu (d) frekvenciou (f). Napríklad ak kmitáme frekvenciou 440 Hz a tón má znieť 1 sekundu, tak potrebujeme kmitnúť 1 * 440 = 440-krát. Alebo ak má tón znieť 2 sekundy, tak potrebujeme kmitnúť 2 * 440 = 880-krát. Ak by mal znieť iba pol sekundy, tak by to bolo 0,5 * 440 = 220-krát. Lenže dĺžka trvania tónu (d) je zadaná v milisekundách, takže aby sme dostali správny výsledok, musíme ho ešte vydeliť 1000 (čím ako keby prevedieme hodnotu d na milisekundy). Cyklus for, ktorý sme použili je najbežnejšie používaný cyklus, pomocou ktorého môžeme nejakú skupinu príkazov uzavretú v zložených zátvorkách zopakovať viackrát. Zapisuje sa takto:
for (príkaz_ktory_sa_vykona_na_zaciatku; podmienka_opakovania; prikaz_ktory_sa_vykona_po_kazdom_opakovani)
{
//...prikazy...
}
Prikaz for teda vŽdy vykoná najskôr svoj prvý príkaz za zátvorkou - v našom prípade vytvorí premennú typu int a nastaví ju na hodnotu 0. Potom skontroluje, či platí podmienka opakovania - v našom prípade, či počet opakovaní (premenná i) je stále menší ako stanovený počet opakovaní (premenná p) - ak áno, prvýkrát vykoná príkazy v tele cyklu (medzi zloženými zátvorkami). Potom nasleduje príkaz, ktorý sa vykoná po každom opakovaní cyklu - posledný výraz v okrúhlych zátvorkách za slovíčkom for - v našom prípade "i++", čo znamená zvýšiť premennú i o jedna a následne sa zasa skontroluje, či stále platí podmienka opakovania. Ak nie, cyklus skončil a program pokračuje ďalším príkazom za telom cyklu. Ak je podmienka stále splnená, znovu sa vykonajú príkazy v tele cyklu, príkaz po každom opakovaní a zasa sa otestuje podmienka - a takto až dovtedy, kým podmienka prestane byť splnená (čo v krajnom prípade nemusí byť nikdy - v takom prípade cyklus nikdy neskončí).
Keď máme funkciu hotovú, môžeme ju využiť na to, aby robot zahral známu melódiu Kohútik jarabí :-) C-D-E-F-F-F-F-E-D-E-E-E-E-D-C-D-D-D-D-E-D-C-C-C s dĺžkami nôt 1-1-2-2-1-1-1-1-2-2-1-1-1-1-2-2-1-1-1-1-2-2-1-1, kde 1 je polová a 2 je celá nota (polová má polovičnú dobu trvania):
Aby to bolo zaujímavejšie na digitálne výstupy D0, D1, D2, D3 môžeme zapojiť rozličné LEDky a spolu s jednotlivými tónmi C,D,E,F zapínať jednotlivé LED - získame takto farebnú hudbu :-)
void ton(long frekvencia, long trvanie)
{
long p = trvanie * frekvencia / 1000L; // L na konci cisla zdoraznuje
long polPeriody = 500000L / frekvencia; // ze ide o konstantu typu long, nemusi tam byt
for (int i = 0; i < p; i++)
{
digitalWrite(11, HIGH);
delayMicroseconds(polPeriody);
digitalWrite(11, LOW);
delayMicroseconds(polPeriody);
}
delay(50);
}
void setup()
{
pinMode(11, OUTPUT); // pipac
pinMode(0, OUTPUT); // farebna hudba - 4 LED
pinMode(1, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
}
// dostane cislo LED 1-4, tu zasvieti a ostatne zhasne
void led(int ktora)
{
if (ktora == 1) digitalWrite(0, HIGH);
else digitalWrite(0, LOW);
if (ktora == 2) digitalWrite(1, HIGH);
else digitalWrite(1, LOW);
if (ktora == 3) digitalWrite(2, HIGH);
else digitalWrite(2, LOW);
if (ktora == 4) digitalWrite(3, HIGH);
else digitalWrite(3, LOW);
}
// C: 262
// D: 293
// E: 330
// F: 349
void hrajC(int dlzka)
{
led(1);
ton(262, dlzka);
}
void hrajD(int dlzka)
{
led(2);
ton(293, dlzka);
}
void hrajE(int dlzka)
{
led(3);
ton(330, dlzka);
}
void hrajF(int dlzka)
{
led(4);
ton(349, dlzka);
}
void loop()
{
hrajC(500);
hrajD(500);
hrajE(1000);
hrajF(1000);
hrajF(500);
hrajF(500);
hrajF(500);
hrajE(500);
hrajD(1000);
hrajE(1000);
hrajE(500);
hrajE(500);
hrajE(500);
hrajD(500);
hrajC(1000);
hrajD(1000);
hrajD(500);
hrajD(500);
hrajD(500);
hrajE(500);
hrajD(1000);
hrajC(1000);
hrajC(500);
hrajC(500);
}
Môže byť? Neviem, asi by bolo ešte lepšie, ak by sme melódiu nemuseli zapisovať takýmto spôsobom. Jednak tu máme iba funkcie na 4 tóny a na klavíri býva aj viac ako 80 klávesov a tak vytvárať funkciu pre každý tón nebude veľmi praktické. Skúsme to ešte trochu lepšie - použijeme pole.
Využijeme pole na to, aby sme si uložili frekvencie všetkých tónov, ktoré bude náš muzikantský program poznať. Pole je taká premenná, do ktorej sa dá uložiť viacero hodnôt naraz, nielen jedna. Napríklad
int a[3]; // premenna a schovava tri cele cisla
a[0] = 10; // prve z nich nech je 10
a[1] = 11; // do druheho vloz 11
a[2] = 12; // a posledne nech obsahuje 12
int b = a[0] + a[1] + a[2]; // vypocitaj sucet vsetkych troch hodnot a uloz ho do premennej b
Serial.print("Sucet je: "); // vypis vysledok na seriovy port
Serial.println(b);
na prístup k jednotlivým hodnotám v poli môžeme použiť aj premennú (niekedy sa jej hovorí indexová, lebo poradie prvku, ku ktorému sa pristupuje sa volá index):
int a[10];
for (int i = 0; i < 10; i++)
{
a[i] = 1 + i * 2;
}
int sucet = 0;
for (int i = 0; i < 10; i++)
{
Serial.print(a[i]);
if (i < 9) Serial.print("+");
sucet = sucet + a[i];
}
Serial.print("=");
Serial.println(sucet);
Zistite, čo presne urobí tento program?
Tak teda naspäť k nášmu hodobníckemu programu.
#define TEMPO 24 // cim vyssia hodnota, tym pomalsie tempo
// pocet not v melodii
int dlzka = 25;
// tony v melodii
char melodiaT[]= {'c', 'e', 'c', 'e', 'g', 'g', 'c', 'e', 'c', 'e', 'g', 'g',
'C', 'h', 'a', 'g', 'f', 'g', 'a', 'g', 'f', 'e', 'd', 'c', 'c' };
// dlzky not v melodii 16 - cela, 8 - polova, 4 - stvrtova, 2 - osminova, 1 - sestnastinova
int melodiaD[] = { 8, 8, 8, 8, 16, 16, 8, 8, 8, 8, 16, 16,
8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 16, 16};
// ake tony pozname - pouzili sme specialne znaky na oznacenie poltonov, zatial nam stacia 2 oktavy
char tony[] = {'c', '#', 'd', '%', 'e', 'f', '$', 'g', '^', 'a', '*', 'h',
'C', '!', 'D', '~', 'E', 'F', '+', 'G', '-', 'A', '/', 'H' };
// hodnoty frekvencii tonov a poltonov sme si nasli na internete:
// radkon.eu/projects/other/tones.html
int frekvencie[] = {261, 277, 293, 311, 329, 349, 369, 391, 415, 440, 466, 493, 523, 554, 587, 622, 659, 698, 739, 783, 830, 880, 932, 987};
void setup()
{
pinMode(11, OUTPUT); // sirenku pripojime na D11
// raz zahraj celu melodiu
for (int i = 0; i < dlzka; i++)
hraj(melodiaT[i], melodiaD[i]);
}
void ton(long f, long d)
{
long p = d * f / 1000L;
long polPeriody = 500000L / f;
for (int i = 0; i < p; i++)
{
digitalWrite(11, HIGH);
delayMicroseconds(polPeriody);
digitalWrite(11, LOW);
delayMicroseconds(polPeriody);
}
delay(50);
}
void hraj(char nota, int trvanie)
{
int kde = 0;
while (tony[kde] != nota) kde++; // najdeme index pozadovaneho tonu
ton(frekvencie[kde], trvanie * TEMPO); // a zahrame ho
}
void loop()
{
// melodiu zahrame priamo vo funkcii setup, aby sa po resete zahrala iba raz a nehrala stale dookola
}
Aká je to pesnička? Kto naprogramuje vianočnú melódiu? :-)
Odpoveď Sama: Pesnička je to "jedna druhej riekla keď koláče atď." a pesničku by som naprogramoval aj ja.
A potom pridal túto:
// tony v melodii
char melodiaT[]= {'E', '~', 'E', '~', 'E', 'h', 'D', 'C', 'a', 'c', 'e', 'a',
'h', 'e', '^', 'h', 'C'};
// dlzky not v melodii 16 - cela, 8 - polova, 4 - stvrtova, 2 - osminova, 1 - sestnastinova
int melodiaD[] = { 8, 8, 8, 8, 8, 8, 8, 8, 16, 8, 8, 8,
16, 8, 8, 8, 16};
Nasledujúci program svieti LEDkou rozličnou silou - pomocou znakov '+' a '-' prijatých cez sériový port sa sila svietenia dá zvýšiť alebo znížiť.
void setup() {
pinMode(13, OUTPUT);
Serial.begin(115200);
Serial.println("Som pripraveny.");
}
int text;
int dlzka1 = 2000;
void loop() {
if (Serial.available() > 0) {
text = Serial.read();
if ((text == '+') && (dlzka1 < 2000)) {
dlzka1 = dlzka1 + 50;
Serial.println(dlzka1);
}
if ((text == '-') && (dlzka1 > 0)) {
dlzka1 = dlzka1 - 50;
Serial.println(dlzka1);
}
}
digitalWrite(13,HIGH);
int dlzka2 = 2000 - dlzka1;
delayMicroseconds(dlzka1);
digitalWrite(13,LOW);
delayMicroseconds(dlzka2);
}
Na precvičenie sme si napísali program, ktorý vypíše na sériový port čísla 0-1000 v dvojkovej sústave:
void setup() {
Serial.begin(115200);
for (int cislo = 1; cislo < 1001; cislo ++) {
Serial.print(cislo, BIN); // druhy nepovinny argument urcuje sustavu v ktorej
Serial.print(" = "); // sa cislo vypisuje, BIN znamena dvojkovu, HEX - sestnastkovu,
Serial.println(cislo); // DEC - desiatkovu a OCT osmickovu
Serial.println();
}
}
void loop() {
// cisla vypiseme iba raz priamo vo funkcii
// setup(), aby sa vypisali len raz
}
SHARP IR senzor
Ide o analógový senzor, to znamená, že na svojom výstupe nedáva iba hodnoty 0 (0 Voltov) alebo 1 (5 Voltov), ale ľubovolnú hodnotu napätia medzi 0 a 5V. Arduino (teda jednočipový mikropočítač ATmega328) má zabudovaný prevodník, ktorý dokáže analógový signál previesť na digitálny (na 10-bitovú hodnotu 0-1023). Začnime programom, ktorý iba vypisuje hodnoty analógového senzora (pripojili sme ho na vstup A5) na sériovom porte (aj so Samovými vylepšeniami):
void setup() {
Serial.begin(115200);
}
int sila = 0;
void loop() {
sila = analogRead(5);
Serial.println(sila);
delay(250);
if (sila > 250 && sila <= 300) {
Serial.println("Pozor! Blizim sa k nejakemu predmetu!");
}
if (sila < 50) {
Serial.println("Pokoj, moj senzor nevidi takmer nic.");
}
if (sila > 300) {
Serial.println("Hej! Takmer som donho narazil!!!");
}
}
Pomocou nasledujúceho programu sa robot pohybuje vpred, až kým nepríde k prekážke, ktorú zameria analógovým senzorom SHARP 2Y0A21 na meranie vzdialenosti (pracuje na princípe infračerveného svetla). Pri prekážke sa otáča, až kým nie je voľná cesta.
#include <Servo.h>
Servo lavy;
Servo pravy;
int dialka = 0;
void setup()
{
lavy.attach(9);
pravy.attach(10);
}
void vpravo(int ms) {
lavy.write(60);
pravy.write(60);
delay(ms);
lavy.write(90);
pravy.write(90);
}
void vlavo(int ms) {
lavy.write(120);
pravy.write(120);
delay(ms);
lavy.write(90);
pravy.write(90);
}
void rovno(int ms) {
lavy.write(60);
pravy.write(120);
if (ms>0) {
delay(ms);
lavy.write(90);
pravy.write(90);
}
}
void vzad(int ms) {
lavy.write(120);
pravy.write(60);
if (ms>0) {
delay(ms);
lavy.write(90);
pravy.write(90);
}
}
void loop()
{
dialka = analogRead(5);
if (dialka < 300) {
rovno(0);
}
else {
vlavo(250);
}
}
Senzor na čiaru pracuje veľmi podobne - tiež je to analógový senzor, ale nie je ešte celý, potrebuje ešte jeden rezistor veľkosti 10kOhm medzi červený a biely (niekedy je žltý) káblik. Presné zapojenie si požičiame zo stránky o robotovi Acrob:
Podľa obrázka sme pripojili dva svetelné senzory na piny A1 a A3. Najkôr by sme senzory chceli vyskúšať a zmerať, aké hodnoty z nich prichádzajú v rôznych situáciách - keď je senzor nad čiernou čiarou a keď je nad svetlou podložkou. Použijeme jednoduchý program, ktorý neustále číta hodnotu z analógového portu A1 a vypisuje ju na sériový port:
void setup() {
Serial.begin(115200);
}
int svetlo = 0;
void loop() {
svetlo = analogRead(1);
Serial.println(svetlo);
delay(250);
}
Zistili sme, že kým je senzor nad stolom, hodnota je okolo 50-60, ak je nad čiernou čiarou, hodnota je až okolo 800-900. Naším cieľom bude napísať program, pomocou ktorého sa robot bude pohybovať po čiare, ktorá je zakrivená rôznymi smermi.
Ak máme dva senzory, postačia nám tri rôzne stavy:
- ľavý senzor vidí čiaru - to znamená, že robot je príliš vpravo a mal by sa začať otáčať vľavo
- pravý senzor vidí čiaru - vtedy je robot príliš vľavo, mal by otáčať vpravo.
- ani jeden senzor nevidí čiaru - to znamená, čiara by mala byť medzi dvoma senzormi, robot sa pohybuje vpred
Využijeme teda príkaz podmienky - if ... else ... ale potrebujeme rozlíšiť nielen dva, ale až tri prípady. Ako to dosiahneme?
if (lavy_senzor_vidi_ciaru)
{
pohybuj sa vlavo
}
else // zostavajuce dva pripady)
{
if (pravy_senzor_vidi_ciaru)
{
pohybuj sa vpravo
}
else // posledny pripad, cize ani jeden senzor nevidel ciaru
{
pohybuj sa rovno
}
}
Môžeme využiť funkcie vlavo(int ms), vpravo(int ms), rovno(int ms), ktoré riadia pohyb robota a ktoré sme naprogramovali minule. Tu je výsledný Samov program:
#include <Servo.h>
Servo lavy;
Servo pravy;
void setup()
{
lavy.attach(9);
pravy.attach(10);
}
void vpravo(int ms) {
lavy.write(60);
pravy.write(60);
delay(ms);
lavy.write(90);
pravy.write(90);
}
void vlavo(int ms) {
lavy.write(120);
pravy.write(120);
delay(ms);
lavy.write(90);
pravy.write(90);
}
void rovno(int ms) {
lavy.write(120);
pravy.write(60);
if (ms>0) {
delay(ms);
lavy.write(90);
pravy.write(90);
}
}
void loop()
{
int lavysenzor = analogRead(3);
int pravysenzor = analogRead(1);
if (lavysenzor>500) {
vlavo(175);
}
else if(pravysenzor>500) {
vpravo(175);
}
else {
rovno(0);
}
}
Doteraz náš robot komunikoval s okolím iba cez USB kábel, ktorý bol cez prevodník pripojený na jeho sériový port. V situáciách, keď sa robot pohybuje v nejakom prostredí a plní pritom svoju úlohu, pripojenie káblom môže byť nepraktické. Namiesto kábla môžeme využiť bezdrôtové spojenie cez BlueTooth, ktoré dokáže prenášať sériovú komunikáciu tiež. Použijeme BT modul s označením HC-05.
Jedna nevýhoda modulu HC-05 je, že pracuje na logike s napätím 3.3 V a robot pracuje na logike 5 V. Preto signálne kábliky, cez ktoré sa prenášajú logické nuly a jednotky nemôžeme vždy priamo prepojiť. Presnejšie: v niektorých prípadoch výstupné signály na logike 3.3 V môžeme pripojiť do vstupných portov s 5 V logikou, lebo 3.3 V väčšinou stačí vybudiť aj 5 V logiku, našťastie v prípade obvodu ATmega328, ktorý je v Acrobovi stačí aj 3.3 V signál na vybudenie logickej 1, takže informácie prichádzajúce z PC cez BT do Acroba môžeme prepojiť priamo: pin Tx na HC-05 prepojíme s pinom D0 na Acrobovi (na ňom je pin RxD - vstupný signál serióvého portu). Opačným smerom to však nie je bezpečné, lebo 5V signál sa väčšinou do 3.3V logiky pripájať nesmie (pokiaľ to nie je zvlášť uvedené v dokumentácii - tzv. "5V tolerant pin" - to ale nie je prípad obvodu HC-05). V nasledujúcom príklade teda použijeme iba jednosmernú komunikáciu z PC do Acroba a Acrob nebude môcť do PC cez sériový port nič vypisovať. Aby to nebolo až také jednoduché, tak na module HC-05 je vyvedený pin EN (enable), ktorý však musíme nastaviť na logickú 1 (tých 3.3 V), aby bol modul aktivovaný a pracoval. 3.3 V si vyrobíme pomocou odporového deliča: ak dva rezistory zapojíme do série, tak sa napätie na tejto vetve obvodu rozdelí podľa pomeru odporov jednotlivých dvoch rezistorov. Použijeme rezistory 1kOhm a 2kOhm takto:
tým pádom získame napätie 3.33V, ktoré je už dostatočne blízko požadovaných 3.3V a pripojíme ho na pin EN. Nezabudneme pripojiť napájanie - 5V (VCC) na VCC modulu HC-05 a prepojiť zem (GND) Acroba a BT modulu.
BT modul využijeme na naprogramovanie diaľkovo riadeného robota. Cez sériový port z programu Putty budeme znakmi riadiť smer pohybu robota - vľavo, vpravo, rovno, vzad, zastaviť stáť.
Do funkcií vlavo(int ms) a vpravo(int ms) doplníme možnosť zadať čas 0 ms, ktorý zodpovedá neohraničenému pohybu (ako v LEGO MINDSTORMS - "unlimited") tak ako to bolo vo funkciách rovno(int ms) a vzad(int ms). Modul HC-05 je z výroby nastavený na nízku komunikačnú rýchlosť 9600, aj keď v prípade potreby sa dá prestaviť na vyššiu (neskôr sa k tomu vrátime). Tu je výsledný program:
#include <Servo.h>
Servo lavy;
Servo pravy;
void setup()
{
Serial.begin(9600);
lavy.attach(9);
pravy.attach(10);
}
void vpravo(int ms) {
lavy.write(60);
pravy.write(60);
if (ms>0) {
delay(ms);
lavy.write(90);
pravy.write(90);
}
}
void vlavo(int ms) {
lavy.write(120);
pravy.write(120);
if (ms>0) {
delay(ms);
lavy.write(90);
pravy.write(90);
}
}
void rovno(int ms) {
lavy.write(120);
pravy.write(60);
if (ms>0) {
delay(ms);
lavy.write(90);
pravy.write(90);
}
}
void vzad(int ms) {
lavy.write(120);
pravy.write(60);
if (ms>0) {
delay(ms);
lavy.write(90);
pravy.write(90);
}
}
void loop()
{
if (Serial.available() > 0) {
int text=Serial.read();
if (text == 'w') {
rovno(0);
}
else if(text == 'd') {
vlavo(0);
}
else if(text == 'a') {
vpravo(0);
}
else if(text == ' ') {
lavy.write(90);
pravy.write(90);
}
}
}
Dokázali by sme urobiť program, ktorý by vedel prejsť bludiskom? :-) Neskúsime sa zamyslieť nad robotom do kategórie Micromouse? Najskôr sa naučíme pracovať s programom OpenSCAD na vytváranie 3D modelov, ktoré sa dajú vytlačiť na 3D tlačiarni.