Arduino LCD Shield – mini textový editor

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

Další podobné články

Ovládání teploty pomocí relé a senzoru DS18B20

Tento projekt umožňuje měřit teplotu pomocí senzoru DS18B20, zobrazit ji na I2C LCD displeji, a ovládat relé podle teplotních limitů. Relé bude aktivní, když je teplota mezi 0 a 22 stupni Celsia. Projekt může sloužit k ovládání topení, ventilace nebo jiného zařízení podle okolní teploty.