Arduino LCD Shield je rozšiřovací modul pro desky formátu Arduino UNO, obsahující LCD displej o velikosti 16x2 znaky a 5 ovládacích tlačítek. Modul je výborně popsán včetně příkladu použití v návodu https://navody.dratek.cz/navody-k-produktum/arduino-lcd-shield-16x2-a-tlacitka.html, z kterého vychází i tento příklad. Proto si jej nejprve prostudujte.
Po vyzkoušení výše zmíněného návodu mne zaujala možnost vyzkoušet tlačítka na modulu pro zápis textu. Na psaní románu není pět tlačítek ideální, ale pro zadání pár slov nebo čísel jdou použít překvapivě dobře. Zápis je řešen tlačítky Up a Down, která umožňují výběr znaku, tlačítko Right znak přidá na konec řetězce a posune se na další pozici, tlačítko Left odmaže poslední zadaný nebo rozeditovaný znak (funguje jako BackSpace) a tlačítko Select zápis ukončí. Ukládací tlačítko by se mi líbilo vpravo (tam kde je Reset), ale je třeba se smířit s jeho umístěním. Původně jsem počítal s tím, že bude třeba řešit autorepeat, tedy funkci, kdy se podržením tlačítka delší dobu začne akce tlačítka opakovat s určitou frekvencí (stejně jako na klávesnici počítače nebo třeba při nastavování budíku). Ukázalo se ale, že při rozumně zvolené frekvenci čtení tlačítek toto není nutné a opakování tlačítka při jeho delším stisku je poměrně ergonomické.
Níže uvedený kód vychází z návodu a kódu výše, používá vestavěnou knihovnu LiquidCrystal a shodnou inicializaci připojeného displeje a definici pinu pro řízení osvětlení.
Podprogram setup v prvním kroku zahájí komunikaci s připojeným displejem a sériovou linkou, poté nastaví řídící pin osvětlení jako výstupní a na 1 sekundu zobrazí úvodní text.
Kód nekonečné smyčky loop využívá níže popsané funkce a tak je velmi jednoduchý. Zavoláním funkce zapisTextu načte zadaný text a pokud není prázdný, pošle jej sériovou linkou. Je možné si jej zobrazit v Sériovém monitoru (Ctrl+Shift+M).
Abychom zbytečně nesvítili displejem, po 2 sekundách se zadaný a zobrazený text vymaže, displej zhasne a čeká se na stisk a opětovné uvolnění libovolné klávesy (kromě Reset). Čekání probíhá uvnitř funkcí Stisk a noStisk (bylo by možné je spojit do jedné, takto rozdělené se mohou někdy hodit). Tlačítko, které bylo stisknuto, se nepoužije pro další funkce, zajistí jen ukončení smyčky a návrat do zadávání dalšího textu.
Funkce zapisTextu vypíše na první řádek displeje nápovědný text a na začátek druhého řádku zobrazí kurzor. Následně nastaví aktuální hodnotu aktuálně předvybraného znaku na 64, což je ASCII kód znaku “@“ těsně předcházejícího znak „A“. Pokud je následně stiskem tlačítka Up zvýšen ASCII kód znaku, zobrazí se právě A.
Při rozeditovaném znaku není pěkné mít zobrazen kurzor a tak je editace znaku indikována jeho blikáním. Cyklus, který prochází, zda je stisknuta nějaká klávesa (využívá k tomu funkci ctiTlacitko popsanou níže) má zpožděním 150 ms nastavenou rychlost načítání tlačítek. Při této rychlosti se daří dobře jak jednotlivý stisk, tak je dostatečně rychlý autorepeat při podržení.
V tomto cyklu se vždy každý třetí průchod (tedy po 450 ms) rozeditovaný znak zhasne, a každý šestý průchod se opět rozsvítí a vynuluje se počítadlo průchodů. Pokud je nějaký znak rozeditován a není stisknuté žádné tlačítko, tento znak tedy bliká asi jednou za sekundu (s frekvencí cca 1 Hz).
Pokud je nějaké tlačítko stisknuto nebo podrženo, příkazem switch...case jsou obslouženy jednotlivé situace. Při stisku Up nebo Down je zvýšen nebo snížen kód znaku (pokud kód není na konci nebo začátku intervalu povolených znaků), je schován kurzor a znak je zobrazen. Dále je nastaven příznak rozeditovaného znaku pro blikání popsané výše a další příkazy. (Při delším stisku Up nebo Down prochází program touto volbou opakovaně a přechází na další nebo předchozí znaky v pořadí, jak jsou definovány v tabulce ASCII, kterou modul pro znaky s kódem do 127 používá, v intervalu kódů jsou k dispozici další speciální znaky, ty v příkladu zadávat nejde).
Stisk Right se použije jen v případě, pokud je znak rozeditován a ještě nebylo zapsáno 16 znaků, které se vejdou na řádek displeje. V tom případě znak je přidán na konec řetězce, posune se pozici kurzoru o znak dopředu a nastaví se příznaky tak, aby se ukončil vnitřní cyklus pro zadání jednoho znaku a znova se zobrazil kurzor na nové pozici.
Stisk Left způsobí v případě, že není znak rozeditován a kurzor na prvním znaku, smazání posledního znaku z řetězce, a posun o kurzoru o znak zpět. Následně je znak na pozici kurzoru vymazán, a to i pokud byl rozeditován. V této sekci je vloženo další zpoždění 0,3 s, které zpomalí opakování při podržení klávesy Left, což omezí nechtěné smazání více znaků nebo celého textu při podržení tlačítka. Následně ukončí vnitřní cyklus pro zadání jednoho znaku a znova se zobrazil kurzor na nové pozici.
Poslední tlačítko Select se chová jako Enter, pokud byl poslední znak rozeditován, přidá jej na konec řetězce, zapsaný text vypíše na první řádek displeje (pokud je prázdný, vypíše se náhradní text), ukončí vnitřní i vnější cyklus a vrátí hodnotu zapsaného řetězce jako výstup funkce.
Poslední funkce ctiTlacitko vychází z funkce nactiTlacitka v návodu výše (zde je její detailní popis). V této modifikaci vrací kód stisknuté klávesy, použil jsem kód vycházející z čísel na numerické klávesnici počítače odpovídající kurzorovým šipkám (viz komentář k funkci).
Toto řešení můžete dále rozvíjet a modifikovat. Funkce zapisTextu může být upravena například takto:
- Po prvním velkém znaku nabízet jako výchozí znak malé „a“.
- Omezit rozsah zadávaných znaků jen na čísla nebo velká písmena. Pak by bylo vhodné při stisku Up na posledním povoleném znaku cyklicky přejít na první znak (např. z 9 na 0) a naopak.
- Doplnit jednoduchý slovník s oblíbenými slovy pro nabízení dalších znaků (zjednodušené T9 známé z mobilů).
- Formátovat zadávané znaky ve tvaru data nebo času.
- Umožnit zadání textů delších než 16 znaků a rolování zadávaného textu.
- Umožnit zadání vybraných českých znaků s diakritikou (modul umožňuje vytvoření až osmi speciálních znaků, jejich uložení do paměti modulu a používání v textu).
Zadaný text je možné místo odeslání sériovou linkou použít ve vašem programu například:
- Pro převod na morseovku a zapípání bzučákem nebo zablikání LED diodou (ty je třeba do řešení připojit).
- Pro nastavení data a času (při použití modulu reálného času)
- Pro zadání hesla pro přístup k wifi (při použití wifi modulu)
- Pro zápis SMS zprávy a telefonního čísla (při použití GSM modulu)
Na řadu dalších vylepšení a úprav přijdete jistě sami.
// Arduino LCD 16x2 Shield // zápis textu tlačítky na modulu // připojení knihovny #include <LiquidCrystal.h> // inicializace LCD displeje LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // nastavení čísla propojovacího pinu // pro osvětlení LCD displeje #define lcdSvit 10 void setup() { // zahájení komunikace s displejem, // 16 znaků, 2 řádky lcd.begin(16, 2); // nastavení pinu pro řízení osvětlení jako výstupu pinMode(lcdSvit, OUTPUT); // komunikace přes sériovou linku rychlostí 9600 baud Serial.begin(9600); // smazání obsahu displeje lcd.clear (); // rozsvícení podvícení digitalWrite(lcdSvit, HIGH); // nastavení výpisu na první znak, první řádek lcd.setCursor(0, 0); // zobrazení úvodního textu po restartu lcd.print("Vitejte... "); //na 1 s delay(1000); } void loop() { // *** hlavní smyčka *** //příprava řetězce pro text String textik = ""; //naplnění zadávací funkcí textik = zapisTextu(); //nyní je možné text použít jakkoli, např. jej odeslat po sériové lince if (textik != "") { Serial.print("Zapsaný text: "); Serial.println(textik); } //text je zobrazený ještě 2 s delay(2000); //ekologické zhasnutí a vymazání displeje digitalWrite(lcdSvit, LOW); lcd.clear (); //čekání na stisk jakéhokoli tlačítka pro "probuzení" stisk (); //ještě čekání na uvolnění všech tlačítek noStisk (); //opakování hlavní smyčky, nový zápis textu } String zapisTextu() { // *** funkce pro zápis textu tlačítky na Shield modulu LCD 1602 s mini klávesncí *** // vynulování zadaného textu String text = ""; // nastavení na 1. znak int pos = 0; // nastavení stavového příznaku pro ukončení zápisu bool hotovo = false; // stavový příznak pro další znak bool next; // stavový příznaku pro rozeditovaný znak bool znak; // smazání obsahu displeje lcd.clear (); // rozsvícení podvícení digitalWrite(lcdSvit, HIGH); // nastavení výpisu na první znak, první řádek lcd.setCursor(0, 0); // zobrazení nápovědného textu na displej lcd.print("Zapis textu: "); // hlavní cyklus zápisu textu do { // nastavení kurzoru na aktuální pozici v textu, druhý řádek lcd.setCursor(pos, 1); // zobrazení kurzoru lcd.cursor(); // nastavení výchozího znaku tak, aby první stisk tlačítka Nahoru, změnil znak na "A" // pro snazší zadávání čísel je možné nastavit na ASCII kód znaku před nulou (31), pro malá písmenka na 96 apod. int character = 64; // nastavení stavového příznaku pro rozeditovaný znak znak = false; // cyklus pro zápis znaků do { // nastavení stavového příznaku pro další znak next = false; // příprava proměnné pro načtení klávesy byte klav; // příprava proměnné pro blikání aktuálně editovaného znaku int i = 0; // cyklus pro blikání aktuálně editovaného znaku a jeho úpraby do { // základní čekání mezi načítáním kláves, řeší i autorepeat delay(150); // posuneme počítadlo pro blikání aktuálně editovaného znaku ++i; // jesti je nějaký znak rozeditovaný a už uběhlo 3x150=600 ms, znak se skryje if (znak and i > 3) { lcd.setCursor(pos, 1); lcd.print(' '); } // jesti je nějaký znak rozeditovaný a už uběhlo dalších 3x150=600 ms, znak se opět zobrazí if (znak and i > 6) { lcd.setCursor(pos, 1); lcd.print(char(character)); // a vynuluje se počítadlo pro blikání i = 0; } // načtení stavu klávesnice (0 - nestiskunuto nic, 2,4,6,8 - stisk šipky (hodnota jako na numerické klávesnici, 5 - Enter klav = ctiTlacitko(); // čekací a blikací cyklus se opakuje, dokud není něco stisknuto } while (klav == 0); // podle stisknuté klávesy se rozhodne další postup switch (klav) { // tlačítko Nahoru - změna znaku na následující v ACSII tabulce case 8: // pokud už není konec povoleného rozsahu if (character < 128) { // zvýšení kódu znaku ++character; // a jeho zobrazení lcd.setCursor(pos, 1); lcd.print(char(character)); // skrytí kurzoru při rozeditovaném znaku, je nahrazen blikáním lcd.noCursor(); // nastavení příznaku rozeditovaného znaku znak = true; } break; // tlačítko Dolů - změna znaku na předchozí v ACSII tabulce case 2: //pokud už nejsme na začátku povoleného rozsahu if (character > 32) { //snížení kódu znaku --character; // a jeho zobrazení lcd.setCursor(pos, 1); lcd.print(char(character)); // skrytí kurzoru při rozeditovaném znaku, je nahrazen blikáním lcd.noCursor(); // nastavení příznaku rozeditovaného znaku znak = true; } break; // tlačítko Doprava - ukončení editace znaku a posun na další pozici case 6: // pokud byl znak rozeditován a ještě není konec displeje if (pos < 16 and znak) { // znak je přidán na konec textu text += char(character); // znak je zobrazen (může být skryt při blikání) lcd.setCursor(pos, 1); lcd.print(char(character)); // posun na další pozici ++pos; // nastavení příznaku posunu next = true; // zrušení příznaku rozeditovaného znaku znak = false; } break; // tlačítko Doleva - smazání posledního znaku a posun o znak zpět (backspace) case 4: // pokud není znak rozeditován a kurzor není na prvním znaku if (!znak and pos > 0) { // smazání posledního znaku z řetězce text = text.substring(1, text.length() - 1); // posun o znak zpět --pos; } // vymazázní znaku na pozici kurzoru lcd.setCursor(pos, 1); lcd.print(' '); //návrat pozice, pokud by byl zobrazen kurzor, aby byl na správném místě lcd.setCursor(pos, 1); // delší čekání, aby autorepeat nesmazal rychle více znaků delay(300); // nastavení příznaku posunu next = true; // zrušení příznaku rozeditovaného znaku znak = false; break; // tlačítko Enter (Select) - dozapsání posledního znaku a zobrazení výsledku case 5: // pokud nebyl zapsán, poslední znak, doplní se do řetězce if (znak) text += char(character); // vymazání displeje a zobrazení zapsaného textu nebo textu "..nezadano nic.." lcd.clear (); lcd.setCursor(0, 0); lcd.noCursor(); if (text != "") lcd.print(text); else lcd.print("..nezadano nic.."); //nastavení příznaků hotovo a next pro ukončení obou cyklů hotovo = true; next = true; break; // toto asi nenastane default: break; } // koncec cyklu jednoho znaku } while (!next); // konec cyklu zápisu textu } while (!hotovo); // vrácení zapsaného textu return text; } void stisk () { // *** funkce čekání na stisk libovolné klávesy (kterou nepředává) *** do delay(100); while (ctiTlacitko() == 0); } void noStisk () { // *** funkce čekání na uvolnění všech kláves *** do delay(100); while (ctiTlacitko() != 0); } char ctiTlacitko() { // *** funkce pro načtení stavu klávesnice (0 - nestiskunuto nic, 2,4,6,8 - stisk šipky (hodnota jako na numerické klávesnici, 5 - Enter // příprava proměnné pro vrácení stavu byte x = 0 ; // načtení údajů z analogového pinu A0 do proměnné int analog = analogRead(0); // postupná kontrola pomocí podmínek if, // pro každé tlačítko je uveden rozsah hodnot, // ve kterých je detekována určité tlačítko a přiřazení jejího kódu if (analog < 50) x = 6; if ( (analog > 95) & (analog < 150) ) x = 8; if ( (analog > 250) & (analog < 350) ) x = 2; if ( (analog > 400) & (analog < 500) ) x = 4; if ( (analog > 600) & (analog < 750) ) x = 5; if ( (analog > 800) & (analog < 1024)) x = 0; // vrácení textu jako výstup funkce return x; }
Seznam použitých komponent:
https://dratek.cz/arduino/974-arduino-uno-r3-atmega328p-1424115860.html
https://dratek.cz/arduino/899-arduino-lcd-shield-1420670167.html