LCD kijelzők vezérlése

A fejezet tartalma:
Figyelmeztetés: Ennek a fejezetnek a mintapéldáit nem használhatjuk az USB kapcsolattal együtt, mivel ezekben ugyanazt az _user_putc() függvénynevet használjuk az LCD-re történő íráshoz ami, az USB kommunikáció számára van fentartva!

Ebben a fejezetben az alfanumerikus (szöveges információ megjelenítésére alkalmas) LCD kijelzők  vezérlésével foglalkozunk. Folyadékkristályos kijelzőkkel (LCD = Liquid Crystal Display) a mindennapi életben számos területén találkozunk, hiszen elterjedten alkalmazzák különféle készülékek (pl. lézernyomtatók, digitális karórák, kenyérsütők, zsebszámológépek, telefonok, italautomaták, digitális mérlegek, digitális lázmérők) kijelzőjeként.

A folyadékkristályok olyan anyagok, amelyek folyékony halmazállapotúak, de molekuláik bizonyos mértékig rendezettek, emiatt bizonyos hőmérséklet-tartományban, vagy elektromos erőtérben optikai viselkedésük a szilárd kristályokhoz hasonló.  A folyadékkristályos kijelző általában két üveglemez között helyezkedik el. Az alsó üveglemezt teljes felületén összefüggő vezetőréteggel borítják, a felső üveglemezen a vezető réteg a megjelenítő alakzat(ok)nak megfelelően különálló részekből áll. Mindig azokra a részekre kerül elektromos feszültség, amelyekből a kívánt ábra előállítható. A feszültség hatására a folyadékkristály molekulái polarizálódnak, az elektromos tér irányába igyekeznek fordulni, s a folyékony kristály ezen a részen a rá eső fényt szórja, átlátszatlanná válik. Az elektromos feszültség megszüntetésével a folyadék újra átlátszóvá válik.

Az LCD kijelzőknek sok fajtája létezik. A legegyszerűbb, ún. passzív kijelzőkön belül is kétféle csoporttal,  kétféle működéssel találkozhatunk:
Az alábbi ábrán egy Optrex gyártmányú DMC-50399 típusú alfanumerikus kijelző fényképe látható. A kijelző 2x20 karakteres, amelyből most csak 2x16 karaktert használunk (látható, hogy a sorok végén 4-4 karakter helye üresen maradt. Megfigyelhetjük, hogy a karakterek 5x8 képpontból (pixel) rajzolódnak ki. Jól látható, hogy a karakterek legalsó pontsora üres (ez a kurzor megjelenítésének van fenntartva), tehát a karakerek valójában csak 5x7-es pontmátrixot használnak.

1. ábra: Alfanumerikus kijelző közeli képe, melyen megfigyelhető a karakterek pontmárixos felépítése

A szöveges információ megjelenítésére alkalmas alfanumerikus LCD kijelzők vezérlő elektronikával egybeépítve, komplett modulként kaphatók. A vezérlő többnyire a már ipari szabványak tekinthető  HITACHI HD44780 vagy azzal kompatibilis IC, mely 8 bites adatúttal és három vezérlőjellel csatlakozik a külvilághoz. A kijelző panel kivezetései egy sorban (2. ábra) vagy két sorban (3. ábra) helyezkednek el. (Megjegyzés: az 2. és 3. ábrán a felirat képszerkesztővel készült, ezért a karakterek elhelyezkedése és formája nem valósághű!)

Az alfanumerikus LCD modulok bekötése

A háttérvilágítással rendelkező kijelzőknek összesen 16 kivezetése van, a reflexiós típusúaknak pedig 14. Előfordul azonban, hogy a  nem használt 15. és 16. kivezetést is elkészítik, hogy a panel csatlakozója kompatibilis legyen a háttérvilágítással rendelkező kijelzőkével.


2. ábra: Alfanumerikus LCD kijelző egysoros kivezetéssel


3. ábra: Alfanumerikus LCD kijelző kétsoros kivezetéssel

Az LCD modul kivezetéseinek funkcióit az alábbi táblázatban foglalhatjuk össze:

1. táblázat: Az LCD modul kivezetései
SorszámJelFunkció
1.VSSA tápegység negatív sarka, GND
2.VCCTápfeszültség (többnyire +5 V)
3.VEEKontraszt szabályozó jel (többnyire 0,5 V körül)
4.RSParancs/adat választó (Register Select
5.R/WOlvasás/írás (adatáramlási irány választása)
6.EEnegedélyező jel (Enable)
7.D0Adatbusz 0.bit
8.D1Adatbusz 1.bit
9.D2Adatbusz 2.bit
10.D3Adatbusz 3.bit
11.D4Adatbusz 4.bit
12.D5Adatbusz 5.bit
13.D6Adatbusz 6.bit
14.D7Adatbusz 7.bit
15.AA háttérvilágítás pozitív sarka (LED anód)
16.KA háttérvilágítás negatív sarka (LED katód)
A D7-D0 lábak a kétirányú adatvonalak,amelyeken keresztül a mikrovezérlő és az LCD moduladatokat cserél. A HD44780 vezérlőnek kívülről két regiszterét érhetjük el. A parancsregiszterbe a vezérlő utasításokat tölthetjük bele (pl.képernyőtörlés, kurzor mozgatása, üzemmód beállítása, stb). Az adatregiszterbe a kiírandó karakterek kódját, vagy a felhasználó által definiálható karakterek bitképét írhatjuk be. A regiszterek közötti választás az RS vonal (RS = Register Select) beállításával történhet: ha az RS lábra magas szintet kapcsolunk (logikai '1'), akkor az adatregisztert, ha pedig alacsony szintet (logikai '0') kapcsolunk, akkor a parancsregiszter választhatjuk ki.

Az R/W vonalon az adatáramlás irányát állíthatjuk be: R/W = 0 írást, R/W = 1 olvasást jelent. Az E (Enalble) vonal alaphelyzetben alacsony (logikai '0') szinten van. Az adatvonalak és az RS, R/W vezérlő vonalak beállítása után rövid időre az E vonalat magas szintre, majd újra alacsony szintre állítjuk (pulzálás). A tényleges adatátvitel ekkor, az E jel magas szintre váltásakor történik. Ügyelnümk kell az időzítések betartására.


 4. ábra: A HD44780 vezérlő jeleinek ídőzítése íráskor


A tAS (Address Set = cím beállás) és a tAH (Address Hold = cím tartása)előírt minimális idejének kivárás automatikusan teljesül, ha az E jelet külön utasítással (pl. bsf/bcf) végezzük, hiszen akkor egy utasításciklusnyi (kísérleti áramkörünk esetében ez 83 ns-ot jelent) várakozási idő lesz az E jel változását megelőző vagy követő események után/előtt. Az E jel magas szinten történő tartása érdekében azonban legalább két-három NOP ciklust be kell iktatnunk a bsf és bcf utasítások közé! Más típusú (HD44780 kompatibilis) vezérlő esetén azonban a 4. ábrán láthatótól eltérő időzítést is előírhat az adott vezérlő adatlapja.

Az alábbi ábrán bemutatjuk, hogy az LCD modult hogyan kapcsoljuk össze a kísérleti áramkörünkkel. Az ábrán a PIC18F14K50 mikrovezérlő esetét mutattuk be. A PIC18F4550 mikrovezérlő esetén is a LEDport-ot (vagyis az RD0...RD7 kimeneteket) használjuk az LCD modul adatvonalainak meghajtására.

5. ábra: Az LCD modul bekötési vázlata (8 bites adatút esetén) 

Megjegyzések:

Négybites bekötés

Az LCD modulok HD44780 vezérlője az 5. ábrán láthatótól eltérő bekötéssel is tud működni:
Ezekkel az egyszerűsítésekkel elérhető, hogy kevesebb (akár 6 db, az 5. ábrán látható 11 helyett) I/O kivezetés felhasználásával is megoldható az LCD modul vezérlése. Az R/W bemenet vezérléséről történő lemondás (fix késleltetés használata a foglaltság jelzőbit figyelése helyett) másik előnye az, hogy így 3,3 V-os jelszinttel dolgozó mikrovezérlőt is használhatunk az LCD modul vezérlésére, s nem kell attól tartanunk, hogy az LCD modul olvasáskor 5 V-os jeleket ad a mikrovezérlőnek az 5 V-os jelszintet nem toleráló bemeneteire (lásd: Explorer16 Demo Board Errata).

Az alfanumerikus LCD modulok parancskészlete

HD44780 vezérlő által elfogadott parancsokat az alábbi táblázatban foglaltuk össze, ahol a nyolc adatbiten kívül az RS és az R/W vezérlő jelek állapotát is feltüntettük. Vegyük észre, hogy a parancsokat a legmagasabb helyiértéken található 1-es azonosítja (lásd az alábbi táblázat átlóját!). A parancs esetleges paraméterei az aacsonyabb helyiértékeken helyezkednek el. A "*" jellel megjelölt pozíciók értéke közömbös (don't care bit).

2. táblázat: A HD44780 vezérlő parancskészlete
RWRSD7D6D5D4D3D2D1D0Funkció
0000000001Clear display: Törli a képernyőt és alaphelyzetbe helyezi a mutatót (0 cím)
000000001*Cursor home: Alaphelyzetbe állítja a mutatót és a képernyő eltolást.
00000001I/DSEntry mode set: Beírási mód (I/D = mutató léptetés, S= képernyő eltolás) beállítása. 
0000001DCBDisplay control: Display ki/be (D), mutató ki/be (C), villogó kurzor ki/be (B)
000001S/CR/L**Shift cursor/screen: Mutató vagy a képernyő (S/C) eltolása jobbra/balra (R/L)
00001DLNF**Function set: adatút-szélesség (DL), sorok száma (N) és fontméret (F) beállítása
0001A5A4A3A2A1A0CGRAM cím beállítása
001A6A5A4A3A2A1A0DDRAM cím beállítása
10BFA6A5A4A3A2A1A0Read busy flag: foglaltság figyelése
01D7D6D5D4D3D2D1D0Write data: adat írása a korábban megcímzett CGRAM vagy DDRAM területre
11D7D6D5D4D3D2D1D0Read data: adat olvasása a korábban megcímzett CGRAM vayg DDRAM területről
A paraméterek jelentése az alábbi táblázatból olvasható ki:

3. táblázat: A fenti parancsok paramétereinek jelentése
I/D0: csökkent a kurzor pozícióját1: növeli a kurzor pozícióját
S0: nincs képernyő eltolás1: van képernyő eltolás
D0: megjelenítés letiltása1: megjelenítésengedélyezése
C0: kurzor megjelenítés letiltása1: kurzor megjelenítés engedélyezése
B0: kurzorvillogás letiltása1: kurzorvillogás bekapcsolása
S/C0: kurzor mozgatása1: képernyő eltolása
R/L0: balra mozgatás1: jobbra mozgatás
DL0: 4 bites adatáramlás1: 8 bites adatáramlás
N0: egysoros megjelenítés1: kétsoros megjelenítés
F0: 5x7 font1: 5x10 font
BF0: parancsfogadásra kész1: foglalt, belső művelet végrehajtása zajlik

Az LCD vezérlő utasításai kicsit részletesebben

A Clear Display utasítás szóközzel tölti fel az adatmemóriát (DDRAM). Utána nullára állítja a címregiszter és alaphelyzetbe állítja a képernyőeltolást. Más szavakkal: törli a képernyőt és a kurzor a bal felső sarokban jelenikmeg (ha láthatónak van beállítva). Az I/D bitet pedig növekményes módba állítja (a kurzor pozíció eggyel nő minden beíráskor).

A Cursor Home utasítás nullára állítja a címregisztert és a képernyő eltolást. A DDRAM tartalma nem változik. a kurzor a bal felső sarokba kerül.

Az Entry mode Set utasítás a beviteli módot állítja be. Az I/D bit értéke szabja meg, hogy karakter beíráskor a kurzor jobbra lépjen (I/D=1) vagy balra lépjen (ennek megfelelően a következő karakter kórja a DDRAM következő, eggyel nagyobb vagy kisebb című rekeszébe kerüljön). A beállított kurzorléptetési irány vonatkozika CGRAM-ba történő írásra is. Az S bit a képernyő eltolását szabályozza. A képernyő eltolását az S = 1 beállítás engedélyezi. Egyébként a képernyő nemtolódik el. Ha a képernyő eltolást engedélyezzük, úgy látjuk, mintha a kurzor nem mozdulna el beíráskor, a képernyő viszont igen. A képernyő nem tolódik el, amikor olvasunk a DDRAM memóriából. Nem tolódik el aképernyő a CGRAM írása vagy olvasása esetén sem.
 
A Display Control utasításnak három paramétere van: D, C és B, amelyekel a megjelenítést (D=display), a kurzort (C=cursor) és a kurzor villogását (B=blink) kapcsolhatjuk ki (0) vagy be (1).

A Shift Cursor/Screen utasítással a  kurzort vagy a képernyőt tolhatjuk jobbra vagy balra, anélkül, hogy az adatmemóriár (DDRAM) írnánk vagy olvasnánk. Az S/C paraméter '0' értéke a kurzot, '1' értéke a képernyőt választja ki, az R/L bit pedig a léptetés irányát szabja meg (0: balra, 1: jobbra). Eltoláskor a címregiszter (a következő beírás helye a memóriában) nem változik meg.

A Function Set utasításnak nagyon fontos szerepe van: az LCD üzemmódját állítja be a vezérlő inicializálásakor.
Fontos tudni, hogy az üzemmód inicializálásonként csak egyszer állítható be! DL az adatút szélességét állítja be (0: 4 bites, 1: 8 bites). Ha 4 bites üzemmódot állítunk be, akkor a D7, D6, D5, D4 adatvonalakat kell használni, s a kiírandó adatnak először a magasabb helyiértékű felét írjuk ki, ezt követően pedig az alacsonyabb helyiértékű felét. Az N paraméter a sorok számát szabja meg: N=0 egysoros mód, N=1 kétsoros mód. Az F paraméter '0' értéke az 5x7-s fontot, az '1' értéke pedig az 5x10-es fontot választja ki.

A CGRAM cím beállításására akor van szükség, ha a felhsználó által definálható karakterek bitképét akarjuk feltölteni. A 0. címre írjuk az első karakter legfelső pontsorát (csak az alsó 5 helyiérték bitje számít), az 1.címre az első karakter felülről második pontsorát, és így tovább.

A DDRAM cím beállítását a kurzor pozicionálására használhatjuk. Például kétsoros megjelenítőnél a 0x40 címen kezdődik a második sor. Egy lcd_gotoxy(sor,oszlop) kurzorpozicionáló függvény pedig a sor*0x40+oszlop címre kell, hogy állítsa a DDRAM címet. (A példáknál feltétleztük, hogy nincs eltolva a képernyő, s az lcd_gotoxy() függvény paramétereinél a sorok és oszlopok számozását 0-tól kezdjük.

Az LCD modul inicializálása

Az LCD modul vezérlője a tápfeszültség megjelenésekor inicializálja magát (Power on Reset), de ha bekapcsoláskor túl lassú a tápfeszültség felfutása, vagy bekapcsolt állapotban van szükségünk újrainicializálásra ("meleg" RESET), akkor szoftveres parancsokkal is kikényszeríthatjük, hogy az LCD modul alaphelyzetbe kerüljön. Ennek pontos módját a HD44780 vezérlő adatlapja írja le, s van néhány apró különbség a nyolc-, illetve négybites üzemmódban végzett inicializálás között. Az LCD hazsnálatának talán ez a legkritikusabb része, mivel helytelen inicializálás után nem fog működni a modul, vagy zagyvaságok jelennek meg a képernyőn.

Inicializálás 8 bites üzemmódban

  1. Várunk legalább 15 ms-ot azután, hogy a tápfeszültség elérte a  4,5 V-ot, majd RS=0, R/W=0 valamint D7...D4 = 0b0011 mellett (D3..D0 beállítása itt közömbös) pulzáljuk az E vezérlőjelet (E-t előbb magas szintre, majd alacsony szintre kapcsoljuk). Magyarul ez azt jelenti, hogy az LCD-nek kiadjuk a Function set parancsot DL=1 beállítással. A foglaltságot (BF bit) most nem vizsgálhatjuk!
  2. Várunk legalább 4,1 ms-ot, majd ismét kiadjuk a fenti módon a Function set parancsot. A foglaltságot (BF bit) most nem vizsgálhatjuk!
  3. Várunk legalább 100 µs-ot, és harmadszor is kiadjuk az 1. pontban leírt módon a Function set parancsot. A foglaltságot (BF bit) most sem vizsgálhatjuk!
  4. Újabb 100 µs-os várakozás után negyedszer is kiadjuk a Function set parancsot, ezúttal azonban a D3 és D2 adatbitek tartalma is számít (N és F paraméterek, lásd 2.és 3. táblázat!).  A 8 bites üzemmódra való tekintettel DL=1, kétsoros kijelzőnél pedig N=1 és F=0 (kétsoros kijelzőnél csak 5x7 pontos karakterek használhatók). Végeredményben tehát esetünkben az adatvonalakra 0x38-et kell küldeni. Ezután a parancs után már figyelhető a foglaltság. A sorok száma és a fontméret csak újrainicializálás után változtatható meg!
  5. Ideiglenesen letiltjuk a megjelenítést egy Display control paranccsal, D=0 beállítással. 
  6. Töröljük a képernyőt egy Clear Display paranccsal.
  7. Újraengedélyezzük a megjelenítést egy Display control paranccsal, D = 1 beállítással. Ugyanitt engedélyezhetjük a kurzor megjelenítését és villogását is.
A fentiekhez hasonlóan történik a 4 bites üzemmód beállítása is, de a fenti 4. pont helyett itt két lépést kell beiktatni, melyekben már DL=0 beállítás szerepel, ami a 4 bites üzemmódot állítja be. A másik nyilvánvaló különbség,  hogy a lenti lista 5. pontjától kezdődően (a 4 bites üzemmódra történő kapcsolástól kezdve) az adatokat vagy parancsokat két részletben, félbájtonként kell kiküldeni.

Inicializálás 4 bites üzemmódban

  1. Várunk legalább 15 ms-ot azután, hogy a tápfeszültség elérte a  4,5 V-ot, majd RS=0, R/W=0 valamint D7...D4 = 0b0011 mellett (D3..D0 beállítása itt közömbös) pulzáljuk az E vezérlőjelet (E-t előbb magas szintre, majd alacsony szintre kapcsoljuk). Magyarul ez azt jelenti, hogy az LCD-nek kiadjuk a Function set parancsot DL=1 beállítással. A foglaltságot (BF bit) most nem vizsgálhatjuk! Megjegyzés:az LCD itt még 8 bites módban van.
  2. Várunk legalább 4,1 ms-ot, majd ismét kiadjuk a fenti módon a Function set parancsot. A foglaltságot (BF bit) most nem vizsgálhatjuk! Megjegyzés:az LCD itt még 8 bites módban van.
  3. Várunk legalább 100 µs-ot, és harmadszor is kiadjuk az 1. pontban leírt módon a Function set parancsot. A foglaltságot (BF bit) most sem vizsgálhatjuk! Megjegyzés:az LCD itt még 8 bites módban van.
  4. Újabb 100 µs-os várakozás után negyedszer is kiadjuk a Function set parancsot, ezúttal azonban D4 = 0 legyen (DL = 0)! A D3..D0 bitek állapota még mindig közömbös. Amikor ezt a parancsot kiadjuk, az LCD még 8 bites módban van. Ennek a parancsnak a hatására kapcsol át 4 bites módba. A következő ponttól kezdődően tehát a parancsokat két részletben (előbb a magas helyiértékű félbájt, majd az alacsonyabb) kell kiküldeni. 
  5. Újabb 100 µs-os várakozás után ötödször is kiadjuk a Function set parancsot, ezúttal azonban a D3 és D2 adatbitek tartalma is számít (N és F paraméterek, lásd 2.és 3. táblázat!).  A 4 bites üzemmódra való tekintettel DL =0, kétsoros kijelzőnél pedig N=1 és F=0 (kétsoros kijelzőnél csak 5x7 pontos karakterek használhatók). Végeredményben tehát esetünkben az adatvonalakra 0x28-at kell küldeni, két részletben. Ezután a parancs után már figyelhető a foglaltság. A sorok száma és a fontméret csak újrainicializálás után változtatható meg!
  6. Ideiglenesen letiltjuk a megjelenítést egy Display control paranccsal, D=0 beállítással. 
  7. Töröljük a képernyőt egy Clear Display paranccsal.
  8. Újraengedélyezzük a megjelenítést egy Display control paranccsal, D=1 beállítással. Ugyanitt engedélyezhetjük a kurzor megjelenítését és villogását is.

Az LCD kijelző vezérlése 8 bites üzemmódban

Az alábbi mintaprogramban 8 bites módban kezeljük az LCD kijelzőt, s bemutatjuk, hogyan írhatunk az LCD-re printf(), illetve putc() függvényhívással. A programban azt is bemutatjuk, hogy a kurzor pozicionálásával hogyan írhatunk adott helyre egy-egy karaktert.

Hardver követelmények: a program az "A kísérleti áramkör" című fejezetben bemutatott referencia áramkörök valamelyikén fut. A kísérleti áramkört az 5. ábrán látható módon ki kell egészíteni egy 2x16 karakteres LCD alfanumerikus kijelzővel (pl. EW162G0GR, RDE-LM12121 vagy RC1602B). A PIC18F4550 mikrovezérlő esetében természetesen nem a C, hanem a D portra kell kötni at adatvonalakat. Az LCD modult a kísérleti áramkörben szereplő LED-ekkel párhuzamosan köthetjük, azok jelenléte nem zavarja a működést.

Projekt konfiguráció: A program működéséhez csak a HID_BOOTLOADER szimbólumot kell definiálni! Az USB kapcsolatot és a programmegszakítást most nem használjuk.

 1. lista: LCD kijelző használata 8 bites üzemmódban (lcd_8bit.c)
#include "piccolo_all.h"
#include <stdio.h>
#include <delays.h>

//-- A hardverfüggő részletek elkülönítése ------------------
#if defined(__18F14K50)
#define BUSY_FLAG PORTCbits.RC7; // foglaltság figyelése
#elif defined(__18F4550)
#define BUSY_FLAG PORTDbits.RD7;
#endif

#define RSHIGH() LATBbits.LATB5=1; // RS vezérlése
#define RSLOW() LATBbits.LATB5=0;
#define RS_init() TRISBbits.TRISB5=0;
#define RWHIGH() LATBbits.LATB6=1; // R/W vezérlése
#define RWLOW() LATBbits.LATB6=0;
#define RW_init() TRISBbits.TRISB6=0;
#define EHIGH() LATBbits.LATB7=1; // E vezérlése
#define ELOW() LATBbits.LATB7=0;
#define E_init() TRISBbits.TRISB7=0;

#define DATA_DIR_RD() LEDtris = 0xFF; // adatvonalak beállítása olvasásra
#define DATA_DIR_WR() LEDtris = 0x00; // adatvonalak beállítása írásra
#define OUTPUT_DATA(x) {LEDport = x;} //adat kiküldésa

/** Késleltető eljárás, amely a Microchip PIC18 könyvtári eljárásait használja.
* A belső ciklus kb. 1 ms késleltetést végez.
* \param d az előírt várakozási idő, 1 ms egységekben
*/
void delay_ms(unsigned int d) {
unsigned int i;
for(i=0; i<d; i++) {
Delay1KTCYx(12); //12 000 TCY késleltetés (~ 1ms)
}
}

//-- Az E vezérlőjel pulzálása
void epulse(void){
Delay10TCYx(2);
EHIGH(); Delay10TCYx(2);
ELOW(); Delay10TCYx(1);
}

//-- egy karakter vagy utasítás kiküldése az LCD-nek
void lcd8_write( unsigned char cmd, //parancs vagy karakterkód
unsigned char data_flag, //1: adat, 0: parancs
unsigned char chk_busy) //1: BF ellenőrzése, 0: csak késleltetés
{
char bflag;
if (chk_busy) {
RSLOW(); //RS = 0 az ellenőrzéshez
DATA_DIR_RD(); //adatvonalaket bemenetnek állítja
RWHIGH(); //R/W = 1, az olvasáshoz
do {
EHIGH(); //adatbájt olvasása
Delay10TCYx(2);
bflag = BUSY_FLAG; //bflag = 1 lesz foglaltság esetén
ELOW(); Delay10TCYx(2);
} while(bflag);
} else {
Delay1KTCYx(12); //nem figyel foglaltságot, csak vár 1 ms-ot
}
DATA_DIR_WR(); //Az adatvonalakat írásra állítja
if (data_flag) { RSHIGH()} //RS=1, adatküldés
else RSLOW(); //RS=0, parancsküldés
RWLOW(); //R/W = 0, a küldéshez
OUTPUT_DATA(cmd); //Adatvonalak beállítása
epulse(); //E vonalpulzálása
}

//-- Az LCD modul inicializálása 8 bites módban
void lcd8_init(void) {
ELOW(); //vezérlő vonalak inicializálása
RSLOW();
RWLOW();
RW_init();
RS_init();
E_init();
DATA_DIR_WR(); //adatvonalak beállítása kimenetnek
//-- Szoftver reset --------------------
Delay10KTCYx(60); //50 ms (>40 ms) várakozás az eszköz feléledésére
lcd8_write(0x30,0,0); //szoftveres reset 1.
Delay10KTCYx(6); //5 ms várakozás (> 4.1 ms)
lcd8_write(0x30,0,0); //szoftveres reset 2.
Delay1KTCYx(2); //166 us várakozás (> 100 us)
lcd8_write(0x30,0,0); //szoftveres reset 3.
//-- Inicializálás --------------------
lcd8_write(0x38,0,0); //8 bites mód, 2 sor, 5x7 font
//-- Innen kezdve figyelhetjük a foglaltságot
lcd8_write(0x08,0,1); //display off
lcd8_write(0x01,0,1); //képernyőtörlés, kurzor kezdőhelyre küldés
lcd8_write(0x0C,0,1); //megjelenítés engedélyezése, kurzor letiltása
stdout = _H_USER; // a felhasználói függvény legyen a
// standard kimenet, ami az LCD-re ír
}

//-- karakterkiíró függvény, amit a stdio.h-ban deklarált kimeneti
//-- függvények (putc, puts, fprintf, printf) meghívhatnak.
void _user_putc (auto char c) {
lcd8_write(c,1,1);
}

const rom char line1[] = "<== 2x16 LCD ==>";
const rom char line2[] = "Now in 8bit mode";

void main(void){
unsigned char i,k;
DISABLE_ALL_ANALOG(); //Minden analóg bemenet tiltása
lcd8_init(); //Az LCD modul inicializálása
while(1) {
lcd8_write(0x01,0,1); //képernyőtörlés
delay_ms(500);
printf(line1); //Szövegkiírás az első sorba
delay_ms(500);
for(i=0; i<16; i++) {
k = (i*7) & 0x0F; //"véletlen" szám előállítása
lcd8_write(0xC0+k,0,1); //Kurzor pozicionálása
putc(line2[k],stdout); //szövegkiírás a második sorba
delay_ms(120);
}
delay_ms(2000);
}
}
A program elején a piccolo_all.h header állományon kívül az MPLAB C18 fejlesztői környezéhez tartozó stdio.h (ez a printf() és a putc() függvény használata miatt kell) és a delays.h (ez a késletető függvényeket deklarálja) állományokat is be kell csatolni.

A hardverfüggő részleteket célszerű elkülöníteni a program további részeitől: makrókat definiálunk az RS, RW, E vezérlő kimenetek és az adatport vezérléséhez, így a program többi része független attól, hogy az LCD egyes kivezetései melyik portlábra vannak kötve. Az így megírt programot könnyű adaptálni egy más bekötéshez.

Az adatport kétirányú, mert a foglaltság vizsgálatánál olvasásra kell állítanunk, egyébként mindig kimenetnek állítjuk.

Az epulse() függvény az adatbeíráshoz szükséges vezérlő impulzus (E magas, majd alacsony állapotba állítása) előállításra szolgál. A bőséges késleltetések garantálják az adat- és címvonal beállását és tartását.

Az lcd8_write() függvény blokkoló típusú. Ha a chk_busy paraméter értéke nullától különböző, akkor a program addig várakozik, amíg a kiolvasott BF bit (busy flag, foglaltság jelzés) '1' állapotban van. Ha pedig a chk_busy paraméter értéke nulla, akkor egy fix idejű (kb. 1 ms) várakozás előzi meg az adat kiírását. A data_flag paraméter az RS (register select) beálítandó értékét adja meg (0: parancs, 1: adatküldés). A cmd paraméter a kiküldeni kívánt adatot adja meg.

Az lcd8_init() függvény az LCD modul inicializálását végzi a HD446780 vezérlő adatlapjának ajánlása szerint, s nyolcbites üzemmódot állít be. Az LCD inicializálásánál kezdetben letiltjuk a foglaltság figyelését (az lcd_write() függvény chk_busy paramétere nulla), s csak az utolsó Function set parancs kiküldése után engedélyezzük. A kijelzőt kétsorosnak konfiguráljuk, 5x7-es fontmérettel. A kurzort és a kurzor villogtatást letiltjuk.

A főprogram elején letiltjuk az analóg bemeneteket. A hardver profilokban (piccolo-14k50.h, illetve piccolo-4550.h) definiáltunk ehhez egy DISABLE_ALL_ANALOG() nevű makrót. Természetesen nem szükséges minden analóg bemenetet letiltani, itt most csak azt kellett biztosítani, hogy az LCD foglaltságát jelző BF bit olvasható legyen. A PICCOLO projekben általában egy, vagy két analóg csatornát használunk. Ezek engedélyezéséhez használhatjuk az ENABLE_ONE_ANALOG(), vagy az ENABLE_TWO_ANALOG() makrókat is. Arra azonban ügyeljünk, hogy ezek a makrók az adatáramlási irányt nem állítják be! 

Az _user_putc(char c) függvénnyel felüldefiniáljuk a "gyári" p18fxxxx.lib programkönyvtár üres (csak helyfoglaló) függvénydefinícióját, s ezzel lehetővé tesszük, hogy a stdio.h-ban deklarált karakterkiíró függvényei (putc, puts, fprintf, printf) az LCD-re írjanak. Ehhez azonban még az is szükséges, hogy a standard kimenetet átállítsuk a felhasználói kimenetre a stdout = _H_USER értékadással.

Megjegyzés: Az _user_putc() függvényt nem használhatjuk az LCD-re történő íráshoz akkor, ha az USB kapcsolatot is engedélyezzük, mivel az _user_putc() függvény USB kommunikáció számára van fentartva!

A kiírást ebben a programban a printf() és a putc() függvénnyel végezzük. A printf() használata meglehetősen memóriafaló megoldás. A második sor i-edik pozíciójába (i = 0..15) a lcd_write(0xC0+i ,0,1) utasítással (DDRAM cím beállítása) állíthatjuk be a kurzort.

A képernyő első sorába a printf() függvény segítségével írunk ki egy sornyi szöveget. A második sort pedig "véletlen" sorrendben, karakterenként íratjuk ki a putc() függvénnyel. A putc() függvény az MCC18 fordító támogatói programkönyvtárához tartozik (a dokumentációja is az MCC18 tepeítési könyvtárában lelhető fel), s két paramétert vár. Az első paraméter a kiírandó karakter kódja. A második paraméter a kimeneti adatfolyamot adja meg (stdio vagy stderr). 

Az alábbi fényképen az LCD modullal kiegészített, PIC18F4550 mikrovezérlővel ellátott kísérleti áramkört és az lcd_8bit.c program futásának erdményét láthatjuk. Az ábrán a fentebb már említett Optrex gyártmányú DMC-50399 típusú alfanumerikus kijelzőt láthatjuk, ami történetesen 2x20 karakteres, de a program változtatás nélkül fut rajta (csak üresen hagyja az utolsó 4 karakterpozíciót).

6. ábra: Az lcd_8bit.c program futtatása PIC18F4550 kísérleti áramkörön

Az LCD kijelző vezérlése 4 bites üzemmódban

Az alábbi mintaprogramban az LCD kijelzőjét 4 bites módban vezéreljük. A kiírást most is a printf() függvény segítségével végezzük, majd a kijelzőt a főprogram végtelen ciklusában minden iterációnál eggyel balra léptetjuk. Az LCD modul belső felépítésének köszönhetően a szöveg automatikusan "körbefordul": amikor 40 karaktert kiléptettünk, újra a sor eleji karakterek jelennek meg.

Hardver követelmények: a program az "A kísérleti áramkör" című fejezetben bemutatott referencia áramkörök valamelyikén fut. A kísérleti áramkört az 5. ábrán láthatóhoz hasonló módon ki kell egészíteni egy 2x16 karakteres LCD alfanumerikus kijelzővel (pl. EW162G0GR, RDE-LM12121 vagy RC1602B). A különbség az, hogy az LCD modulnak most csak a D7..D4 lábait kötjük be a LEDport felső félbájtjára (ez PIC18F14K50 esetén az RC7..RC4 kimeneteket, PIC18F4550 esetén pedig az RD7...RD4 biteket jelenti. Most a vezérlő vonalak is a LEDport-ra csatlakoznak (E = LED3, R/W = LED2, RS = LED1). Az LCD modult vezérlő vonalait a kísérleti áramkörben szereplő LED-ekkel párhuzamosan köthetjük, azok jelenléte nem zavarja a működést.

Projekt konfiguráció: A program helyes működéséhez csak a HID_BOOTLOADER szimbólumot kell definiálni! Az USB kapcsolatot és a programmegszakítást most nem használjuk.

 2. lista: LCD kijelző vezérlése 4 bites üzemmódban (lcd_4bit.c)
#include "piccolo_all.h"
#include <stdio.h>
#include <delays.h>

//-- A hardverfüggő részletek elkülönítése
#define RSHIGH() LEDport |= 0x01;
#define RSLOW() LEDport &= 0xFE;
#define RWHIGH() LEDport |= 0x02;
#define RWLOW() LEDport &= 0xFD;
#define EHIGH() LEDport |= 0x04;
#define ELOW() LEDport &= 0xFB;
#if defined(__18F14K50)
#define BUSY_FLAG PORTCbits.RC7;
#elif defined(__18F4550)
#define BUSY_FLAG PORTDbits.RD7;
#endif
#define DATA_DIR_RD() LEDtris = 0xF0;
#define DATA_DIR_WR() LEDtris = 0x00;
#define OUTPUT_DATA(x) {LEDport = (LEDport & 0x0F) | x;}

//-- Az E vezérlőjel pulzálása
void epulse(void){
Delay10TCYx(2);
EHIGH(); Delay10TCYx(2);
ELOW(); Delay10TCYx(1);
}

//-- Egy bájt (parancs vagy adat) kiírása
void lcd_write(unsigned char cmd,
unsigned char data_flag,
unsigned char chk_busy,
unsigned char dflag){
char bflag,c;
if (chk_busy) {
RSLOW(); //RS = 0 a foglaltság figyeléshez
DATA_DIR_RD(); //adatvonalak vételre állítása
RWHIGH(); //R/W = 1 olvasáshoz
do {
EHIGH();
Delay10TCYx(2);
bflag = BUSY_FLAG; //felső 4 bit olvasása
ELOW(); Delay10TCYx(2);
epulse(); //alsó félbájt kiléptetése
} while(bflag);
} else {
Delay10KTCYx(12); //Foglaltság figyelés helyett késleltetés
}
DATA_DIR_WR(); //Adatvonalak írásra állítása
if (data_flag) {
RSHIGH(); //RS=1, ha adatküldés következik
}
else RSLOW(); //RS=0, ha parancsot küldünk
RWLOW(); //R/W = 0, íráshoz
c = cmd & 0xF0; //felső 4 bit kiírása
OUTPUT_DATA(c);
epulse();
if (dflag) {
c = (cmd & 0x0F)<<4; //alsó 4 bit kiírása
OUTPUT_DATA(c);
epulse();
}
}

//-- Az LCD modul inicializálása
void lcd_init(void) {
//-- Az LCD-t vezérlő vonalak inicializálása
DATA_DIR_WR();
ELOW();
 RSLOW();
 RWLOW();
//-- Az LCD modul szoftveres reset-elése és inicializálása
Delay10KTCYx(60); //50 ms várakozás az LCD feléledésére
lcd_write(0x30,0,0,0); //8 bites üzemmód
Delay10KTCYx(6); //5 ms várakozás
lcd_write(0x30,0,0,0); // 4 bites üzemmód
lcd_write(0x30,0,0,0); // 8 bites üzemmód
lcd_write(0x20,0,0,0); // 4 bites üzemmódba kapcsolunk
//-- Innen kezdve a 4 bites üzemmód él (küldésnél dflag=1 kell)
lcd_write(0x28,0,0,1); // 2 soros display, 5x7 font
//-- Innen kezdve figyelhető a BF jelzőbit (chk_busy=1)
lcd_write(0x08,0,1,1); // display letiltás
lcd_write(0x01,0,1,1); // képernyőtörlés
lcd_write(0x0C,0,1,1); // display be, cursor, és villogás ki
stdout = _H_USER; // a felhasználói függvény legyen a
// standard kimenet, ami az LCD-re ír
}

//-- karakterkiíró függvény, amit a stdio.h-ban deklarált kimeneti
//-- függvények (putc, puts, fprintf, printf) meghívhatnak.
void _user_putc (auto char c) {
lcd_write(c,1,1,1);
}

void main(void){
DISABLE_ALL_ANALOG(); //Minden analóg bemenet tiltása
//-- Az LCD modul inicializálása
lcd_init ();
printf(" Isten hozott a PICCOLO projekthez!");
lcd_write(0xC0,0,1,1); //Kurzor mozgatása a második sor elejére
#if defined(__18F14K50)
printf(" PIC18F14K50 MCU, lcd_4bit.c program");
#else
printf(" PIC18F4550 MCU, lcd_4bit.c program");
#endif
while(1) {
Delay10KTCYx(120);
Delay10KTCYx(120);
Delay10KTCYx(120);
lcd_write(0x18,0,1,1); //Balra léptetjük a megjelenített szöveget
}
}
A program elején a piccolo_all.h header állományon kívül az MPLAB C18 fejlesztői környezéhez tartozó stdio.h (ez a printf() függvény használata miatt kell) és a delays.h (ez a késletető függvényeket deklarálja) állományokat is be kell csatolni.

A hardverfüggő részleteket célszerű elkülöníteni a program további részeitől: makrókat definiálunk az RS, RW, E vezérlő kimenetek és az adatport vezérléséhez, így a program többi része független attól, hogy az LCD egyes kivezetései melyik portlábra vannak kötve. Az így megírt programot könnyű adaptálni egy más bekötéshez.

Az adatport kétirányú, mert a foglaltság vizsgálatánál olvasásra kell állítanunk. A négybites üzemmódra való tekintettel a LEDtris regiszternek (ami a mikrovezérlő kiválastásától függően TRISC vagy TRISD) csak a felső négy bitjét állítgatjuk ('1'-be állítás VAGY művelettel, '0'-ba állítás), az alsó négy bitet mindig kimenetnek állítjuk.

Az epulse() függvény az adatbeíráshoz szükséges vezérlő impulzus (E magas, majd alacsony állapotba állítása) előállításra szolgál. A bőséges késleltetések garantálják az adat- és címvonal beállását és tartását.

Az lcd_write() függvény blokkoló típusú. Ha a chk_busy paraméter értéke nullától különböző, akkor a program addig várakozik, amíg a kiolvasott BF bit (busy flag, foglaltság jelzés) '1' állapotban van. Ha pedig a chk_busy paraméter értéke nulla, akkor egy fix idejű (kb. 1 ms) várakozás előzi meg az adat kiírását. Ha a dflag paraméter nulla, akkor csak a felső félbájtot küldjük ki (inicializálásnál, amikor az LCD modulmég 8 bites módban van, s az alsó négy adatbit értéke közömbös) használjuk ezt a módot. A négybites üzemmód beállítása után azonban dflag értéke nullától különböző legyen! A data_flag paraméter az RS (register select) beálítandó értékét adja meg (0: parancs, 1: adatküldés). A cmd paraméter a kiküldeni kívánt adatot adja meg.

Az lcd_init() függvény az LCD modul inicializálását végzi a HD446780 vezérlő adatlapjának ajánlása szerint, s négybites üzemmódot állít be. Az LCD inicializálásánál kezdetben letiltjuk a foglaltság figyelését (az lcd_write() függvény chk_busy paramétere nulla), s csak az utolsó Function set parancs kiküldése után engedélyezzük. A kijelzőt kétsorosnak konfiguráljuk, 5x7-es fontmérettel. A kurzort és a kurzor villogtatást letiltjuk.

A főprogram elején letiltjuk az analóg bemeneteket. A hardver profilokban (piccolo-14k50.h, illetve piccolo-4550.h) definiáltunk ehhez egy DISABLE_ALL_ANALOG() nevű makrót. Természetesen nem szükséges minden analóg bemenetet letiltani, itt most csak azt kellett biztosítani, hogy az LCD foglaltságát jelző BF bit olvasható legyen. A PICCOLO projekben általában egy, vagy két analóg csatornát használunk. Ezek engedélyezéséhez használhatjuk az ENABLE_ONE_ANALOG(), vagy az ENABLE_TWO_ANALOG() makrókat is. Arra azonban ügyeljünk, hogy ezek a makrók az adatáramlási irányt nem állítják be! 

Az _user_putc(char c) függvénnyel felüldefiniáljuk a "gyári" p18fxxxx.lib programkönyvtár üres (csak helyfoglaló) függvénydefinícióját, s ezzel lehetővé tesszük, hogy a stdio.h-ban deklarált karakterkiíró függvényei (putc, puts, fprintf, printf) az LCD-re írjanak. Ehhez azonban még az is szükséges, hogy a standard kimenetet átállítsuk a felhasználói kimenetre a stdout = _H_USER értékadással.

Megjegyzés: Az _user_putc() függvényt nem használhatjuk az LCD-re történő íráshoz akkor, ha az USB kapcsolatot is engedélyezzük, mivel az _user_putc() függvény USB kommunikáció számára van fentartva!

A kiírást ebben a programban a printf() függvénnyel végezzük, ami elég memóriafaló megoldás. A második sor elejére a lcd_write(0xC0,0,1,1) utasítással (DDRAM cím beállítása) térhetünk át.

A végtelen ciklusban az LCD-re kiírt szöveget minden ciklusban eggyel balra léptetjük. Vegyük észre, hogy a szöveg "körbefordul": amikor 40 karaktert kiléptettünk, újra a sor eleji karakterek jelennek meg.

Karaktertábla, karakterek definiálása

A következő mintaprogramban az LCD kijelzőjének karakterkészletét mutatjuk be. A CGRAM-ban a kisbetűs, magyar ékezetes karaktereket is definiáltuk (az ö betű kivételével, mivel az az eredeti karakterkészletben is megtalálható). A kijelző vezérlése most is 4 bites módban történik. Az üdvözlő üzenet megjelenítése után a kijelző karaktertábláját kétsoronként jelenítjük meg, s az SW1 nyomógomb lenyomásával léptethetjük tovább.

Hardver követelmények: a program az "A kísérleti áramkör" című fejezetben bemutatott referencia áramkörök valamelyikén fut. A kísérleti áramkört az 5. ábrán láthatóhoz hasonló módon ki kell egészíteni egy 2x16 karakteres LCD alfanumerikus kijelzővel (pl. EW162G0GR, RDE-LM12121 vagy RC1602B). A kijelzőt négybites módban használjuk, tehát az LCD modulnak csak a D7..D4 lábait kötjük be a LEDport felső félbájtjára (ez PIC18F14K50 esetén az RC7..RC4 kimeneteket, PIC18F4550 esetén pedig az RD7...RD4 biteket jelenti). A vezérlő vonalak is a LEDport-ra csatlakoznak (E = LED3, R/W = LED2, RS = LED1). Az LCD modult vezérlő vonalakat a kísérleti áramkörben szereplő LED-ekkel párhuzamosan köthetjük, azok jelenléte nem zavarja a működést.

Projekt konfiguráció: A program helyes működéséhez csak a HID_BOOTLOADER szimbólumot kell definiálni! Az USB kapcsolatot és a programmegszakítást most nem használjuk.


 3. lista: LCD kijelző karaktertáblájának megjelenítése 4 bites üzemmódban (lcd4_kodtabla.c)
/* PICCOLO project
* Copyright (c) 2010 Istvan Cserny (cserny@atomki.hu)
*
*/

#include "piccolo_all.h"
#include <stdio.h>
#include <delays.h>

/** \file
Az ipari szabványnak tekinthető HD44780 vezérlővel ellátott 2x16 karakateres
LCD kijelző kódtábláját mutatja be, 4 bites módban. A CGRAM-ban a kisbetűs,
magyar ékezetes karaktereket is definiáltuk.

Hardver igény:
- PICCOLO projekt alapkapcsolás PIC18F14K50 vagy PIC18F4550 mikrovezérlővel,
- 2x16 karakteres LCD kijelző, 4 bites üzemmódban. Az adatvonalak a LEDport
felső félbájtjára vannak kötve, a vezérlő vonalak pedig a LEDport alsó bitjeire
(RS = LED1, R/W = LED2, E = LED3).
*/

unsigned char chk_busy;
unsigned char i,ch;
const rom unsigned char betwk[] = {0x02,0x04,0x0E,0x01,0x0F,0x11,0x0F,0x00, //á
0x02,0x04,0x0E,0x11,0x1F,0x10,0x0E,0x00, //é
0x02,0x04,0x0C,0x04,0x04,0x04,0x0E,0x00, //í
0x02,0x04,0x0E,0x11,0x11,0x11,0x0E,0x00, //ó
0x02,0x04,0x11,0x11,0x11,0x13,0x0D,0x00, //ú
0x0A,0x00,0x11,0x11,0x11,0x13,0x0D,0x00, //ü
0x05,0x0A,0x11,0x11,0x11,0x13,0x0D,0x00, //ű
0x05,0x0A,0x0E,0x11,0x11,0x11,0x0E,0x00}; //ő


//-- A hardverfüggő részletek elkülönítése
#define RSHIGH() LEDport |= 0x01;
#define RSLOW() LEDport &= 0xFE;
#define RWHIGH() LEDport |= 0x02;
#define RWLOW() LEDport &= 0xFD;
#define EHIGH() LEDport |= 0x04;
#define ELOW() LEDport &= 0xFB;
#if defined(__18F14K50)
#define BUSY_FLAG PORTCbits.RC7;
#elif defined(__18F4550)
#define BUSY_FLAG PORTDbits.RD7;
#endif
#define DATA_DIR_RD() LEDtris = 0xF0;
#define DATA_DIR_WR() LEDtris = 0x00;
#define OUTPUT_DATA(x) {LEDport = (LEDport & 0x0F) | x;}

//-- Az E vezérlőjel pulzálása
void epulse(void){
Delay10TCYx(2);
EHIGH(); Delay10TCYx(2);
ELOW(); Delay10TCYx(1);
}

//-- Egy bájt (parancs vagy adat) kiírása
void lcd_write(unsigned char cmd,
unsigned char data_flag,
unsigned char chk_busy,
unsigned char dflag){
char bflag,c;
if (chk_busy) {
RSLOW(); //RS = 0 a foglaltság figyeléshez
DATA_DIR_RD(); //adatvonalak vételre állítása
RWHIGH(); //R/W = 1 olvasáshoz
do {
EHIGH();
Delay10TCYx(2);
bflag = BUSY_FLAG; //felső 4 bit olvasása
ELOW(); Delay10TCYx(2);
epulse(); //alsó félbájt kiléptetése
} while(bflag);
} else {
Delay10KTCYx(12); //Foglaltság figyelés helyett késleltetés
}
DATA_DIR_WR(); //Adatvonalak írásra állítása
if (data_flag) {
RSHIGH(); //RS=1, ha adatküldés következik
}
else RSLOW(); //RS=0, ha parancsot küldünk
RWLOW(); //R/W = 0, íráshoz
c = cmd & 0xF0; //felső 4 bit kiírása
OUTPUT_DATA(c);
epulse();
if (dflag) {
c = (cmd & 0x0F)<<4; //alsó 4 bit kiírása
OUTPUT_DATA(c);
epulse();
}
}

//-- Az LCD modul inicializálása
void lcd_init(void) {
//-- Az LCD-t vezérlő vonalak inicializálása
DATA_DIR_WR();
ELOW();
RSLOW();
RWLOW();
//-- Az LCD modul szoftveres reset-elése és inicializálása
Delay10KTCYx(60); //50 ms várakozás az LCD feléledésére
lcd_write(0x30,0,0,0); //8 bites üzemmód
Delay10KTCYx(6); //5 ms várakozás
lcd_write(0x30,0,0,0); // 4 bites üzemmód
lcd_write(0x30,0,0,0); // 8 bites üzemmód
lcd_write(0x20,0,0,0); // 4 bites üzemmódba kapcsolunk
//-- Innen kezdve a 4 bites üzemmód él (küldésnél dflag=1 kell)
lcd_write(0x28,0,0,1); // 2 soros display, 5x7 font
//-- Innen kezdve figyelhető a BF jelzőbit (chk_busy=1)
lcd_write(0x08,0,1,1); // display letiltás
lcd_write(0x01,0,1,1); // képernyőtörlés
lcd_write(0x0C,0,1,1); // display be, cursor, és villogás ki
stdout = _H_USER; // a felhasználói függvény legyen a
// standard kimenet, ami az LCD-re ír
}

void lcd_init_cgram(void) {
lcd_write(0x40,0,1,1); // kurzor a CGRAM elejére
for(i=0; i<64; i++) {
lcd_write(betwk[i],1,1,1); // definiálható karakterek feltöltése
} // ékezetes karakterekkel
lcd_write(0x80,0,1,1); // kurzor vissza, a DDRAM elejére
}

//-- LCD-re egy karaktert kiíró függvény a _H_USER stream számára
void _user_putc(char c) {
switch(c) {
case 'á': c = 0x00; break;
case 'é': c = 0x01; break;
case 'í': c = 0x02; break;
case 'ó': c = 0x03; break;
case 'ú': c = 0x04; break;
case 'ü': c = 0x05; break;
case 'ű': c = 0x06; break;
case 'ő': c = 0x07; break;
case 'ö': c = 0xEF; break;
}
lcd_write(c,1,1,1);
}

void main(void){
DISABLE_ALL_ANALOG(); //Minden analóg bemenet tiltása
mInitAllLEDs(); // A LED-ek (RB0..RB3) inicializálása
mInitSwitch1(); // SW1 inicializálása
lcd_init(); // Az LCD modulinicializálása
lcd_init_cgram(); // Az ékezetes betűk feltöltése
stdout = _H_USER; // Az LCD legyen a standard kimenetnt
printf(" Adjon az Isten ");
lcd_write(0xC0,0,1,1); // kurzor a második sor elejére
printf(" szebb jövőt! ");
for(i=0; i<25; i++) {
Delay10KTCYx(240); // 25 x 200 ms várakozás
}
lcd_write(0x01,0,1,1); // képernyőtörlés
ch = 0x00;
while(1) {
lcd_write(0x80,0,1,1); // kurzor az első sor elejére
//-- Az alábbi sor csak akkor aktiváljuk, ha legalább 20 karakteres LCD-t használunk!
// printf("%02x: ",ch);
for(i=0; i<16; i++) {
lcd_write(ch,1,1,1);
ch++;
}
lcd_write(0xC0,0,1,1); // kurzor a második sor elejére
//-- Az alábbi sor csak akkor aktiváljuk, ha legalább 20 karakteres LCD-t használunk!
// printf("%02x: ",ch);
for(i=0; i<16; i++) {
lcd_write(ch,1,1,1);
ch++;
}
while(SW1);
Delay1KTCYx(240); // 20 ms várakozás
while(!SW1);
Delay1KTCYx(240); // 20 ms várakozás
}
}
Mivel a programban az LCD inicializálása és használata az előző programhoz hasonlóan történik, itt most csak azokkal a részekkel foglalkozunk, amelyeknél eltérés van.

A program elején létrehozunk egy betwk[] nevű, konstansokat tartalmazó tömböt, amelyet a programmemória területén helyezünk el (a const rom módosítóval deklarált konstansok a programmemóriába kerülnek).

Az LCD-t kezelő függvényeket kiegészítettük a CGRAM feltöltését végző lcd_init_cgram() függvénnyel.
void lcd_init_cgram(void) {
lcd_write(0x40,0,1,1); // kurzor a CGRAM elejére
for(i=0; i<64; i++) {
lcd_write(betwk[i],1,1,1); // definiálható karakterek feltöltése
} // ékezetes karakterekkel
lcd_write(0x80,0,1,1); // kurzor vissza, a DDRAM elejére
}
A _user_putc() függvényben elhelyezett switch utasítás automatikusan az LCD kódtáblája szerinti ékezetes karakter kódjára cseréli a bejövő karakterkódot. Így az ékezetes karakterek kezelése teljesen transzparens módon történik. Például így írhatjuk: 
  printf("öt hűtőházból kértünk színhúst");
Megjegyzés: Az _user_putc() függvényt nem használhatjuk az LCD-re történő íráshoz akkor, ha az USB kapcsolatot is engedélyezzük, mivel az _user_putc() függvény USB kommunikáció számára van fentartva!

A főprogramban az inicializálások után a végtelen ciklusban négy kisebb ciklust szervezünk:
  1. Beállítjuk a kurzort az LCD első sorának elejére (DDRAM  0x00 cím), s ha a kijelzőnk soronként legalább 20 karakteres, akkor hexadecimálisan kiírjuk a kezdő karakterkódot (a ch változó aktuális értékét) a printf()  függvény segítségével. Ezután egy for ciklusban kiírunk 16 db egymást követő karaktert, s közben inkrementáljuk a ch változó értékét.
  2. Beállítjuk a kurzort az LCD második sorának elejére (DDRAM  0x00 cím), s ha a kijelzőnk soronként legalább 20 karakteres, itt is kiírjuk hexadecimálisan a kezdő karakterkódot. Ezután egy újabb for ciklusban kiírjuk a soron következő 16 db karaktert.
  3. Az SW1 nyomógomb lenyomására várakozunk.
  4. Az SW1 nyomógomb felengedésére várakozunk.
A fentiek eredményeképpen mindig két-két sornyi karakter jelenik meg az LCD-n, s csak az SW1 gomb lenyomása és felengedése után lép tovább a program a következő 32 karakter megjelenítésére. Amint a 8 bites ch változó túlcsordul, automatikusan elölről kezdődik a megjelenítés.

Vegyük észre, hogy az ékezetes karakterek a 0x00 címtől kezdődően helyezkednek el, s a felhasználó által definiálható nyolc karakter kétszer is megjelenik, tehát 0x08-tól kezdődően is előhívhatók. A 0x10 - 0x1F valamint a 0x80-0x9F címtartományban nincsenek megjeleníthető karakterek. Az 0xA0 - 0xFF tartományban pedig a japán katakana ABC jelein kívül néhány görög betű és speciális jel érhető el. Az 0xE0 - 0xFF tartomány karakterei közül némelyek azonban csonkítottak, teljes rajzolatuk csak 5x10-es üzemmódban látható (pl. lelógó szárú betük).

Megjegyzés:
Vigyázzunk, a főprogram for ciklusaiban nehogy a _user_putc() vagy a putc() függvénnyel írassuk ki a karaktereket, mert ez esetben a _user_putc() függvényben elhelyezett átkódolás miatt nem a valódi karaktertábla jelenik meg!

A Microchip PIC18 perifériakönyvtár használata

A C18 fejlesztői környzethez tartozó PIC18 perifériakönyvtár az LCD kezelésére szolgáló függvényeket is tartalmaz. A következő mintaprogramban ezeknek a függvényeknek a használatát mutatjuk be. Mielőtt azonban használatba vehetnénk ezeket, újra kell konfigurálni és fordítani a a p18f4520.lib, illetve az p18f4520.lib állományt, mert "bele van drótozva", hogy az LCD vezérléséhez melyik portot, s hogy 4 vagy 8 bites módban használja adatútként, s hogy mely portlábakat használja vezérlésre (RS, R/W, E), s ez nem illeszkedik a kísérleti áramkörünknél használt bekötéshez. Az LCD-t kezelő függvények használatához tehát először módosítani kell az xlcd.h állományt és újra kell fordítani az adott mikrovezérlőhöz tarozó perifériakönyvtárat (esetünkben a p18f14k50.lib, illetve az p18f4550.lib állományt), az alábbi útmutatást követve:

A feltételezett kiindulási helyzet: telepítetve van MPLAB 8.15a és Microchip C18 v3.22 fordító (utóbbi a C:\Program Files\Microchip\MCC18 mappában). Feltehetőleg a fordító újabb kiadásainál is hasonlóan kell eljárni...
  1. Készítsünk munkaterületet a C:\MCC18 mappában, s másoljuk bele a C18 telepítési mappa \h és \src mappáját (utóbbit az alkönyvtárak tartalmával együtt)!
  2. Hozzunk létre egy üres C:\MCC18\lib mappát is! (Ebbe kerülnek majd a fordítás során létrejövő programkönyvtárak)
  3. Módosítsuk a C:\MCC18\h mappában az xlcd.h állományt! Az LCD-vel kiegészített kísérleti áramkör 8 bites üzemmódjához igazodva (adatport = LEDport, a vezérlő jelek pedig RS=RB5, R/W=RB6 és E=RB7, lásd az 5. ábrán). A PIC18F14K50 mikrovezérlő esetében tehát az xlcd.h állomány így kezdődjön: 
    /* Interface type 8-bit or 4-bit
    For 8-bit operation uncomment the #define BIT8
    */
    #define BIT8

    /* When in 4-bit interface define if the data is in the upper
    or lower nibble. For lower nibble, comment the #define UPPER
    */
    #define UPPER //Ennek most nincs jelentősége

    /* DATA_PORT defines the port to which the LCD data lines are connected */
    #define DATA_PORT PORTC
    #define TRIS_DATA_PORT TRISC

    /* CTRL_PORT defines the port where the control lines are connected.
    These are just samples, change to match your application.
    */
    #define RS_PIN LATBbits.LATB5 /* PORT for RS */
    #define TRIS_RS TRISBbits.TRISB5 /* TRIS for RS */
    #define RW_PIN LATBbits.LATB6 /* PORT for RW */
    #define TRIS_RW TRISBbits.TRISB6 /* TRIS for RW */
    #define E_PIN LATBbits.LATB7 /* PORT for E */
    #define TRIS_E TRISBbits.TRISB7 /* TRIS for E */
  4. Ellenőrizzük, hogy az alábbi programok elérhetők és parancssorból működőképesek:
  1. Állítsuk be az elérési útvonalat az include direktívákhoz: 
      SET MCC_INCLUDE=C:\MCC18\h
  1. A C:\MCC18\src könyvtárban adjuk ki az alábbi parancsot: 
      make_one_device_t.bat 18F14K50
  1. Az alkalmazások fordításánál ügyeljünk arra, hogy az általunk módosított xlcd.h állományt és az C:\MCC18\lib mappában megjelenő új könyvtár(aka)t csatoljuk a projektünkhöz! Legegyszerűbb, ha biztonsági másolatot készítünk a régi állományokról és bemásoljuk a helyükre az új xlcd.h és p18f14K50.lib állományokat.

Egy mintapélda az LCD kezelésére a "gyári" perifériakönyvtár felhasználásával

Az alábbi mintaprogramban a "gyári" perifériakönyvtár felhasználásával 8 bites adatúttal, kétsoros módban inicializáljuk az LCD modult, az 5x7-es fontméret használva. Mindkét sorba kiírunk egy-egy rövid szöveget.

Hardver követelmények: a program az "A kísérleti áramkör" című fejezetben bemutatott referencia áramkörök valamelyikén fut. A kísérleti áramkört az 5. ábrán látható módon ki kell egészíteni egy 2x16 karakteres LCD alfanumerikus kijelzővel (pl. EW162G0GR, RDE-LM12121 vagy RC1602B).
Projekt konfiguráció:


4. lista: LCD kijelző kezelése a "gyári" perifériakönyvtár felhasználásával (xlcd_8bit.c)

#include "piccolo_all.h"
#include "xlcd.h"
#include "delays.h"

void DelayFor18TCY(void) {Delay10TCYx(2); } //20 TCY delay
void DelayPORXLCD(void) {Delay10KTCYx(60);} //50 ms delay
void DelayXLCD() {Delay1KTCYx(60); } //5 ms delay
 
const rom char szoveg[]= "PICCOLO projekt";

void main(void) {
DISABLE_ALL_ANALOG(); //Minden analóg bemenet tiltása
OpenXLCD( EIGHT_BIT & LINES_5X7 );
while( BusyXLCD() );
putrsXLCD(szoveg); //Az első sor kiírása
while( BusyXLCD() );
SetDDRamAddr(0xC0); //Kurzor a második sor elejére
while( BusyXLCD() ); //A második sor kiírása
#if defined(__18F14K50)
putrsXLCD("PIC18F14K50 MCU");
#else
putrsXLCD("PIC18F4550 MCU");
#end
while(1);
}
Az XLCD modul függvényeihez definiálnunk kell három késleltető eljárást: egy 20 utasításciklusnyi késleltetést, a Power On Reset utáni feléledést kiváró, kb. 50 ms késleltetést és egy, a szoftveres resetelésnél használt 5 ms-os késleltetést.

A főprogramban az OpenLCD() függvénnyel inicializálhatjuk az LCD modult. Az EIGHT_BIT paraméter használata itt kötelező, mivel az adatút szélessége csak a perifériakönyvtár újrafordításával változtatható meg! A kétsoros üzemmódot és az 5x7-es fontot előíró LINES_5X7 paraméter opcionális (egysoros módot is választhattunk volna). A paraméterek szimbolikus definíciója a program elején becsatolt xlcd.h állományban található. 

Megjegyzések: