Ve druhém článku o displejích se dostaneme k LCD displejům. Popíšeme si znakové i grafické displeje a jak s nimi pracovat.
Úvod
Od jednodušších maticových displejů se dnes dostaneme ke (konstrukčně) složitějším displejům LCD. Ty v současné době nalezneme téměř všude. Stačí rozebrat například rozbitý fax a téměř jistě v něm nějaký nalezneme.
LCD displeje
Jak už jsem zmínil před chvílí, existuje celá řada LCD displejů. Všeobecně se užívají dva typy dělení. První z nich rozděluje LCD displeje do dvou skupin na znakové a grafické. Druhý z nich je dělí na barevné a monochromatické (jednobarevné). My si nyní jednotlivé skupiny představíme.
Znakové LCD
Tato skupina displejů je snazší na ovládání. Ovladač jim totiž zasílá pouze informace o tom, jaký znak mají kde zobrazit. Tyto znaky jsou předem definované – displej obsahuje základní „slovník“ znaků. Existuje celá řada velikostí displejů, které se však neudávají v počtech pixelů, ale v počtu řádků a míst pro znaky, kdy každé místo má na displeji přesně dané umístění. Můžeme se tedy běžně setkat s displeji 8×1 (jeden řádek s osmi znaky) až 40×4 (čtyři řádky po čtyřiceti znacích). Ovládání poté probíhá tak, že nastavíme kurzor na místo, na které chceme znak napsat a poté ho odešleme. Znakové displeje jsou většinou monochromatické. Můžeme se ale setkat s různými barvami podsvícení displeje.
Pro lepší představu níže vidíme umístění míst pro znaky na displeji – každý obdélník odpovídá jednomu místu.
Pro práci se znakovými LCD displeji je Arduino vybaveno knihovnou, která je zahrnutá již v základním balíku IDE. Ta umožňuje ovládání všech znakových displejů, které jsou kompatibilní s řadičem Hitachi HD44780, což většina současných displejů je. Tyto displeje mají většinou šestnáct pinů. V tomto příkladu budeme pracovat s tímto 20×4 LCD displejem. Pokud se podíváme na jeho zadní stranu, nalezneme zde piny popsané 1 a 16. Pojďme si je nyní představit.
Číslo pinu | Symbol | Popis |
1 | VSS, GND | GND napájení displeje |
2 | VDD, VCC | +5V napájení displeje |
3 | V0 | Pin pro nastavení kontrastu LCD (bude vysvětleno později) |
4 – 6 | RS, R/W, E | Řízení řadiče |
7 – 14 | DB0 – DB7 | Datové piny |
15 | LED+ | Anoda podsvícení displeje |
16 | LED- | Katoda displeje |
Poznámka: Některé starší displeje nemusejí mít podsvícení – piny 15 a 16 u nich tedy nenajdeme.
Displej zapojíme podle schématu. Také můžeme přes 10 Ohm rezistor připojit napájení k podsvícení. Potenciometr zde slouží k nastavení kontrastu displeje. Rezistor i potenciometr jsou součástí naší sady.
Jak už jsem řekl dříve, obsahuje Arduino IDE pro komunikaci se znakovými LCD knihovnu. Ta má pro ovládání displeje několik funkcí. Použití některých z nich si ukážeme na příkladu.
Funkce | Popis |
LiquidCrystal lcd() | Vytvoří objekt s názvem lcd pro práci s displejem. Jako parametry se udávají piny, na které je připojen displej. Více informací o různých kombinacích parametrů nalezneme v dokumentaci |
lcd.begin(s,ř) | Zahájí práci s displejem. Parametry jsou: počet sloupců a počet řádků. |
lcd.clear() | Tato funkce smaže všechny zobrazené znaky na displeji a nastaví kurzor do levého horního rohu. |
lcd.home() | Nastaví kurzor do levého horního rohu. |
lcd.setCursor(s,ř) | Nastaví kurzor na danou pozici – sloupce, řádky. |
lcd.write(znak) | Vypíše na displej jeden znak. Pozice kurzoru se posune o jedno místo doprava (v základním nastavení). |
lcd.print(data) | Vypíše na displej řetězec, nebo číslo. Pozice kurzoru se posune o počet zobrazených znaků doprava (v základním nastavení). |
lcd.cursor() | Zobrazí na displeji pozici kurzoru podtržením znaku, na kterém je nastaven. |
lcd.noCursor() | Skryje zobrazený kurzor. |
lcd.blink() | Zobrazí blikající kurzor. |
lcd.noBlink() | Skryje blikající kurzor. |
lcd.noDisplay() | Skryje všechny zobrazené znaky, ale nesmaže je. Komunikace s displejem nadále probíhá. Můžeme zapisovat znaky, které si displej pamatuje, jen je nezobrazí. |
lcd.display() | Zobrazí vše, co bylo skryto funkcí .noDisplay() pokud mezitím došlo ke změně znaků na displeji, zobrazí se stav po změně. |
lcd.scrollDisplayLeft() | Posune všechny zobrazené znaky o jedno místo doleva. |
lcd.scrollDisplayRight() | Posune všechny znaky doprava. |
lcd.leftToRight() | Nastaví automatický posun kurzoru po vypsání znaku doprava (což je výchozí stav). |
lcd.rightToLeft() | Nastaví automatický posun kurzoru po vypsání znaku doleva. |
lcd.createChar(cislo, data) | Tato funkce přináší možnost vytvoření vlastního znaku. Parametr data obsahuje informace o znaku (bude vysvětleno v příkladu). Cislo nám říká, pod jaké číslo se uloží do „slovníku“ znaků. To může nabývat hodnot 0 až 15. Pod tímto číslem jej poté můžeme pomocí funkce .write() zobrazit. |
V prvním příkladu si vypíšeme na disleji počet vteřin od začátku běhu programu.
#include <LiquidCrystal.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //nastavení pinů void setup() { lcd.begin(20, 4); //inicializace displeje } void loop() { if(millis() % 1000 == 0){ lcd.clear(); lcd.setCursor(9,1); lcd.print(millis()/1000); } }
Nyní si ukážeme, jak se používá funkce .createChar(). Pod hodnotu 1 si vytvoříme znak dvou trojúhelníků. Námi definovaný znak se funkci předá jako jednorozměrné pole čísel (pro jednoduchou editaci v binární soustavě). V některých programovacích prostředích se pro zápis čísla ve dvojkové soustavě používá syntaxe 0b1111 (což odpovídá desítkové hodnotě 15). Arduino IDE však tento zápis neumožňuje. Pro pohodlnější práci s nižšími binárními čísly však v prostředí nalezneme několik předdefinovaných konstant začínajících velkým písmenem B. Konkrétně se jedná o rozsah hodnot od B0 (= 0) až B11111111 (= 255). Konstanta B00000010 tedy odpovídá hodnotě 2. Více o číselných soustavách si můžete přečíst na Wikipedii.
#include <LiquidCrystal.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); byte znak[8] = {B11111, B01010, B00100, B00000, B00000, B00100, B01010, B11111}; void setup() { lcd.createChar(1, znak); lcd.begin(20, 4); lcd.write(1); } void loop() {}
Ve třetím příkladu se na displej vypíše text, který mu pošleme po sériové lince. K určení pozice na displeji slouží pomocná proměnná i. Pokud je celý displej zaplněn, smaže se a kurzor se vrátí do levého horního rohu.
#include <LiquidCrystal.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); int i = 0; void setup() { Serial.begin(9600); lcd.begin(20,4); } void loop() { while(Serial.available() > 0){ lcd.setCursor(i%20, (i-i%20)/20); lcd.write(Serial.read()); i++; if(i == 80){ i = 0; lcd.clear(); } } }
Poznámka: Existují i znakové displeje se sériovým řadičem. Jeden z nich naleznete například v našem setu. Těm se ale dnes nebudeme věnovat.
Grafické monochromatické LCD
Další skupinou displejů jsou grafické displeje, z nichž jednodušší jsou ty jednobarevné. My se jimi budeme zabývat pouze zběžně. Více se zdržíme až u barevných displejů. V tomto článku si ukážeme použití displeje ATM12864D (128 x 64 pixelů) s Arduinem Mega. Tento displej obsahuje řadič KS0107B, k jehož ovládání je pro Arduino napsaná knihovna (i pro typ A a C). Tu stáhneme z archivu knihovny. Dalším důležitým dokumentem je datasheet displeje, ve kterém najdeme funkce jednotlivých pinů. Posledním dokumentem, který budeme potřebovat je dokumentace knihovny.
Displej s Arduinem propojíme podle následující tabulky. Pro jiné typy desek je zapojení popsáno v dokumentaci knihovny na straně 2.
Displej | Arduino Mega | Displej | Arduino Mega | Displej | Arduino Mega | ||
1 | GND | 8 | 23 | 15 | 33 | ||
2 | +5V | 9 | 24 | 16 | 34 | ||
3 | nepřipojeno | 10 | 25 | 17 | RESET | ||
4 | 36 | 11 | 26 | 18 | nepřipojeno | ||
5 | 35 | 12 | 27 | 19 | nepřipojeno | ||
6 | 37 | 13 | 28 | 20 | GND | ||
7 | 22 | 14 | 29 |
Piny 3, 18 a 19 nejsou připojeny k Arduinu. Zapojení pinů 3 a 18 s potenciometrem je naznačeno na následujícím obrázku. Pin 19 je připojen na +5V přes 220 ohm Rezistor.
Všechny funkce jsou přehledně popsány v již zmíněné dokumentaci. My si ukážeme, jak jednoduše na displej vykreslit graf hodnot naměřených na pinu A0.
#include <glcd.h> int data[128]; void setup(){ GLCD.Init(); //inicializace displeje } void loop(){ data[0] = map(analogRead(A0), 0, 1023, 0, 63); for(int i = 127; i > 0; i--){ bod(i,data[i]); data[i] = data[i-1]; } delay(40); GLCD.ClearScreen(); } void bod(int x, int y){ y = 63 - y; //převrácení os GLCD.SetDot(x,y, BLACK); //nastavení bodu GLCD.SetDot(x+1,y, BLACK); GLCD.SetDot(x+1,y-1, BLACK); GLCD.SetDot(x,y-1, BLACK); }
Barevné grafické LCD
Nyní už se konečně dostáváme k barevným displejům. Představíme si dotykový displej s úhlopříčkou 2,8 palce a 320×240 pixely. Nebudeme se tedy zabývat pouze zobrazováním, ale i interakcí s dotykovou plochou. Ta nás bude zajímat jako první. Pro její funkci budeme potřebovat knihovnu Touch Screen Driver.
Vytvoříme si program, který nám odešle souřadnice právě zmáčknutého bodu. Na začátek programu vložíme kód, který za nás určí typ Arduina (v tomto případě Leonardo) a nastaví piny použité pro dotykovou vrstvu. Část kódu společně s vložením knihoven, vytvořením objektu displeje a kalibrací dotykové plochy vypadá následovně.
#include <stdint.h> #include <SeeedTouchScreen.h> #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // mega #define YP A2 #define XM A1 #define YM 54 #define XP 57 #elif defined(__AVR_ATmega32U4__) //leonardo #define YP A2 #define XM A1 #define YM 18 #define XP 21 #else #define YP A2 #define XM A1 #define YM 14 #define XP 17 #endif TouchScreen ts = TouchScreen(XP, YP, XM, YM);
Funkcí definic se nemusíme zabývat. Hlavní je, že nám tato část kódu zajistí správný chod displeje. Dalším krokem je kalibrace dotykové plochy. Na displeji je použita rezistivní technologie dotykové membrány. Její princip by se dal zjednodušeně popsat tak, že tlak na pružnou membránu se projeví určitým elektrickým odporem měřeným na této membráně. V ideálním případě by se například při tlaku v bodě [10,20] naměřil odpor na membráně měřící odpor v ose x 10 ohm a u y 20 ohm. Naše plocha ale není takto ideální a musíme ji tedy nějakým způsobem zkalibrovat. Přibližné hodnoty maximální a minimální hodnoty odporu na osách vidíte v tabulce.
minimum | maximum | |
X | 232 | 1780 |
Y | 166 | 1826 |
V další části tedy musíme do kódu zahrnout údaje pro kalibraci.
int min_x = 232; int max_x = 1780; int min_y = 166; int max_y = 1826; int x, y; void setup(){ Serial.begin(9600); }
V dalším kroku vytvoříme objekt pro bod, se kterým budeme dále pracovat. Jak se zachází s objekty jsme si vyzkoušeli už při programování v Processing, takže to pro nás nebude žádné překvapení. Bod má tři vlastnosti: x, y odpovídající odporu na souřadnicích a z, která uchovává informaci o tlaku (s ní se dá poté do programu zakomponovat i citlivost dotyku). Pro získání souřadnic x a y musíme porovnat naměřené hodnoty s údaji pro kalibraci. Poté je už můžeme po sériové lince vypsat.
void loop(){ Point p = ts.getPoint(); x = map(p.x, min_x, max_x, 0, 239); y = map(p.y, min_y, max_y, 0, 319); if(p.z > 10){ Serial.print(x); Serial.print(':'); Serial.println(y); } delay(500); }
Poznámka: Kalibrace dotykové plochy displeje se dá provést získáním naměřených hodnot pomocí funkce .getPoint a postupním přejížděním os z minima do maxima. Poté stačí najít maximální a minimální hodnotu pro obě osy a změnit kalibrační proměnné.
Už jsme zjistili, jak se dá z displeje zjistit souřadnice stlačeného bodu. Druhá (a důležitější) část je práce se samotnou zobrazovací plochou. I k tomuto účelu existuje pro náš displej knihovna, kterou stáhneme zde. Ta nám přináší funkce pro jednodušší práci s displejem. Níže vidíte některé z nich.
Funkce | Popis |
Tft.TFTinit() | Inicializuje displej. |
Tft.setPixel(x, y, barva); | Vykreslí bod na daných souřadnicích. |
Tft.drawCircle(x, y, r, barva) | Nakreslí kruh dané barvy se středem v [x,y] o poloměru r. Více o barvách níže. |
Tft.fillCircle(x, y, r, barva) | Stejné jako předchozí funkce, pouze se zobrazí místo kruhu kružnice. |
Tft.drawLine(x1, y1, x2, y2, barva) | Nakreslí úsečku dané barvy z bodu [x1,y1] do [x2, y2] |
Tft.drawVerticalLine(x, y, d, barva) | Nakreslí vertikální úsečku dané barvy s počátkem v bodu [x, y] o délce d. |
Tft.drawHorizontalLine(x, y, d, barva) | Stejné jako u předchozí funkce, pouze bude výsledná úsečka horizontální. |
Tft.drawNumber(cislo, x, y, v, barva) | Vypíše číslo typu int dané barvy na souřadnicích x, y o velikosti v. |
Tft.drawFloat(cislo, x, y, v, barva) | Stejné jako u předchozí funkce. Zobrazí číslo typu float. |
Tft.drawRectangle(x,y,delka,vyska,barva) | Zobrazí okraje obdélníku dané barvy s levým horním rohem v souřadnicích x a y o délkách hran delka a vyska. |
Tft.fillRectangle(x,y,delka,vyska,barva) | Stejné jako předchozí funkce, pouze s tím rozdílem, že bude výsledný útvar vyplněný. |
Tft.fillScreen(x_levo, x_pravo, y_dole, y_nahore, barva) | Vyplní displej mezi souřadnicemi danou barvou. |
Tft.drawTraingle(x1, y1, x2, y2, x3, y3, barva) | Zobrazí trojúhelník dané barvy s vrcholy [x1,y1], [x2, y2], [x3, y3]. |
Tft.drawChar(znak, x, y, v, barva) | Zobrazí znak dané barvy na souřadnicích [x,y] o velikosti v. |
Tft.drawString(retezec, x, y, v, barva) | Vypíše řetězce dané barvy na souřadnicích [x,y] velikosti v. |
Tft.setDisplayDirect(smer) | Nastaví směr textu. Smer může mít hodnoty: LEFT2RIGHT, RIGHT2LEFT, DOWN2UP a UP2DOWN. |
Displej umí pracovat s 216 (65536) různými barvami. Informace o barvě jednoho pixelu tedy zabere 16 bitů. Červené a modrá barva mají každá pět bitů, zelená má bitů šest, protože je na ní lidské oko více citlivé, tudíž rozezná více jejích rozdílů. Tento způsob míchání barev se nazývá 16-bit high color a více se o něm můžete dočíst na anglické Wikipedii. Červená a modrá barva tedy můžou mít 32 různých sytostí, na zelenou jich připadá 64. Knihovna obsahuje několik předdefinovaných konstant barev. Jsou to:
Barva | Název konstanty | HEX kód |
červená | RED | 0xf800 |
zelená | GREEN | 0x07e0 |
modrá | BLUE | 0x001f |
černá | BLACK | 0x0000 |
žlutá | YELLOW | 0xffe0 |
bílá | WHITE | 0xffff |
azurová | CYAN | 0x07ff |
purpurová | BRIGHT_RED | 0xf810 |
šedá | GRAY1 | 0x8410 |
šedá | GRAY2 | 0x4208 |
Pokud by nám nestačila výchozí paleta, můžeme si vytvořit i barvy vlastní. Jeden ze způsobů vidíte na příkladu. Jedná se o trošku pokročilejší programování, na které přijde řeč možná až časem. S čísly se zde pracuje na úrovni jednotlivých bitů. Důležité ale je to, že funkce barva má tři parametry – r, g a b pro jednotlivé barvy a vrací číslo barvy. Parametry r a b mohou nabývat hodnot od 0 do 31. Parametr g 0 až 63. Následující program zobrazí na displeji tři kružnice tří základních barev.
#include <stdint.h> #include <TFTv2.h> //knihovna displeje #include <SPI.h> //knihovna pro komunikaci s displejem void setup(){ Tft.TFTinit(); Tft.drawCircle(120, 60, 20, barva(31,0,0)); Tft.drawCircle(120, 160, 20, barva(0,63,0)); Tft.drawCircle(120, 260, 20, barva(0,0,31)); } void loop(){ } uint16_t barva(int r, int g, int b){ r = r % 32; g = g % 64; b = b % 32; r = r << 11; g = g << 5; return r | g | b; }
Jednotlivě jsme již vyřešili dotykovou plochu i displej. Nyní pojďme dát vše dohromady. Vytvoříme si aplikaci pro jednoduché malování černou barvou na bílé pozadí. Aplikace bude obsahovat také tlačítko reset.
#include <stdint.h> #include <SeeedTouchScreen.h> #include <TFTv2.h> #include <SPI.h> #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // mega #define YP A2 #define XM A1 #define YM 54 #define XP 57 #elif defined(__AVR_ATmega32U4__) //leonardo #define YP A2 #define XM A1 #define YM 18 #define XP 21 #else #define YP A2 #define XM A1 #define YM 14 #define XP 17 #endif TouchScreen ts = TouchScreen(XP, YP, XM, YM); int min_x = 232; int max_x = 1780; int min_y = 166; int max_y = 1826; int x, y; void setup(){ Tft.TFTinit(); Tft.fillScreen(10,230,10,230, WHITE); Tft.fillScreen(10,230,240,310, GRAY2); Tft.drawString("RESET", 45, 255, 5, BLACK); } void loop(){ Point p = ts.getPoint(); x = map(p.x, min_x, max_x, 0, 239); y = map(p.y, min_y, max_y, 0, 319); if(p.z > 10){ if(x >= 10 && x <= 220 && y >= 250 && y <= 310){ Tft.fillScreen(10,230,10,230, WHITE); } else if(x >= 10 && x <= 220 && y >= 10 && y <= 230 ){ Tft.fillRectangle(x,y,2,2,BLACK); } } delay(1); }
Zdroje obrázků
V případě jakýchkoliv dotazů či nejasností se na mě neváhejte obrátit v komentářích.