KKM: Grafički LCD Display 128x64

Početnik si s Croduinom. Ili s elektronikom? Za oko ti je zapeo određeni modul, ali ne znaš kako ga koristiti? Bez brige, tu je KKM! Kako Koristiti Module (KKM) je serija blog tutorijala e-radionice na kojoj ćeš pronaći sve što ti treba kako bi započeo rad sa svojim omiljenim modulom. Tutorijali obuhvaćaju: tehničke karakteristike, princip rada, upute kako povezati modul s Croduinom te osnovni kod. Sve ostalo prepuštamo tebi na maštu.

Uvod

U ovome tutorijalu pisat' ćemo o grafičkom displayu, zašto i kada ga koristiti, na koji način ga spojiti s Croduinom i na koji način ga programirati da ispisuje ono što želimo. Razlog zašto koristiti grafički display umjesto običnog 16x2 LCDa se nameće sam, a to je da osim teksta možemo ispisivati i grafiku u što spadaju simboli, oznake, slike, pa čak i tekst koji ima drugačiji font nego na klasičnom displayu. Još jedan razlog zašto koristiti grafički display je taj što se može izvesti vrlo jednostavan GUI (Graphic User Interface) s kojim je upravljanje i podešavanje nekim vašim projektom puno intuitivnije i lakše. Kao prvu stepenicu prema tome, idemo naučiti kako se kontrolira naš display.

 

Kako modul radi

Na slici je vidljiva zadnja strana našeg grafičkog displaya. Vidljivo je da ima 20 pinova koji se koriste za napajanje displaya, komunikaciju, podešavanje kontrasta i pozadinsko LED osvjetljenje. No, na njemu ima još jedna vrlo bitna stvar koja na prvu nije vidljiva, a to je selekcija između paralelne i serijske komunikacije. Naime, ovaj display (točnije čip, tj. kontoler displaya) podržava serijsku SPI komunikaciju i paralelnu komunikaciju. O kontroleru nešto kasnije u nastavku.

 

Prespajanjem srednjeg pada s padom pored slova S, display tada prima serijsku komunikaciju, a ukoliko se prespoji srednji pad s padom pored slova P, display koristi paralelnu komunikaciju. Izgled tih padova (JP2) i njihov smještaj na displayu vidljivo je na slijedećoj slici.

 

 

Da bi uopće mogli povezati LCD i Croduino, potrebno je znati koja je funkcija pojedinog izvoda na 20 pinskom konektoru. Na slijedećoj slici može se vidjeti koja je funkcija svakog od tih 20 pinova.

 

 

Poneki displayi imaju izbor paralelne i serijske komunikacije na samom konektoru (PSB Pin). U tome slučaju, nije potrebno prespajati prethodno navedene padove. Za slučaj da se koristi serijska komunikacija, RS pin je zapravo CS (Chip select), RW je DIN (Data in ili kod SPI komunikacije MOSI), te E kao SCK (Serial Clock), pinovi DB0 do DB7 se tada ne koriste. Ukoliko želite jednostavnost i bitan vam se svaki potrošeni pin, a možete si riskirati nešto malo sporiji ispis, preporučamo korištenje serijske komunikacije (SPI), no ako želite da osim displaya priključite još ponešto na SPI, savjet je da koristite display na paralelnom režimu rada (nije da display neće raditi, no poneki moduli ne vole dijeliti SPI sa još nekim modulom, pa treba to imati na umu). Još su ostala tri pina koje moramo objasniti, a to si RST, VOUT i V0. RST pin je pin koji restartira cijeli display na početno stanje. Poneke biblioteke koriste taj pin da bi se tijekom inicijalizacije biblioteke inicijalizirao (točnije restartirao) sam display. Ukoliko biblioteka (ili pak neki Arduino kod) ne zahtjeva taj pin, potrebno ga je spojiti na napon napajanja (+5VDC) kako bi display bio u normalnom režimu rada spreman za primanje komandi i podataka. V0 pin određuje kakav kontrast želimo, no samo podešavanje ponešto je drugačije nego kod 16x2 displaya. Kod njega spajali smo promjenjivi otpornik između V0 i mase (GND). U ovom slučaju taj promjenjivi otpornik spajamo između V0 i VOUT jer je za podešavanje kontrasta na ovom displayu potreban negativan napon, koji sam taj display može generirati. Poneki displayi već na sebi imaju mali SMD trimer u tu svrhu, pa u tom slučaju, uopće ne trebamo spajati ikakav otpornik između V0 i VOUT, nego samo podesiti taj mali SMD trimer koji će podesiti kontrast LCDa.

 

Spomenuto je da display ima kontroler na sebi (jedna od tri crna epoxy kruga na zadnjoj strani displaya), a radi se o ST7920 integriranom krugu, ostala dva čipa su ST7921 koji povećavaju broj pixela koji kontroler može upravljati. Sam kontroler ima više načina rada, a jedan od njih je da se display može koristiti kao obični alfabtičko-numerički display koji ima značajno veći font nego klasični 16x2 display ili pak može raditi u proširenom setu komandi, pa raditi kao grafički display. Mi ćemo se ovdje zadržati na proširenom setu komandi koji nam omogućuje da "crtamo" različite oblike po njemu. Ako se želite više pozabaviti ovim prvim načinom rada, način na koji način to možete izvesti može se naći u datasheetu displaya.

Za kraj ćemo reći koje vrste grafičkih LCD displaya možete pronaći. Neka glavna podjela je da postoje tri vrste: monokromatski (crno-bijeli), grayscale (mogu prikazivati nijanse jedne boje, recimo sive) i u boji. Naš display spada u monokromatski jer može prikazivati samo upaljen ili ugašen piksel. Oni koji mogu mijenjati intenzitet upaljenog piksela, su greyscale displayi i vrlo su teško nabavljivi i skupi, stoga su rijetko viđeni u nekim projektima. Za kraj najinteresantniji su displayi u boji, koji postoje u različitim izvedbama (serijska, paralelna komunikacija ili pak pokretanje s SD kartice), različitih rezolucija i s različitom količinom bita boje po jedom pikselu (od 8 bita do 24 bita), sa ili bez touchscreena. Takvi displayi su vrlo lako dostupni.

Kako povezati

U ovom dijelu tutorijala, pokazat' ćemo kako povezati Croduino s LCDom. Treba napomenuti da će biti dvije sheme, jedna koja pokazuje kako LCD koristiti u serijskoj komunikaciji, a druga kako ga koristiti u paralelnoj.

Shema za serijsku (SPI) komunikaciju koju koristi ST7920 GFX Library.

 

 

Shema za paralelnu komunikaciju, koju koristi u8g biblioteka.

 

Arduino kod

Kao što je prethodno rečeno, koristit' će se dva načina rada, paralelna i serijska komunikacija. Da bi si olakšali pisanje koda, koristit' ćemo biblioteke (jedna je ST7920 GFX Library koja se temelji na dobro poznatom Adafruit GFX Libraryu (uputstva za Adafruit GFX biblioteku mogu se pregledati ovdje), no za sada ne podržava paralelnu komunikaciju, a druga je U8G library koja podržava i serijsku u paralelnu komunikaciju, ali je nešto kompliciranija za rukovanje). Ukoliko ne znate kako instalirati biblioteke, savjetujemo da pogledate naš tutorijal o tome.

Prvo ćemo se pozabaviti ST7920 GFX Libraryem, stoga otvorite Arduino IDE, zatim FIle->Examples->ST7920_GFX_Library->ST7920_graphic_test i uploadajte taj kod na Croduino. Ako je sve u redu, na displayu će se prikazivati test program za display. Ukoliko se to ne dogodi, provjerite spojeve.

Kada znamo da sada sve radi, idemo napraviti jednostavan ispis slike i nekog teksta.

 

#include "ST7920_GFX_Library.h" //Dodavanje biblioteke za kontorlu grafickog displaya baziranog na ST7920 kontroleru.
#include "Adafruit_GFX.h" //Dodavanje Adafruit GFX biblioteke koja je odgovorna za crtanje raznih oblika po displayu.
#include <SPI.h> //Dodavanje biblioteke za SPI serijsku komunikaciju.

//Ova biblioteka koristi serijsku komunikaciju (SPI komunikacija). Da bi se odabrao serijski nacin rada komunikacije
//s displayom, potrebno je PSB pin staviti na 0V (ground). Inače, display nece raditi i na njemu nece biti ništa ispisano.

#define CS_PIN 10 //Definiranje na koji pin Croduina zelimo spojiti CS (RS) pin LCDa. U ovom slucaj je to deseti pin Croduina.
#define velicinaSlikeX 56
#define velicinaSlikeY 56
ST7920 display(CS_PIN); //Konstruktor LCD biblioteke.

//Bitmap slika (logo e-radionice. :) )
//Za pretvaranje slike u niz podataka za Arduino, potrebno je otvoriti ovaj link:
//https://www.skaarhoj.com/FreeStuff/GraphicDisplayImageConverter.php

static const uint8_t logo[] PROGMEM = {
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x03,0xff,0xc0,0x00,0x00,
 0x00,0x00,0x1f,0xff,0xf8,0x00,0x00,
 0x00,0x00,0x7f,0xff,0xfe,0x00,0x00,
 0x00,0x01,0xff,0xff,0xff,0x80,0x00,
 0x00,0x03,0xfc,0x00,0x3f,0xe0,0x00,
 0x00,0x0f,0xe0,0x00,0x07,0xf0,0x00,
 0x00,0x1f,0x80,0x00,0x01,0xf8,0x00,
 0x00,0x3f,0x00,0x00,0x00,0xfc,0x00,
 0x00,0x7e,0x00,0x00,0x00,0x3e,0x00,
 0x00,0xf8,0x00,0x00,0x00,0x1f,0x00,
 0x00,0xf0,0x00,0x00,0x00,0x0f,0x00,
 0x01,0xf0,0x00,0x00,0x00,0x07,0x80,
 0x03,0xe0,0x00,0x00,0x00,0x07,0xc0,
 0x03,0xc0,0x00,0x00,0x00,0x03,0xc0,
 0x07,0xc0,0x3f,0xff,0x80,0x01,0xe0,
 0x07,0x80,0xff,0xff,0x80,0x01,0xe0,
 0x0f,0x83,0xff,0xff,0xfe,0x00,0xf0,
 0x0f,0x07,0xf0,0x31,0xfe,0x00,0xf0,
 0x0f,0x0f,0x37,0x31,0xfe,0x00,0xf0,
 0x0e,0x0e,0x3f,0x79,0xc0,0x00,0x78,
 0x1e,0x1c,0x3f,0x79,0xc0,0x00,0x78,
 0x1e,0x1c,0x02,0x31,0xc0,0x00,0x78,
 0x1e,0x38,0x00,0x01,0xc0,0x00,0x78,
 0x1f,0xff,0x8e,0x01,0xfc,0x00,0x78,
 0x1f,0xff,0xff,0x01,0xfc,0x00,0x78,
 0x1f,0xff,0xfe,0x01,0xfc,0x00,0x78,
 0x00,0x38,0x00,0x01,0xc0,0x00,0x78,
 0x00,0x1c,0x1c,0x21,0xc0,0x00,0x78,
 0x00,0x1c,0x3c,0x71,0xc0,0x00,0x78,
 0x00,0x1e,0x1c,0xf1,0xc0,0x00,0x78,
 0x00,0x0f,0x18,0x71,0xfe,0x00,0x78,
 0x00,0x07,0xf8,0xe1,0xfe,0x00,0xf0,
 0x07,0x03,0xf8,0xc1,0xfe,0x00,0xf0,
 0x07,0x81,0xff,0xff,0xfe,0x00,0xf0,
 0x07,0x80,0x7f,0xff,0x80,0x01,0xe0,
 0x07,0xc0,0x00,0x00,0x00,0x01,0xe0,
 0x03,0xc0,0x00,0x00,0x00,0x03,0xc0,
 0x03,0xe0,0x00,0x00,0x00,0x07,0xc0,
 0x01,0xf0,0x00,0x00,0x00,0x07,0x80,
 0x00,0xf8,0x00,0x00,0x00,0x0f,0x00,
 0x00,0xfc,0x00,0x00,0x00,0x1f,0x00,
 0x00,0x7e,0x00,0x00,0x00,0x3e,0x00,
 0x00,0x3f,0x00,0x00,0x00,0xfc,0x00,
 0x00,0x1f,0x80,0x00,0x01,0xf8,0x00,
 0x00,0x0f,0xe0,0x00,0x07,0xf0,0x00,
 0x00,0x03,0xfc,0x00,0x3f,0xc0,0x00,
 0x00,0x01,0xff,0xff,0xff,0x80,0x00,
 0x00,0x00,0x7f,0xff,0xfe,0x00,0x00,
 0x00,0x00,0x1f,0xff,0xf8,0x00,0x00,
 0x00,0x00,0x01,0xff,0x80,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
void setup() {
 display.begin(); //Inicijalizacija biblioteke
 display.clearDisplay(); //Ocisti sve iz predmemorije (buffera) displaya.

 //Najcrtaj bitmap sliku na zadanom mjestu (x = 35, y = 0) velicine 56x56 piksela.
 display.drawBitmap(35, 0, logo, velicinaSlikeX, velicinaSlikeY, BLACK);
 display.setCursor(20, 57); //Postavi kursor za tekst na koordinatu x = 20, y = 57
 display.setTextColor(WHITE, BLACK); //Sada zelimo da tekst bude u boji pozadine, a sve okolo teksta u boji piksela
 display.print("e-radionica.com"); //Ispisi tekst
 display.display(); //Ispisi sve podatke na display (sve dok se ne pozove ta funkcija, nista nece biti ispisano na displayu).
}

void loop() {
 display.setTextColor(BLACK, WHITE); //Sada zelimo da tekst bude u boji piksela, a da sve okolo njega bude u boji pozadine
 display.setCursor(0,0); //Zelimo da se tekst ispise u gornjem lijevom kutu
 display.print(millis()/1000); //Ispisi koliko dugo je Croduino upaljen.
 display.display(); //Ispisi sve to na display
 delay(990); //Pricekaj malo do slijedeceg ispisa.
}

 

Sada idemo napraviti već nešto kompliciranije, a to je jedan vrlo jednostavan sat (koji neće biti previše točan jer se brojanje sekundi bazira na Arduinovoj millis(); funkciji, ali je za primjer kako napraviti ispis tog sata na grafičkom displayu odlična).

 

#include "ST7920_GFX_Library.h" //Dodavanje biblioteke za kontorlu grafickog displaya baziranog na ST7920 kontroleru.
#include "Adafruit_GFX.h" //Dodavanje Adafruit GFX biblioteke koja je odgovorna za crtanje raznih oblika po displayu.
#include <SPI.h> //Dodavanje biblioteke za SPI serijsku komunikaciju.

//Ova biblioteka koristi serijsku komunikaciju (SPI komunikacija). Da bi se odabrao serijski nacin rada komunikacije
//s displayom, potrebno je PSB pin staviti na 0V (ground). Inače, display nece raditi i na njemu nece biti ništa ispisano.

#define CS_PIN 10 //Definiranje na koji pin Croduina zelimo spojiti CS (RS) pin LCDa. U ovom slucaj je to deseti pin Croduina.
#define satX 70 //Definiranje x koordinate gdje ce se analogni sat nalaziti na displayu.
#define satY 31 //Definiranje y koordinate gdje ce se analogni sat nalaziti na displayu.
#define pinMinute 2 //Definiranje pina na Croduinu s kojim cemo namjestati minute
#define pinSati 3 //Definiranje pina na Croduinu s kojim cemo namjestati sate
ST7920 display(CS_PIN); //Konstruktor LCD biblioteke.

int sekunde = 0, minute = 0, sati = 0; //Definiranje i inicijalizacija varijabli za sat.
unsigned long time1 = 0;
void setup() {
 display.begin(); //Inicijaliziraj bibiloteku za LCD
 display.clearDisplay(); //Obrisi cijelu predmemoriju za LCD da bi mogli poceti nesto iscrtavati na displayu.
 pinMode(pinMinute, INPUT_PULLUP); //Pin s kojim zelimo podesavati minute mora biti definiran kao ulaz na Croduinu.
 pinMode(pinSati, INPUT_PULLUP); //Pin s kojim zelimo podesavati sate mora biti definiran kao ulaz na Croduinu.
}

char digitalniSat[9];
void loop() {
 if ((unsigned long)(millis() - time1) >= 1000) { //Cekaj jednu sekundu (1000 milisekundi), te zatim povecaj sekunde
 time1 = millis();

 ispisiSat(); //Funkcija koja poziva ispisivanje cijelog sata (i analogonog i digitalnog) na displayu.
 
 sekunde++; //Povecaj sekunde za jedan.
 if (sekunde > 59) { //Ukoliko je prosla minuta (60 sekundi), vrati sekunde na nulu i povecaj minute.
 sekunde = 0;
 minute++;
 }

 if (minute > 59) { //Ukoliko je prosao jedan sat (60 minuta), vrati minute na nulu i povecaj sate.
 minute = 0;
 sati++;
 }

 if (sati > 23) sati = 0; //Ako je prosao cijeli jedan dan, vrati sate nazad na nulu.
 }

 if (!digitalRead(pinMinute)) { //Ukoliko podesavamo minute
 sekunde = 0; //Prvo vrati sekunde nazad na nulu, da nam ne bi sekunde dosle do 59 i povecale same minute za jedan
 minute++; //Zatim povecaj minute za jedan
 if (minute > 59) minute = 0; //Provjeri da li su minute presle 59, ako jesu, neka podesavanje minuta krene od pocetka.
 ispisiSat(); //Ispisi novo vrijeme na display.
 delay(250); //Mala pauza izmedju podesavanja.
 }
 
 if (!digitalRead(pinSati)) { //Ukoliko podesavamo sate
 sekunde = 0; //Prvo vrati sekunde nazad na nulu, da nam ne bi sekunde dosle do 59 i povecale same sate za jedan
 sati++; //Zatim povecaj sate za jedan
 if (sati > 23) sati = 0; //Provjeri da li su sati presli 23, ako jesu, neka podesavanje sata krene od pocetka.
 ispisiSat(); //Ispisi novo vrijeme na display.
 delay(250); //Mala pauza izmedju podesavanja.
 }
}

void ispisiSat() {
 display.clearDisplay(); //Ocisti sve sa zaslona
 sprintf(digitalniSat, "%2d:%02d:%02d", sati, minute, sekunde); //Ispisi digitalni sat koristeci sprintf funkciju. Ta funkcija radi kao i printf funkcija samo sto sve sprema u string koji se u ovom slucaju zove digitalniSat
 display.setCursor(0, 0); //Zelimo da digitalni sat bude u gornjem lijevom kutu
 display.print(digitalniSat); //Ispisi digitalni sat.
 display.drawCircle(satX, satY, 30, BLACK); //Iscrtaj obrub (okvir) analognog sata koji ima polumjer 30 piksela.
 display.drawCircle(satX, satY, 29, BLACK); //Da obrub bude malo deblji, crtamo jos jednu kruznicu za polumjer jedan piksel manji nego prethodna.
 //Malo matematike. :) Za crtanje kazaljki, koristimo drawLine funkciju, koja zahtjeva pocetnu x i y koordinatu, te krajnju x i y koordinatu.
 //Pocetne nisu problem, to su srediste kruznice koja opisuje sat. Krajnje radimo pomocu trigonometrijskih funkcija.
 //Pomocu kosinus funkcije proracunavamo krajnu x koordinatu. Posto cos() funkcija zahtjeva da argument bude u radijanima, moramo nas proracun iz stupnjeva prebaciti u radijane (deg*PI/180).
 //Trebamo proracunati koliko stupnjeva je jednna sekunda. To mozemo dobiti podijelimo li 360/60 = 6. Dakle sekunde mnozimo sa 6 da bi dobili stupnjeve.
 //Jos samo na to trebamo oduzeti PI pola, jer sat pocinje od tako da su kazaljke postavljene vertikalno, a ne horizontalno.
 //Na kraju samo cijeli taj proracun pomaknemo za polozaj sata, odnosno za pocetnu koordinatu linije (satX i satY).
 //Broj ispred sin i cos funkcije govoru o tome koliko ce biti dugacka kazaljka (za sekunde je to 25 piksela).
 int x0 = 25 * cos(((sekunde * 6.0) / 180 * PI) - PI / 2) + satX;
 int y0 = 25 * sin(((sekunde * 6.0) / 180 * PI) - PI / 2) + satY;
 display.drawLine(satX, satY, x0, y0, BLACK);
 x0 = 20 * cos(((minute * 6.0) / 180 * PI) - PI / 2) + satX;
 y0 = 20 * sin(((minute * 6.0) / 180 * PI) - PI / 2) + satY;
 display.drawLine(satX, satY, x0, y0, BLACK);

 //Kod sati jos ima jedan mali dodatak, a to je da kako se povecavaju minute, tako da se i kazaljka za sate lagano pomice.
 //Posto jedan sat ima pomak od 30 stupnjeva, a jedan sat ima 60 minuta, potrebno je samo minute podijeliti s dva i pridodati ukupnom kutu zakreta kazaljke za sate.
 x0 = 20 * cos((((sati * 30.0) + (minute/2)) / 180 * PI) - PI / 2) + satX;
 y0 = 20 * sin((((sati * 30.0) + (minute/2)) / 180 * PI) - PI / 2) + satY;
 display.drawLine(satX, satY, x0, y0, BLACK);
 display.display(); //Na kraju iscrtaj cijeli prikaz na displayu.
}

 

Sada kada smo isprobali serijsku komunikaciju, okušat' ćemo se s paralelnom koristeći u8g biblioteku. Prednost ove biblioteke je ta što koristi vrlo malo radne memorije (RAM), stoga može biti adaptirana u neke kompleksnije projekte. No, to joj je ujedno i mana, jer je znatno sporija od ST7920 GFX Biblioteke. Uputstva biblioteke (zajedno sa svim funkcijama koje biblioteka koristi, mogu se naći ovdje.

 

#include "U8glib.h"

//Pinovi na Croduinu-> 8 Bitna komunikacija: D0..D7: 2, 3, 4, 5, 6, 7, 8, 9, E=10, RS=11, RW=12, PBS = +5V, RST = +5V
U8GLIB_ST7920_128X64_4X u8g(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);

void setup() {
 u8g.setRot180(); //Rotiramo prikaz za 180 stupnjeva
}

void loop() {
 u8g.firstPage(); //Ovo je funkcija s kojom zapocinjemo crtanje po displayu
 do { //Ispis na displayu je napravljen tako da je podjeljen u vise page-ova (dijelova).
 draw(); //Pozivamo funkciju u kojoj se nalazi sve sto zelimo ispisati na displayu
 } while ( u8g.nextPage() ); //Nakon sto je ispisao jedan page, idi na slijedeci, sve dok ne ispises sve pageove odnosno, cijeli display
 delay(990); //Pricekaj malo do slijedeceg ispisa.
}

void draw() {
 u8g.setFont(u8g_font_fixed_v0); //Odaberi s kojim fontom se zeli ispisivati po displayu (popis fontova: https://github.com/olikraus/u8glib/wiki/fontsize)
 u8g.setFontPosTop(); //Zelimo da referentna pozicija za ispis stringa bude u gornjem lijevom kutu toga stringa
 u8g.setPrintPos(0,0); //X i Y kooridinata gdje se string zeli ispisati na displayu
 u8g.print("e-radionica.com"); //Ispis zeljenog stringa na zadanu poziciju
 u8g.setPrintPos(30,50); //Promjena koordinata za ispis novog stringa
 u8g.print(millis()/1000); //Ispis novog stringa na display
}
Leave a Reply