Pořídit si z Arduina výstup na TV přijímač nebo videomonitor není jen pro ty, kdo chtějí zkoušet psát jednoduché hry, ale je to praktický a kupodivu velmi levný prostředek, který poslouží při psaní programů a odlaďování funkce zařízení. Tentokrát se podíváme na konkrétní příkazy knihovny TVout a příklady použití.
Příkazy
Přičleníme-li knihovnu TVout, vytvoří se v našem programu na začátku dva řádky:
#include <TVout.h>
#include <video_gen.h>
a my případně (budeme-li chtít využívat různé rozměry písma) přidáme třetí s přičleněním fontů:
#include <fontALL.h>
Následně můžeme používat mimo jiné tyto příkazy. Jako vždy je důležité rozlišení velkých a malých písmen.
-
TVout jmeno; – vytvoření objektu jmeno třídy TVout (píše se typicky mezi deklarace proměnných)
-
jmeno.begin(video); – parametr video nabývá hodnot _PAL nebo _NTSC. Nastaví zadanou normu a spustí generování obrazu
-
jmeno.clear_screen(); – smaže obsah displeje (celá pracovní plocha černá)
-
jmeno.invert(); – invertuje celou pracovní plochu displeje
-
jmeno.set_pixel(x,y,barva); – vykreslí bod v absolutních souřadnicích x,y. Parametr barva: 0 – černá,1 – bílá, 2 – inverzně. Souřadnice (parametry) jsou 0-127 a 0-95, takže typicky typu int.
-
jmeno.get_pixel(x,y); – funkce vrací barvu bodu v daném bodě x,y: 0–černá, 1-bílá
-
jmeno.draw_line(x1,y1,x2,y2,barva); – vykreslí čáru v absolutních souřadnicích od bodu x1,y1 do bodu x2,y2. Parametr barva: 0–černá,1–bílá, 2-inverzně
-
jmeno.draw_row(y,x0,x1,barva); – velmi rychlevykreslí vodorovnou čáru od bodu x0,y do bodu x1,y barvou 0–černá,1–bílá, 2-inverzně
-
jmeno.draw_column(x,y0,y1,barva);– velmi rychle vykreslí svislou čáru od bodu x,y0 do bodu x,y1 barvou 0–černá,1–bílá, 2-inverzně
-
jmeno.draw_rect(x,y,sirka,vyska,barva,vypln); – vykreslí obdélník od bodu x,y (levý horní roh) o šířce sirka, a výšce vyska s barvou čáry (0–černá,1–bílá, 2-inverzně) a s výplní (0–černá,1–bílá, 2-inverzně,-1-nechat původní). Parametr vypln není povinný, bez něj se ponechá původní obsah plochy.
-
jmeno.draw_circle(x,y,polomer,barva,vypln); – vykreslí kružnici se středem v bodě x,y o poloměru polomer, s barvou čáry (0–černá,1–bílá, 2-inverzně) a s výplní (0–černá,1–bílá, 2-inverzně,-1-nechat původní). Parametr vypln není povinný.
-
jmeno.shift(vzdalenost,smer); – posune obsah displeje o počet bodů udaných parametrem vzdalenost ve směru 0-nahoru, 1-dolů, 2-vlevo, 3-vpravo. Obsah, který „vyjede“ ze zobrazovaného pole se ztrácí. Používá se typicky k „rolování“ obrazovky.
-
jmeno.print_char(x,y,chr); – od bodu x,y (levý horní roh znaku) vypíše znak s kódem chr
-
jmeno.set_cursor(x,y); – nastaví pozici textového kurzoru na bod x,y
-
jmeno.select_font(font); – nastaví font – parametr může být font4x6, font6x8, font8x8 nebo font8x8ext. Externí fonty lze předefinovat a vytvořit si tak v podstatě libovolnou grafiku.
-
jmeno.print(x,y,str); – vypíše na pozici x,y řetězec str, při přesahu přes kraj obrazovky přechází na další řádek
-
jmeno.print(str); – vypíše na pozici textového kurzoru řetězec str
-
jmeno.print(x,y,int,base); – vypíše od pozice x,y proměnnou typu int (unsigned int, long, unsigned long) udanou v zadané soustavě (parametr base: 2, 8, 10, 16). Převod funguje i pro jakýkoli jiný základ soustavy. Parametr base se nemusí uvádět, implicitní je desítková soustava.
-
jmeno.print(x,y,double,presnost); – vypíše na pozici x,y hodnotu proměnné typu double s přesností na daný počet desetinných míst, bez udání přesnosti na 2 desetinná místa. Pozice x,y není povinná, může pracovat podle poslední polohy kuzroru.
-
jmeno.delay_frame(pocet); – čeká daný počet snímků podle parametru počet, slouží k synchronizaci s vykreslováním obrazu
-
jmeno.tone(frekvence, delka); – jako generování tónu standardním tone, ale funguje správně i při generování obrazu a vždy je pro pin 11 u Arduina Uno
-
všechny příkazy print existují i ve verzi println s odřádkováním
Jednoduché příklady
První příklad pokreslí obrazovku sítí bodů (rozsvítí bíle sudé body v řádcích, ale jen na sudých řádcích) a potom se snaží cyklicky programem generovat pulzy na výstupu 5. Podobný program byl použit při snímání obrázku dokumentujícího vliv generování obrazu respektive interruptů na pravidelnost pulzů.
//video test - sude body v sudych radcich #include <TVout.h> // knihovna TVout #include <video_gen.h> TVout TV; // objekt TV tridy TVout void setup(){ // nastaveni TV.begin(_PAL); // generovani signalu PAL TV.clear_screen(); // smazani obrazovky for (int i=0;i<96;i=i+2){ // cyklus pro radky for (int j=0;j<128;j=j+2){ // cyklus pro body v radku TV.set_pixel(j,i,1); // vykresleni bodu } } pinMode(5,OUTPUT); // pin 5 na vystup } void loop(){ // opakovany program digitalWrite(5,HIGH); // pulzy na vystup 5 digitalWrite(5,LOW); // }
Další příklad smaže obrazovku, počká 1s, vykreslí bílý rámeček kolem celé pracovní plochy, pak 1s počká a vše se opakuje. Na pohled rámeček bliká.
//video test - blikajici ramecek #include <TVout.h> // knihovna TVout #include <video_gen.h> TVout TV; // objekt TV tridy TVout void setup(){ // nastaveni TV.begin(_PAL); // generovani signalu PAL } void loop(){ // opakovany program TV.clear_screen(); // smazani obrazovky delay(1000); // pockat 1s TV.draw_rect(0,0,127,95,1); // obdelnik kolem cele plochy delay(1000); // pockat 1s }
Třetí příklad vypíše největším možným písmem nahoru na obrazovku řadu číslic, pak počká, a nechá je plynule přejet a zmizet směrem dolů.
//video test - rolovani #include <TVout.h> // knihovna TVout #include <video_gen.h> #include <fontALL.h> // knihovny vsech fontu TVout TV; // objekt TV tridy TVout
void setup(){ // nastaveni TV.begin(_PAL); // generovani signalu PAL TV.select_font(font8x8); // nastaveni fontu }
void loop(){ // opakovany program TV.print(0,0,"1234567890"); // text vypsat na 0,0 delay(1000); // pockat 1s for(int i=0;i<96;i++){ // 96x TV.shift(1,1); // rolovat dolu o 1 bod delay(1);} // doba mezi posuny delay(1000); // pockat 1s }
Čtvrtý příklad vypíše na obrazovku kousek textu a následně jej nechá po dvou sekundách přezrcadlit. Ukazuje zejména, že na obrazovku se dá nejen zapisovat, ale také z ní obsah číst a je-li to potřeba, třeba i odeslat na tiskárnu.
//video test - zrcadleni #include <TVout.h> // knihovna TVout #include <video_gen.h> #include <fontALL.h> // knihovny vsech fontu TVout TV; // objekt TV tridy TVout int nahore, dole; // promenne pro prohozeni void setup(){ // nastaveni TV.begin(_PAL); // generovani signalu PAL TV.select_font(font8x8); // nastaveni fontu TV.print(0,0, "123456789012345"); // text vypsat TV.print(0,12,"Toto je pokusny"); // text vypsat TV.print(0,24,"text k ukazce "); // text vypsat TV.print(0,36,"zrcadleni"); // text vypsat } void loop(){ // opakovany program for(int i=0;i<47;i++){ // cyklus pro radky for(int j=0;j<128;j++){ // cyklus pro sloupce nahore=TV.get_pixel(j,i); // nacist horni bod dole=TV.get_pixel(j,95-i); // nacist dolni bod TV.set_pixel(j,i,dole); // ulozit horni bod TV.set_pixel(j,95-i,nahore); // ulozit dolni bod } } delay(2000); // pockat 2s }
Pátý příklad představí více z možností grafiky jednoduchým (ale nikoli krátkým) programem. Cílem je nechat cyklicky střídat tři „obrazovky“. První ukáže základní vykreslené tvary, druhá fonty a třetí předvede náhodný „pohyb“ objektu v reálném čase. Nic se neovládá, zapojen je jen základní videoadaptér k Arduinu. Je-li to možné, nastavte televizní přijímač nebo monitor na poměr stran obrazu 4:3, pak jsou body téměř čtvercové a zejména kružnice vypadají jak mají.
//video1 - predvedeni grafiky #include <TVout.h> // knihovna TVout #include <video_gen.h> #include <fontALL.h> // vsechny fonty TVout TV; // objekt TV tridy TVout int x1,y1,x2,y2; // pracovni promenne pro pohyb float krok,krokx,kroky; // pracovni promenne pro pohyb void setup(){ // nastaveni TV.begin(_PAL); // generovani signalu PAL TV.clear_screen(); // smazani obrazovky } void loop(){ // smycka programu // Cast 1 - Obrazovka Zakladni tvary TV.draw_rect(0,0,127,95,1); // ramecek kolem cele plochy for (int i=0; i<7; i++){ // ctverce TV.draw_rect(3*i+8,3*i+8,50-6*i,40-6*i,1); } for (int i=0; i<5; i++){ // kruznice TV.draw_circle(100,28,i*5,1); } delay(500); // pockat 0,5s for (int i=5; i<123; i++){ // cary invertujici TV.draw_line(64,55,i,90,2); delay(20); } delay(6000); // pockat 6s for (int i=0; i<98; i++){ // odrolovani nahoru TV.shift(1,0); delay(5); } // Cast 2 - Obrazovka Fonty a vypis hodnot TV.select_font(font8x8); // nastavit font 8x8 TV.print(0,0,"Font 8x8 - ABCD"); TV.select_font(font6x8); // nastavit font 6x8 TV.print(0,10,"Font 6x8 - ABCDEFGHIJ"); TV.select_font(font4x6); // nastavit font 4x6 TV.print(0,18,"Font 4x6 - ABCDEFGHIJKLMNOPQRST"); TV.select_font(font6x8); // dale font 6x8 for (int j=0; j<6; j++){ // vypis tabulky znaku for (int i=0; i<16; i++){ TV.print_char(i*8,j*8+40,j*16+i+32); delay(20); } } delay(6000); // pockat 6s for (int i=0; i<129; i++){ // odrolovani vlevo TV.shift(1,2); delay(5); } // Cast 3 - Obrazovka nahodny pohyb TV.print(0,0,"Nahodny pohyb"); // nadpis x1=64; y1=48; // pocatecni poloha for (int i=0;i<20;i++){ // 20 pohybu x2=random(0,120); // nove souradnice y2=random(8,87); krok=sqrt(pow(x2-x1,2)+pow(y2-y1,2)); // delka pohybu krokx=(x2-x1)/krok; // krok pro x kroky=(y2-y1)/krok; // krok pro y for (int j=0;j<int(krok);j++){ // pohyb po krocich TV.draw_rect(int(krokx*j+x1-1),int(kroky*j+y1),6,6,1);// vypsat TV.delay_frame(1); // pockat a synchronizovat TV.draw_rect(int(krokx*j+x1-1),int(kroky*j+y1),6,6,0);// smazat } x1=x2; y1=y2; // dalsi pohyb } for (int i=0; i<129; i++){ // odrolovani vpravo TV.shift(1,3); delay(5); } } // konec programu
Zatím uvedené příklady měly za úkol jen předvést možnosti a ukázat způsob zápisu, poslední bude mít trochu praktičtější použití. Doplníme zapojení o dva trimry, dvě tlačítka a piezoreproduktor. Jeden trimr vytvoří změny napětí, to budeme měřit a zobrazíme pomocí virtuálního ručkového měřidla. Druhý trimr udává svým napětím mezní povolenou hodnotu. Je-li jím stanovená mez na první vstupu překročena, oznámí se to jako chyba přerušovaným tónem. Chyba a její indikace trvá i když následně skončí důvody pro její vyhlášení. Jedno tlačítko resetuje sledování minima a maxima, druhé tlačítko ruší hlášení chyby. Žádná část měřidla nesmí za provozu viditelně blikat, tón musí znít čistě.
//video2 - analogove meridlo #include <TVout.h> // knihovna TVout #include <video_gen.h> #include <fontALL.h> // vsechny fonty TVout TV; // objekt TV tridy TVout float krok=0.004887586; // prevod bit/napeti V int U1,U2,U1s,U2s,Umax,Umin,Umez; // pracovni promenne napeti unsigned long cykl; // pocitani cyklu boolean chyba; // prekroceni meze napeti void setup(){ // nastaveni pinMode(2,INPUT_PULLUP); // vstup tlacitko pinMode(3,INPUT_PULLUP); // vstup tlacitko TV.begin(_PAL); // generovani signalu PAL TV.clear_screen(); // smazani obrazovky TV.draw_rect(0,0,127,95,1); // ramecek meridlo for (int i=14;i<=114;i=i+2){ // stupnice male dilky TV.draw_line(i,30,i,25,1);} for (int i=14;i<=114;i=i+10){ // stupnice velke dilky TV.draw_line(i,30,i,20,1);} TV.select_font(font4x6); // nastavit font 4x6 for (int i=0;i<6;i++){ // popis meritka TV.print(i*20+13,13,i);} TV.select_font(font6x8); // nastavit font 6x8 TV.print(7,85,"Min"); // vypis Min TV.print(75,85,"Max"); // vypis Max Umax=0; Umin=1023; chyba=false; // pocatecni stavy } void loop(){ // smycka programu U1=analogRead(5); // mereni napeti U2=analogRead(4); // mereni meze TV.draw_rect(13,32,102,52,0,0); // mazani rucky TV.draw_line(64,85,map(U1,0,1023,14,114),32,1); // rucka TV.draw_circle(64,85,3,1,1); // stred meridla if (U1>Umax)Umax=U1; // kontrola mezi if (U1<Umin)Umin=U1; // TV.select_font(font6x8); // nastavit font 6x8 TV.print(28,85,float(Umin*krok)); // vypis Min TV.print(98,85,float(Umax*krok)); // vypis Max Umez=map(U2,0,1023,14,114); // vypocet meze TV.set_pixel(Umez,32,1); // ukazatel meze TV.draw_row(33,Umez-1,Umez+2,1); // ukazatel meze if(digitalRead(2)==LOW){ // TL - mazani min a max Umax=0; Umin=1023;} if(digitalRead(3)==LOW) chyba=false; // TL - mazani chyby if(U1>U2) chyba=true; cykl++; // pocitani cyklu if(chyba && cykl%25==0) TV.tone(800,250);// zvuk chyby TV.delay_frame(1); // synchronizace } // konec programu
Na tomto programu stojí za to si podrobněji všimnout nejen způsobu vytvoření virtuálních měřidel, ale ještě něčeho. Jen ty části měřidla, které se mění a tudíž musí překreslovat, se obsluhují v cyklu. Tím se výrazně šetří čas.
Další věcí je na první pohled nenápadná, ale velmi důležitá synchronizace, kterou má na starosti poslední příkaz TV.delay_frame . Pokud jej vypustíte, program samozřejmě poběží, ale uvidíte obrovský rozdíl. Programová smyčka trvá určitou dobu a pracuje v situaci, kdy se (naprosto pravidelně) odskakuje interruptem do zobrazení a přenáší obsah paměti na obrazovku. Když jsou tyto dva děje nezávislé na sobě, bude se stávat, že se například čára smaže těsně před tím, než by měla být zobrazena, pak proběhne zobrazení a čára se vykreslí do paměti v době, kdy už je po vykreslení na obrazovku. Důsledkem toho je, že čára není vidět, dokud se zase načasování zobrazení a běhu prohramu „nerozjede“. Na pohled se to projevuje jako velmi rušivé blikání některých nebo všech částí grafiky, těch, které jsou pravidelně překreslovány.
Celá smyčka našeho programu je schopná proběhnout v době mezi vykreslováním dat na obrazovku. Proto je na konci (mohlo by to být i na začátku) příkaz TV.delay_frame(1), který na rozdíl od „obyčejného“ delay nemá za úkol jen čekat, ale synchronizovat program podle zobrazení, čekat až proběhne vykreslení jednoho snímku na obrazovku (čas je udáván v těchto snímcích). Jeden průběh smyčky se dokončí dřív než se vykresluje, takže v době vykreslování už nedochází k žádným změnám a obraz je klidný, nebliká.
Pokud by byl program složitější a trval déle, než se dá mezi snímky stihnout, bylo by nutné „rozsekat“ více příkazy delay_frame program na více částí, které by se stihnout mezi snímky daly, přitom každá část by musela plně obsloužit své objekty (čáry, rámečky, body, …). Zajistit klidný výsledný obraz znamená nejen naprogramovat úlohu tak, aby vůbec fungovala, ale současně ji celou správně načasovat a synchronizovat s generováním obrazu.
Podobné je to s generováním zvuku. Obvyklý příkaz tone zde selže a vytvoří jen chrčení, je třeba použít příkazu z knihovny TV.tone, ten na daném pinu fungovat bude.
Závěr
Na stejných knihovnách obohacených využitím možnosti měnit externí fonty 8×8 bodů je postavena řada her dostupných na internetu – například Tetris, TV tenis nebo Life. To je jistě také zajímavé a víceméně se tím Arduino dostává do pozice, v níž byly před mnoha léty osmibitové počítače, jen je za nesrovnatelně nižší cenu. Jak už jsem uvedl na začátku potenciál spolupráce s televizí nebo videomonitorem ale vidím hlavně jinde, v názornějším výstupu dat z čidel při zkouškách zapojení, ve výpisech při ladění programu a podobně. Používáte-li Arduino, zkuste to, není to nijak složité a žádné náklady to nevyžaduje.