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