CH552 (CH55x) alaplap Arduino környezetben

Tartalom:

  • WeAct Studio CH552 alaplap felhasználásának indokai
  • Telepítés Arduino IDE környezetben
  • Példa programok a legfontosabb tulajdonságok kipróbálásához
  • Szoftveres I2C, DS3231 RTC óra illesztése
  • Karakteres LCD kijelző illesztése
  • C források a C++ helyett, nehézségek

2021-22 gyászév volt az elektronikai alkatrészek beszerzése szempontjából. Nagyot romlott a forint, bevezették a Kínából rendelt árukra az ÁFA fizetési kötelezettséget és már távolkeleti barátaink sem szeretik kifizetni helyettünk a szállítási költséget. És ki tudja miért, de az Arduino termékek ára ezen indokokon túlmenően is növekedett! Kedvencem az Arduino Nano alaplap immár 800Ft helyett 2000Ft összegért szerezhető be. Bár volt egy átmeneti időszak, amíg még ennél is drágább volt. Kétségbeesve próbáltam hát olcsóbb alkatrészeket keresni. Így találtam rá a CH552 alaplapra, ami 600Ft/db áron érkezett, bár még 400Ft szállítási költség is terhelte a pénztárcámat ehhez kapcsolódóan. Mivel kapásból hármat vettem, nagyon kedvező ár alakult ki.

Így néz ki:

Világítás vezérlést szerettem volna megvalósítani, amire tökéletesen alkalmas lenne. Azonban a világításvezérlés lakásunkban kapott egy plusz tulajdonságot, miszerint bármely kapcsoló dupla megnyomásával lekapcsolható minden lámpa akárhol is felejtettük égve. Vannak a lakásnak olyan részei, ahová nem tudtam kábeleket vinni, így a központi lekapcsolást olcsó 433Mhz-s adó-vevő párral oldottam meg. Ezt az adó-vevő párt a RadioHead programkönyvtárral hajtom meg, ami viszont nem működik a CH552-vel. Így végül úgy tűnik, az alaplapok fiókba kerülnek, hátha később még találok hozzá funkciót!

A fenti hiányosságokat már csak akkor sikerült megértenem, amikor az alaplap legfontosabb tulajdonságait megfejtettem, így most közzé teszem amit már tudok, hátha valaki más is hasznát veszi (illetve magam is saját leírásaimat olvasgatom hosszabb idő eltelte után, amikor már elfelejtettem mindent)!

Alig van elérhető dokumentáció a net-en. A legrészletesebb doksi a github-on található itt: https://github.com/NoNamedCat/ch55xduino

Mire is használható ez az alaplap?! Jóval szerényebb mint a nano, de jobb mint egy ATtiny85, valahol a kettő között áll! A legfontosabb tulajdonságok:

  • Belső 5V -> 3.3V konverter, mert az USB kivezetések 3.3V-os feszültséggel működnek. Egyébként 2.8V-5.5V között bármilyen tápfesz lehetséges.
  • A flash 16Kb (valójában csak 14Kb használható),
  • 1Kb RAM, plusz 256byte RAM, aminek a szerepe nem teljesen világos, de utóbbi a gyors írásokra szolgál, míg az 1Kbyte a tömeges adattárolásra.
  • Három időzítő, ebből az egyik külső kivezetésekről indítható, leállítható és két kivezetés jeleinek időtartam mérésére is használható.
  • Két 8 bites PWM kimenet.
  • Egy SPI.
  • Két UART.
  • Négy 8 bites ADC bemenet.
  • Hat érintésre érzékeny (touch) bemenet.

Sajnos beépített I2C nincs, de szoftveres megoldással lehet helyettesíteni. Láthatóan mindenben egy kicsivel több, mint egy ATtiny alaplap, aminek alig van kivezetése, és csak 8Kb belső flash memóriája van. Egészen egyszerű dolgokra jól használhatónak tűnik. Korlátozottan még az Arduino IDE környezetből is programozható. Sajnos nincs hozzá C++ fordító, csak a C nyelvet használhatjuk. A C nyelv még nem objektum orientált nyelv, így a legtöbb programkönyvtár nem lesz használható, de kisebb nagyobb munkával a programok átalakíthatók. Csábítónak tűnik a 6db érintésre érzékeny bemenet. Már kipróbáltam, valóban elegendő megérinteni a kivezetést! Így nem kell a kütyünkre (bármi legyen is az) nyomógombot szerelni. Elég egy kicsike fémfelület, amit az újunkkal elérünk, pl. egy kiálló csavarfej stb.

Csak azért, hogy nagyon tudományosnak érezzem magam, bemásolom még a chip belső felépítésének vázlatát:

Valamint egy képet a lábkiosztásról:

Valahol a net-en találtam egy angol nyelvű adatlapot. Ezt letöltheted magadnak innen! Egyébként mindenütt (pl. datesheet.com-on is) csak kínai van, ez így nagy szó. Megtudtam az adatlapból, hogy a chip 2,8V-tól 5,5V-ig működőképes.
Ha alapból 5V-on használjuk, akkor a bemenetek 0-5V feszültségeket fogadnak, kivéve a P3.6 és a P3.7 (a kivezetések számozása kicsit szokatlan). Ezek a kivezetések ugyanis a belső USB meghajtó kivezetései, és ezek valamiért 3.3V-al működnek.
A kivezetés bemenetnek állítása esetén 15Kohm-os felhúzó ellenállásból, illetve 1,5Kohm-os lehúzó ellenállásból lehet választani. A lehúzó ellenállás bekapcsolásának módját még nem tudom. Eddig még nem kellett. Az is lehetséges, hogy csak félreértés részemről!
Ha a kivezetést kimenetként használjuk, akkor a 0V és tápfesz között kapcsolgat (kivéve az említett P3.6 és P3.7), ha pl. 5V-on használjuk, akkor a magas szint 5V. A kimenet terhelhetősége 8mA, de a teljes chip összes terhelése nem lehet több mint 12mA. A belső 24Mhz-s órajel valószínűleg egy RC oszcillátor, mert az egyik táblázatban a frekvencia értékét kb. 0.5Mhz pontossággal adták meg. Feltehetőleg ezért is került az alaplapra külső alkatrészként egy 24Mhz-s kristály. Így ezzel nem kell foglalkoznunk, és nem kell aggódnunk, az órajel kvarcz pontos! Érdemes észben tartani még, hogy a flash memória újra írásainak száma 200!!! az alaplap szerint. Mire ezeket a sorokat írom, már vagy ötvenet elhasználtam!

A kivezetésekhez tartozó sok-sok funkció részletes magyarázatát az adatlapból sikerült megfejteni. A fentebb található lábkiosztás ábrán aláhúzással jelzett kivezetések inverz módon működnek (a funkciót a 0 jelszint aktíválja). Pl. a RST (reset) kivezetés jelzése nincs aláhúzással jelölve, vagyis magas jelszinttel lehet reset-elni a chip-et. Ez elég szokatlan, mert az Arduino alaplapokon a reset mindig 0-val történik. Egyébként az adatlap szerint a reset kivezetésnek beépített és fix felhúzó ellenállása van. A többi kivezetés felhúzó ellenállását ki lehet kapcsolni, de alapértelmezetten bekapcsolásra kerülnek a chip indulásakor.

RSTKülső reset bemenet
T0, T1TIMER0,1 külső bemenetek
T2TIMER2 Külső órajel bemenet
T2EXTIMER2 újraindító/leállító bemenet
CAP1, CAP2TIMER2 capture mód bementetek
TIN0 ~ TIN5Érintőgombos kapacitásérzékelő bemenetek
AIN0 ~ AIN3ADC analóg jelbemenetek (0v-Vcc között 256 jelszint)
INT0, INT1Külső megszakítás bemenetek.
UDM és UDPUSB D- és D+ csatlakozási pontok
UCC1, UCC2C típusú USB kétirányú konfigurációs csatorna
VBUS1, VBUS2USB C típusú busz feszültségészlelés bemenet
XI, XOKülső kristály ki és bemenet
RXD, TXDUART0 soros adat be és kimenet
RXD1, TXD1UART1 soros be és kimenet
SCS, MOSI, MISO, SCKSPI interfész, SCS chip kiválasztási bemenet, a MOSI a master kimenet / slave bemenet, A MISO a master bemenet / slave kimenet, az SCK órajel
PWM1, PWM2PWM1 kimenet, PWM2 kimenet

Sajnos a fent említett github leírás számomra nem volt teljesen érthető, ezért napokat töltöttem azzal, hogy megfejtsem, hogyan is lehet ezt az alaplapot az Arduino IDE-vel együtt használni. Első lépésben le kell tölteni a megfelelő alaplapkezelő könyvtárat. Ez még egyszerű! Az Arduino IDE programban a file/beállítások menüpontban a további alaplapkezelő URL-ek közé illesszük be a következő sort:

https://raw.githubusercontent.com/DeqingSun/ch55xduino/ch55xduino/package_ch55xduino_mcs51_index.json

Ezt követően az eszközök/alaplap/alaplap kezelő menüpontot indítsuk el, és a kereső mezőbe írjuk be „CH55x”:

Telepítsük a megtalált alaplapkezelőt! Mint a nevében is látható, nem C++ képes a programkönyvtár. Ha jól értettem meg a leírtakat, ennek oka, hogy a CH55X mikrovezérlő család alapját a 8051-es CPU képezi, melynek utasítás készlete olyan, amire nagyon nehéz lett volna (esetleg nem is lehetséges) a C++ nyelv fordítójának átalakítása. Így maradt a C nyelv, ami sokkal szerényebb, de legalább a megszokott Arduino IDE környezetben dolgozhatunk tovább. Egyébként a C++ programok visszabutítása C-re nem tűnik nagyon bonyolultnak, a fenti github oldalon erre is találunk instrukciókat. Jómagam kezdetben az objektum orientált programozást nem is értettem, ezért nem használtam ki az abban rejlő lehetőségeket. Valamint elég sok program könyvtár tartalmát „hobbyból” átemeltem a saját programsoraim közé, megismertem az adott modul működését és újra megírtam a kezelő függvényeket. Pl. ledmatrix és Ds3231 RTC óra stb. Így ez most hasznomra is válhat!

A következő nagy kihívás az alaplap használatához a megfelelő USB driver telepítése. Ezt ugyanis a Windows10 nem telepíti! Nagyon nehezen értettem meg, hogy kétféle állapot kell az alaplap használatához, és az egyikhez kell csak drivert telepíteni:

  1. Programozás. Ahhoz, hogy programot töltsünk az alaplapra, úgy kell az USB porthoz csatlakoztatni, hogy közben nyomva tartjuk a boot nevű nyomógombot (az alaplapon P36 felirat látható). Ez nem csinál mást, mint egy felhúzó ellenállással magasra húzza az USB port D+ kivezetését. Nagyon macerás a programfeltöltés, mert az a műveletet megfelelő ritmusban kell elvégezni. Azt tapasztaltam ugyanis, hogy csatlakoztatás után elindul a CH552 chip-ben a bootloader program, ami csak néhány másodpercig várakozik az adatok érkezésére, aztán újraindítja chip-et „nem” bootloader üzemmódban. Ha az Arduino IDE nem végez a program fordítással, és nem kezdi meg a feltöltést, akkor meghiúsul a feltöltési kísérlet. Az én Arduino IDE példányom hosszú ideig fordít, így csak akkor dugom be lenyomott nyomógombbal az alaplapot, amikor éppen megjelenik a fordítás végén az a kb. két soros fehér betűs üzenet, ami a lefordított program méretéről és a felhasznált memória méretéről tájékoztat. Ekkor a feltöltés egy másodperc alatt megtörténik.
  2. USB soros kommunikáció PC-vel. Amikor a programból adatokat küldünk soros portra, a soros monitornak meg kell adnunk egy sorosport számot (pl. COM20 stb.). Ehhez kell egy USB-Serial illesztő driver. Ezt a drivert akkor fogja használni a PC, amikor a boot gomb megnyomása nélkül dugjuk be az USB portba az alaplapot. Program feltöltés után természetesen nem kell kihúzni és visszadugni, elegendő megnyomni a reset gombot. Sajnos a soros monitort reset után tapasztalatom szerint újra kell indítani.

Mielőtt a driver telepítés menetét megismerjük, érdemes egy pillantást vetni az alaplap kapcsolási rajzának a részletére. Rögvest kiderül belőle, hogy mit is csinál a boot nyomógomb, ami az alaplapon P36 felitattal van ellátva.

A rajzon a CH552P változat szerepel, aminek kicsit kevesebb a kivezetése, de ez most szempontunkból mindegy. Az ismertetett alaplapon egyébként a CH552T változat található, aminek összesen 20 kivezetése van.

És ha már kapcsolási rajzokat nézegetünk, érdemes rápillantani az USB Type-C típusú csatlakozó környezetére is. Ez a kapcsolási rajz egy Adafruit alaplaphoz készült (WeAct-ra nem találtam), de feltételezem, hogy ugyanígy lett bekötve. Az USB-C csatlakozóról annyit érdemes tudni, hogy 24 csatlakozási pontja van, és lefelé kompatibilis az USB-A és USB-B csatlakozókkal, és ezt úgy biztosítja, hogy megtalálható benne a D+ és D- jelzésű érpár. Ha csak ezeket használjuk, akkor az USB-C kábel másik végén az USB-A csatlakozó összesen négy kivezetésébe ezt a kettőt, illetve a földet és a tápfeszt kell bekötni.

Itt azonban van még két felhasznált kivezetés a 24-ből, a CC1 és a CC2. Ezeket a csatlakozási pontokat az USB-C akkor használná, ha a másik oldalon is USB-C csatlakozó található. Ekkor ugyanis ezeknek a kivezetési pontoknak a feszültség állapotaiból állapítja meg a kábel csatlakoztatásának pozícióját (kétféle képpen lehet csatlakoztatni), valamint ha tápfeszt is visz a kábel, akkor a maximális áramot, Vbus feszültséget stb. Ezek most nem kellenek nekünk. Viszont ezeknek a pontoknak a feszültségét képes az alaplapra szerelt CH552 megmérni, mert pont olyan kivezetésekhez csatlakoznak (UCC1 és UCC2), ami beállítható analóg bemenetnek.

A driver telepítéshez érdemes rendszergazda felhasználóval belépni a Windows-ba, és elindítani az eszközkezelőt. Ha bedugjuk a nyomógomb megnyomása nélkül az alaplapot, akkor az eszközkezelő megjeleníti a Windows beépített driver-el működő USB eszközt és a kapott portszámot.

Ehhez tehát elvben nem kell driver! Azonban nekem ez nem ment ilyen könnyen. Minden bizonnyal már előtte össze-vissza telepítgettem és próbálkoztam, így ismeretlen eszközt kaptam csatlakoztatáskor. Az a gyanúm, hogy ez csak nálam, és csak akkor előforduló eset lehetett, mert van egy másik laptopom is, azon kapásból a fenti kép jelent meg az eszközkezelőben, csak más lett a portszám! Egyébként az ismeretlen eszköz esetén az alább ismertetett ZADIG programmal az „USB Serial CDC” drivert kell telepíteni, ha valakinél ez előfordulna.

Mint említettem az előbb, szükségünk lesz egy ZADIG nevű programra. Ez telepíti ugyanis a megfelelő drivert arra az esetre, amikor úgy dugjuk az alaplapot az USB portba, hogy nyomva tartjuk a boot gombot!

Töltsük le tehát a ZADIG programot innen: https://zadig.akeo.ie/ (vagy máshonnan), és indítsuk el. Ez a program fogja elvégezni a megfelelő USB driver telepítését. Ha nem rendszergazda felhasználóval vagyunk bejelentkezve a Windows-ba, akkor kelleni fog a rendszergazdai jelszó. A ZADIG működéséről annyit kell tudni, hogy nem kell újraindítani a programot ha új eszközt csatlakoztatunk. Folyamatosan frissíti az eszköz listát. Azonban előtte az „options” menüben be kell kapcsolni az eszközök megjelenítését:

Ha tehát csatlakoztattunk egy új eszközt, várjuk meg amíg megjelenik az eszközkezelőben, és nyissuk le a ZADIG eszközlistát. Az alábbi képek azt ábrázolják, amint csatlakoztattam a CH552 alaplapot benyomott boot nyomógombbal.

Fontos még megjegyeznem, hogy jómagam a boot gombot folyamatosan nyomva tartottam attól való félelmemben, hogy amikor a bootloader már nem vár tovább, akkor el fog tűnni az ismeretlen eszköz. Úgy emlékszem, ez meg is történik, de igyekeztem villámgyorsan csinálni mindent. A boot gomb folyamatos nyomva tartása mindazonáltal feleslegesnek tűnik. Amikor kiválasztottuk az ismeretlen eszközt, keressük meg alatta az alábbi képen látható drivert, és nyomjuk meg az „Install driver” gombot. Ennek felirata „Replace driver” is lehet, ha esetleg a gépen található valamilyen régebbi telepítés:

Ha esetleg nem lenne jól látható a képen, az említett github leírás instrukciói alapján a „libusb-win32 (v1.2.6.0)” drivert kell telepíteni.

Két laptopom van, mindkettőn telepítettem a drivereket, és mindkettő másként viselkedett. Ennek oka, hogy ez egyiken már járhatott a ZADIG program emlékeim szerint, és valamit telepítettem vele. Megmutatnám, hogy azon mit tapasztaltam, amikor csatlakoztattam benyomott boot gombbal a CH552-t:

Ezzel készen is vagyunk a modul első programjának feltöltésére. Sajnos az alaplapkezelővel telepített példa programok nem teljesen pontosak. Nyilván a github-on található alaplapkezelő esetében nem az általam beszerzett Wemos Studio CH552 szolgált alapmodulként (ez a képeken látszik is a github leírásban), és ezért más kivezetésen van az beépített led. A többi programban is lehetnek egyéb pontatlanságok. Az első program az alap blink program lett, amit azzal spékeltem meg, hogy az USB portra is küldök szöveget, amit az USB soros driver szolgai módon átfordít nálam a COM20-ra az egyik laptopomon, a másikon COM8-ra. Mint említettem már, ehhez a program feltöltés után kell egy reset az alaplapon, és a soros monitort is újra kell indítani. Az alábbi forrásban a setup()-ban található „INDUL…” szöveg nem is tud megjelenni, ehhez kellene előtte egy jó nagy időzítés, ami alatt a reset után újra indítom a soros monitort.  A példaprogram megértéséhez még annyit kell tudni, hogy a CH552 kivezetésein a 1-es és 3-as port bitjei jelennek meg (lásd feljebb a belső felépítés blokkvázlata). Ezért a kivezetések jelentése kicsit szokatlan az Arduino világhoz képest. pl. a 3-as port 0. bitjének kivezetés száma P3.0. Erre a kivezetésre kötötték a beépített led-et, és ennek száma a programban „30”!

void setup() {
  pinMode(30, OUTPUT);           //a beépített led a 30-as kivezetésen található
  USBSerial_println("INDUL..."); //Szöveget küldünk az USB portra
}

void loop() {
  digitalWrite(30, HIGH);        //led világít
  USBSerial_println("LED BE");
  delay(1000);                      
  digitalWrite(30, LOW);         //led nem világít
  USBSerial_println("LED KI");
  delay(1000);                      
}

Az USB portra történő írásban már látható, hogy mit is jelent az, hogy a C++ helyett csak C nyelvben írhatunk programokat. Az eddig „C++”-ban megszokott módszer az volt, hogy „.”-al választottuk el egy objektum metódusait. Emlékeztetőül a „Serial” objektum „println” metódusának használatával így nézne ki a fenti programsor: „Serial.println()”. Mivel az objektum orientált programozás nem áll rendelkezésre, az objektumok osztály definicióiban található függvényeket mind-mind külön meg kellett írnia az alkotóknak. Így lett C-ben USBSerial_println() függvény! A Serial helyett az USBSerial azért született, mert az a vezérlő ténylegesen a saját beépített USB portjára ír. Van neki igazi soros portja is (UART0 és UART2). Ha pl. megnézed a példák között szállított UsbSerialAdaptor nevű mintapéldát, ott láthatod, hogy a 10-es kivezetésen megvalósított soros TX kimenetre a Serial10_println() függvénnyel tudsz írni! Azért „Serial10”, mert ennek a soros portnak a kimenete a P1.0 azaz „10”-es kivezetésre lett kivezetve. Emlékeztetőül az Arduino nano egyetlen soros portjára a Serial.println() programsorral tudnunk információt küldeni! Ha Te kedves kezdő a fenti szavakból nem értesz mindent, akkor ne keseredj el, rengeteg oktatóanyagot találsz a net-en a témáról. Én is most tanulom!

A következő mintapélda a bemenet használata. A P3.3 bemenetre (azaz a 33-as kivezetésre) egy nyomógombot kötöttem. A program lekérdezi a bemenet állapotát, és fel vagy lekapcsolja a beépített led-et az állapottól függően:

void setup() {
  pinMode(30, OUTPUT);          //A led a P3.0 kivezetésen van
  pinMode(33, INPUT_PULLUP);    //Egy külső nyomógombot tettem a 33-as kivezetésre, és bekapcsoltam a belső felhúzó ellenállást
                                //Ha nem kell a belső felhúzó ellenállás, akkor simán "INPUT"-ot kell írni.
}

void loop() {
  if (digitalRead(33)) {        //Kiolvassuk a 33-as bemenet állapotát
    digitalWrite(30, HIGH);     //led világít
  } else {
    digitalWrite(30, LOW);      //led nem világít
  }
}

A következő mintapélda az touch bemenetek használatát mutatja be. A „Touchkey.h”-t kell includ-al beemelni, és kész is. A konkrét működést (a chip belső technikai működését) nem értem, de feltételezem, hogy valamilyen kapacitív hatást érzékel, és vélelmezem, hogy a leolvasáshoz idő szükséges. Kíváncsi voltam, hogy mekkora vezetéket köthetek a bemenetre ahhoz, hogy jól működjön. 50cm-es vezetékkel még működött, azaz a bemenetre kötött vezeték másik végét érintettem. A fiókban egy 2m-es riasztó vezetéket is találtam. Annak a csatlakoztatásakor már bizonytalan volt a bemenet. Hol 0, hol 1 jelent meg a programban. Általában inkább 1-et mutatott, illetve egy érintés után hosszú ideig nem váltott vissza 0-ra. Tehát a kivezetésre csak néhány cm-es vezetéket célszerű kapcsolni!

#include <TouchKey.h>

byte touch_on[]={0,0,0,0,0,0};  //a tömb elemei 0 és 1-el jelzik, ha az adott bemenetet megérintették

void setup() {
  pinMode(30, OUTPUT);          //A led a P3.0 kivezetésen van
  TouchKey_begin(1|2|4|8|0|32); //Engedélyezzük az egyes bemeneteket: TIN0(P1.0), TIN1(P1.1), TIN2(P1.4), TIN3(P1.5), TIN4(P1.6), TIN5(P1.7)
                                 //Sajnos a fordítónak nem lehet binárisan megadni a paramétert (pl.: B00110011)
                                 //Ezért kellett vagy kapcsolattal összemaszkolni az eredményt. Ha egy bemenetet nem akarunk 
                                 //érintő bemenetnek használni, akkor oda 0-át kell írni. Pl. ha P1.6-ot másra használjuk, akkor 
                                 //így kell megadni a bemenő paramétert 1|2|4|8|0|32 (ez ennek felel meg: B00111101)
}

void loop() {
  touch_lekerdez();
  //a TIN1.0 bemenetttel vezéreljük a beépített led-et.
  if (touch_on[0]) {
    digitalWrite(30, HIGH);
  } else {
    digitalWrite(30, LOW);
  }
  //Megjelenítjük a tömb elemeit, hogy lássuk a soros monitoron is az eredményt
  USBSerial_print(touch_on[0]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[1]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[2]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[3]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[4]);
  USBSerial_print(" ");
  USBSerial_println(touch_on[5]);
}

void touch_lekerdez() {
  TouchKey_Process();                    //bemenet ellenőrzésének előkészítése
  byte touch_eredmeny=TouchKey_Get();    //bemenet állapotának lekérdezése. A változóban bitenként adja vissza
                                         //a bemenetek állapotát (amelyik bemenet engedélyezve van). Pl. ha csak
                                         //TIN1.0 lett megérintve, akkor az eredmény 1. Ha csak TIN1.1 lett megérintve
                                         //akkor az eredmény 2. Ha mindkettő, akkor 3.
  //A lekérdezés eredményéből kimaszkoljuk a biteket és feltöltjük a tousc_on[] tömb elemeit
  if (touch_eredmeny&1) {touch_on[0]=1;} else  {touch_on[0]=0;}
  if (touch_eredmeny&2) {touch_on[1]=1;} else  {touch_on[1]=0;}
  if (touch_eredmeny&4) {touch_on[2]=1;} else  {touch_on[2]=0;}
  if (touch_eredmeny&8) {touch_on[3]=1;} else  {touch_on[3]=0;}
  if (touch_eredmeny&16) {touch_on[4]=1;} else  {touch_on[4]=0;}
  if (touch_eredmeny&32) {touch_on[5]=1;} else  {touch_on[5]=0;}
}

A negyedik mintapéldám azt mutatja be, hogyan lehet az analóg bemeneteteket használni. Egyébként pont úgy mint C++-ban, csak vannak apróságok, amik másként működnek. Speciel ennél a példaprogramnál jöttem rá, hogy egy változó deklarálásakor az azonnali értékadásban nem használhatok függvényt! Profi C-s programozóknak ez biztosan egyértelmű, nekem rá kellett jönnöm! A példa programban ugyanis arra is kerestem a választ, hogy hány mintavételt tudnék csinálni az ADC-vel egy másodperc alatt, és ehhez kellett egy long változó, amit a millis() értékével szerettem volna feltölteni a deklarációkor. Egyébként így is sikerült megmérnem, hogy ~73.000 mérés történt. Természetesen csalóka az adat, mert más programsorok is bekerültek a loop()-ba, amikkel szintén megy az idő, de nekem ez az adat is beszédes. Arduino nano esetén úgy emlékszem kb. 10.000 mintavételt tapasztaltam. Persze ne feledjük, ez az ADC csak 8 bites! A programban kipróbáltam a PWM kimenetet is, a led fényerejét rögvest be is állítom az ADC bemenetére adott feszültséggel. A bemenetre a feszültség egy potméterről érkezik, amit a GND és a chip tápfesz közé kötöttem. A referencia feszültség a tápfesz a chip-ben!

byte poti_fesz=0;  //Ebbe a változóba olvasom be a bemenet feszültségét. Elég a byte típus, mert csak 8 bites az ADC
long idozites;     //Másodpercenként kiírom, hogy mennyi mérést végzett az ADC, ennek időzítéséhez segédváltozó
long meres_db=0;   //A mérések darabszáma, másodpercenként nullázom, mérésenként növelem.

void setup() {
  pinMode(30, OUTPUT);   //A led a P3.0 kivezetésen van
  pinMode(32, INPUT);    //Egy külső potmétert tettem 32-es kivezetésre, és ezt tekergetem. Belső felhúzó ellenállás nem kell.
  idozites=millis();     //a változó deklarálásakor az értékadásba nem lehet függvényt írni (gondolom ez C tulajdonság)
}

void loop() {
  poti_fesz=analogRead(32);       //kiolvassuk a bemenet feszültséget (0V=0 ... 5V=255);
  meres_db++;
  if (idozites+1000<millis()) {
    USBSerial_print("Meresek száma /sec:");   //Eredményt kiírjuk a soros monitoron 
    USBSerial_println(meres_db);   //Eredményt kiírjuk a soros monitoron 
    meres_db=0;
    idozites=millis();
    analogWrite(30, poti_fesz);     //A poti feszültsége alapján beállítjuk a PWM kimenet kitöltési arányát (fényerőt)
  }
}

Mivel a DS3231 RTC órához már régebben saját függvényeket írtam, és ezekben nem használtam ki a C++ összes lehetőségét (azaz C-ban írtam meg mindent), lehetőségem volt, hogy kipróbáljak egy valós működő dolgot. Ehhez persze szükség van az I2C kapcsolatra, ami alapból nincs a chip-be építve. Tehát első lépés a szoftveres I2C kipróbálása. Természetesen működik a példaprogram. Az I2C portscenner programot csináltam meg elsőnek, és rádugdostam az egyik Ds3231 modulomat. Íme a működő program forrása:

#include <SoftI2C.h>
byte valasz;

void setup() {
  scl_pin = 30; //SoftI2C külső változója az SCL kivezetés beállítására
  sda_pin = 31; //SoftI2C külső változója az SCL kivezetés beállítására
  I2CInit();    //I2C inicializálása
}

void loop() {
  USBSerial_println("I2C port scennelés start:");
  for (byte i = 0; i < 128; i++) {    //végigmegyünk az összes lehetséges címen
    USBSerial_print("Cím: 0x");
    USBSerial_print(i, HEX);
    I2CStart();                       //I2C átvitel indítása 
    valasz = I2CSend(i << 1 | 1);     //adott című slave megcímzése. Az utolsó bítben állítjuk be, hogy olvasni akarunk (1)
                                      //vagy írni (0) a slave címére.
    USBSerial_print(" Válasz:");      //Ha a válasz 0, akkor találtunk egy eszközt az adott címen
    USBSerial_println(valasz);
    I2CStop();
    delay(1);
  }
  delay(2000);
}

Kicsit átalakítottam az eredetit, mert látni szerettem volna, hogy milyen hibakódot ad vissza stb. Meglehetősen szokatlan a szoftveres I2C függvények használata, de végül is könnyedén megfejthető a működés. Nem is részletezném, akinek kell, ismerje meg alaposabban. Itt található az eredeti Arduino változat!


Most érkezett el a nagyobb feladat. Elővettem a saját magam által barkácsolt DS3231 kezelő függvényeket és elkezdtem soronként átírni. Az eredeti program a DS3231 leírás legvégén a “Wire.h” használatával. Az új program a szoftveres I2C használatával pedig ez lett:

#include <SoftI2C.h>

//függvény deklarációk
byte bcd_Dec(byte);
byte dec_bcd(byte);
bool setTime24(unsigned int,byte,byte,byte,byte,byte,byte);         //rosszul megadott dátum vagy idő esetén 0-val tér vissza
                                                                    //Hónapot nem ellenőrzi pontosan, csak azt figyeli, hogy ne 
                                                                    //legyen negatív vagy 31-nél nagyobb.
bool getTime24(unsigned int*,byte*,byte*,byte*,byte*,byte*,byte*);  //A függvény a változók memória címét veszi át, 
                                                                    //és közvetlenül a változók tartalmát fogja módosítani.
                                                                    //0-át ad vissza, ha elemes táplálásnál leállta az oszcillátor
                                                                    //azaz az óra valószínűleg nem pontos. 1-et ad vissza, ha elemes
                                                                    //táplálásnál nem volt leállás, azaz az óra pontosan járt tovább.
float getTemperature();

unsigned int ev;    //év tárolása
byte ho;            //hó tárolása 
byte nap;           //nap tárolása 
byte het_napja;     //hét napja (1-hétfő....7-vasárnap)
byte ora;           //óra tárolása (24 órás üzemmódot fogunk használni)
byte perc;          //perc tárolása 
byte masodperc;     //másodperc tárolása 
bool pontos;        //ez a változó fogja megadni az idő kiolvasását követően, hogy pontos-e a kiolvasott időpont 
                    //(ha elemes működés közben leáll a DS3231 oszcillátora, akkor az "OSF" flag bebillen, és ezt olvassuk ki


void setup() {
  scl_pin = 30; //SoftI2C külső változója az SCL kivezetés beállítására
  sda_pin = 31; //SoftI2C külső változója az SCL kivezetés beállítására
  I2CInit();    //I2C inicializálása
  //az óra beállítása (csak egyszer kell megcsinálni, aztán ki kell kommentezni
  masodperc=0;
  perc=50;
  ora=5;
  het_napja=7;
  nap=13;
  ho=11;
  int ev=2022; 
  delay(4000); //a reset és soros monitor elindítására hagyok időt magamnak
  //az alábbi sorok elől ki kell venni a kommentet, ha be akarjuk egyszer állítani a DS3231-ben az időt
  //if (!setTime24(2022,12,7,2,9,40,0)) {     //óra beállítása: négyszög kimenő jelet kikapcsolja, 
                                            //32Khz-s kimenetet kikapcsolja és 24 órás üzemmódot állít be.
                                            //Beállítja a pontos időt a paraméterben megkapott változók értékével 
  //  USBSerial_println("Óra beállítás sikertelen, rosszul megadott dátum vagy idő!");
  //}
  
}

void loop() {
  USBSerial_println("Pontos idő:");
  pontos=getTime24(&ev,&ho,&nap,&het_napja,&ora,&perc,&masodperc);                   //kiolvassuk az időt a változóba
  USBSerial_print(ev,DEC);USBSerial_print("-");
  USBSerial_print(ho,DEC);USBSerial_print("-");
  USBSerial_print(nap,DEC);USBSerial_print("  ");
  USBSerial_print(het_napja);
  USBSerial_print("  ");USBSerial_print(ora,DEC);
  USBSerial_print(":");USBSerial_print(perc,DEC);
  USBSerial_print(":");USBSerial_println(masodperc,DEC);
  if (pontos==0) {USBSerial_println("Az óra valószínűleg nem pontos");}
  USBSerial_print("Hőmérséklet:");USBSerial_println(getTemperature());
  delay(1000);
}

byte bcd_Dec(byte val) {  // Convertál bcd számból decimálisba
  return ( (val/16*10) + (val%16) );
}
byte dec_bcd(byte val) {  // Convertál decimális számból bcd-be
  return ( (val/10*16) + (val%10) );
}

bool setTime24(unsigned int _ev, byte _ho, byte _nap, byte _het_napja, byte _ora, byte _perc, byte _masodperc) {
  //Ezzel a függvénnyel állítjuk be az időt, és a DS3231 alap állapotát is (24 órás üzemmód, négyszögjel kimenet stb.).
  //A dátumot és időt pontosan át kell adni, egyben állít be mindent. Az értékeküket allenőrzi, de a hónap napjainak
  //számát nem ellenőrzi pontosan. 
  //a változók tartalmának ellenőrzése. 
  if (_ev>2100 | _ho>12 | _nap>31 | _het_napja>7 | _ora>23 | _perc>59 | _masodperc>59) {
    return 0;
  }
  //status regiszter beállítása
  I2CStart();
  I2CSend(0x68 << 1 | 0);         //Írni fogunk a DS3231-re.
  I2CSend(0x0E);                  //A control regiszterbe fogunk írni, ez annak a címe a chip-ben
  I2CSend(0x63);                  //Ez a control regiszter új tartalma (ez az érték binárisan 0b01100011)
                                  //BBSQW bit 1, vagyis elemes táplálásnál is működik az oszcillátor
                                  //ha 1-re állítanánk, elemes táplálásnál nem járna az óra, csak őrizni az utolsó időpontot
                                  //A RS2 és RS1 bit 0-val beállítja a négyszögjel kimenetet 1Hz-ra, ezen lehet változtatni:
                                  // 0b01101011 esetén 1024Khz, 0b01110011 esetén 4096Khz, 0b01111011 esetén 8192Khz.
                                  //A INTCN bit 0-val beállítja, hogy az INT/SQW kimenet beállított frekvenciájú jelet ad ki.
  //címben a státus regiszter következik, nem kell megcímezni csak írni, és az adat oda kerül
  I2CSend(0x00);                  //A status regiszterben töröljük az OSF fleg-et, ami 1-el jelzi majd, ha elemes táplálásnál 
                                  //nem működött az oszcillátor, vagyis az óra valószínűleg nem pontos.
                                  //Az EN32Khz bit-et is 0-ra írjuk, azaz letiltjuk a 32Khz-s kimenetet
  I2CRestart();
  I2CSend(0x68 << 1 | 0);         //Írni fogunk a DS3231-re.
  I2CSend(0x00);                  //Most az adatregiszterekbe írjü1k a pontos időt, ez a legelső adatregiszter címe a chip-ben
  I2CSend(dec_bcd(_masodperc));
  I2CSend(dec_bcd(_perc));
  I2CSend(dec_bcd(_ora) & 0b10111111);
  I2CSend(dec_bcd(_het_napja)); 
  I2CSend(dec_bcd(_nap));
  I2CSend(dec_bcd(_ho));
  I2CSend(dec_bcd(_ev-2000)); 
  I2CStop();
  return 1;
}

bool getTime24(unsigned int* p_ev, byte* p_ho, byte* p_nap, byte* p_het_napja, byte* p_ora, byte* p_perc, byte* p_masodperc) {
  //Ezzel a függvénnyel visszakjuk az aktuális időt és dátumot. 24 órás formátumban és az évet évszázaddal kapjuk vissza.
  //A paramétereket referencia szerint adjuk át, vagyis a változó memória címeit. Ezért a függvény közvetlenül 
  //a változók tartalmát módosítja. 
  //kiolvassuk a dátumot és az időpontot 
  I2CStart();
  I2CSend(0x68 << 1 | 0);     //Írni fogunk a DS3231-re.
  I2CSend(0x00);              //az adatregisztereket fogjuk kiolvasni, ez a kezdőcím a chip-ben
  I2CRestart();
  I2CSend(0x68 << 1 | 1);     //Olvasni fogunk a DS3231-ből.
  *p_masodperc = bcd_Dec(I2CRead());  //A mutató átlat tárolt memóriacímen található byte típusú változó tartalmát módosítjuk
  I2CAck();
  *p_perc = bcd_Dec(I2CRead());
  I2CAck();
  *p_ora = bcd_Dec(I2CRead());
  I2CAck();
  *p_het_napja = bcd_Dec(I2CRead());
  I2CAck();
  *p_nap = bcd_Dec(I2CRead());
  I2CAck();
  *p_ho = bcd_Dec(I2CRead() & 0b01111111);
  I2CAck();
  *p_ev = bcd_Dec(I2CRead())+2000;   //A mutató átlat tárolt memóriacímen található int típusú változó tartalmát módosítjuk
  I2CNak();
  I2CRestart();
  //Kiolvassuk a status regiszterből a osf flag-et, ami megmondja, ha elemes táplálásnál leállt az oszcillátor
  I2CSend(0x68 << 1 | 0);     //Írni fogunk a DS3231-re.
  I2CSend(0x0F);              //ez a státus regiszter címe
  I2CRestart();
  I2CSend(0x68 << 1 | 1);     //Olvasni fogunk a DS3231-ből.
  if (I2CRead() & 0b10000000) {return 0;} else {return 1;}  //Kiolvassuk a status regisztert, kimaszkoljuk az OSF bitet.
                                                            //Ez alapján 1-el jelezzük, ha az óra valószínűleg pontos
  I2CStop();
}

float getTemperature() {
  // Kiolvassa a hőmérséklet értékét a 0x11h és 0x12h regiszterekből
  float temp;
  I2CStart();
  I2CSend(0x68 << 1 | 0);                      //Írni fogunk a DS3231-re.
  I2CSend(0x11);                               //a DS3231 chip 0x11 és 0x12-es regisztere a hőmérséklet regiszter
  I2CRestart();
  I2CSend(0x68 << 1 | 1);                      //Olvasni fogunk a DS3231-ből.
  temp=I2CRead()+(0.25*(float)(I2CRead()>>6)); //hőmérséklet MSB és LSB regisztere kiolvasás, hőmérséklet számítás
  I2CStop();
  return temp;                                 //visszatérési érték
}

Az eredeti programból csak a DS3231 parancskészletének tudását használtam fel, a programból szinte egy betű sem maradt változatlanul. Elég nagy szívás volt a C++ program átírása C-re, mert rá kellett jönnöm egy csomó eddig ismeretlen megkötésre. Természetesen sokat segít, ha az ember előre utána olvas a szabványos C illetve C++ programnyelvnek, de ezt én csak utólag tettem meg. Kicsit kényelmetlennek találtam a programozást az Arduino IDE-ben tapasztaltakhoz képest:

  • A program forrásban a függvényeket deklarálni kell, különben a setup() és loop() a forráskód végén kell, hogy legyen, illetve a sorrendiség miatt (egy függvény előbb legyen a forrásban mint az őt meghívó másik függvény) a forráskód füleket sem használhatjuk. Ha azonban a szabványos C szerinti deklarációt megcsináljuk, már külön fülre rakhatjuk a függvény definíciókat.
  • Kicsivel később derült ki számomra, hogy a függvénydeklarációkat komolyan kell venni, nem érdemes ellustulni, bár elvileg megfelelő lehet egy-egy függvény elhelyezése a forrás legelején. Már az LCD kijelző meghajtását írom, amikor kiderült, hogy sok függvény után, már le sem fordul a program, soha nem látott hibákat produkál. A függvény deklarációk hipp-hopp minden hibát eltüntettek.
  • Nem tudtam változó struktúrát deklarálni! A struktúrát még létre tudta a forrás hozni, de amikor az adott struktúrával akartam változót deklarálni, az már hibára futott. Ezzel sem küzdöttem sokat.
  • Változó értékadásban nem lehet függvény, pl. long a=millis(); nem működik.
  • Az USBSerial_print() függvény nem tud bool típusú változót írni. (Lehet, hogy a Serial.println() sem tud, csak most jut eszembe, miközben írok, hogy meg kellene nézni).
  • Változó értékadásnál nem tudtam a bitenkénti bináris értéket megadni. Pl. 0x63 helyett nem írhattam ezt: 0b01100011. Később kiderült, hogy mégis lehetséges, bár nem értem, hogy mit rontottam el első körben. Ezt inkább a szívások közé sorolom, mert miután átírtam minden bináris formátumot hex-re, kiderült, hogy mégis megy. Ugyanis benne felejtettem a forrásban két helyen is a bináris megadást. Már nem írtam vissza.

Miután elkészült a fenti program, utólag csináltam egy saját program könyvtárat, hogy kényelmesebben tudjam használni a függvényeket. Ennek használatával a példa program nagyon egyszerű lett:

#include <SoftI2C.h>
#include <DS3231_CH55X.h>

unsigned int ev;    //év tárolása
byte ho;            //hó tárolása 
byte nap;           //nap tárolása 
byte het_napja;     //hét napja (1-hétfő....7-vasárnap)
byte ora;           //óra tárolása (24 órás üzemmódot fogunk használni)
byte perc;          //perc tárolása 
byte masodperc;     //másodperc tárolása 
bool pontos;        //ez a változó fogja megadni az idő kiolvasását követően, hogy pontos-e a kiolvasott időpont 
                    //(ha elemes működés közben leáll a DS3231 oszcillátora, akkor az "OSF" flag bebillen, és ezt olvassuk ki

void setup() 
{
  scl_pin = 30; //SoftI2C külső változója az SCL kivezetés beállítására
  sda_pin = 31; //SoftI2C külső változója az SCL kivezetés beállítására
  I2CInit();    //I2C inicializálása
  //az óra beállítása (csak egyszer kell megcsinálni, aztán ki kell kommentezni
  masodperc=0;
  perc=50;
  ora=5;
  het_napja=7;
  nap=13;
  ho=11;
  int ev=2022; 
  delay(4000); //a reset és soros monitor elindítására hagyok időt magamnak
//  if (!setTime24(2022,12,7,2,9,40,0)) {     //óra beállítása: négyszög kimenő jelet kikapcsolja, 
                                             //32Khz-s kimenetet kikapcsolja és 24 órás üzemmódot állít be.
                                             //Beállítja a pontos időt a paraméterben megkapott változók értékével 
//    USBSerial_println("Óra beállítás sikertelen, rosszul megadott dátum vagy idő!");
//  }
  
}

void loop()
{
  USBSerial_println("Pontos idő:");
  pontos=getTime24(&ev,&ho,&nap,&het_napja,&ora,&perc,&masodperc);                   //kiolvassuk az időt a változóba
  USBSerial_print(ev,DEC);USBSerial_print("-");
  USBSerial_print(ho,DEC);USBSerial_print("-");
  USBSerial_print(nap,DEC);USBSerial_print("  ");
  USBSerial_print(het_napja);
  USBSerial_print("  ");USBSerial_print(ora,DEC);
  USBSerial_print(":");USBSerial_print(perc,DEC);
  USBSerial_print(":");USBSerial_println(masodperc,DEC);
  if (pontos==0) {USBSerial_println("Az óra valószínűleg nem pontos");}
  USBSerial_print("Hőmérséklet:");USBSerial_println(getTemperature());
  delay(1000);
}

Ha Te kedves olvasóm szintén szeretnéd használni az #include “DS3231_CH55X” sort a programban, akkor csak annyit kell tenned, hogy letöltöd innen a könyvtárat ZIP formátumban, kibontod és bemásolod a Dokumentumok mappában található Arduino\Libraries mappába a ZIP-ben található DS3231_CH55X mappát. Arduino IDE újraindítása után megtalálod a fenti példaprogramot a példák között!


Ezt követte időben az LCD kezelő program megírása. Ez elég nagy szívás volt. Viszonylag gyorsan megértettem a kijelző meghajtó chip-ek logikáját, és megírtam (illetve lelestem) az alapvető függvényeket. Első lépésben C++-ban írtam meg, és csináltam is egy könyvtárat Arduino alaplapokhoz. Itt található a részletes leírás, és le is tölthető a programkönyvtár. Szerintem jól sikerült, immár alapból tudja a kijelző a magyar ékezetes karaktereket is.

Aztán jött az átírás (butítás) C-be. A C++ objektumorientált lehetőségeinek jelentőségét itt értettem meg igazán. Javaslom mindenkinek a C és C++ alapos tanulmányozását. Az alap kijelző parancsok gyorsan elkészültek (karakter kiírása, háttérvilágítás ki-be stb.) A gond a számok formázott kiírásával kezdődött. A numerikus adatok kiírásához eredetileg a C-ben is létező spintf() függvényt szándékoztam használni. Napokig kerestem nem létező hibákat, mert a fordító számomra értelmetlen hibaüzeneteket dobott. Amennyire sikerült megértenem, a chip 1Kbyte ram-ja túl kevés. Állandóan elfogyott a memória, bár a fordító nem pontosan ezt írta ki. Így a végén saját print() függvényeket írtam, kényszerből a jellemző formátumokra. long (ez jó a byte és int-nek is), float, dátum és idő kiírásra is születtek függvények. Túl sok magyarázat nem kell a forráshoz, lehet használni:

#include <SoftI2C.h>

//****** függvény deklarációk *********************************-
void send(byte,byte);
void wr4bit(byte);
void i2cWrite(byte);
inline void lcd_command(byte);
inline size_t lcd_write(byte);
void lcd_createChar(byte,byte charmap[]);
void lcd_clear();
void lcd_home();
void lcd_noBacklight(); 
void lcd_backlight(); 
void lcd_noDisplay();
void lcd_display();
void lcd_begin(byte,byte,byte);
void lcd_setCursor(byte,byte);
void lcd_off();
void lcd_on();
void lcd_noCursor();
void lcd_cursor();
void lcd_noBlink();
void lcd_blink();
void lcd_scrollDisplayLeft();
void lcd_scrollDisplayRight();
void lcd_leftToRight();
void lcd_rightToLeft();
void lcd_autoscroll();
void lcd_noAutoscroll();
void lcd_print(char xstring[]);
void lcd_print_long(long,byte,bool);
void lcd_print_float(float,byte,byte,bool);
void lcd_print_date(int,byte,byte,bool);
void lcd_print_time(byte,byte,byte,bool);

//**** szükséges globális változók ***********************************-
byte _lcd_cim;
byte _lcd_oszlop;
byte _lcd_sor;
byte _backlightval;   //háttérvilágítás alapértelmezetten kikapcsolva (I2C illesztő chip P3 port=0)
byte _displaycontrol; //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.
byte _displaymode;    // 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)


void setup() {
  scl_pin = 30; //SoftI2C külső változója az SCL kivezetés beállítására
  sda_pin = 31; //SoftI2C külső változója az SCL kivezetés beállítására
  I2CInit();    //I2C inicializálása
  lcd_begin(0x3F,20,4);               //Az egyik 20x4-es kijelzőm címe 0x3F, a 16x2-es kijelzők címe általában 0x27
  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("Magyar ékezetes");       //Írás a kijelzőre a beállított pozíciótól
  lcd_setCursor(0,1);                 //LCD kurzor a 2. sor 6. karakter pozícióba
  lcd_print("Bemutató program"); 
  delay(2000);

  lcd_clear();                        //kijelző törlése
  lcd_setCursor(0,0);                 //LCD kurzor a 1. sor 1. karakter pozícióba
  lcd_print("Írás karkt.-ként"); 
  lcd_setCursor(0,1);                 //LCD kurzor a 1. sor 1. karakter pozícióba
  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("Háttér világitá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 vilá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("Világítás marad!");delay(2000);
  lcd_noDisplay();delay(2000);lcd_display();delay(2000);
  //kikapcsoljuk a display-t. Teljes kikapcsolá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 pozíció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 mozdítjuk egy-egy pozíció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 pozíció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épernyő 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ő karakter a harmadik sorban lép be 
  lcd_setCursor(10,1);
  for (byte i=0; i<10; i++) {lcd_print_long(i,0,0);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épernyő 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ő karakter a harmadik sorban lép be 
  lcd_setCursor(10,1);
  for (byte i=0; i<10; i++) {lcd_print_long(i,0,0);delay(300);} 
  lcd_noAutoscroll(); 
  lcd_leftToRight(); 
  delay(3000);

  //---------------Jobbra írási mód (alaphelyzetnek megfelelő)---------------------------------------------
  lcd_clear();lcd_print("Jobbra írás:");delay(3000);
  lcd_leftToRight(); 
  lcd_setCursor(0,1);
  for (byte i=0; i<10; i++) {lcd_print_long(i,0,0);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_long(i,0,0);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("Long (8 hossz):");
  lcd_setCursor(0,1);lcd_print_long((long) 52312,8,0);
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Long (zerofill):");
  lcd_setCursor(0,1);lcd_print_long((long) -52312,8,1);
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Float: (4.2 hossz):");
  lcd_setCursor(0,1);lcd_print_float((float)54.23923,4,2,0);         //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: (zerofill):");
  lcd_setCursor(0,1);lcd_print_float((float)54.23923,4,2,1);         //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("Dátum (no cent):");
  lcd_setCursor(0,1);lcd_print_date(22,03,20,0); 
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Dátum (cent):");
  lcd_setCursor(0,1);lcd_print_date(2022,03,20,1); 
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Dátum (no sec):");
  lcd_setCursor(0,1);lcd_print_time(5,9,25,0); 
  delay(2000);
  lcd_clear();lcd_setCursor(0,0);lcd_print("Dátum (sec):");
  lcd_setCursor(0,1);lcd_print_time(5,9,25,1); 
}

void loop() {
}

void lcd_i2cWrite(byte xadat) {     //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);
  I2CStart();
  I2CSend(_lcd_cim << 1 | 0);        
  //  Wire.write((int)(adat) | _backlightval);
  I2CSend((int)(xadat) | _backlightval);                 
  //  Wire.endTransmission();
  I2CStop();
}

void lcd_wr4bit(byte ertek) {
  lcd_i2cWrite(ertek | 0x04);   // 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 & 0xFB);   //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
}                                  


//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
}


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


//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 &= 0x07;                              //0b00000111 //Csak az alsó három bit-et vesszük figyelembe (8 tároló)
  lcd_command(0x40 | (tarolo << 3));           //0b01000000 //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]);
  }
}

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 = 0x00;        //0b00000000 háttérvilágítás alapértelmezetten kikapcsolva (I2C illesztő chip P3 port=0)
  _displaycontrol = 0x0C;      //0b00001100 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 = 0x06;         //0b00000110 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: 0b00100000)
  if (_lcd_sor > 1) {lcd_command(0x28);}     //0b00101000 több soros kijelző esetén alapértelmezetten  4 bites mód , 2 soros mód, 5x8 karaktermap mód
  else {lcd_command(0x20);}                  //0b00100000 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 0b00000001
  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] = {0x02,0x04,0x0E,0x01,0x0F,0x11,0x0F};                              //0b10,0b100,0b1110,0b1,0b1111,0b10001,0b1111 //á betű karakterképe
  lcd_createChar(0,tt);                                                           //betöltés a 0 című CGRAM byte-ba
  tt[0]=0x02;tt[1]=0x04;tt[2]=0x0E;tt[3]=0x11;tt[4]=0x1F;tt[5]=0x10;tt[6]=0x0E;   //é betű karakterképe
  lcd_createChar(1,tt);                                                           //betöltés a 1 című CGRAM byte-ba
  tt[0]=0x02;tt[1]=0x04;tt[2]=0x00;tt[3]=0x0E;tt[4]=0x04;tt[5]=0x04;tt[6]=0x0E;   //í betű karakterképe
  lcd_createChar(2,tt);                                                           //betöltés a 2 című CGRAM byte-ba
  tt[0]=0x02;tt[1]=0x04;tt[2]=0x00;tt[3]=0x0E;tt[4]=0x11;tt[5]=0x11;tt[6]=0x0E;   //ó betű karakterképe
  lcd_createChar(3,tt);                                                           //betöltés a 3 című CGRAM byte-ba
  tt[0]=0x05;tt[1]=0x0A;tt[2]=0x00;tt[3]=0x0E;tt[4]=0x11;tt[5]=0x11;tt[6]=0x0E;   //ő betű karakterképe
  lcd_createChar(4,tt);                                                           //betöltés a 4 című CGRAM byte-ba
  tt[0]=0x02;tt[1]=0x04;tt[2]=0x11;tt[3]=0x11;tt[4]=0x11;tt[5]=0x13;tt[6]=0x0D;   //ú betű karakterképe
  lcd_createChar(5,tt);                                                           //betöltés a 5 című CGRAM byte-ba
  tt[0]=0x05;tt[1]=0x0A;tt[2]=0x00;tt[3]=0x11;tt[4]=0x11;tt[5]=0x13;tt[6]=0x0D;   //ű betű karakterképe
  lcd_createChar(6,tt);                                                           //betöltés a 6 című CGRAM byte-ba
}


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(0x80 | (oszlop + sor_cimeltolas[sor])); //0b10000000 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&=0xFB;         //0b11111011 a _displaycontrol változó 3. bitje felel a kijelző be és kikapcsolásért, 0-val kikapcsol
  lcd_command(_displaycontrol);
}
void lcd_display() {
  _displaycontrol|=0x04;         //0b00000100 a _displaycontrol változó 3. bitje felel a kijelző be és kikapcsolásért, 1-el bekapcsol
  lcd_command(_displaycontrol);
}

//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=0x00;          //0b00000000 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=0x08;          //0b00001000 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_clear(){
  lcd_command(0x01);           //0b00000001 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(0x02);           //0b00000010 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
}



// 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=0x00;            //0b00000000 Háttérvilágítást vezérlő változóban a 4. bit felülírása 0-val
  _displaycontrol&=0xFB;         //0b11111011 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=0x08;            //0b00001000 Háttérvilágítást vezérlő változóban a 4. bit felülírása 1-el
  _displaycontrol|=0x04;         //0b00000100 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 &=0xFD ;       //0b11111101 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 |= 0x02;       //0b00000010 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 &= 0xFE;       //0b11111110 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 |= 0x01;       //0b00000001 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(0x18);   //0b00011000
}
void lcd_scrollDisplayRight(void) {
  lcd_command(0x1C);   //0b00011100
}


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

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

// 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 |= 0x01;               //0b00000001 A kijelző tartalmát tolja el (shift) a kurzortól kezdve 
  lcd_command(0x04 | _displaymode);   //0b00000100
}

// 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 &= 0xFE;               //0b11111110 A cursort mozgatja, és nem a kijelzőt tartalmat (no shift)
  lcd_command(0x04 | _displaymode);   //0b00000100
}

//különbözű numerikus változó típusok kiírásának függvényei
//------------------------------------------------------------------------------------------------
void lcd_print(char xstring[]) {  //az LCD kijelzőre küld egy karakter tömböt
  byte i=0;
  byte j=0;
  while ((byte)xstring[i]>8 & 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)xstring[i]==195 | (byte)xstring[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)xstring[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)xstring[i]);}
    i++;
  }
}


void lcd_print_long(long szam,byte hossz,bool zerofill) {   //egy integer értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
   char string[10];             //Számjegyek tárolására
   int index=0;                 //tömbindex
   bool minusz=0;               //negatív előjel jelzése
   byte maradek=0;              //segédváltozó a maradékos osztás eredményének átmeneti tárolására
   byte feltoltes=0;            //ha 0-nál nagyobb hossz lett megadva, akkor fel kell tölteni szóközökkel 
                                //a szám előtt a fix széleséghez.Ehhez segédváltozó, a számjegyek számát számolom benne
    if (szam<0) {               //Ha negatív, akkor kell egy mínusz jel a végén, most meg abszolut érték
      minusz=1;
      szam=-szam;
    }
    if (szam>0) {               //Ha nagyobb mint 0, akkor indítjuk az átalakítást
      while (szam>0) {
        maradek=szam%10;
        string[index]=48+maradek;    //a megjelenítendő karakter kódja ("0"=48)
        index++;                     //A tömbbe növekvő index szerint írunk, megjeeníteni visszafelé fogunk 
        szam=(szam-maradek)/10;
        feltoltes++;                 //számoljuk, hány számjeg van
      }
    }
    else {                           //ha egyenlő 0-val, akkor csak egy 0-át le kell írnunk
      string[index]=48;   
      index++;
      feltoltes=1;
    }
    if (minusz==1 && !zerofill) {    //Ha negatív, én nics zerofeltés, akkor rögtön kell egy mínusz jel
      string[index]=45;
      index++;
      feltoltes++;                   //a minusz jel is elfoglal egy karaktert, ezért eggyel kevesebb szóköz kell
    }
    if (minusz==1 && zerofill) {     //Ha negatív és nulla feltöltés van, akkor eggyel kevesebb zero kell a minusz jel miatt
    feltoltes++;}                    //ha negatív, akkor eggyel kevesebb 0-át kell írni elé
    if (hossz>0) {                   //lett megadva egész rész hosszúság, és a számunk rövidebb
      for (int i=0;i<hossz-feltoltes;i++) { //annyi szóközt rakunk be, hogy a megadott egész hossz legyen az egész teljes hosszúsága 
        if (zerofill) {
          string[index]=48;          //ez a 0 ASCII kódja
        }
        else {
          string[index]=32;          //ez a szóköz ASCII kódja
        }
        index++;
      }
    }
    if (minusz==1 && zerofill) {     //Ha negatív és nulla feltöltés van, akkor a legvégén kell mínusz jel
      string[index]=45;
      index++;
      feltoltes++;                   //a minusz jel is elfoglal egy karaktert
    }
    index--;
    for (index;index>=0;index--) {
      lcd_write(string[index]);
    }
}

void lcd_print_float(float szam, byte egesz_hossz, byte tizedes_hossz,bool zerofill) {   //egy integer értéket alakít át karaktertömbbe, és ezt küldi az lcd kijelzőre 
  lcd_print_long((long)szam,egesz_hossz, zerofill);
  if (tizedes_hossz>0) {         //kell tizedes értéket is megjeleníteni
    float tort=szam-(long)szam;  //Ez a szám törtrésze
    long szorzo=10;              //A tizedes értékből egészeket kell csinálni (egy tizedes esetén 10-el szorzunk)
    for (int i=1;i<tizedes_hossz;i++) {szorzo=szorzo*10;}  //több tizedes esetén 100-al, 1000-el stb.
    tort=round(tort*szorzo);     //A tizedesjegyek száma alapján 10 hatvánnyal, kerekítjük, vesszük az egész részét, és az erdeményt bontjuk számjegyekre
    lcd_write(46);               //tizedespont
    lcd_print_long((long)tort,tizedes_hossz,1);
  }
}

void lcd_print_date(int ev,byte ho,byte nap, bool evszazad) {
  if (evszazad) {lcd_print_long((long)ev,4, 1);}
  else {lcd_print_long((long)ev,2,1);}
  lcd_write(46);   //tizedespont
  lcd_print_long((long)ho,2,1);
  lcd_write(46);   //tizedespont
  lcd_print_long((long)nap,2,1);
}

void lcd_print_time(byte ora,byte perc,byte masodperc,bool second_disp) {
  lcd_print_long((long)ora,2,1);
  lcd_write(58);   //kettőspont
  lcd_print_long((long)perc,2,1);
  if (second_disp) {
    lcd_write(58);   //kettőspont
    lcd_print_long((long)masodperc,2,1);
  }
}

Természetesen a végén ehhez is csináltam program könyvtárat. Található a könyvtárban példa program, lényegében ugyanaz mint a fenti program setup()-ban, csak a többi függvényt az #include “LiquidCrystal_I2C_CH55X_HUN.h” sorral szerkeszti hozzá a fordító az érdemi részekhez. A könyvtárat letölheted innen ZIP formátumban. Telepítése az előzőekben leírtak szerint.

További példa programokat egyelőre nem készítettem. Ha minden a terveim szerint alakul (…és soha nem úgy alakul), akkor néhány hét múlva felrakom ide a leírás végére az Dallas DS18B20 hőmérő példa programjait. I2C-vel működő EEPROM-ra van is mintapélda. FRAM illesztése annyira egyszerű, hogy szoftveres I2C-val azonnal mehet elméletileg. A 6 digites LCD számkijelzőhöz meg csak SPI kommunikáció kell, és ehhez is vannak saját függvényeim, tehát elméletileg ez sem lehet gond. Ennyi alkatrészből már össze lehet rakni az Apolló űrhajót! Több energiát egyelőre nem akarok belefektetni!

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!