SKORI WEBLAPJA
LCD kijelzõ vezérlése 3 vezetéken

avagy...
2x16-os LCD kijelzõ vezérlése ESP8266-al és ARDUINO-val

Tervem szerint ez a cikk a közismert és gyakran használt, alfanumerikus, HD44780 vezérlõjû vagy azzal kompatibilis kijelzõ, nem a leginkább szokványos módon történõ vezérlésérõl fog szólni, némi kitérõvel az ESP8266 wifi chip és az arduino programozás felé.

A dolog azzal kezdõdött, hogy elkezdtem ismerkedni az ESP8266 Wifi chippel. Ez egy nagyon olcsón (szinte fillérekért) beszerezhetõ áramkör, az ezzel készült fejlesztõ panelhez, legolcsóbban jelenleg (2017 jan.) kb. 1000Ft körüli áron lehet hozzájutni. Az általam beszerzett wifi chip egy 80MHz-es órajelû mikrovezérlõt tartalmaz, I/O portokkal, 96Kb RAM-al, 4Mb flash memóriával, 2,4GHz-es wifiel, és integrált antennával. A fejlesztõ panel ezen kívül tartalmazza az USB illesztõt a programozáshoz, a 3,3V-os tápegységet a wifi chip számára, reset és flash nyomógombot. Szóval ez egy olcsó "elekrtomos programozós LEGO" :), lehet vele játszani.

A netet böngészve a programozási lehetõségekrõl, nagyjából 3 lehetõség körvonalazódik:
  1. AT parancsokkal történõ vezérlés.
    Ezzel a módszerrel soros porton keresztül pl. a GSM chipekhez, vagy modemekhez hasonlóan, úgynevezett AT parancsokkal vezérelhetjük a wifi chipet. Megfelelõ parancs van a wifi csatlakozáshoz, hálózat listázáshoz és minden egyébhez. Teljesen jól használható ez a módszer, de a verzérlõ parancsok küldéséhez kell egy mikrovezérlõt illeszteni az eszközhöz.
     
  2. LUA parancsokkal vagy programmal történõ vezérlés.
    Az ESP chipre LUA fordító is rátölthetõ, a LUA nyelven írt programunkat soros porton keresztül fel lehet tölteni az eszközünkre, és futtatni tudja a tárolt programot. Kisebb vezérlési feladatokra, egyszerübb weboldal megjelenításre ezzel simán alkalmas önmagában is, külön mikrovezérlõ nélkül. Ahoz, hogy az ESP-t programozni tudjuk nem kell profinak lenni a LUA rejtelmeiben, az alapok pedig könnyen megtanulhatók. Érzésre persze nekem kicsit olyan mintha egy régi számítógé BASIC nyelvû interpreterét használnám: azaz jópofa játék, de egy idõ után ez is kevés.
     
  3. Programozás ARDUINO IDE-vel A közismert ARDUINO-val foglakozó fejlesztõk hamar felismerték az ESP8266-ban rejlõ lehetõségeket, és elkészültek a megfelelõ kiegészítõk ahoz, hogy ugyanúgy programozhassuk mint bármelyik arduino fejlesztõpanelt. Ezzel gyakorlatilag egy C vagy C++ nyelvhez nagyon hasonló programnyelven készíthetünk programokat, aminek a futtatásához az ESP-n kívül semmilyen külsõ mikrovezérlõre nincs szükség. Az ESP flash memóriájában filerendszer hozható létre igy a saját programján kívül fájlokat is tárolhatunk rajta (pl ha webszervert készítünk belõle, tárolhatjuk rajta a HTML fájljainkat, képeket, bármit)
Miután a lehetõségeket megismertem, az AT parancsos megoldást kapásból elvetettem, mert kissé pazarlásnak érzem, hogy egy 80MHz-es mikrovezérlõ mindössze AT parancsokat dekódoljon és hajtson végre amikor sokkal többre is képes lehet. Arról nem beszélve, hogy sokkal több energia kell hozzá mire egy olyan mûködõ dolgot készítünk így, ami már használható is valamire és nem csak játszottunk vele. A LUA nyelvvel próbálkoztam egy darabig, de nem tudtam igazán megszeretni, és ezzel is kevésnek éreztem a lehetõségeimet. Aztán jött az arduino, ami véleményem szerint jelenleg a legjobb fejlesztõ környezet az ESP-hez, ezzel lehet a leginkább kihasználni a lehetõségeit, és ezzel lehet akár tobábbi komolyabb hardver nélkül is a legjobb dolgokat készíteni ezzel a chippel. Készítettem is egy webszervert, ami jelenleg annyit tud, hogy a webfelületrõl konfigurálhatók a wifi beállítások, több hálózat megjegyzésére képes, a lehetõségek közül megkeresi a legjobban vehetõ hálózatot és ahoz próbál elõször csatlakozni. Létrehoz egy AP-t is, ha nincs olyan wifi hálózat amihez sikerül csatlakoznia, akkotr ezen keresztül is el lehet érni. A HTML és egyéb fájlokat a meglevõ webfelületrõl is fel lehet tölteni az ESP fájlrendszerébe, vagy éppen törölni róla ami már nem kell. Tehát mostmár sokszor a fejlesztõ környezet sem kell a webszerver által megjelenített oldalak bövítéséhez.

Most jön a képbe az LCD kijelzõ. Arról van szó, hogy pl. amikor egy wifi hálózathoz csatlakozik az eszköz, akkor kap egy dinamikus, hálózat specifikus IP címet, amin keresztül elérhetjük pl. a webfelületét. Ahoz, hogy ezt a címet ne kelljen keresni, jó lenne kiírni egy kijelzõre, más egyéb adatokkal együtt. A legegyszerübb megoldás erre a célra az lenne, ha egy mezei 2x16-os LCD kijelzõt kapcsolnánk rá az ESP-re. Ennek a kezelése simán belefér az ESP mikrovezérlõjének feladataiba. Azonban itt beleütközünk abba, hogy egy apró olcsó chippel dolgozunk, aminek nincs túl sok I/O portja, az LCD kijelzõ meg minimum 6 adatvonalat igényel 4bits módban, és 10 adatvonalat 8 bites üzemmódban. Ha az ESP I/O lábait másra is akarjuk használni, akkor célszerû spórolni vele, tehát szerettem volna viszonylag kevés I/O láb felhasználásával megoldani a dolgot. Ráadásul nagyon egyszerû, gyors, és nagyon olcsó megoldást kerestem, és meg is találtam: 74HCT595 Ez egy olcsó, soros bemenetû, soros/párhuzamos kimenetû léptetõregiszter, tárolóval egybeépítve. Lehet, hogy ez így nem sokat mond, de arról van szó, hogy egy órajel ütemében egyetlen adatvonalon kiküldhetjük sorban az adatbiteket, az IC-nek majd egy impulzussal ez beíródik a tárolóba, és megjelenik az IC kimenõ lábain. Ebbõl annyi a lényeg, hogy ha az alánbbi kapcsolást megépítjük, akkor a mikrovezérlõtõl mindössze 3 I/O lábra lesz szükségünk ahhoz, hogy az LCD kijelzõn megjelenítsük amit szeretnénk. Az I/O lábak funkciói: órajel, adat, és beírás/ENable. A dologban még az a vicces, hogy ilyen módon még könnyebb is leprogramozni az LCD vezérlését, ahhoz képest mintha közvetlenül a mikrovezérlõre lennen kapcsolva.
Tehát a kijelzõhöz úgy jut el a 8 bit adat, hogy az elsõ bitet beállítjuk a data lábon, majd a clock lábra adunk egy impulzust, majd ugyanígy járunk el a többi adatbit esetében is. Ezután a data lábat beállítjuk aszerint, hogy adat vagy parancs bájtot küldünk-e a kijelzõnek, és adunk egy impulzust az enable lábra. Az impulzus felfutó élére a tárolóba beíródik a 8 adatbit amit kiléptettünk, és megjelenik az LCD kijelzõ lábain, a lefutó élére pedig megjelenik/végrehajtódik a kijelzõn. Ebbõl a mûködésból szointe adódik a kapcsolás is: Az IC SCK lába lesz az órajel bemenet, a SERial input lába az adatbemenet ami egyúttal az LCD parancs/adat lábával is össze van kötve, a beírás/ENable bemenet pedig az IC tároló bemenete és az LCD EN lába összekötve.
A léptetõregiszternek több változata is van, pl: 74HC595 és 74HCT595 a fenti áramkörhöz a HCT verzióra van szükség. Az ESP 3,3V-os digitáslis jelekkel dolgozik, az LCD kijelzõ 5V-os tápot kap, bár mûködik 3,3V-os jelszintekkel is. Ha a léptetõregiszter 5V-ról mûködik akkor csak a HCT verzió fogadja el a 3,3V-os jelszintet. A sima HC verziónak csak akkor "kötelessége"jól mûködnie 3,3V-os jelszintekkel, ha errõl a tápról üzemel. A gyakorlatban ugyan 5V-ról is mûködött sima HC verzióval az áramkör, de ez nem garantálható, ezért nem ajánlott. A léptetõregiszter létezik SMD és normál lábas tokozással is, minkettõhöz készitettemm NYÁK tervet:
Szokásomtól eltérõen a kapcsolási rajzot és a nyáktervet is közzéteszem az EAGLE nyáktervezõ program formátumában is: SKORI_LCD_SHIFT
A kijelzõbe tüskesort, a nyákba pedig tüskesor aljzatot ültettem be, így a nyák egyszerüen rádugható a kijelzõre. A bemeneti pontokba pedig szintén tüskéket ültettem be, de végülis vezetékek is beforraszthatók. A nyákterv THT IC verzióhoz készült változata úgy van kialakítva, hogy raszteres próbanyákon is könnyedén megéíthetõ legyen - azaz minden furat a nyákterven pontosan raszterre eseik.

Léteznek persze más megoldások is erre a feladatra, pl. a mindössze 1 adatvonalat igénylõ soros portos megoldás, ehez azonban egy külön mikrovezérlõt kellene felprogramozni. A fenti áramkört viszont csak össze kell rakni és egybõl használható is. A léptetõregsiszter sokkal gyorsabb mint ahogy kijelzõ tudná fogadni az adatokat, így a sebességet továbbra is az utbbi határozza meg. Tehát a léptetõregiszter használata nem kíván semmivel sem több processzoridõt a mikrovezérlõtõl, mint az LCD közvetlen vezérlése. Mint kicsit lejjebb látható is lesz a programkód még egyszerûbb is lesz mint ami a közvetlen vezérléshez kellene. Nézzük a mûkedéshez szükséges programkódot ill. függvényeket:
#define LCDSH_data  13
#define LCDSH_clock 14
#define LCDSH_en    12


void lcdsh_wrbyte(unsigned char data, int delay=0)             //1 bájt adat vagy parancs küldése az LCD-nek
{
  for (byte mask=128; mask ; mask >>=1)                        //8-szor fog lefutni
    {
      digitalWrite(LCDSH_data, mask & data);                   //az aktuális adatbit beállítása
      delayMicroseconds(1); digitalWrite(LCDSH_clock, 1);      //órajel impulzus felfutó él
      delayMicroseconds(1); digitalWrite(LCDSH_clock, 0);      //órajel impulzus lefutó él -> 1 órajel impulzus
    }                                                          //ciklus vége, mind a 8 adatbit elküldve

  digitalWrite(LCDSH_data, !delay);                            //LCD-RS beállítása, ha nincs delay, akkor sima adatbyte volt

  digitalWrite(LCDSH_en, 1);                                   //1db EN impulzus: felfutó él az adatok->tárolva
  delayMicroseconds(1);
  digitalWrite(LCDSH_en, 0);                                   //EN impulzus lefutó él az adatok->tárolva->LCD-be írva

  if (delay) delayMicroseconds(delay);                         //ha volt megadva várakozás, akkor parancsot küldtünk,
                                                               //és várakozás az LCD parancs befejezõdésére)....
}//end fv
A fenti programrészletben elõször definiáltam a 3db I/O portkivezetést amikett használok az LCD vezérlésáhez.
Az lcdsh_wrbyte() függvény funkciója 1 bájt elküldése az LCD kijelzõnek. Tehát pl. az lcdsh_wrbyte('a'); parancs egy kis a betût fog kiírni a kijelzõre. Ha a függvénynek két paramétert adunk, akkor az elsõ bájtot parancsként elküldni a kijelzõnek, majd a második paraméter szerinti usec. ideig várakozik (a parancs végrahajtási idejét adjuk meg itt). Teháp pl. az lcdsh_wrbyte(0x80,50); parancssal a kurzort az esõ sor elsõ poziciójába teszi, és megvárja amíg a kijelzõ végrehajtja a parancsot. Tehát ezzel a függvénnyel kezelni tudjuk az LCd kijelzõ szinte összes funkcióját. Nagyjából erre a fügvényre épül az összes többi LCD kijelzõt kezelõ függvény. A mûködése: beállít egy mask változót 128-ra (gyak. a felsõ bit 1, a többi nulla) Ezt a bitet lépteti jobbra míg a változó nulla nem lesz. A mask-al éselve a küldendõ bájtot, mindíg a következõ bitjét kapjuk meg a ciklusban. Ezzel be is állíthatjuk a DATA lábat, majd egy órajelimpulzus és el is küldtünk 1 bitet. A ciklus 8szor lefut és ezzel el is küldi a bájtot. Ezután beállítja az LCD RS lábát (adat/parancs mód), majd egy impulzus az EN lábra, és az LCD kijelzõ meg is kapta az adatokat. Ha parancsot küldtünk, akkor célszerû megvárni amíg végrehajtja a kijelzõ, mert ha hamarabb küldünk uj adatot/parancsot akkor azt nem fogja értelmezni a kijelzõ. Azonban mielõtt használnmi kezdhetjük a kijelzõt a programban, még inicializálni kell, ez annyit jelent, hogy be kell állítani a kijelzõ üzemmódját, és egyéb paramétereit. erre szolgál az alábbi függvény:
void lcdsh_Init(){
  pinMode(LCDSH_data,  OUTPUT);
  pinMode(LCDSH_clock, OUTPUT);
  pinMode(LCDSH_en,    OUTPUT);                              //vezérlõlábak beállítása: kimenet
  lcdsh_wrbyte(B00111000,5000);                              //8bites mód, 2soros mód, 5msec várakozás
  lcdsh_wrbyte(B00001000,50);                                //disp. off, 50usec
  lcdsh_wrbyte(B00111000,50); lcdsh_wrbyte(B00111000,50);    //8bites mód, 2sor , duplán kell kiadni a parancsot
  lcdsh_wrbyte(B00001100,50);                                //disp. on, cursor off, blink off, 50usec
  lcdsh_wrbyte(B00000001,2000);                              //clear, 2msec
  lcdsh_wrbyte(B00000010,2000);                              //cursor home, 2msec
  lcdsh_wrbyte(B00000110,50);                                //entry mod increase, no shift, 50usec
  lcdsh_wrbyte(B10000000,50);                                //set DD ram address, 50usec
}//end fv
Tehát a fenti függvényt elégendõ egyszer lefuttatni, mielõtt használni kezdjük a kijelzõt. Azonban elõpfordulhat olyan eset hogy bizonyos kijelzõk érzékenyek a környezeti zavarokra és "kiakadnak" ilyenkor ujra lehet inicializálni. Elõfordulhat olyan eset is, hogy menet közben dugjuk rá, vagy cseréljuk le a kijelzõt a készüléken, ez ugyan nem szép dolog, de ha ilyenkor a programban ujra lefut az inicializálás, akkor már használható is a kijelzõ.

A hardvert és a programot kipróbáltam sokféle LCD kijelzõvel, és kompatibilis, alfanumerikus OLED kijelzõvel is, és mindegyikkel jibátlanul mûködött. A késleltetési ídõkbõl talán lehetne lefaragni, de jelenleg sem mondható lassúnak a kijelzés, kb. 40usec egy karakter kiírása, azaz alig több mint 1msec (1 ezredmásodperc) alatt tele lehet írni a 2x16-os kijelzõt. Rövidebb idõzítéseket használva nekem elõfordult, hogy némelyik kijelzõ idõnként elvesztett egy karaktert, de lehet, hogy az említett (bontott) kijelzõ nem teljesítette az adatlapján szereplõ paramétereket. Nézzük, hogy lehet szöveget kiírni a kijelzõre, ill. kurzort pozicionálni:
void lcdsh_str(char * s){                       //string kiírása
  for ( ; *s ; s++) lcdsh_wrbyte(*s);           //a ciklus karakterenként küldi az LCD-nek a szöveget
}//end fv

void lcdsh_str(char *s, byte len){             //string kiírása, len karakteren (ha hosszabb levágja, ha rövidebb szóközzel kiegészíti)
  for ( ; *s && len; s++,len--) {lcdsh_wrbyte(*s); }
  for ( ;len; len--) lcdsh_wrbyte(32);
}//end fv

void lcdsh_Line1 (){                           //kurzor az elsõ sor elejére
  lcdsh_wrbyte(0x80,50);
}//end fv

void lcdsh_Line2 (){                           //kurzor a második sor elejére
  lcdsh_wrbyte(0xC0,50);
}//end fv

void lcdsh_Cursor (char oszlop, char sor){     //kurzor adoss sor/oszlop pozicióba
  switch (sor) {case 2: oszlop += 0x40; break; case 3: oszlop += 0x14; break; case 4: oszlop += 0x54; break; };
  lcdsh_wrbyte( oszlop|0x80,50 );// set RAM address
}//ed fv

//példák:
lcdsh_str("Hello Wolrd");                     //kiírás a kurzor poziciójától kezdve
lcdsh_Line1();lcdsh_str("Elso sor",16);       //szöveg kiírása az eslõ sor elejétõl, de végig kitölti a sort,
                                              //így ami elõzõleg volt a kijelézõn az egyúttal törlõdik.
A programot az Arduino fejlesztõrendszerében írtam, tehát igen jó eséllyel használható bármilyen arduino eszközön, nem csak kizárólag az ESP8266-on. Aki programozott már más mikrovezérlõt, annak nyilván nem gond ilyen programot megírni, de a fenti kód is pillanatok alatt átírtható szinte bármilyen mikrovezérlõre. Remélem sokaknak hasznára válik a fenti cikk, és lesz akinek talán sikerül öltletet adni. Esetleg valaki pont ESP8266-hoz szeretne egy kijelzõt illeszteni.

u.i.
Köszönet Attila86-nak, hogy legyártotta kérésemre ezt a kis nyákot!
Köszönet Ottónak, hogy segitett az SMD 74HCT595 forrasztásában, mivel Õ ezt még szabadszemmel is látja...
Az elgépeléseket, hibákat kérem elnézni, mivel nem sok idõm volt erre a cikkre, így a tartalom volt az elsõdleges.
Aki valami nagyon zavaró hibára akad, azt mailban elküldheti nekem, és amint lesz rá idõm javítani fogom!
Úgy néz ki máris elérkezett a folytatás ideje:
LCD kijelzõ vezérlése 2 vezetéken
Mielõtt a lényegere térek jöjjön pár sor a miértrõl is. A 3 vezetékes megoldás szoftverével kisérleteztem , hogy meddig lehet lefaragni a késleltetési idõket, úgy, hogy még stabil maradjon a kijelzõ mûködése. A gyakorlatban arra jutottam, hogy az szinte mindegy, hogy hol vannak berakva a késleltetések, anyi a lényeg, hogy kb 40...50µsec alatt lehet kiírni egy karaktert. Ha ennél lassabban írjuk a kijelzõt akkor semmi gond, viszont ha gyorsabban akkor véletlenszerûen kihagy karaktereket. Ebbõl az is következik, hogy pl. az órajel esetében nem szükséges L és H szinten ugyanannyi várakozást betenni a programba, hanem lehet asszimmetrikus is az órajel. Ezt tovább gondolva viszont az órajel kitöltési tényezõje (avagy az L és H szint idõaránya) is hordozhat információt, ha ezt kihasználjuk. A 3 vezetékes megoldásból a "data" vezetéket az alábbi kapcsolással megspórolhatjuk, úgy, hogy a léptetõregiszterbe írandó adatot, az órajelbõl állítjuk elõ, egy 1µs idõállandójú R-C taggal.:


A fenti áramkört megvalósítva, a kijelzõt ugyanakkora sebességgel tudjuk kezelni mint a 3 vezetékes módszerrel, azaz a még mindíg kijelzõ korlátozza a sebességet, ill. ettõl függ a kiírásra fordított idõ, tehát lényegében nem igényel több erõforrást ez a megoldás. A kijelzõ RS (adat/parancs) kivezetését ami eddig az adat lábra volt kötve, átraktam az órajel lábra. A szoftver esetében a lcdsh_wrbyte függvényt kell átírni, illetve az LCDSH_data I/O láb definiálását kell törölni a programból. Tehát az alábbi programkóddal lehet elküldeni 1 bájtot a kijelzõnek:
void lcdsh_wrbyte(unsigned char data, int delay=0){                // 1 bájt adat vagy parancs küldése az LCD-nek, 2 I/O lábon
  for (byte mask=128; mask ; mask >>=1){                           // 8-szor fog lefutni
        digitalWrite(LCDSH_clock, mask & data);                    // clock lábbeállítása az adatbitnek megfelelõen
        delayMicroseconds(3);                                      // 3µs várakozás, hogy a kondi feltöltödjön vagy kisüljön
        noInterrupts();                                            // a felfutó él elõtt tiltani kell a megszakításokat
        digitalWrite(LCDSH_clock, 0);digitalWrite(LCDSH_clock, 1); // felfutó él az órajelben a "kondiban tárolt" bit beírásához a léptetõregiszterbe
        interrupts();                                              // a felfutó él után ujra lehetnek megszakítások
  }//end for //ADATBITEK beáll
  digitalWrite(LCDSH_clock, !delay); 							   //RS beállítása
  digitalWrite(LCDSH_en, 1);  delayMicroseconds(1); digitalWrite(LCDSH_en, 0);      //EN impulzus
  if (delay) delayMicroseconds(delay-25);                          //ha volt megadva várakozás, akkor várakozik (az LCD parancs befejezõdésére)
}//end fv
A program nem igazán lett bonyolultabb, de a mûködése kicsit érdekesebb. A for ciklusban, ugyanúgy mint a korábban, egy mask változó bitléptetésével választja ki az elküldendõ bitet. Elõször beállítjuk az órajel lábat az adatbitnek megfelelõen, majd 3 µsec várakozás következik, hogy a 10k ellenálláson keresztül a 100pF kondinak legyen ideje feltöltõdni vagy kisülni. Ezután egy felfutó élt (0 majd rögtön 1) írunk ki a clock lábra - ennek hatására "kondiban tárolt" bit beíródik a léptetõregiszterbe. A felfutó él ideje alatt tiltani kell a megszakításokat, mert ha ennek az idejét megnyújtaná egy megszakítás, akkor a kondi töltése megváltozhatna és hibás bitet írna ki a regiszterbe a program. A cilkusmag mindigh H szintû órajelre végzõdik, így a ciklusmag következõ futásakor az adatbit beállítása nem okozhat fals L-H átmenetet (azaz feltfutó élt). Erre egyedül a cilkus legelsõ futásakor van lehetõség, ami egy hibás bit kiírását eredményezhetné, ami azonban azért nem okoz problémát, mert utána még 8 bitet kiléptetünk, így az esetleges hibás bit nem maradhat benne a léptetõregiszterben. A cilkus lefutása után, tehát ha kiléptettük mind a 8 bitet, a clock lábon beállítjuk a kijelzõ RS lábát (vagy marad H szinten vagy L szint lesz). Itt egyik esetben sem, lesz felfutó él, így a léptetõregiszter tartalma emiatt nem változik meg. Ezután az EN-re adott impulzussal a korábbiakhoz hasonlóan a kijelzõ megkapja a küldött bájtot. Ezután ha volt delay, azaz parancsot küldtünk a kijelzõnek, akkor várakozunk a parancs lefutási idejére. Ez utóbbiból le lehet vonni a bájt kiírási idejének egy részét (-25µs). A korábban közölt függvényeket nem kell módosítani, ugyanúgy használhatók lesznek, de az inicializáló függvénybõl ki kell venni az LCDSH_data azaz a data láb kezelését.
Ha nen használ megszakításokat az az eszköz amire a programot készítjük vagy átírjuk, akkor a megszakítás tiltás/engedélyezés parancsot kihagyhatjuk a programból.
A 2 kivezetéses LCD kijelzõ illesztést kipróbáltam többféle LCD, + egy OLED kijelzõvel, és teljesen megbízhatóan, stabilan mûködött.
Ahogy elnézem a programja végülis nem is lett bonyolultabb mint a 3 vezetékes verzió hasonló függvénye. Ha valaki kipróbálja ezt a megoldást más arduinoval, esetleg teljesen más hardverrel, akkor szívesen fogadnám a tapasztalatait.
Skori
@2017.jan.