30 Ekim 2018 Salı

Karakter LCD için 3.3V TTL Sinyalleri 5V'a Yükseltelim


Merhaba. Bir önceki yazıda, anlatacağım olayların birbirinden bağımsız olduğunu söylemiştim ama bu kısmen doğru. Önceki yazıda hazırladığım masaüstü makina bana paralel portu için gerekliydi. Maalesef artık paralel portlu makinalar bulunmuyor. Paralel porta ihtiyacınız varsa bunun için özel üretilen PCI kartlardan almak gerekiyor. USB-Paralel port çeviriciler standart paralel port arayüzü sunamıyor. Standart arayüzden kastım 0x378 veya 0x3BC portu. Paralel port, LCD devresi için gerekliydi. Bu yazıda hepsinden biraz bahsedeceğim. Bu arada benim favorim 0x3BC ama kullandığım BIOS port seçimini desteklemiyor. Bu yüzden 0x378'i kullanmak zorunda kaldım.

Paralel portu kullanmadan önce BIOS'tan, açık olduğunu ve uygun bir modda çalıştığını kontrol ettim. Aslında uygun mod için belli bir kastım yok, port açıksa en basit "Output only" bile yeterli.

Sözünü ettiğim makinaya USB ile CentOS kurdum. dmesg çıktısından paralel portunu da kontrol ettim.

[root@xxxxxxxx ~]# dmesg | grep -i parport
parport_pc 00:08: reported by Plug and Play ACPI
parport0: PC-style at 0x378, irq 7 [PCSPP]

Köşeli parantezde portun modu yazıyor. Benim portum standart paralel port (SPP) olarak çalışıyor. Çift yönlü paralel port için [PCSPP, Tristate], Enhanced Parallel Port (EPP) için [PCSPP, Tristate, EPP] ve Extended Capabilities Port (ECP) için [PCSPP, Tristate] ile birlikte parantez içinde ikincil port adresi yazmalı. Aklımda kaldığı kadarıyla SPP, tek yönlü 50KB/s hızla çalışıyor. Çift yönlü mod aynı hızda ama adı üzerinde çift yönlü; EPP veya ECP'den biri 500KB/s'den 2MB/s'ye kadar hızlı çalışıyor. Yazının konusu bu olmadığından fazla detayına girmek istemiyorum.

Yapmak istediğim devrenin şeması şöyleydi:
Devre şeması (orjinali: http://www.aljaz.info/elektro/lcd/lcd-lpt.htm)
İlgili kaynağı takip etmedim ama benim kullandığım kaynaktaki devre şeması da pull-up dirençler dışında bunun aynısınıydı. Proje basit olduğundan herkesin aynı devreyi kurması çok da şaşırtıcı değil.

Devreyi kurup LCD'ye yazması gereken kodu* çalıştırdığımda ekranda bir çıktı yoktu. Farklı şekilde denesem de sonuç değişmedi. LCD yerine LED'ler bağladım ve kodun çalışma hızını saniyede bir karaktere kadar düşürdüm. Fazla ayrıntısına girmeyeceğim. En son port gerilimini ölçtüğümde sorunun portun 3.3V çıkış vermesi olduğunu buldum. Aslında emin olmak için LCD'nin datasheet'ine bakıp 3.3V'un TTL'deki kararsız bölgeye düştüğüne emin olmam gerekirdi ama bunu yapmadım.
(*): LCD kodunu yazının sonlarına doğru bulabilirsiniz.


Paralel portla 366 Celeron makinada da uğraştığımı düşününce çok sürpriz değil. Test için aşağıdaki kodu kullandım.

#include<stdio.h>
#include<sys/io.h>

#define BASE 0x378

int main(int argc, char* argv[])        {
    if(ioperm(BASE, 1, 1))      {
        fprintf(stderr, "Access denied to %x\n", BASE);
        return 1;
    }
    outb(1, BASE);
    return 0;
}

https://commons.wikimedia.org/wiki/File:IEEE_1284_36pin_plughead.jpgKod en düşük anlamlı biti set edip çıkıyor. En düşük anlamlı bit, hem DB25 paralel portta hem de Centronics portta ikinci pine karşılık geliyor. Bu arada "Centronics port", aslında Centronics firması tarafından geliştirilen ve çift yönlü paralel portu tanımlayan IEEE 1284 standardı için kullanılıyor. Benim kastettiğim, port değil yanda fotoğrafını koyduğum bağlantı. Bu bağlantı yukarıdaki benim fotoğrafta da görülebiliyor.

Bu bağlantının asıl adı Micro Ribbon Connector'müş. Fotoğraftaki, yazıcılarda kullanılan 36 pin'li konektör. Ben ağız alışkanlığıyla Centronics port demeye devam edebilirim.

Pinleri karıştırmamak için porta 1 yerine 255 gönderilirse tüm data pinlerindenden [2-9] +3.3V alınabilir.

74HC244 Bağlantı şeması
Internette bulabildiğim kadarıyla bu gerilim farkından özellikle CNC'ciler şikayetçi. 6502.org forumunda, port 1K'lık veya 4.7K'lık pull-up dirençlerle bağlanarak 5V'a çıkarılabileceği yazıyordu [ burada ], bunu güvenilir bulmadığım için denemedim. Bunun yerine aklıma 8-bit tristate buffer entegresiyle girişleri yükseltmek geldi. Bu iş için 74HC244 entegresini kullandım. Datasheet'ine baktığımda lojik 1 için minimum giriş gerilimi 2.0V. Yani paralel porttan aldığım 3.3V'u girişe lojik 1 olarak uygulayıp çıkışta 5V alabilirim.

Bunun için yukarıda şemasını çizdiğim basit bir devre kurdum. Entegrenin enable pinini kullanmadığım için bunu toprağa bağladım. Bir tane de 100nF kondansatör koydum. Yalnız LCD'yi 8 bit modda çalıştırdığımdan LCD'nin RS ve E pinleriyle birlikte 10 bite ihtiyacım vardı. Bu nedenle iki tane 74HC244 kullandım. +5V'u sabit disk bağlantısından, GND'yi paralel port şasisinden kullandım. İlk denemeyi tek pinle yaptım. Sonuç olumlu.


Fotoğraftaki twisted pair kablo +5V. Eski CAT5 kabloları böyle değerlendiriyorum. Şemadaki 100nF kondansatörü breadboard'da koymaya üşendim. Üstte girişten okuduğum, altta da çıkıştan okuduğum gerilim görülüyor. O halde artık tüm pinleri entegreler üzerinden LCD'ye bağlayıp deneyebilirim.


Devrenin son hali yukarıdaki fotoğrafta. Aslında kontrastı sabitleyip arka ışıklandırmayı kullanmayacaktım ama pinlerden birini hatalı bağladığım için sorun yaşadım. Ekranı mı göremiyorum yoksa veride mi sorun var, anlamak için bağladım, sonra da geri sökmedim. Kod aşağıda: 

#include<stdio.h>
#include<sys/io.h>

#define BASE 0x378    // Base port adresi
#define CTRL 0x37A    // Kontrol portu adresi
#define DELAY 3000    // Busy wait icin

void lcdKomut(unsigned char veri)    {
    outb(veri, BASE);
    outb(8   , CTRL);    // RS = 0; E = 1
    usleep(DELAY);
    outb(1   , CTRL);    // RS = 1; E = 0
    usleep(DELAY);
}

void lcdVeri(unsigned char veri)    {
    outb(veri, BASE);
    outb(0, CTRL);    // RS = 1, E = 1
    usleep(DELAY);
    outb(1, CTRL);    // RS = 1, E = 0
    usleep(DELAY);
}


int main(int argc, char* argv[])    {
    if(ioperm(BASE, 3, 1))    {
        fprintf(stderr, "Access denied to %x\n", BASE);
        return 1;
    }

    lcdKomut(0x38);    // 8 bit, 2 satir, 5x7 px
    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');
 
    outb(0, BASE);
    return 0;
}

Aslında paralel portun kontrol yazmacı, veri yazmacının 2 fazlası. Bu nedenle ikinci define, "#define CTRL BASE+2" olsa daha iyi. DELAY parametresi, LCD ekranın komutları işlemesi için gereken süre. Bunu 300 µs'ye kadar indirdiğimde bu kodda sorun yaşamadım ama başka kodlarda yaşadım. 3 ms, deneme yanılmayla bulduğum bir değer. Eğer R/W pinini kullanıyor olsaydım LCD ekranın busy flag'ini D7 bitinden okuyabilirdim. DELAY için üst sınır bulunmuyor.

lcdKomut() fonksiyonu komut göndermek için, lcdVeri() fonksiyonu da veri göndermek için iki fonksiyon. Bunların çalışması sırasında pinlerin değerini kodun açıklamasına yazdım. Komut gönderildikten sonra E = 0 olması RS pininin değerinden önemli. E = 0'a hemen çekilmezse anlayamadığım biçimde kendisinden sonra gelen veri iki kere gönderiliyordu. Bu sorunu ayrıntılı ele almak üzere ileri bir yazıya sakladım.

main() fonksiyonunda LCD'ye temel komutları gönderdim. 0x80 komutunu kaldırdım çünkü hatırladığım kadarıyla 0x01 komutu zaten 0x80'i içeriyor. Veri gönderme fonksiyonuyla ekrana yazacağım verileri gönderdim. Kürsör veri gönderildikçe kendiliğinden ilerliyor. 0xC0 komutu ikinci satır başına gidiyor. Eğer ikiden fazla satır olsaydı diğer satırlara gitmek için başka komutlar var.

Bu yazı TTL gerilim değeriyle ilgili olduğundan burada bitiriyorum. Pin  bağlantı tablosu, bağlantıda yaşadığım sorun ve başka LCD komutlarıyla ilgili ayrı bir yazı daha yayınlayacağım.