Merhaba. Bu yazı LCD yazısının devamı gibi ama donanımla değil yalnız yazılımla ilgili olacak. Önceki yazıda söz ettiğim sorunların çözümüne değineceğim ve LCD'de özel karakterler nasıl tanımlanıyor onu göstereceğim. Maalesef özel karakter tanımlama konusunda internette kaynak az sayıda. Bu iş örneğin Arduino gibi daha yüksek seviyeli platformlarda bir fonksiyonla yapılabiliyor.
Bu anlatımda önceki bölümdeki olay sırasına uymaya çalışacağım.
Pinlerle İlgili Sorun
İlk yaşanan sorunda, ekrana hiç karakter yazdıramamıştım. Aklıma, aslında karakterlerin ekrana yazıldığı ama göremediğim geldi, çok önce bunu yaşamıştım. LCD kontrast (3) pinini 4.7K - 2.2K değerinde bir dirençle toprağa bağlamak basit bir devre için yeterli, ben de öyle yapacaktım ama "acaba göremiyor muyum?" düşüncesiyle kontrastı ayarlamak istediğimden sabit direnci potansiyometreyle değiştirdim. Arka ışıklandırma da, LCD pin 15 Vcc ve pin 16 GND olmak üzere LED'lere bağlı. Yeşil ekranlarda ışıklandırmaya gerek olmuyor. Ben de ışıklandırmayı kullanmak istemediğimden yeşil LCD seçmiştim.
Devreyi kolaya kaçmadan tekrar kurduğum halde sorun çözülmemişti. Kodun sonundaki outb(0, BASE) satırını outb(255, BASE) olarak değiştirip veri pinlerine gelen gerilimi kontrol ettim. Bunlarda sorun yoktu. Sonra koddaki delay parametresini voltmetrenin okuyabileceği yavaşlığa getirip (3 sn) E ve RS pinlerine baktım. E, her adımda kapanıp açıldığı halde RS'de hareket yoktu. Kullandığım pin bağlantı şemasını aşağıda var:
PP Sinyal
|
DB25 Pin
|
Centronics Pin
|
IC In
|
IC Out
|
LCD Sinyal (Pin)
|
nStrobe(C0)
|
1
|
1
|
IC2_17
|
IC2_3
|
E (6)
|
nSelect (C3)
|
17
|
36
|
IC2_15
|
IC2_5
|
RS (4)
|
Data0 (D0)
|
2
|
2
|
IC1_2
|
IC1_18
|
D0 (7)
|
Data1 (D1)
|
3
|
3
|
IC1_4
|
IC1_16
|
D1 (8)
|
Data2 (D2)
|
4
|
4
|
IC1_6
|
IC1_14
|
D2 (9)
|
Data3 (D3)
|
5
|
5
|
IC1_8
|
IC1_12
|
D3 (10)
|
Data4 (D4)
|
6
|
6
|
IC1_17
|
IC1_3
|
D4 (11)
|
Data5 (D5)
|
7
|
7
|
IC1_15
|
IC1_5
|
D5 (12)
|
Data6 (D6)
|
8
|
8
|
IC1_13
|
IC1_7
|
D6 (13)
|
Data7 (D7)
|
9
|
9
|
IC1_11
|
IC1_9
|
D7 (14)
|
Kurulum sırasında yararlandığım kaynakta [ http://www.interfacebus.com/Design_ieee1284b_Connector_PinOuts.html ] nSelect için 13. pini gösteriyor. Burayı yanlış anlamışım çünkü aslında 13, bir giriş pini. Benim normalde kullandığım kaynakta [ https://www.lammertbies.nl/comm/cable/parallel.html ] yönler de gösteriliyordu. Çıkışı 36. pinden değil 13. pinden alıyormuşum, bu pin de giriş olduğundan hep lojik 1'deymiş. Bu arada jumper kabloların (renkli olanlar) ucundaki plastiğin eni, Centronics'in port aralıklarından fazla olduğundan çift sayılı portlarda tel; tek sayılılarda jumper kablo kullandım (örnek). Önce temassızlıktan şüphelendim ama öyle olsa kod her çalıştırıldığında farklı sonuçlar üretirdi. Sinyallerin portlarla ilişkisine de ileriki paragraflarda değineceğim.
Bu arada LCD'deki DELAY parametresinde üst sınır olmamasına dair şöyle bir video var:
lcdKomut() Fonksiyonundaki Sorun
Fonksiyon ilk haliyle aşağıdaki gibiydi:
void lcdKomut(unsigned char veri) {
outb(veri, BASE);
outb(8 , CTRL); // RS = 0; E = 1
usleep(DELAY);
outb(9 , CTRL); // RS = 0; E =0
usleep(DELAY);
}
outb(veri, BASE);
outb(8 , CTRL); // RS = 0; E = 1
usleep(DELAY);
outb(9 , CTRL); // RS = 0; E =0
usleep(DELAY);
}
Nedense bu haliyle her komut gönderdiğimde ardından gönderdiğim veri iki kere gidiyordu. Yani Deneme123 yerine DDeneme1123 oluyordu. Bunun, paralel port denetleyicisinin pini yeterince hızlı lojik 0'a çekememesi, daha E = 0 olmadan bir sonraki C komutunun veriyi veriyoluna sürmesi olduğunu düşünsem de aynı davranışı uzun DELAY parametrelerinde bile gözlemledim. Koddaki 9'u 1'le değiştirdim ve sorun düzeldi. Bununla ilgili olarak çok sevdiğim bir görsel var:
Sorun çözüldüğü için uzun uzun araştırmak istemedim. Belki osiloskopla gidip gelen sinyalleri tekrar incelemek gerekir ama dediğim gibi uğraşmak istemedim.
Paralel Port Durum ve Kontrol Sinyalleri
Sinyallerden bahsetmişken durum ve kontrol sinyallerine de kısaca değinmem gerekir. Paralel portta üç grup sinyal bulunur: veri, durum ve kontrol. Bu gruplardaki sinyaller sırayla BASE, BASE+1 ve BASE+2 portlarıyla kontrol edilir. Veri sinyalleri basittir: BASE porta hangi byte yazılırsa, [2-9] pinlerinin değeri ona göre ayarlanır ve öyle kalır. Durum sinyalleri yazıcının durumunu okuma amaçlı olduğundan giriş için kullanılabilir. Örn. yazıcı bir kesme isteği ürettiğinde, kağıt bittiğinde veya sıkıştığında vb.
nStrobe ve nSelect, kontrol sinyal grubundadır. Kontrol grubu, bağlı yazıcıyı kontrol etmek için vardır ve "active low" sinyallerdir. Bu nedenle sinyalin başına "n" veya "~" ile gösterilir. nStrobe, bilgisayar yazıcıya veri gönderirken saat sinyali olarak kullanılır ve her veri değişiminde set etmek gerekir. nSelect, yazıcı seçildiği zaman set edilir. Kontrol yazmacı 0x378 + 2 = 0x37A portunda, sırayla nStrobe ve nSelect sinyali bu portun 0. ve 3. bitlerindedir. Dolayısıyla nStrobe'u lojik 0 yapmak için porta 1 yazmak, nSelect'i lojik 0 yapmak için porta 8 yazmak gerekir.
LCD Komutları ve Özel Karakter Tanımlama
Önceki yazıda LCD komutlarından bazılarını kullandım ama hepsine değinmedim. Aslında LCD'lerle uğraşırken 3-4 tane komut yeterli. Ben LCD'lerle çalışırken şu görseli kullanıyorum: https://goo.gl/images/QHtKee . Google görsellerinde "lcd commands" aratınca benzeri bir çok sonuç çıkıyor. En doğrusu, her zaman LCD'lerin datasheet'ini esas almak ama hızlıca birşeyler yapmak için 60 sayfa belge okumak zor. Ben drive'a bir tane datasheet yükledim. Bununla CGRAM'i programlayıp LCD'de kendi karakterlerimi oluşturacağım.
CGRAM, character generator RAM adında fontların tutulduğu bellek. DDRAM ise ekranda görünen hücrelerdeki karakterin kodunu tutan bellek. DDRAM'e bir veri yazıldığında CGRAM/CGROM look-up tablosu olarak kullanılıyor. CGRAM programlanabilir olup kullanıcıya kendi karakterlerini oluşturma olanağı veriyor. Datasheet'te, CGRAM'i adreslemek için 6 bit ayrılmış görünüyor ama standart bir LCD'de bu bitlerin hepsinin kullanıldığına emin değilim. CGRAM'in adresi sıfırdan başlıyor, bu arada.
Sayfa 24, Tablo6'da komut tablosu var. Önce komutla, kaçıncı karaktere yazacağımı bildirmem gerek. CGRAM'i adreslemek için 0b01XX XXXX komutunu vermeliyim. Buradaki X'ler adres olduğundan sıfırıncı adres için operand 0x40. s31'de CGRAM'e verinin lcdVeri() ile gönderilebileceği yazıyor. Yazılacak karakter 5 bit genişliğinde bitmap verisi olarak gönderilmeli, bu da önceki sayfalarda var. Dikkat edilmesi gereken bir konu da CGRAM'a yazılırken LCD ekran kapatılmalı.
Karakter tanımlamak için eski kodu biraz değiştirdim. Tanımlamalar, lcdVeri() ve lcdKomut() fonksiyonları aynı. Sadece main()'i aşağıya yapıştırdım:
int main(int argc, char* argv[]) {
int i;
if(ioperm(BASE, 3, 1)) {
fprintf(stderr, "Access denied to %x\n", BASE);
return 1;
}
lcdKomut(0x38); // 8 bit, 2 satir, 5x7 px
lcdKomut(0x08); // ekrani kapat
const unsigned char ozelkarakter[] = {
// gulen surat gibi birsey
0B01110, 0B10001, 0B11011, 0B10001,
0B11011, 0B10101, 0B10001, 0B01110,
// gulen suratin tersi
0B10001, 0B01110, 0B00100, 0B01110,
0B00100, 0B01010, 0B01110, 0B10001,
// maca
0B00100, 0B01110, 0B11111, 0B11111,
0B10101, 0B00100, 0B01110, 0B00000,
// sinek
0B00000, 0B01110, 0B10101, 0B11111,
0B10101, 0B00100, 0B01110, 0B00000,
// kupa (kalp)
0B00000, 0B00000, 0B01010, 0B11111,
0B11111, 0B01110, 0B00100, 0B00000,
// karo
0B00000, 0B00100, 0B01110, 0B11111,
0B11111, 0B01110, 0B00100, 0B00000
};
lcdKomut(0x40); // 0. CGRAM adresi
for (i = 0; i <= 47 ; i++)
lcdVeri(ozelkarakter[i]);
lcdKomut(0x01); // ekrani sil
//lcdKomut(0x80); // satirbasi
lcdKomut(0x0F); // ekrani ac, kursor blink
lcdVeri('D'); lcdVeri('e'); lcdVeri('n');
lcdVeri('e'); lcdVeri('m'); lcdVeri('e');
lcdKomut(0xC0); // ikinci satir
lcdVeri('1'); lcdVeri('2'); lcdVeri('3');
lcdVeri(' '); lcdVeri(' '); lcdVeri(' ');
// ozel karakterler
lcdVeri(0x00); lcdVeri(0x01); lcdVeri(0x02);
lcdVeri(0x03); lcdVeri(0x04); lcdVeri(0x05);
outb(0, BASE);
return 0;
}
int i;
if(ioperm(BASE, 3, 1)) {
fprintf(stderr, "Access denied to %x\n", BASE);
return 1;
}
lcdKomut(0x38); // 8 bit, 2 satir, 5x7 px
lcdKomut(0x08); // ekrani kapat
const unsigned char ozelkarakter[] = {
// gulen surat gibi birsey
0B01110, 0B10001, 0B11011, 0B10001,
0B11011, 0B10101, 0B10001, 0B01110,
// gulen suratin tersi
0B10001, 0B01110, 0B00100, 0B01110,
0B00100, 0B01010, 0B01110, 0B10001,
// maca
0B00100, 0B01110, 0B11111, 0B11111,
0B10101, 0B00100, 0B01110, 0B00000,
// sinek
0B00000, 0B01110, 0B10101, 0B11111,
0B10101, 0B00100, 0B01110, 0B00000,
// kupa (kalp)
0B00000, 0B00000, 0B01010, 0B11111,
0B11111, 0B01110, 0B00100, 0B00000,
// karo
0B00000, 0B00100, 0B01110, 0B11111,
0B11111, 0B01110, 0B00100, 0B00000
};
lcdKomut(0x40); // 0. CGRAM adresi
for (i = 0; i <= 47 ; i++)
lcdVeri(ozelkarakter[i]);
lcdKomut(0x01); // ekrani sil
//lcdKomut(0x80); // satirbasi
lcdKomut(0x0F); // ekrani ac, kursor blink
lcdVeri('D'); lcdVeri('e'); lcdVeri('n');
lcdVeri('e'); lcdVeri('m'); lcdVeri('e');
lcdKomut(0xC0); // ikinci satir
lcdVeri('1'); lcdVeri('2'); lcdVeri('3');
lcdVeri(' '); lcdVeri(' '); lcdVeri(' ');
// ozel karakterler
lcdVeri(0x00); lcdVeri(0x01); lcdVeri(0x02);
lcdVeri(0x03); lcdVeri(0x04); lcdVeri(0x05);
outb(0, BASE);
return 0;
}
Bu kodla özel karakterler tanımlandı ve ekrana basıldı: