V redakční poště se objevil e-mail, kde jeden z čtenářů seriálu o Arduinu prosil o radu. Ocituji podstatné části:
Chystam se vylepsit nasi aparaturu pro fyzikalni mereni (rozptylu svetla), konkretne pridat jednoduchy titrator pohaneny krokovym motorkem, servo se zaklopkou pro preruseni paprsku laseru a relatko pro zapinani/vypinani externiho prislusenstvi pripojeneho do zasuvky (michacka).
Zda se, ze Arduino by k tomu mohlo byt vhodne, ponevadz s programovanim sice mam nejake zkusenosti, ale se stavenim elektrotechnickych konstrukci – od zakladu – prakticky zadne.
(…) Zatim jsem vsak nebyl schopen najit clanek o tom, jak ovladat Arduino jinak nez z IDE nebo z nahraneho firmware. Potrebuji totiz napsat samostatnou aplikaci s uzivatelskym rozhranim, ktera bude obstaravat obsluhu prislusneho hardware. Z duvodu kompatibility a spoluprace s ostatnim kontrolnim software bude pracovat pod Windows a reagovat na zpravy systemu
(psat to budu zrejme v Delphi). Prakticky kazda dokumentace se zminuje o tom, ze napsat
samostatny, nezavisly program lze. Bohuzel nejak nejsem schopen najit zdroj, od ktereho se odrazit. Je mi jasne, ze jsem zrejme neco prehledl… Nemel byste nahodou nejaky tip?
Jak komunikovat s Arduinem z PC?
Existuje vícero možností – např. pomocí Ethernet shieldu a zabudovaného webového rozhraní – ale tou nejjednodušší metodou je využít sériové rozhraní, stejné jako používáme pro programování. Pokud jste se na Arduino podívali podrobněji, zjistili jste, že se, když je připojené přes USB, tváří jako „virtuální sériový port“.
Tuto metodu jsme už jednou použili pro čtení hodnot z připojeného senzoru. Dnes tedy vše zopakujeme, ale v opačném směru – nebudeme data přijímat do PC, ale vysílat. Technicky samozřejmě nic nebrání tomu obě metody zkombinovat a data vysílat i přijímat zároveň.
Jako příklad „ovládaného zařízení“ si vybereme to nejjednodušší, co máme po ruce – LED. Připojíme je na výstupy s PWM a pomocí ovládacího programu z PC budeme regulovat jejich jas.
Co je to PWM?
PWM (Pulse Width Modulation) je metoda, při níž se simuluje řízení výkonu u digitálních výstupů. Na výstup se posílají impulsy o vysoké frekvenci, a poměr doby trvání logické jedničky a nuly udává relativní výkon. Pokud budeme např. LED napájet impulsy s dostatečně vysokou frekvencí, aby ji lidské oko nevnímalo jako blikání, pak změnou poměru doby, kdy LED svítí a kdy LED nesvítí, lze simulovat plynulou regulaci. Stejná metoda se používá např. pro plynulé stmívání lustrů, pro řízení otáček motorů a v mnoha dalších situacích. Většina mikrokontrolérů, včetně toho v Arduinu, má funkci PWM pro některé digitální výstupy zabudovanou.
Zapojení je jednoduché – připojíme si dvě LED, vždy mezi výstupy 9 a 10 (ty mají funkci PWM) a zem (GND). Vyjdeme z příkladu zvaného Dimmer.
const int ledPin = 9; void setup() { Serial.begin(9600); pinMode(ledPin, OUTPUT); } void loop() { byte brightness; if (Serial.available()) { brightness = Serial.read(); analogWrite(ledPin, brightness); } }
Program je velmi jednoduchý a přímočarý – inicializační část nastaví sériové rozhraní a patřičný pin jako výstupní. V nekonečné smyčce se pak čtou přijatá data ze sériového rozhraní a přijaté bajty jsou použity pro nastavení PWM na daném výstupu funkcí analogWrite. Parametr může nabývat hodnot 0–255, kde 0 znamená „stále vypnuto“ a 255 „stále zapnuto“.
Když program přeložíme a spustíme, můžeme si vyzkoušet posílání příkazů přímo z terminálu. Když pošleme, řekněme, mezeru (její kód má hodnotu 32), bude LED svítit jen slabě, když pošleme znak ~ (s kódem 126), bude LED svítit viditelně silněji.
Rozhraní pro počítač
Posílání hodnot přes terminál může sice někomu připadat jako dostatečně luxusní, ale pohodlnější bude vytvořit si jednoduchý ovládací program. Jeho funkce bude jednoduchá: pomocí nějakého vhodného vstupního prvku bude moci uživatel měnit hodnotu, a ta bude posílána po sériovém rozhraní do Arduina.
Můžete využít svůj Oblíbený Programovací Jazyk. Já použil, pro tento účel dostatečný, nástroj Processing (i ten jsme tu už zmiňovali). Použil jsem knihovnu Gui Components 4 Processing, která nabízí základní prvky pro vytváření uživatelského rozhraní, jako jsou tlačítka, posuvníky, checkboxy atd.
import guicomponents.*; import processing.serial.*; Serial port; GWSlider sdr; void setup() { size(300, 140); port = new Serial(this, Serial.list()[1], 9600); sdr = new GWSlider(this,20,80,260); sdr.setValueType(GWSlider.DECIMAL); sdr.setLimits(0f, 0f, 1.0f); sdr.setRenderValueLabel(false); PFont font; font = loadFont("Ubuntu-Regular-48.vlw"); textFont(font, 44); } void draw() { background(200,200,255); fill(0, 102, 153); text("Jas LED", 150, 50); } void handleSliderEvents(GSlider slider) { int n = floor(slider.getValuef() * 256); port.write(n); }
Font je vytvořen z .ttf souboru pomocí IDE Processing (Tools – Create Font). Program samotný je opět velmi jednoduchý a přímočarý. Je nastavena sériová komunikace na určitém portu PC (v mém případě je to druhý) a vytvořen posuvník v základním nastavení s hodnotami od 0 do 1. Pokud uživatel změní pozici posuvníku, je vyvolána obsluha události – funkce handleSliderEvents. V obsluze je načtena hodnota posuvníku, převedena na číslo 0–255 a to je posláno na sériový port.
Výhoda použitého prostředí Processing je v tom, že je napsáno v Javě a kód překládá rovněž do Javy, takže výsledek lze provozovat i mimo Processing IDE – stačí použít funkci Export, a získáme standalone aplikace pro Mac, Linux i Windows.
Protokol
Jednoduchá aplikace s jednou LED si vystačí s prostým protokolem „pošlu hodnotu, a ta je použita“. Co ale když zapojíme dvě diody? Nebo ještě víc? V takovém případě nelze prostě posílat hodnoty, protože by Arduino nemuselo bezpečně poznat, jaká hodnota je určena pro jaký výstup. Musíme si navrhnout komunikační protokol.
Pro naši ukázku se dvěma LED použijeme jednoduchý protokol, založený na textu. Každá změna bude poslána jako dvojice čísel, oddělených čárkou, a ukončena znakem CR, LF nebo @ (kvůli terminálu v IDE Arduino, které neumí správně poslat znak konce řádku). Třeba takto: „ 127,66\n
“.
Pokud by bylo potřeba posílat vždy jen jednu hodnotu, mohli bychom protokol navrhnout např. tak, že první znak každého řádku by udával LED, kterou chceme měnit, a za ním by byla nová hodnota. Např. „ A127\nB66\n
“.
V podstatě nejsme při návrhu protokolu nijak omezeni a měli bychom jej navrhnout tak, aby se do budoucna dal snadno rozšířit a aby bylo snadné jeho zpracování. Pokud máme protokol, ale už nějak definovaný, např. průmyslovým standardem, není o čem diskutovat a implementujeme ten.
Náš příklad nebude využívat žádný průmyslový standard a vystačí si s prostým protokolem, jaký jsme si popsali výše.
Realizace
Program pro Arduino se lehce rozroste – kromě potřebné inicializace druhého portu pak především o funkce, spojené s parsováním vstupních dat. Nejprve musíme načítat data ze sériového rozhraní, a pokud přijde znak konce řádku, rozdělíme došlý řetězec na dvě čísla, a ta si převedeme na hodnoty typu byte.
const int ledPin1 = 9; const int ledPin2 = 10; String line; void setup() { Serial.begin(9600); pinMode(ledPin1, OUTPUT); pinMode(ledPin2, OUTPUT); } void doCommand(String s) { char val[5]; byte brightness; int comma = s.indexOf(','); if (!comma) { Serial.println("Invalid format\n"); return; } String work = s.substring(0,comma); work.toCharArray(val,4); brightness = atoi(val); analogWrite(ledPin1, brightness); work = s.substring(comma+1); work.toCharArray(val,4); brightness = atoi(val); analogWrite(ledPin2, brightness); } void loop() { byte c; if (Serial.available()) { c = Serial.read(); if (c == 10 || c == 13 || c==64) { doCommand(line); line=""; return; } line+=(c); } }
Hlavní obsluha se přesunula do funkce doCommand(). Ta je vyvolána v okamžiku, kdy je přijatý znak 10, 13 či 64. Je nalezena pozice čárky, jsou vyseknuty dva řetězce a převedeny na celé číslo pomocí funkce atoi. Ta bohužel neumí pracovat s objektem String, proto je řetězec nejprve převeden na ASCIIZ (pole znaků ukončené znakem 0), známý z jazyka C.
Na protější straně bude rozšířený program v Processing – přidáme druhý slider a převod hodnot 0–255 na řetězec.
import guicomponents.*; import processing.serial.*; Serial port; GWSlider sdr1, sdr2; void setup() { size(300, 140); port = new Serial(this, Serial.list()[1], 9600); sdr1 = new GWSlider(this,20,60,260); sdr1.setValueType(GWSlider.DECIMAL); sdr1.setLimits(0f, 0f, 1.0f); sdr1.setRenderValueLabel(false); sdr2 = new GWSlider(this,20,100,260); sdr2.setValueType(GWSlider.DECIMAL); sdr2.setLimits(0f, 0f, 1.0f); sdr2.setRenderValueLabel(false); PFont font; font = loadFont("Ubuntu-Regular-48.vlw"); textFont(font, 44); } void draw() { background(200,200,255); fill(0, 102, 153); text("Jas LED", 150, 50); } void printstring(Serial port, int n) { String s = str(n); for(int i=0;i<s.length();i++) { port.write(s.charAt(i)); } } void handleSliderEvents(GSlider slider) { int n = floor(sdr1.getValuef() * 255); printstring(port, n); port.write(','); n = floor(sdr2.getValuef() * 255); printstring(port, n); port.write(13); }
Výsledek si můžete prohlédnout na videu:
Závěr
Pro složitější aplikace, například jako je výše zmíněné ovládání několika zařízení, budeme muset nadefinovat o něco komplexnější či obecnější komunikační protokol, ale princip zůstane stejný – obslužný program v PC bude posílat data po sériové lince a Arduino bude přijatá data zpracovávat a podle nich reagovat.
Desky Arduino Uno a Arduino Mega 2560 k redakci zapůjčil obchod HW Kitchen, Arduino a Ethernet Shield obchod Czechduino. Děkujeme za laskavé zapůjčení.