Nevyhazujte starou televizi, s Arduinem ji využijete!

28. 2. 2011
Doba čtení: 6 minut

Sdílet

Není to tak dávno, kdy byla televize téměř výhradním zobrazovacím zařízením pro domácí počítače. Obraz se sice chvěl, byl rozmazaný a blikal, ale byla to nejdostupnější periferie. Pokud máte nějakou takovou televizi s VIDEO vstupem, můžete k ní jednoduše připojit Arduino a nechat ho generovat jednoduchou grafiku.

Trocha teorie

Televizní signál je při analogovém šíření kódován buď v systému NTSC (USA, Kanada, Japonsko), SECAM (Rusko, některé africké státy) nebo PAL (zbytek světa). Moderní televize dokáží přijímat signál ve všech těchto normách; pokud budete mít starší kus, bude pravděpodobně pracovat v normě PAL/SECAM.

Normy PAL a SECAM se od sebe liší hlavně způsobem přenosu informací o barvě, jinak jsou téměř totožné. Větší rozdíly jsou mezi PAL a NTSC. PAL zobrazuje 625 řádků s obnovovací frekvencí 25 Hz, NTSC 525 řádků s frekvencí 30 Hz. Ve skutečnosti je obraz zobrazován prokládaně – každý snímek je přenášen ve formě dvou půlsnímků s polovičním počtem řádků, které jsou navzájem o řádek posunuty. Např. v normě PAL se vysílá nejprve lichý půlsnímek s řádky 1, 3, 5, …, po něm sudý s řádky 2, 4, 6, … Každý půlsnímek má 312,5 řádků a trvá 1/50 sekundy. (U NTSC se analogicky vysílají půlsnímky během 1/60 sec.)

Analogová televize byla, vzhledem k nedokonalým přenosovým linkám, poměrně robustní zařízení, které jen tak nějaká chyba v signálu nevykolejila. Počítače v 80. letech toho využívaly – generovaly signál, u něhož se na časování tak úplně nehledělo, a signál neprokládaly – generovaly zkrátka snímky s 312 řádky a obnovovací frekvencí 50 Hz.

Černobílý signál

Generování černobílého signálu je poměrně jednoduché – tedy v porovnání s generováním barevného signálu, které je lepší řešit pomocí speciálních obvodů. Pokud zůstaneme u černobílého videosignálu, v pohodě jej stihneme vytvářet dnešními mikrokontroléry, a ještě jim zůstane čas dělat něco jiného.

Jako výborný příklad je možné uvést konstrukci ing. Petera Chrenka – jeho zapojení s jednočipem Atmel ATMega128 dokáže s minimem externích součástek (v podstatě jen RAM a stabilizátor napájení) emulovat chování mikropočítače PMD-85. Videosignál generuje samotný mikrokontrolér, a ve „volných chvílích“ (při zatmění paprsku, kdy se signál netvoří) provádí strojový kód procesoru 8080 a obsluhuje ostatní periferie.

… ale je to honička!

Zvládnout generování signálu lze, ale není to zrovna procházka růžovým sadem, jak zjistíme při pohledu na časování.

Během jedné padesátiny sekundy (20 ms) musíme vygenerovat kompletní obraz. Ten se skládá (zůstaneme v normě PAL) z 312,5 řádků. Na jeden řádek tak máme rovných 64 µs. Řádková frekvence je tedy 1/0.000064 = 15625 Hz – což je „magické číslo“, na které občas v souvislosti s generováním videosignálu narazíme.

Televize nevykresluje signál od kraje do kraje, navíc potřebuje zasynchronizovat, aby věděla, kde vlastně řádek začíná. Proto každý řádek začíná horizontálním synchronizačním pulsem (HSYNC), který trvá 4 µs. Pak následuje 8 µs „tmy“ (protože starým televizím trvalo nějakou dobu, než synchronizační signál zpracovaly). Po úvodním zatmění následuje vlastní jasový signál, který trvá 52 µs.

Videosignál má definované úrovně napětí (0–1V) pro jednotlivé situace. Synchronizačnímu pulsu odpovídá 0 V, černá je 0,3V a bílá 1.0V. Jeden řádek videosignálu vypadá zhruba takto:

V jednom rámci se přenáší 312,5 řádku, ale televize nezobrazuje všechny – některé z řádků jsou prázdné a slouží k synchronizaci obrazu (hodně staré televize měly vzadu potenciometr, kterým se dala synchronizace srovnat, pokud začal obraz „utíkat“ nahoru nebo dolů). V některých „prázdných řádcích“ se dnes přenáší teletext. Vlastní vertikální synchronizační puls (VSYNC) má podobu několika prázdných půlřádků s kratším HSYNC (2 µs), pak následuje samotná synchronizace (5 řádků s dlouhým synchronizačním pulsem – 30 µs) a pak opět několik půlřádků. Právě ve formátu VSYNC se liší liché a sudé půlsnímky.

Další informace o časování PAL videosignálu:

Generování signálu

K vygenerování signálu použijeme jednoduché zapojení – dva odpory, připojené na vývody mikrokontroléru, které dohromady vytvoří jednoduchý D-A převodník, na jehož výstupu bude, podle nastavení vstupů, cca 0V, 0,3V a 1V. Použijeme pin PD7 (Arduino digital output 7) jako výstup VIDEO a pin PB1 (Arduino digital output 9) jako výstup SYNC. Takto:

Hodnoty jsou zvoleny tak, aby odpovídaly standardní řadě hodnot, a výsledek se co nejvíc blížil potřebným hodnotám. Na videovstup můžeme pohlížet jako na další odpor 75Ω a protože známe i výstupní napětí na pinech mikrokontroléru (5V), můžeme spočítat úroveň napětí pomocí Ohmova zákona.

Isync = Uin / (Rsync+Rout) // 5 / 1075 = 4,6511 mA
Ivideo = Uin / (Rvideo+Rout) // 5 / 545 = 9,1745 mA
Usync = Isync * Rout // = 0.34V
Uvideo = Ivideo * Rout // = 0.688V
Uvideo+sync = (Ivideo+Isync) * Rout // = 1.03V

Vidíme tedy, že jsme získali sadu napětí, pomocí které můžeme pohodlně vytvořit černobílý obraz (či černo-šedo-bílý).

Úrovně napětí
VIDEO SYNC V Význam
0 0 0 Synchronizační puls
0 1 0.34 Černá
1 0 0.68 Šedá
1 1 1.03 Bílá

Po dobu generování HSYNC stačí stáhnout piny VIDEO a SYNC k log. 0. Pak nastavíme SYNC na log. 1 a získáme na výstupu „téměř černou“. Změnou hodnoty na výstupu VIDEO pak přepínáme černou a bílou.

Kolik toho stihneme?

V Arduinu běží vnitřní časovač na frekvenci 16MHz. Vzhledem k tomu, že AVR mají architekturu RISC, trvá většina instrukcí jeden hodinový cyklus. Instrukce tedy trvají 0.0625 µs. Během jednoho řádku proběhne krásných kulatých 1024 cyklů v době, kdy zobrazujeme obrázek, máme k dispozici maximálně 832 T.

Pokud budeme zobrazovat černobílý obraz, můžeme využít každý bajt paměti pro osm obrazových bodů. Vyzvedneme si bajt do registru a provedeme osmkrát za sebou OUT na PORTB (rychlejší, ale „nečistý“ způsob: využijeme toho, že pro VIDEO je vyhražen bit 7, a pouze provedeme shift doleva). To provedeme ve smyčce tak dlouho, dokud nevykreslíme celý řádek. Pokud budeme chtít mít na řádku 128 bodů, znamená to, že na každý máme 6 hodinových cyklů; využijeme tak 768T, zbývající čas je „zatmění“, během něhož může procesor dělat něco jiného.

Vertikální rozlišení, tedy počet „řádků displeje“, závisí na tom, kolik máme k dispozici paměti – budeme potřebovat H/8*V bajtů. Rozumné je volit počet řádků takový, aby pixely byly přibližně čtvercové. Pokud zvolíme rozlišení 128×96, budeme potřebovat 1536 bajtů. To se pohodlně vejde do vnitřní paměti RAM (ta má u Arduina Duemilanove i u Arduina UNO velikost 2kB). Každý řádek pak zobrazíme třikrát po sobě (288 řádků), zbytek do 312,5 necháme tmu (a procesor počítat).

Realizace

Takhle přesné časování nedocílíme rozhodně běžnými programovými prostředky; musíme sáhnout k assembleru. Prostředí Arduino v sobě obsahuje překladač AVR-CPP, který umožňuje vkládat do C++ kódu assembler stejně, jako to umožňují jiné CPP překladače z rodiny GNU.

Nebudeme si ukazovat, jak takovou knihovnu napsat (možná někdy v budoucnu) a místo toho využijeme hotovou knihovnu arduino-tvout. Její autor už připravil nezbytné rutiny pro generování video signálu (viz video-gen.cpp) i základní obslužné rutiny pro práci s video RAM (kreslení čar, vykreslování písma apod. – viz TVout.cpp). Veškeré časování se děje pomocí časovačů a přerušení, proto pozor, pokud budeme chtít zkombinovat tuto knihovnu s aplikací, která přerušení využívá.

My můžeme k napsání vlastního programu použít standardní prostředí Arduina a volat funkce této knihovny. V obsluze setup zavoláme funkci begin(), která inicializuje vše potřebné. Ke kreslení pak využijeme ostatní nabízené funkce.

Pokud chceme použít např. sériový port, musíme sáhnout k upravené verzi obsluhy, která nevyužívá přerušení – pollserial. Namísto obslužné rutiny, volané přerušením, použijeme vlastní, kterou knihovna zavolá vždy, když dokončí vykreslování řádku. K tomu slouží funkce set_hbi_hook().

#include <TVout.h>
#include <pollserial.h>
#include <fontALL.h>

TVout TV;
pollserial pserial;

void setup()  {
  TV.begin(PAL,184,72);
  TV.select_font(font6x8);
  TV.println("Serial Terminal");
  TV.println("-- Version 0.1 --");
  TV.println("\n57600 Baud, 8-N-1");
  TV.set_hbi_hook(pserial.begin(57600));
}

void loop() {
  if (pserial.available()) {
    TV.print((char)pserial.read());
  }
}

Vidíme nastavení obsluhy „horizontálního zatemnění“ – funkce pserial.begin() vrací ukazatel na obslužnou rutinu. Ve smyčce pak kontrolujeme, zda přišel znak, a pokud ano, vypíšeme jej. Ze sériového terminálu si můžeme zkusit, jak knihovna vypisuje znaky – zná CR, LF, BS i FF a scrolluje.

S dvěma odpory můžeme tedy proměnit Arduino a televizi se vstupem pro kompozitní video na černobílý textový terminál a použít jej třeba pro výpis ladicích informací či jako zobrazovací zařízení tam, kde nevadí hrubší černobílá grafika.

bitcoin_skoleni

Knihovna nabízí i jednoduchý generátor zvuku, jak ukazuje následující demonstrace. Autor použil cca 20 let starý barevný TV přijímač; známé „pruhy“ přes obraz jsou způsobeny rozdílnou synchronizací kamery a TV, v televizi samosebou nejsou, stejně jako není viditelné moiré, které se ve videu objevuje (barevné „obrazce“).

Příště si ukážeme, jak spojit televizní výstup s Ethernet shieldem.

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í.

Autor článku

Martin Malý je autorem serveru Bloguje, mikroblogu Teidu či služby pro zkracování odkazů Jdem.cz. Vedl také magazín Zdroják.