1602 LCD kijelző (ATtiny85 alaplappal is…)

Tartalom:

  • I2C illesztővel kiegészített LCD kijelző összekötése az Arduino-val
  • LCD kijelző használata az ATtiny85 alaplappal (kis memória a kevés kivezetés)
  • Kijelző használathoz szükséges programkönyvtár telepítése
  • Több kijelző használata egyszerre (kijelző címének változtatása)
  • Karaktertábla használata, saját karakterek (magyar ékezetesítés megvalósítása)
  • Példaprogram a használat megértéséhez
  • Kijelző belső parancsainak megismerése, saját függvények a kijelző magyar ékezetes karakterek használatához
  • Saját programkönyvtár, amivel régebbi programok változtatás nélkül kezelhetnek magyar ékezetes karaktereket.

———————————————————————————————-

(Kedves olvasó! Ezt a leírást 2021 október elején kénytelen voltam javítani. Évekig nem vettem észre, hogy a leírásban LCD kijelző kezelő könyvtár telepítési leírása rossz! A hiba úgy derült ki, hogy kénytelen voltam feltelepíteni újra az Arduino fejlesztő környezetet a gépemre, és amikor a telepítésben az I2C busszal működő LCD kijelzőhöz értem (a saját leírásomat használtam), semmi nem működött. Ekkor vettem észre, hogy ebben a leírásban nem az I2C buszon működő könyvtárat telepítettem, hanem egy olyan programkönyvtárat, ami az LCD kijelzőt illesztő nélkül kezeli, sok-sok kimenetet elhasználva az Arduino kivezetések közül. Tehát ha régebben olvastad ezt a leírást, akkor valószínűleg nem működött a kijelző. A hibát az okozta, hogy sok programkönyvtárat kipróbálok a munkám során, és valami összekeveredett! Immár jó a könyvtár telepítési leírás, és az összes LCD kijelzőt használó programom működik, ha az itt megadott telepítési leírást követed! Bocsi!)

A kereskedelemben igen sokféle LCD kijelző kapható. Egy kezdőnek a legegyszerűbb és legolcsóbb a 2 soros soronként 16 karakteres kijelző használata. A kijelző ára 800Ft környékén mozog, így nem nagy beruházás. Gyengén látóknak (mint jómagam) szükség lehet nagyobb karakterméretű de teljesen azonos működésű típusra. A Chipcad-nél találtam 3000Ft környékén ilyen kijelzőt, ennek mérete óriási, messziről olvasható!
A kijelzőnek igen sok kivezetése van, így az Arduino Uno R3 panelen alig marad valami, ha így akarjuk bekötni. Szerencsére kapható hozzá egy kiegészítő áramkör kb. 500Ft értékben, ami I2C buszra illeszti a kijelzőt, így összesen csak két kivezetésre lesz szükségünk. Az I2C busz már “gyárilag beépítésre került az Az Arduino Uno R3-ba (illetve az ATmega chipekbe), így azt szinte azonnal használatba vehetjük. Az Arduino UNO esetén a programmemória és a belső RAM mérete nem szab határokat, kedvünkre válogathatunk a rengeteg előre elkészített programkönyvtár közül. Azonban a kicsi alaplapok pl. az ATtiny85, melynek csak 6 felhasználható kivezetése van, és 6 kbyte programmemóriája, valamint 512 byte ram-ja, már nem ennyire egyszerű a használat. Erről a leírás végén olvashatsz, ha esetleg ilyen alaplappal is találkozni fogsz! De egyenlőre maradjunk az Arduino UNO használata mellett!

Így nézhet ki az Arduino és az I2C illesztővel egybeépített LCD kijelző hardveres összekötése:

Az LCD-re az I2C illesztő egységet simán csak rá kell dugni a gyárilag előre kialakított furatokra, és beforrasztani. Ehhez persze kell egy kis forrasztási tapasztalat, de nem nagy ördöngösség. Valahogy így néz ki a két panel összeforrasztva a szemben álló lukakat és tüskéket:

Ha nem szeretnél forrasztani, akkor olyan LCD kijelzőt keress, amit már eleve összeépítetek és beforrasztották az I2C illesztő áramkört az LCD kijelző modulra.
Az interneten számtalan program könyvtár található annak érdekében, hogy ne kelljen sokat programoznunk, és kényelmesen használhassuk a kijelzőnket. Én ezt a könyvtár telepítést még 2016-ban csináltam, és akkor I2C busszal működő programcsomagot nem találtam az Arduino könyvtár kezelő funkciójában. Nyugodtan keress magadnak ott egy megfelelő programcsomagot. Megmutatom, hogy hol keress, de én nem innen telepítettem magamnak programokat. Indítsd el az Arduino IDE programot és válaszd a könyvtár kezelés menüpontot:

Amit fentebb látsz a képen az éppen nem jó, mert nem I2C buszos illesztőn keresztül vezérli az LCD kijelzőt. Mivel én a kezdetek kezdetén nem találtam megfelelő programot a fentebb mutatott könyvtár kezelőben, a neten kerestem programcsomagot a Github-on. Találtam is. Ezt a program csomagot viszont másként kellett telepíteni. Itt találtam meg: https://github.com/fmalpartida/New-LiquidCrystal. Erről az oldalról egy ZIP tömörített állomány lehet letölteni. Jelenleg is letöltheted a programcsomagokat ilyen formában, csak kényelmesebb a könyvtárkezelő menüpontból. Ha netán egyszer majd arra kényszerülsz, hogy ZIP állományból kell telepítened programcsomagot, akkor a következőt kell tenned! Keresd meg az Arduino IDE-ben a következő menüpontot:

Tallózd ki a gépedről a letöltött ZIP állományt (valószínűleg a „Letöltések” mappában találod), és várj néhány másodpercet! Ezzel készen is vagy. Az Arduino IDE csinált a „Felhasználók\Dokumentumok\Arduino\libraries” mappában a programcsomagnak egy alkönyvtárat, ide kibontotta a ZIP állomány tartalmát, és készen is vagyunk. Ez a programcsomag tartalmaz példa programokat, amiket megtalálsz az Arduino IDE Fájl menüjében a példák között (program újraindítás után). Én a NewLiquidCrystal.zip nevű állományt találtam meg akkor, és azóta is jól működik, minden programom ezt használja, amit a weblapon találsz (illetve már nem, mert csináltam egy saját könyvtárat 2022-ben, erről az írás végén olvashatsz).

Sajnos a programkönyvtárhoz kapott mintapéldák nem túl jók, de az általam készített bemutató programot jól használhatod a megismeréshez. Szinte minden funkciót beleraktam, de a gyakorlatban ezeknek csak a töredékét szoktam használni! Néhány alapvető infót megadnék, ami jól jön a teljesen kezdőknek!

A programban meg kell adnunk az I2C buszon az eszköz címét. Ez alapértelmezetten 0x27 a 16×2 méretű kijelzőnél. Vettem magamnak 20×4 méretű kijelzőt is, annak gyárilag a 0x3F cím lett beállítva, bár amikor vettem egy másodikat is, annak ismét a 0x27-et állította be a készítő. Ha azonban több kijelzőt is szeretnénk egyszerre csatlakoztatni, szükséges az egyes kijelzők (illetve a kijelzőket illesztő I2C panel) címeinek megkülönböztetésére. Ehhez az illesztő áramkör címeit három db rövidzár beforrasztásával, vagy éppen a forrasztás leszedésével tudjuk változtatni. A képen láthatók a panelen kialakított rövidzárak, mellette pedig a táblázat, hogy mely címekhez mely rövidzárat kell használni a háromból:

Ha esetleg nem boldogulsz az eszköz címével, használd az I2C portscenner programot, ami megmondja. Itt találod a leírását!

A kijelzőkbe bele építették a karakter generátort, ami azt jelenti, hogy nekünk csak a karakter ASCII kódjait kell küldenünk a kijelzőnek, és a kijelző már tudja, melyik karakter hogy néz ki, és melyik pontot kell aktívvá tenni a karakter pontmátrixban ahhoz, hogy szemünk el tudja olvasni a megjelenő betűt vagy számot. A megjelenő karakterek ASCII kódjaihoz itt egy jól használható táblázat:

Ha az adott karakter ASCII kódjára vagy kíváncsi, az oszlop legfelső karakterének kódja az oszlop feletti szám, ettől kezdve számolj lefelé a neked szükséges karakterig soronként! Pl. a nagy „A” betű kódja 65.
Lehetőség van magyar ékezetes betűk használatára is. Ez azonban meglehetősen körülményes, mert a kijelző memóriájába kell feltölteni a karakter képeket, és egy külön utasítással lehet megjeleníteni a megfelelő karakter pozícióban. Így egy ékezetes magyar szó, csak több részletben írható ki. Természetesen ez nem katasztrofális hátrány, a teljesen kész és átgondolt szöveget érdemes utolsó lépésként ékezetesíteni. A magyar ékezetes karakterek képét a 0-7 kódokra lehet feltölteni, ami azt jelenti, hogy ha pl. a 2-es kódra töltöd fel az „á” betű képét, akkor az „A” betű a 65-ös ASCII kódra jelenik meg, míg az „á” betű a 2-es ASCII kódra (amit küldesz a kijelzőnek)! Ez így nagyon macerás, de működik.
Íme egy felkommentezett mintaprogram. Az utasítások a kommentekkel együtt remélhetőleg érthetők lesznek. Két előre megírt program gyűjteményt kell a fordítónak megadnunk, amit majd a fordító programunkhoz fog adni. Ezek a függvény könyvtárak az #include <> utasítással kerülnek beillesztésre.

#include <Wire.h>                              //I2C library
#include <LiquidCrystal_I2C.h>        //I2C LCD kezelő könyvtár
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  //LCD paraméterek megadása
void setup() 
{
  lcd.begin(16,2);                    //LCD inicializálása
  lcd.backlight();                    //háttérvilágítás bekapcsolása
  lcd.setCursor(0,0);                 //LCD kurzor a 1. sor 1. karakter pozícióba
  lcd.print("Hello!");                //Hello felirat a kurzortól kezdve
  lcd.setCursor(5,1);                 //LCD kurzor a 2. sor 6. karakter pozicióba
  lcd.print("Szia!");                 //Szia felirat a kurzortól kezdve
  delay(3000);                        //3 másodperc várakozás
  lcd.clear();                        //kijelző törlése
   for (byte i=0; i<16; i++) {
    lcd.setCursor(i,1);               //LCD kurzor a 2. sor i. pozicióba
    lcd.print("#");                   //a kurzor pozicióba egy "#"-jel beírása
    delay(100);
  }
  for (byte i=0; i<16; i++) {
    lcd.setCursor(i,1);               //LCD kurzor a 2. sor i. pozicióba
    lcd.print(" ");                   //a kurzor pozició karakterének letörlése egy szóközzel.
    delay(100);
  }
  //egyéb kijelző kezelő függvények bemutatása 
  //háttérvilágítás ki és bekapcsolása
  lcd.clear();lcd.println("Hatter vilagitas");lcd.setCursor(0,1);lcd.print("kikapcsolas");
  delay(3000);lcd.noBacklight();delay(3000);lcd.backlight();delay(3000);
  //Cursor pozició villogtatása sötét hátérrel.
  lcd.clear();lcd.print("Blink funkcio"); lcd.blink(); delay(3000);lcd.noBlink();
  //Bekapcsoljuk a blink funkciót és a szöveg kiírását követően balra mozdítjuk a kurzort
  lcd.clear();lcd.blink();lcd.print("Cursor balra");delay(3000);lcd.moveCursorLeft();delay(3000);lcd.noBlink();
  //Bekapcsoljuk a blink funkciót és a szöveg kiírását követően jobbra mozdítjuk a kurzort
  lcd.clear();lcd.blink();lcd.print("Cursor jobbra");delay(3000);lcd.moveCursorRight();delay(3000);lcd.noBlink();
  //szöveg kiírást követően bekapcsoljuk a kurzort, majd kis idő mulva kikapcsoljuk
  lcd.clear();lcd.print("Cursor on");delay(3000);lcd.cursor();delay(3000);
  lcd.clear();lcd.print("Cursor off");delay(3000);lcd.noCursor();delay(3000);
  //kikapcsoljuk a display-t. A vikágítás marad, de szövegek nem láthatóak, azonban a tartalom nem törlődik, bekapcsoláskor ujra láthatóvá válik
  lcd.clear();lcd.print("Disp. ki es be!");lcd.setCursor(0,1);lcd.print("Vilagitas marad!");delay(3000);
  lcd.noDisplay();delay(3000);lcd.display();delay(3000);
  //kikapcsoljuk a display-t. Teljes kikapcsiolás, a világítás is lekapcsolódik. A tartalmat azonban nem felejti el, visszakapcsoláskor ujra láthatóvá válik az előzőleg kiírt szöveg
  lcd.clear();delay(3000);lcd.print("Disp. teljes ki");lcd.setCursor(0,1);lcd.print("es bakapcsolasa");delay(3000);
  lcd.off();delay(3000);lcd.on();delay(3000);
  //A kiírt szöveget balra mozdítjuk egy pozicióval
  lcd.clear();lcd.print(" Scroll balra");delay(3000);lcd.scrollDisplayLeft();delay(3000);
  //A kiírt szöveget jobbra mozditjuk egy pozicióval
  lcd.clear();lcd.print("Scroll jobbra");delay(3000);lcd.scrollDisplayRight();delay(3000);
  //kiírjuk az "Autoscroll:" szöveget és bekapcsoljuk az autoscroll funkciót. Kiírunk 10 számot,
  //és az eredetileg kiírt szöveg balra tolódik egy egy pozicióval a számok kiírásakor
  lcd.clear();  lcd.print("Autoscroll:");delay(3000);
  lcd.autoscroll();for (int i=0; i<10; i++) {lcd.print(i);delay(200);}lcd.noAutoscroll();
  //bekapcsoljuk a jobbra írás funkciót, és a write függvénnyel kiírunk egy szöveget
  lcd.clear();lcd.leftToRight(); lcd.write("Jobbra irt szov.");delay(3000);
  //balra írás mód
  lcd.clear();lcd.setCursor(15,0);lcd.rightToLeft();lcd.write("Balra irt szoveg");delay(3000);lcd.leftToRight();
  //Magyar éklezetes kisbetűk használata
  //ékezetes betűk karakterképének definiciója tömbökben. Csak nyolc saját karakterünk lehet, ebből 7-et kell felhasználnunk
  byte aI_t[8] = {B10,B100,B1110,B1,B1111,B10001,B1111};             //á betű karakterképe
  byte eI_t[8] = {B10,B100,B1110,B10001,B11111,B10000,B1110};   //é betű karakterképe
  byte iI_t[8] = {B10,B100,B0,B1110,B100,B100,B1110};                     //í betű karekterképe
  byte oI_t[8] = {B10,B100,B0,B1110,B10001,B10001,B1110};            //ó betű karakterképe
  byte uI_t[8] = {B10,B100,B10001,B10001,B10001,B10011,B1101}; //ú betű karakterképe
  byte oII_t[8] = {B00101,B01010,B0,B1110,B10001,B10001,B1110};    //ő betű karakterképe
  byte uII_t[8] = {B00101,B01010,B0,B10001,B10001,B10011,B1101};  //ű betű karakterképe
  //Az ö 239, az ü 245 ascii kódon megtalálható akarakterek között, azt nem kell definiálni
  //saját kerakterek betöltése a kijelző memóriájába
  lcd.createChar(0, aI_t);lcd.createChar(1, eI_t);lcd.createChar(2, iI_t);lcd.createChar(3, oI_t);
  lcd.createChar(4, uI_t);lcd.createChar(5, oII_t);lcd.createChar(6, uII_t);
  //Csak azért, hogy könnyebb legyen az ékezetes karaktereket kiírni, beszédesebb nevű változókba
  //töltöm azt a kódot, amit a kijelzőnek kell küldeni a karakter megjelenítéséhez
  byte aI=0;byte eI=1;byte iI=2;byte oI=3;byte uI=4;byte ooo=239;byte uoo=245;byte oII=5;byte uII=6;
  lcd.clear();
  //ékezetes karakterek kiírása a kijelzőre
  lcd.write(eI);lcd.print("kezetes bet");lcd.write(uII);lcd.print("k:");
  lcd.setCursor(7,1);
  lcd.write(aI);lcd.write(eI);lcd.write(iI);lcd.write(oI);lcd.write(uI);lcd.write(239);
  lcd.write(245);lcd.write(oII);lcd.write(uII);
}
void loop()
{
}

Ha esetleg valakinek egy 4 sor 20 karakteres kijelzőt sikerülne beszerezni, annak gondolnia kell arra, hogy a kijelző I2C címe esetleg 3F, így a programban ezt kell írni a harmadik sorba:
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
Egyébként a 4 soros kijelző minden gond és változtatás nélkül működik, csak több infó fér el rajta. Kicsit drágább is, ha jól emlékszem 1500Ft körül lehet beszerezni (I2C illesztő nélkül).


Két kijelző használata:

Nincs pillanatnyilag két kijelzőm, ezért az alábbi programot nem próbáltam ki. Azonban más moduloknál mindig jól működött.
Szerintem első lépésben külön címet kell beforrasztani a kijelzőkön, és a programban a következőket kell írni:

#include <Wire.h>                              //I2C library
#include <LiquidCrystal_I2C.h>        //I2C LCD kezelő könyvtár
LiquidCrystal_I2C lcd1(0x26, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  //egyik LCD paramétereinek megadása
LiquidCrystal_I2C lcd2(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  //másik LCD paramétereinek megadása

Itt talán látható, hogy más-más objektum nevet adtam a két kijelzőnek. Így már külön-külön lehet rájuk hivatkozni a programban. pl.:

 lcd1.begin(16,2);                  //LCD1 inicializálása
 lcd1.backlight();                  //háttérvilágítás bekapcsolása
 lcd1.setCursor(0,0);               //LCD1 kurzor a 1. sor 1. karakter pozícióba
 lcd1.print("Hello!");              //Hello felirat a kurzortól kezdve

 lcd2.begin(16,2);                   //LCD2 inicializálása
 lcd2.backlight();                   //háttérvilágítás bekapcsolása
 lcd2.setCursor(0,0);                //LCD2 kurzor a 1. sor 1. karakter pozícióba
 lcd2.print("Hello!");               //Hello felirat a kurzortól kezdve

Ékezetes betűk írása nagyon nehézkes a programban, ezért csináltam egy általános függvényt, ami ezt kényelmesebbé teszi. Esetleg nézd meg ezt is! Illetve ennek az írásnak a végén találsz egy letölthető saját programkönyvtárat, ami a program lényegi változtatása nélkül kezeli a magyar ékezetes karaktereket a kiírásra kerülő szövegekben. Mindössze a program elején az #include sort kell lecserélni, valamint az ezt követő lcd objektum létrehozásán kell minimálisan változtatni.

Sőt! Kis idő elteltével további változó típusokra is csináltam univerzális megjelenítő függvényt. Így kényelmesen lehet dátumokat, időpontokat és számadatokat írni a kijelzőre. Kezdőknek talán azért is érdemes megnézni ezt a programot, mert néhány érdekesség kiderül a változók kezeléséről, valamint arról, hogyan lehet C++-ban ugyanazt a függvényt több példányban is elkészíteni, egy programon belül, és mi ennek az értelme!

ATtiny85 modul és az LCD karakteres kijelző

Mielőtt ezt a leírás részt elolvasod, érdemes megismerkedni az ATtiny85 modullal, mert ahhoz, hogy programot tudj rátölteni, még le kell töltened egy alaplap kezelő könyvtárat is, mert az Arduino IDE alap helyzetben nem tudja kezelni. Valamint kell egy drivert is telepíteni a Windows 10-hez. Ennek részletes leírása itt!

Az a gond az ATtiny85 chip-el, és persze a belőle készített modullal, hogy nagyon kicsi a program memória (8 illetve 6 kbyte), és nagyon kicsi a ram (512 byte). A szokásos könyvtárak azért nem tudnak működni, mert nem férnek el a memóriában. Amikor erről olvasgattam, úgy tűnt, más apróbb különbségek is vannak a hardverben, ami miatt módosítani kell a kezelő programokon, de ezekkel nem foglalkoztam részletesen. Az biztos, hogy új, sokkal kisebb méretű kezelő programok kellenek, feltehetőleg szerényebb funkcionalitással, vagy sokkal optimálisabban megírt forráskóddal. Egy I2C LCD kijelzőhöz két dolog kell, a Wire könyvtár ami az I2C buszt kezeli, és egy LCD kijelző programkönyvtár. Mindkettő létezik ATtiny85-re, és az a jó hírem, hogy ha telepítetted at ATtiny85 alaplapkezelő könyvtárat, akkor már a gépeden is van mindkettő, csak használni kell.

Íme a működésre bírható példa program:

#include <TinyWireM.h>                  // I2C könyvtár ATtiny85-höz
#include <LiquidCrystal_I2C.h>          // LCD könyvtár ATtiny85-höz
LiquidCrystal_I2C lcd(0x3F,20,4);       // kijelző I2C címének és méretének beállítása


void setup(){
  TinyWireM.begin();                    // I2C inicializálása
  lcd.init();                           // LCD inicializálása
  lcd.backlight();                      // Háttérvilágítás bekapcsolása
  lcd.setCursor(0,0);                   // kezdő pozíció (0. karakter a 0. sorban)
  lcd.print("Hello, Tiny85!");
  lcd.setCursor(0,1);
  lcd.print("Hello, LCD!");            // kezdő pozíció (0. karakter a 1. sorban)
}


void loop(){
}

Természetesen nekem nem sikerült lefordítani a programot. Kaptam egy hibaüzenetet:

Ebből a hibahalmazból nem volt nehéz kideríteni, hogy mi a baj! Fejlesztés közben elég sok programkönyvtárat kipróbálok, telepítgetem amit találok, de eddig soha nem szedtem le azt, amit már nem használok. Sajnos itt most ugyanazt a nevet két programkönyvtárban is megtalálta a fordító, és feltehetőleg azt kezdte el használni, amit először megtalált. Látható, hogy a TinyWireM is kétszer van meg (ezt nem is tudtam, hogy már régebben letöltöttem), de ezek valószínűleg azonosak, így nincs belőle baj. Viszont a „LiquidCrystal_I2C.h” benne van a NewliquidCristal nevű programkönyvtárban (erre emlékszem, szoktam használni), és a most letöltött alaplapkezelővel is kaptam egy ugyanilyen nevűt. Nincs mit tenni, a NewliquidCristal könyvtárat legalább erre az egy alkalomra meg kell szüntetni! Ez nagyon egyszerű, ehhez csak azt kell tudni, hogy az Arduino IDE a könyvtár kezelőben letöltött könyvtárakat alapértelmezetten (Windows10-ben) az adott felhasználó, dokumentumok könyvtára alatt létrehozott „Dokumentumok\Arduino\Libraries” alkönyvtárba helyezi telepítéskor. Elég innen elmásolni valahová a teljes alkönyvtárat! Én csináltam egy „Dokumentumok\Arduino\nem_használt_Libraries” alkönyvtárat, és oda másoltam. Előfordulhat, hogy régebbi programjaimban használtam valamikor és még szükség lesz rá. Bár valószínűleg azokat a programokat egyszerűbb lesz átalakítani ehhez a könyvtárhoz. Még az is lehet, hogy semmit nem kell csinálni, ezzel is működni fognak, de ezt előre nem lehet tudni biztosan.

Mindenesetre így már lefordul a program, és működik is! Feltöltéskor először ki kell húzni az USB-ből az alaplapot, és csak akkor bedugni, amikor szól a az Arduino IDE. Erről részletesebben itt olvashatsz!


Részletes belső működés, magyar ékezetes karakterek, saját (letölthető) programkönyvtár

Nemrégiben vásároltam egy CHH552 alaplapot. Rendkívül olcsó (6-800Ft/db), azonban nagy hibája, hogy csak C nyelv áll rendelkezésre a használatához. Bár az Arduino IDE környezetet lehet használni, de a C++ összes lényeges előnyét, az objektum orientált tulajdonságokat elveszítjük. Ennek legfontosabb következménye, hogy nem használhatjuk a jól megszokott könyvtárakat. Úgy gondolom, hogy egy alaplap használatához hozzá tartozik néhány alap funkció, ezek egyike, hogy lehessen vele karakteres LCD kijelzőre írni. Mivel a C++ -ban megírt könyvtárak elvesztek, nagy fába vágtam a fejszémet, és elkezdtem kialakítani egy saját függvény gyűjteményt. Természetesen nem lehetett megkerülni, hogy megismerjem a kijelző belső működését. Most következik az ismeretanyag, amire az átírás során szert tettem. Bónusz, hogy a saját függvényeket felvérteztem a magyar ékezetes karakterek használatával, és ehhez semmilyen átalakítást nem kell elvégezni a meglévő programokon. Pontosabban megmaradnak a megszokott függvények, még az objektum példányok metódusainak nevét sem kell átírni, mindössze az #include sort kell kicserélni.

Kezdjük az LCD kijelzők meghajtó chipjeinek tulajdonságaival! Több típus is létezik, pl. a 2×16 karakteres kijelzőkben általában a HITACHI HD44780 chipek valamelyik változatát használják, a négy soros kijelzőkbe pedig az ST7066 vagy SPLC780D chip-ek kerülnek. Bár ezeket más gyártók készítik, az utasítás készletük ugyanaz. Ez pedig a mi nagy szerencse!

A meghajtó chipek a lényegi működése, utasítás készlete, memória kezelése, kommunikációja ugyanaz. Tartalmaznak egy párhuzamos portot (8 kivezetés) amin 8 bites parancsokat illetve 8 bites ASCII kódokat lehet beküldeni a chip regisztereibe illetve memóriájába. Lehetőség van azonban arra is, hogy az adatokat két lépésben 4 bit szélességben küldjük be. Mindjárt ki fog derülni, hogy ez nagyon fontos tulajdonság. A párhuzamos adatporton kívül még van három kivezetés, ami számunkra fontos szerepet játszik:

  • EN – Ennek a bemenetnek a lefutó éle végzi a párhuzamos portra beállított adat beolvasását.
  • RS – eldönti, hogy az adat amit beküldünk, a kijelzőn megjelenő adat, vagy a parancs regiszterbe kell kerülön, ami megváltoztatja a kijelzés valamilyen tulajdonságát
  • RW – Eldönti, hogy írunk a chip-be a párhuzamos porton keresztül, vagy adatot olvasunk ki. Ennek a bemenetnek köszönhetően a beírt tartalmat vissza is olvashatjuk, illetve lekérdezhetjük a chip végzett-e egy előző parancs végrehajtásával. Átlagos felhasználás esetén nincs szükség az olvasásra.

 A chip további kivezetései az LCD kijelző egyes pixeleinek vezérlését végzik mátrix elrendezésben, illetve egyéb számunkra jelenleg lényegtelen feladatot látnak el.

Mindegyik chip rendelkezik egy kijelző memóriával aminak DDRAM a neve. Ebbe a RAM-ba kell beleírnunk a karakterek ASCII kódját. A karakter megjelenítését a chip egy beépített karakter generátor segítségével végzi, azaz minden egyes lehetséges karakter képe benne van a chip-ben. A chipeknek több változata is van, és ezek általában a karaktergenerátorok tartalmában különböznek.

A chipekben még egy másik RAM is található, amibe saját karakterek képét tölthetjük le. Ennek neve CGRAM. Sajnos ez nem nagy memória, összesen 8 db karakter 5×8-as képét tárolhatjuk. Így sajnos a magyar kerektereknek vagy a kicsi, vagy a nagy betűs verziójára van hely. Én a kisbetűket választottam. Így egy nagybetűvel kezdődő szót is csak kisebetűvel tud a chip megjeleníteni (pl. Álom -> álom). Programom azonban fogadja a nagybetűket is, csak automatikusan a kisbetű képe jelenik meg.

Az LCD meghajtó chipeknek nagy szerencsénkre azonos parancskészlete van. Úgy tudom, ezt a HITACHI HD44780-hoz készítették és a többi gyártó ezt vette át. A parancskészlet igen kevés parancsot tartalmaz. Balról jobbra olvasva a 8 bites parancs bitjeit, a legelső 1-es bit pozíciója dönti el mit is kell a chip belső logikájának végrehajtania. Ezt a „parancs bitet” követhetik paraméterek, ha vannak. Az alábbi táblázat összefoglalja a lehetséges parancsokat:

A fenti táblázatban használt betű kódok és lehetséges értékeik:

I/D=1 – DDRAM cím növelés (jobbra írás)
I/D=0 – DDRAM cím csökkentés (balra írás)
S=1 – Teljes kijelző shift (minden karakter jobbra vagy balra lép egyet egy karakter írása után)
S/C=1 – Kijelző tartalom mozgatás (shift)
S/C=0 – Cursor mozgatás
R/L=1 – Cursor vagy kijelző tartalom eltolás jobbra
R/L=0 –  Cursor vagy kijelző tartalom eltolás balra
DL=1 –  8 bites parancsmód
DL=0 – 4 bites parancsmód (két egymást követő írás kell egy parancs vagy adat beviteléhez)
N=1 –  2 soros kijelző
N=0 – 1 soros kijelző
F=1 – 5 × 10 pontos karakterkép
F=0 – 5 × 8 pontos karakterkép
BF=1 – chip foglalt
BF=0 – chip elérhető

A parancsok működésének megértése után már nem is tűnik nagyon bonyolultnak a kijelző vezérlése. Amit még tudni kell, hogy a kijelző meghajtásához egy I2C párhuzamos port konvertáló chip-et építenek be abba a modulba, amit a kijelzőre kell forrasztani. Ez a meghajtó chip csak 8 bites kimenettel rendelkezik, ezért azonnal látható, hogy szükség van a 4 bites írási módra. A 8 bitből ezért 4-et használunk fel az adatok továbbítására, hármat elhasználunk az EN,RS,RW bemenetek meghajtására, és a megmaradó 1 kivezetés a háttérvilágítást fogja ki és bekapcsolni. Találtam rajzot is, amiből visszafejthető a működés:

Mint sejthető, az adatokat és a vezérlő bemeneteket külön I2C írási ciklusokkal lehet kijuttatni. Ennek oka, hogy az LCD meghajtó chip igényli, hogy a párhuzamos adat bemeneteken előbb beállítsuk  be minden biten az adatot, várjunk egy kicsit, és csak ezt követően állítsunk elő egy lefutó élet (1-0 átmenet) az EN bemeneten. Ez a lefutó él jelzi a chip-nek, hogy beolvashatja a bemenetek állapotát a  belső regiszterbe vagy a DDRAM-ba. Ehhez található egy jó kis ábra az adatlapon:

Ráadásul ebből a beolvasásból két azonos folyamat kell, mert 4 bites módot használunk, tehát egy adat vagy parancs két külön ciklusban jut be a chip-be. Az általam alapul használt könyvtár még ennél is ügyetlenebb, mert először egy I2C írással kiküldi az adatot, ez után egy újabb írással magasra viszi az EN bemenetet (I2C konverter IC P2 kimenete), majd egy újabb I2C írással előállítja az EN bemeneten a 1-0 lefutó élet. Tehát egy 4 bites adat kiírása három I2C portra írás, amit a 4 bites mód miatt meg is kell ismételni, azaz összesen hat! Ezen változtattam, és az adattal együtt az En kivezetést is magasra állítom, így már csak az alacsony szint előállításához kell egy I2C írás. Így lett nálam egy karakter vagy parancs kiküldése négy I2C írás. Még így is látható, hogy a 16 karakteres kijelző összes karakterének végig írása 16×4=64 adat továbbítás az I2C buszon. Minden egyes adattovábbítás a kijelzőhöz tartozó I2C párhuzamos konverter chip címének kiküldésével kezdődik, mire az visszaválaszol, jön az értékes adat továbbítás, majd a kommunikáció lezárása. Alapból egy I2C port órajele 100Khz az Arduino-ban (magasabbra is állítható), ami éppen hogy csak elegendő ahhoz, hogy szemmel ne legyen észrevehető a tekintélyes időigény. Ha az I2C sebességét valamilyen okból le kell csökkentenünk, már látni fogja a szemünk a sor végig írásának folyamatát a kijelzőn! Pont úgy, mint a „Nyolcdik utas a halál” című filmben a computer betűnként írta üzenetét a parancsnoknak. Közben még kattogó hongot is hallatott, mintha írógépen nyomtatná a szöveget. Szóval ilyet mi is tudunk!!

A fenti folyamat hibátlan végrehajtásához a programban statikus memóriában kell tartani ez egyes bitek állapotát, és egy-egy parancs kiadásakor vagy kapcsolattal kell összemásolni a biteket, hogy valaminek a megváltoztatása ne befolyásoljon valami mást. Pl. ha csak a cursort akarjuk bekapcsolni, de nem szeretnénk a kijelzőt kikapcsolni, akkor Display Control parancs kiküldésekor a D bitnek 1-ben kell maradnia, míg a C bitet 0 helyett 1-re írjuk át. Mivel a kijelző háttérvilágítás kapcsolgatása az LCD meghajtó chip kommunikációjától teljesen függetlenül történik, arra is figyelni kell, hogy a 4 bites módban kiküldött parancs vagy adat küldözgetése közben soha ne változzon a P3 kimenet állapota. Ezért a háttérvilágítás ennek megfelelő bitjét minden egyes íráskor be kell állítani „vagy” kapcsolattal az I2C buszra küldött byte-hoz.

Nem ékeskedek más tollaival, az összes parancsot kimásoltam az egyik LiquidCrastal_I2C könyvtár .cpp állományából. Sok mindent átalakítottam, egyszerűsítettem, butítottam, de lényegében minden maradt az eredeti. Mindössze a végcél figyelembevételével megszüntettem a C++ osztály definiciókat, és a metódusokat sima C függvényekké alakítottam. Az egyetlen dolog amit hozzátettem, hogy a begin() függvénybe beleírtam, hogy töltse be a CGRAM-ba a magyar ékezeteket. A konstruktor megszűnt, a statikus változók a program elejére kerültek. A szövegek kiírásakor változás, hogy amikor adatot írok a DDRAM-ba, beazonosítom a magyar ékezetes karaktereket, és a kijelzőnek a megfelelő ASCII kódot küldöm. Ehhez azt kell még tudni, hogy az ASCII kódtábal első 8 karakterkódján semmi olyan karakterkód nincs, amit a kijelző megjeleníthetne, azért ide tették a CGRAM terület kiválasztását. Tehát amikor magyar ékezetes karaktert küldünk a kijelzőnek, akkor 0-6 közötti ASCII kódot kell kiküldeni (csak hét tárolót használok a nyolcból). Fontos infó még, hogy az Arduino IDE-ben írt stringekben a magyar ékezetes karakterek két byte-on kerülnek be UTF-8 kódolással. Tehát amikor felismerem a decimális 195 vagy 197 kódot, akkor azt nem szabad kiküldeni a kijelzőnek, helyette a következő karakterkódot kell beazonosítani és a megfelelő kódot küldeni a 0-6 tartományból. Lehet, hogy kicsit így leírva zavaros a dolog, de a forrásból könnyebb kiolvasni, mint ebből a leírásból. Vagy egyszerűen csak kényelmesen használni kell és kész!

Íme a függvényekkel C stílusban megvalósított forráskód:

#include <Wire.h>                              //I2C library
//Ezek a változók kellenek az LCD kezelő függvények működéséhez
byte _lcd_cim;         //LCD ijelző címe az I2C buszon
byte _lcd_sor;         //LCD kijelző sorainak száma (csak 2 és négy soros kijelzőn tudtam kipróbálni a működést)
byte _lcd_oszlop;      //LCD kijelző oszlopainak száma (csak 16 és 20 karakteres kijelzőt tudtam kipróbálni)
byte _displaycontrol;  //Az LCD kijelző ki és bekapcsolását, blink és cursor funkciót vezérlő bitek utolsó beállított állapotának tárolása.
                       //Több függvény is használja illetve kiküldi az utolsó állapotot, ezért kell, hogy statikus változó legyen
byte _displaymode;     //Az LCD kijelző működési módját (jobbra balra írás, vagy képernyő görgetés) meghatározó bitek utolsó beállított állapotának tárolása.
                       //Több függvény is használja illetve kiküldi az utolsó állapotot, ezért kell, hogy statikus változó legyen
byte _backlightval;    //Háttérvilágítás ki és bekapcsolt állapotának utolsó állapotát tárolja. Mivel az I2C illesztő chip P3 portjára
                       //van kötve a háttérvilágítás, bármilyen adat vagy parancs 4 bites kiküldséekor újra küldésre kerül, ezért kell
                       //statikus változónak lennie.


void setup() 
{
  Wire.begin();                       //A kijelző használatához kell az I2C
  lcd_begin(0x3f,20,4);               //kijelző inicializálása (20x4 soros)
  //lcd_begin(0x27,16,2);               //kijelző inicializálása (16x2 soros)
  lcd_backlight();                    //háttérvilágítás bekapcsolása
  lcd_setCursor(0,0);                 //LCD kurzor a 0. sor 0. karakter pozícióba
  lcd_print("Magyar ékezetes");       //felirat a kurzortól kezdve
  lcd_setCursor(0,1);                 //LCD kurzor a 0. sor 1. karakter pozicióba
  lcd_print("Bemutató program");      
  delay(2000);                        //2 másodperc várakozás

  lcd_clear();                        //kijelző törlése
  lcd_setCursor(0,0);                 
  lcd_print("Írás karkt.-ként");      
  lcd_setCursor(0,1);                 
  lcd_write(65);  //"A" betű ASCI kódja 65
  lcd_write(66);
  lcd_write(67);
  lcd_write(68);
  lcd_write(69);
  //ékezetes betűt csak lcd_print()-el lehet írni, mert az két byte-on megy ki UTF-8 kódolással a string-ben
  delay(2000);
  
  //------------------egyéb kijelző kezelő függvények bemutatása----------------------------------------
  //háttérvilágítás ki és bekapcsolása
  lcd_clear();lcd_print("Hatter vilagitás");lcd_setCursor(0,1);lcd_print("kikapcsolás");
  delay(2000);lcd_noBacklight();delay(1000);lcd_backlight();delay(1000);
  //Cursor pozició villogtatása sötét hátérrel.
  lcd_clear();lcd_print("Blink funkció"); lcd_blink(); delay(3000);lcd_noBlink();
  //Bekapcsoljuk a blink funkciót és a szöveg kiírását követően balra mozdítjuk a kurzort
  lcd_clear();lcd_blink();lcd_print("Cursor áthely.");delay(2000);lcd_setCursor(6,0);;delay(2000);lcd_noBlink();
  //szöveg kiírást követően bekapcsoljuk a kurzort, majd kis idő mulva kikapcsoljuk
  lcd_clear();lcd_print("Cursor on-off");lcd_cursor();delay(3000);
  lcd_noCursor();delay(3000); 
  //kikapcsoljuk a display-t. A vikágítás marad, de szövegek nem láthatóak, azonban a tartalom nem törlődik, bekapcsoláskor ujra láthatóvá válik
  lcd_clear();lcd_print("Disp. ki és be!");lcd_setCursor(0,1);lcd_print("Vilagítás marad!");delay(2000);
  lcd_noDisplay();delay(2000);lcd_display();delay(2000);
  //kikapcsoljuk a display-t. Teljes kikapcsiolás, a világítás is lekapcsolódik. A tartalmat azonban nem felejti el, visszakapcsoláskor ujra láthatóvá válik az előzőleg kiírt szöveg
  lcd_clear();lcd_print("Disp. teljes ki");lcd_setCursor(0,1);lcd_print("és bekapcsolása");delay(2000);
  lcd_off();delay(2000);lcd_on();delay(2000);

  //---------------Képernyő tartalom eltolása jobbra illetve balra-------------------------------------
  //A DDRAM változtatása nélkül jobbra illetve balra toljuk el a teljes képernyő tartalmat
  //A kiírt szöveg vándorol a képernyőn (lépeget a DDRAM-ban a tartalom), ami az egyik végén
  //kilép, amásik végén belép és fordítva. Négy soros kijelző esetén az első sorban kilépő
  //karakter a harmadik sorban lép be, aztán ami itt kilép a másodikon lépbe, majd a negyedikre
  //kerül át (illetve fordítva)
  lcd_clear();lcd_print("Scroll balra");delay(1000);
  for (byte i=0;i<12;i++) {lcd_scrollDisplayLeft();delay(200);} //A kiírt szöveget többször balra mozdítjuk egy-egy pozicióval
  delay(2000);
  lcd_clear();lcd_print("Scroll jobbra");delay(1000);
  for (byte i=0;i<20;i++) {lcd_scrollDisplayRight();delay(200);} //A kiírt szöveget többször jobbra mozditjuk egy-egy pozicióval
  delay(2000);
  
  //kiírjuk az "Autoscroll:" szöveget és bekapcsoljuk az autoscroll funkciót. Kiírunk 10 számot,
  //és az eredetileg kiírt szöveg balra tolódik egy egy pozicióval a számok kiírásakor. Ami a sor
  //elején vagy végén kilép, az a következő sorban belép (négysorosnál a sorrend 1-3-2-4. sor)
  //---------------Autóscroll funkció balra-------------------------------------------------------------
  lcd_setCursor(0,0);
  lcd_clear();  lcd_print("Autoscroll bal");delay(3000);
  lcd_leftToRight();   //Az írási irány balról jobbra, de ekkor az autoscroll balra mozdítja a szöveget
  lcd_autoscroll();    //Autóscroll funkció bekapcsolása. Ennek hatására nem a cursor mozdul el egy karakter
                       //kiírásakor, hanem a teljes képenyő tartalom. Ami a sor elején kilép, az a második sor végén
                       //belép. Négysoros kijelzőnél az első sorból kilépő karalter a harmadik sorban lép be 
  lcd_setCursor(10,1);
  for (byte i=0; i<10; i++) {lcd_print(i);delay(300);} 
  lcd_noAutoscroll();
  delay(3000);
  //---------------Autóscroll funkció jobbra--------------------------------------------------------------
  lcd_setCursor(0,0);
  lcd_clear();  lcd_print("Autoscroll jobb");delay(3000);
  lcd_rightToLeft();  //Az írási irány jobbról balra, de ekkor az autoscroll jobbra mozdítja a szöveget
  lcd_autoscroll();   //Autóscroll funkció bekapcsolása. Ennek hatására nem a cursor mozdul el egy karakter
                      //kiírásakor, hanem a teljes képenyő tartalom. Ami a sor végén kilép, az a második sor elején
                      //belép. Négysoros kijelzőnél az első sorból kilépő karalter a harmadik sorban lép be 
  lcd_setCursor(10,1);
  for (byte i=0; i<10; i++) {lcd_print(i);delay(300);} 
  lcd_noAutoscroll(); 
  lcd_leftToRight(); 
  delay(3000);

  //---------------Jobbra írási mód (alaphelyzetnek megfelelő)---------------------------------------------
  lcd_clear();lcd_print("Jobbra irás:");delay(3000);
  lcd_leftToRight(); 
  lcd_setCursor(0,1);
  for (byte i=0; i<10; i++) {lcd_print(i);delay(300);} 
  delay(3000);
  
  //---------------Balra írási mód karakterenkénti írással-------------------------------------------------
  lcd_clear();lcd_setCursor(0,0);lcd_print("Balra írás:");
  lcd_setCursor(0,1);lcd_print("Megl.szöv:");delay(3000);    //A sor elején található szövegre ráír
  lcd_rightToLeft();
  lcd_setCursor(10,1);
  for (byte i=0; i<10; i++) {lcd_print(i);delay(300);} 
  delay(3000);
  lcd_leftToRight();  //normál jobbraírási mód visszaállítása

  //---------------Teljes magyar ékezetkészlet bemutatása (csak kis betűk férnek el) ----------------------
  lcd_clear();lcd_setCursor(0,0);lcd_print("Ékezetes betűk:");
  lcd_setCursor(0,1);lcd_print("ÁáÉéÖöŐőÜüűŰúÚÍí");delay(4000);

  //---------------Különböző változó típusok írása---------------------------------------------------------
  lcd_clear();lcd_setCursor(0,0);lcd_print("Byte DEC HEX BIN");
  lcd_setCursor(0,1);
  lcd_print((byte) 138,DEC);lcd_print(" ");   //byte decimális értékként
  lcd_print((byte) 138,HEX);lcd_print(" ");   //byte hexadecimálisan
  lcd_print((byte) 138,BIN);lcd_print(" ");   //byte binárisan
  delay(4000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Integer:");
  lcd_setCursor(0,1);lcd_print((int) 31002);lcd_print(" ");lcd_print((int) -165);
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Unsigned Int:");
  lcd_setCursor(0,1);lcd_print((unsigned int) 59862);
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Long:");
  lcd_setCursor(0,1);lcd_print((long) 5231002);lcd_print(" ");lcd_print((int) -165);
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Unsigned long:");
  lcd_setCursor(0,1);lcd_print((unsigned long) 55449862);
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Float:");
  lcd_setCursor(0,1);lcd_print((float)54.23923,2);         //Ha a megadott tizedesek száma kisebb mint az értékes tizedesek, akkor kerekít
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Float:");
  lcd_setCursor(0,1);lcd_print((float)-123456789.23493,2); //A float Arduino C++ esetén 8 értékes számjegy a tizedesekkel együtt
                                                           //Ha az egész érték több mint 8 karakter, akkor az egésszek további számjegyei
                                                           //illetve tizedesek értékes jegyei között már csak 0 szerepel, azaz kerekít 
                                                           //8 értékes jegyre (a tizedes pont a 9. karakter)
}

void loop()
{
}


void lcd_begin(byte lcd_cim, byte lcd_oszlop, byte lcd_sor) {
  // Kijelző bekapcsolási procedura, alap konfiguráció:
  // 1. Átállítás 4-bit írási módba
  // 2. Működési funkció beállítások:
  //    DL=0; 8 bites interfész adatok
  //    N=0; 1 soros kijelző vagy N=1 két soros kijelző
  //    F=0; 5x8 pont karakterbetűtípus
  // 3. Kijelző control alaphelyzet:
  //    D = 1; Kijelző bekapcsolva
  //    C = 0; Kurzor kikapcsolva
  //    B = 0; A villogás kikapcsolva
  // 4. Kijelző törlés
  // 5. Belépési mód beállítása:
  //    I/D = 1; Írás után a DDRAM cím növekszik (jobrra ír)
  //    S = 0; Nincs képernyő eltolás
  // 6. Home funkcióba kerül a cursor (legkisebb DDRAM cím)
  // 7. Háttérvilágítás kikapcsolva
  // 8. Magyar ékezetes karakterképek letöltése a CGRAM-ba
  _lcd_cim=lcd_cim;
  _lcd_oszlop=lcd_oszlop;
  _lcd_sor=lcd_sor;
  _backlightval = B00000000;   //háttérvilágítás alapértelmezetten kikapcsolva (I2C illesztő chip P3 port=0)
  _displaycontrol = B00001100; //utasítás regiszter (IR) alaphelyzet: display be, cursor ki, blink ki
                               //jobbról 1.bit blink be-ki, 2.bit cursor be-ki, 3.bit display be-ki.
                               //Jobbról a 4.bit 1, mert ezeknek a beállító parancsa ezzel a bittel kezdődik.
  _displaymode = B00000110;    // alapértelmezett cursor mozgatási irány (jobbra) és képernyő tartalom eltolás (csökkenő).
                               //Ezeknek nincs közvetlen hatása, de a következő tartalom kiírásakor e szerint fog változni
                               //a cursor pozíció, illetve a képernyő eltolás (ha be vannak kapcsolva).
                               // irány beállítása. Jobbról 3. a parancs jelzése, 2.bit cursor mozgatási irány (jobbra 1, balra 0), ű
                               // 1. bit képernyő tartalom eltolási irány (növekvő 1, csökkenő 0)
  delay(50);  //az adatlap szerint legalább 40 ms-ra van szükségünk a parancsok küldése előtt, miután a tápfesz 2,7 V fölé emelkedik
  // 8 bites parancsmódban indul a chip, ezért első lépésben át kell kapcsolni 4 bites módba. 
  // Az átkapcsoláshoz 3x kell kiküldeni. Az átkapcsoláshoz 2x kell, de megszakadhat
  // a folyamat előzőleg, ezért 3x. Ez az adatlap ajánlása, bár elvileg kétszer is elég lenne.
  lcd_wr4bit(0x03 << 4); //"0011" feltolva a D7-D4 bitekre (Functio set parancs: B00110000, DL=1 8 bites mód) 
  delayMicroseconds(4500); // Minimális végrehajtási idő 4.1ms
  lcd_wr4bit(0x03 << 4);
  delayMicroseconds(4500); // Minimális végrehajtási idő 4.1ms
  lcd_wr4bit(0x03 << 4);
  delayMicroseconds(150);
  lcd_wr4bit(0x02 << 4); //DL=0 4 bites mód (ez állítja be a 4 bites módot, function set parancs: B00100000)
  if (_lcd_sor > 1) {lcd_command(B00101000);}   //több soros kijelző esetén alapértelmezetten  4 bites mód , 2 soros mód, 5x8 karaktermap mód
  else {lcd_command(B00100000);}             //egy soros kijelző esetén alapértelmezetten  4 bites mód , 1 soros mód, 5x8 karaktermap mód
  lcd_display();                             //default érték beállítása (kijelző on, cursor off, blink off) a _displaycontrol 
                                             //változót írja ki a chip-re parancs módban (RS kivezetés=0)
  lcd_clear();                               //Képernyő törlés (RS kivezetés=0, kiküldött byte B00000001
  lcd_command(_displaymode);   //corsor mozgatás jobbra, képernyő eltolás csökkenő beállítása
  lcd_home();                  //cursor a 0,0 pozicióba
  lcd_noBacklight();             //háttérvilágítás be
  //Hiányzó magyar ékezetes karakterek feltöltése a CGRAM-ba
  byte tt[7] = {B10,B100,B1110,B1,B1111,B10001,B1111};                                   //á betű karakterképe
  lcd_createChar(0,tt);                                                                  //betöltés a 0 című CGRAM byte-ba
  tt[0]=B10;tt[1]=B100;tt[2]=B1110;tt[3]=B10001;tt[4]=B11111;tt[5]=B10000;tt[6]=B1110;   //é betű karakterképe
  lcd_createChar(1,tt);                                                                  //betöltés a 1 című CGRAM byte-ba
  tt[0]=B10;tt[1]=B100;tt[2]=B0;tt[3]=B1110;tt[4]=B100;tt[5]=B100;tt[6]=B1110;           //í betű karakterképe
  lcd_createChar(2,tt);                                                                  //betöltés a 2 című CGRAM byte-ba
  tt[0]=B10;tt[1]=B100;tt[2]=B0;tt[3]=B1110;tt[4]=B10001;tt[5]=B10001;tt[6]=B1110;       //ó betű karakterképe
  lcd_createChar(3,tt);                                                                  //betöltés a 3 című CGRAM byte-ba
  tt[0]=B00101;tt[1]=B01010;tt[2]=B0;tt[3]=B1110;tt[4]=B10001;tt[5]=B10001;tt[6]=B1110;  //ő betű karakterképe
  lcd_createChar(4,tt);                                                                  //betöltés a 4 című CGRAM byte-ba
  tt[0]=B10;tt[1]=B100;tt[2]=B10001;tt[3]=B10001;tt[4]=B10001;tt[5]=B10011;tt[6]=B1101;  //ú betű karakterképe
  lcd_createChar(5,tt);                                                                  //betöltés a 5 című CGRAM byte-ba
  tt[0]=B00101;tt[1]=B01010;tt[2]=B0;tt[3]=B10001;tt[4]=B10001;tt[5]=B10011;tt[6]=B1101; //ű betű karakterképe
  lcd_createChar(6,tt);                                                                  //betöltés a 6 című CGRAM byte-ba
}

void lcd_clear(){
  lcd_command(B00000001);  //Kiküldjük a chip-re a tartalmat, miközben RS kivezetés = 0 
  delayMicroseconds(2000);  //Végrehajtási idő adatlap szerint 1.5msec. Cursor pozíciót is beállítja 0-ra.
}

void lcd_home(){
  lcd_command(B00000010);  //A cursor pozíciót 0-ra (0 sor, 0 oszlop) állítja. Valójában az adat 
                           //regiszter cím mutatóját állítja 0-ra.
  delayMicroseconds(2000);  //Végrehajtási idő adatlap szerint 1.5msec
}

void lcd_setCursor(byte oszlop, byte sor){     //Cursor pozíció beállítása, oszlop és sor pl. 0,1 0. oszlop az 1. sorban
  int sor_cimeltolas[] = { 0x00, 0x40, 0x14, 0x54 }; //ezek a különböző sorok első karaktereinek címeinek kiszámításához szükséges növekmények
  if (sor > _lcd_sor) {
    sor = _lcd_sor-1;    // Ha nagyobb sor számot ad meg mint a kijelző sorainak száma, akkor az utolsóba írunk
  }
  lcd_command(B10000000 | (oszlop + sor_cimeltolas[sor])); //DDRAM címzése
}

//Kikapcsoljuk a kijelző feliratainak megjelenítését (háttérvilágítás ha van, marad)
void lcd_noDisplay() {
  _displaycontrol&=B11111011;    //a _displaycontrol változó 3. bitje felel a kijelző be és kikapcsolásért, 0-val kikapcsol
  lcd_command(_displaycontrol);
}
void lcd_display() {
  _displaycontrol|=B00000100; //a _displaycontrol változó 3. bitje felel a kijelző be és kikapcsolásért, 1-el bekapcsol
  lcd_command(_displaycontrol);
}
// Kijelző teljes ki é bekapcsolása (háttérvilágítás is)
void lcd_off() {                 //Kijelző megjelenítés kikapcsolása, háttérvilágítás lekapcsolása, DDRAM tartalma marad
  _backlightval=B00000000;       //Háttérvilágítást vezérlő változóban a 4. bit felülírása 0-val
  _displaycontrol&=B11111011;    //a _displaycontrol változó 3. bitje felel a kijelző be és kikapcsolásért, 0-val kikapcsol
  lcd_command(_displaycontrol);  //Display control parancs kiküldése, ezzel együtt háttérvilágítás kapcsolása is megtörténik
}
void lcd_on() {                 //Kijelző megjelenítés bekapcsolása, háttérvilágítás lekapcsolása,  DDRAM tartalma megmaradt, utolsó kiírt tartalom megjelenik
  _backlightval=B00001000;      //Háttérvilágítást vezérlő változóban a 4. bit felülírása 1-el
  _displaycontrol|=B00000100;   //a _displaycontrol változó 3. bitje felel a kijelző be és kikapcsolásért, 1-el bekapcsol
  lcd_command(_displaycontrol); //Display control parancs kiküldése, ezzel együtt háttérvilágítás kapcsolása is megtörténik
}

// cursor (aláhúzás) ki és bekapcsolása
void lcd_noCursor() {
  _displaycontrol &= B11111101;    //A _displaycontrol változó 2. bitje felel a cursor ki és bekapcsolásáért. Cursor ki 0-val!
  lcd_command(_displaycontrol);
}
void lcd_cursor() {
  _displaycontrol |= B00000010;   //A _displaycontrol változó 2. bitje felel a cursor ki és bekapcsolásáért. Cursor be 1-el!
  lcd_command(_displaycontrol);
}

// Cursor villogás ki és bekapcsolása
void lcd_noBlink() {
  _displaycontrol &= B11111110;  //A _displaycontrol változó jobbról 1. bitje felel a cursor villogás ki és bekapcsolásáért. Villogás ki 0-val!
  lcd_command(_displaycontrol);
}
void lcd_blink() {
  _displaycontrol |= B00000001;  //A _displaycontrol változó jobbról 1. bitje felel a cursor villogás ki és bekapcsolásáért. Villogás be 1-el!
  lcd_command(_displaycontrol);
}

//Képernyő görgetése és cursor mozgatása a RAM tartalmának megváltoztatása nélkül. Minden sor egyszerre mozog, de a 
//második sorban kilépő karakterek (jobbra tolás) nem lépnek be az első sor elején. Balra toláskor az első sor elején kilépő
//karakterek nem lépnek be az utolsó sor végén.
void lcd_scrollDisplayLeft(void) {
  lcd_command(B00011000);
}
void lcd_scrollDisplayRight(void) {
  lcd_command(B00011100);
}

// A szöveg kiírása balról jobbra halad
void lcd_leftToRight(void) {
  _displaymode |= B00000010;             //a cursort jobbra lépteti egy karakter kiírását követően (DDRAM címet növeli)
  lcd_command(B00000100 | _displaymode);
}

// A szöveg kiírása jobbról balra halad
void lcd_rightToLeft(void) {
  _displaymode &= B11111101;       //a cursort balra lépteti egy karakter kiírását követően (DDRAM címet csökkenti)
  lcd_command(B00000100 | _displaymode);
}

// A szöveget a cursor-tól jobbra tolja el (görgeti). A cursor aktuális pozíciója nem változik
void lcd_autoscroll(void) {
  _displaymode |= B00000001;  //A kijelző tartalmát tolja el (shift) a kurzortól kezdve 
  lcd_command(B00000100 | _displaymode);
}

// A szöveget a cursor-tól balra tolja el (görgeti). A cursor aktuális pozíciója nem változik
void lcd_noAutoscroll(void) {
  _displaymode &= B11111110; //A cursort mozgatja, és nem a kijelzőt tartalmat (no shift)
  lcd_command(B00000100 | _displaymode);
}

//Feltölti egy karakter kép feltöltését a CGRAM-ba
//Összesen 8 x 5 byte áll rendelkezésre.
void lcd_createChar(byte tarolo, byte charmap[]) {  //tarolo a ramterület kezdőcíme, charmap a karakterképet tároló tömb
  tarolo &= B00000111;                              //Csak az alsó három bit-et vesszük figyelembe (8 tároló)
  lcd_command(B01000000 | (tarolo << 3));           //jobbról a 7. bit jelöli ki a parancsot, 6-4. bit a cím
                                                    //A CGRAM címének beállítása után az adat küldések (RS=1) a CGRAM-ba íródnak
  for (int i=0; i<8; i++) {                         //Adatok írása, amik a megcímzett CGRAM-ba kerülnek (minden írás növeli a címet)
    lcd_write(charmap[i]);
  }
}


//Háttérvilágítás ki és bekapcsolása. A háttérvilágítás led-je az I2C illesztő IC 
//P3 portjára van kötve, és nem az LCD vezérlő kapcsolgatja. Ezért ebben az esetben 
//csak a _backlightval változó jobbról 4. bitjét kell beállítani, és kiküldeni
//bárilyen adatot az lcd_i2cWrite() függvénnyel, ami egy vagy kapcsolattal
//a kiküldött adat jobbról 4. bitjét beállítja az I2C IC portján.
void lcd_noBacklight(void) {   //Háttérvilágítás kikapcsolása
  _backlightval=B00000000;     //bit beállítása a változóban
  lcd_i2cWrite(0);             //egy 0-ás adat küldése az I2C-ra, hogy frissüljön a bit (nincs B00000000 parancs, amit a chip értelmezne)
}
void lcd_backlight(void) {     //Háttérvilágítás bekapcsolása
  _backlightval=B00001000;     //bit beállítása a változóban
  lcd_i2cWrite(0);             //egy 0-ás adat küldése az I2C-ra, hogy frissüljön a bit (nincs B00000000 parancs, amit a chip értelmezne)
}
void lcd_setBacklight(byte ertek){   //más könyvtárakkal készült programok könnyebb adaptáláshoz
  if (ertek) {
    lcd_backlight();    // bekapcsolja a háttérvilágítást
  } else {
    lcd_noBacklight();  // kikapcsolja a háttérvilágítást
  }
}
bool lcd_getBacklight() {      //_backlightval változó lekérdezése
  return _backlightval == B00001000;
}


//Adat vagy parancs küldés
inline void lcd_command(byte ertek) {
  lcd_send(ertek, B00000000);         //LCD vezérlő RS kivezetése 0, akkor parancsot küldünk
}
inline size_t lcd_write(byte ertek) {
  lcd_send(ertek, B00000001);         //LCD vezérlő RS kivezetése 1, akkor adatot küldünk a DDRAM-ba vagy a CGRAM-ba
  return 1;
}


//Alacsonyszintű írási parancsok. 
//--------------------------------------------------------------------------
// Az adat vagy parancs kiírása az LCD meghajtóra 4 bites módban.
//A kiküldött parancsot alsó és felső 4 bitre kell szétválasztani, 
//és egymás után kiküldeni az LCD meghajtónak (a kiküldés több lépésből 
//áll, mert az En kivezetést közben 1-ről 0-ra kell ejteni, majd vissza 1-re
void lcd_send(byte ertek, byte mode) {
  byte highnib=ertek&0xf0;      //felső négy bit 
  byte lownib=(ertek<<4)&0xf0;  //alsó négy bit
                                   //az I"C meghajtó csip P7-P4 portjára kell ezeket írni
  lcd_wr4bit((highnib)|mode);  //felső négy bit küldése
  lcd_wr4bit((lownib)|mode);   //alsó négy bit küldése
}

void lcd_wr4bit(byte ertek) {
  lcd_i2cWrite(ertek | B00000100);   //adat beállítása az I2C meghajtó P7-P4 kivezetés, valamint az En kivezetésre 
                                     //magas jelszintet állítunk be. Ekkor még nem történik semmi.
  delayMicroseconds(1);              // Miután magasra állítottuk, adatlap szerint várni kell 450nsec-et
  lcd_i2cWrite(ertek & B11111011);   // En (P2) alacsony, ekkor olvassa be az adatot az LCD meghajtó
  delayMicroseconds(50);             // Adat beolvasás LCD meghajtóba adatlap szerint 37us, ezt megvárjuk
}                                  

void lcd_i2cWrite(byte adat){       //egy byte kiírása az I2C illesztőre. A kiírt adat a P0-P7 kivezetéseken jelenik meg
                                    //Az I2C illesztő chip P2 kivezetésére van kötve a Led világítás, Ezért annak értékét 
                                    //minden adat átadásakor kezelni kell (vagy kapcsolattal).
  Wire.beginTransmission(_lcd_cim);
  Wire.write((int)(adat) | _backlightval);
  Wire.endTransmission();
}


//különbözű numerikus változó típusok kiírásának függvényei
//------------------------------------------------------------------------------------------------
void lcd_print(String string) {  //az LCD kijelzőre küld egy karakter tömböt
  byte i=0;
  byte j=0;
  while ((byte)string[i]>8 and j<_lcd_oszlop) {   //A binárus formátum képzése után a 8-as indexű tömbelembe 8-at tesz (lecseréli az általam berakott 0-át)
                                            //E miatt nem a 0-át figyelem, nem csak 0 lehet a záró karakter.
    j++;
    if ((byte)string[i]==195 or (byte)string[i]==197) {   //Az UTF 8 kódkészletben a magyar ékezetes betűk 195 vagy 197-el kezdődnek, és ezt követi egy következő
                                              //byte-on az ékezetes karakterkép következő byte-ja. Tehát az első byte-ot ki kell hagyni, és csak
                                              //a következővel kell foglalkozni. A magyar ékezeteket a CGRAM-ból jelenítjük meg, ezért a megfelelő
                                              //0-7 tartományban lévő kódra kel cserélni a másodikbyte-on érkező kódot. 
      i++;                                    //A tömbnek ezt az elemét nem írjuk a kijelzőre, a következő tömbelemet vizsgáljuk és kiírjuk a megfelelő karaktert
      switch ((byte)string[i]) {
        case 129: lcd_write(0);break;    //Á  
        case 161: lcd_write(0);break;    //á
        case 137: lcd_write(1);break;    //É
        case 169: lcd_write(1);break;    //é
        case 141: lcd_write(2);break;    //Í
        case 173: lcd_write(2);break;    //í
        case 147: lcd_write(3);break;    //Ó
        case 179: lcd_write(3);break;    //ó
        case 150: lcd_write(239);break;  //Ö
        case 182: lcd_write(239);break;  //ö
        case 144: lcd_write(4);break;    //Ő
        case 145: lcd_write(4);break;    //ő
        case 154: lcd_write(5);break;    //Ú
        case 186: lcd_write(5);break;    //ú
        case 156: lcd_write(245);break;  //Ü
        case 188: lcd_write(245);break;  //ü
        case 176: lcd_write(6);break;    //Ű
        case 177: lcd_write(6);break;    //ű
      }
    }
    else {lcd_write((byte)string[i]);}
    i++;
  }
}

void lcd_print(byte ertek) {   //egy integer értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
  char string[_lcd_oszlop];
  sprintf(string, "%d", ertek);
  lcd_print(string);
}

void lcd_print(byte ertek,byte formatum) {   //egy integer értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
  char string[_lcd_oszlop];
  switch (formatum) {
    case 16:
      sprintf(string, "%X", ertek);break;
    case 10:
      sprintf(string, "%d", ertek);break;
    case 2:
      byte j=128;
      for(byte i=0;i<7;i++) {           
        if(ertek>=j){ertek-=j;string[i]= '1';}
        else {string[i]='0';}
        j=j/2;
      }
      string[8]=0;break;
  }
  lcd_print(string);   //a string átadásakor valamiért dec 8 kerül a 8. tömbelembe. Nem értem miért?
}

void lcd_print(int ertek) {            //egy integer értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
  char string[_lcd_oszlop];
  sprintf(string, "%d", ertek);
  lcd_print(string);
}

void lcd_print(unsigned int ertek) {   //egy unsigned integer értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
  char string[_lcd_oszlop];
  sprintf(string, "%u", ertek);
  lcd_print(string);
}

void lcd_print(long ertek) {            //egy long értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
  char string[_lcd_oszlop];
  Serial.println("long");
  sprintf(string, "%ld", ertek);
  lcd_print(string);
}

void lcd_print(unsigned long ertek) {   //egy unsigned long értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
  char string[_lcd_oszlop];
  sprintf(string, "%lu", ertek);
  lcd_print(string);
}

void lcd_print(float ertek, byte tizedes) {   //egy float értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
                                              //második paraméterként megadható a tizedes jegyek száma (kötelező megadni)!
  char string[_lcd_oszlop];
  dtostrf(ertek, 1, tizedes, string);  
  lcd_print(string);
}

Mivel sikeres volt az átalakítás, megjött a kedvem a saját függvények használatához. Azonban nem szeretném mindig másolgatni a program sorokat. Még az is baj, hogy ez a „C stílusú” program csak egyetlen kijelzőt tud vezérelni. Így nincs más hátra, mint előre. Visszaalakítottam hát az egészet C++ programnak. Létrehoztam a LiquidCristal_I2C_HUN osztályt, megcsináltam a metódusokat, a programot .h és .cpp állományokra bontottam ki, és készen is lett egy programkönyvtár, amit már csak include-olni kell a program elején. Itt letöltheted magadnak a programkönyvtár zip állományát ha ki akarod próbálni! Legegyszerűbb letöltés után az Arduino IDE „Vázlat / Könyvtár tartalmazása / .ZP könyvtár hozzáadása” menüponttal kitallózni, és kész is van minden. Az Arduino IDE újraindítása után a példák között megtalálható egy csaknem teljeskörű bemutató program. Használd egészséggel!

Mennyire volt hasznos amit olvastál? Értékelés után szövegesen is leírhatod megjegyzéseidet és véleményedet!

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet! Ha kérdésed van, ne felejtsd el megadni az email címedet!