LCD Ekran Kullanımı (Dot Matrix) Bölüm 2

Bu bölümü okumadan önce LCD Ekran Kullanımı (Dot Matrix) Bölüm 1 'i okumanızı tavsiye ederim. Modül pin isimleri, görevleri, komutlar ve adres bilgilerine değindiğim ilk bölümden farklı olarak uygulamaya yönelik daha detaylı bilgiler vermeye çalışacağım. Burada arduino kodlarını paylaşmayacağım bunu yapan çok sayıda blog ve site var. C dilinde yazılmış kod parçaları paylaşacağım. İlk olarak modül ve mikro denetleyicinin bağlantılarını gerçekleştirelim. 4 Bit veri aktarımı kullanarak anlatım yapacağımı belirtmiştim.
Modülün D4,D5,D6,D7 (D4-D7) pinleri veri alış-verişi yapan pinleridir. Paralel veri alma verme işleminde program kısmında kolaylık olması açısından aynı porta bağlamak uygun olacaktır. Arduino uno D4-D7 ya da atmega328p olarak PORTD4-PORTD7 pinlerine bağlanacak. RS saklayıcı seçici D8 ya da PORTB0, EN saat darbesi D9 ya da PORTB1 pinine bağlanacak. RW pini genelde Gnd bağlantılı eğer değilse D11 ya da PORTB3 bağlayabilirsiniz. D10 bende bulunan ve piyasadaki birçok modelde kontrast ve arka aydınlatma bağlı durumdadır. LED+ D10 ya da PORTB2 ye bağlıdır. Bu şekilde bir modül değilse kontrast (VSS,VEE,VCC) ve arka aydınlatma (VCC,LED+, LED-) yazdığım sırayla 10k potansiyometre ile bağlantı yapılacak. Tüm bağlantılar yapıldıktan sonra port ve makro tanımlarını yapacağız ve fonksiyonları oluşturacağız. Öncesinde tekrar gibi olsa da alttaki grafik dikkatle incelenmeli. kontrolcüye bilgilerin nasıl gönderilip alınacağını kavramak için önemli.
Grafikte görülen veri ve komut yazma işlemlerinde "EN" saat darbesi ve "DATA" kısmı ortak ikisi arasında sadece "RS" farkı var. Ortak olan kısım için fonksiyon tanımlarsak, data portuna veriyi göndereceğiz EN "1" ve "0" saat darbesiyle modül kontrolcüsüne veriyi aktaracağız. Data portu PORTD D4-D7 çıkışlarıydı, örnek olarak "0011" gibi bir veri göndermek istesek PORTD=0b0011xxxx şeklinde data portu çıkışlarını "1" ya da "0" yapmamız gerekir. Bunu yapmak için göndereceğimiz verinin her bir bit verisini çıkışa tek tek yazabileceğimiz gibi PORTD|= 0b0011xxxx şeklinde de yapabiliriz. Gördüğünüz gibi sadece bit 7 ve bit 4 arası yüksek bitleri kullanıyoruz. Kontrolcüyü 4bit olarak ayarlayacağımız için bit0 -bit3 işleme alınmayacak. Tanımlayacağımız fonksiyon parametresinin düşük bitlerini silip data portuna yazmalıyız.



void lcd_gonder(uint8_t  gelen){
       PORTD=(gelen & 0xF0);
Görüldüğü gibi "gelen" parametresinin 0xF0 yani 0b11110000 ile "and" işlemine tabi tuttuk. Örnek: PORTD=0b00001010 olsun ve gelen=0b00110101olsun, gelen veriyi "and" ile 0b00110000 haline getirdik ve PORTD=0b00110000 olarak data portuna yazdık. Bu şekilde data portuna yazdığımızda farklı bir amaç için kullanabileceğimiz "D3-D0" 4 adet çıkışı ya da girişi de değiştirmiş olduk. Bu istenmeyen durumu çözmek için PORTD ye de 0x0F yani 0b00001111 ile and işlemi uygulamalıyız. PORTD=(PORTD&0x0f)|(gelen & 0xF0); olarak düzeltirsek yukarıdaki örneğimiz değişir. PORTD=0b00001010, gelen=0b00110101 "and" işlemi sonunda parantez içleri (0b00001010), (0b00110000) olur "or" işlemiyle iki parantezin sonucu (0b00111010) olur. Bu sayede veriyi yazarken düşük bitleri değiştirmemiş oluruz. Porta bilgiyi çıkış olarak ilettik "1" ve "0" durumuna göre çıkışlar tetiklendi. Bu bilginin kontrolcünün ilgili saklayıcısı tarafından alınması için "EN" pinin "1" sonrasında "0" olması gerekiyor. 4Bit veri alacak şekilde ayarladığımız kontrolcüye 8 Bit olan veriyi ikiye bölerek göndereceğiz demiştim. Önce yüksek bit sonra düşük bit gönderilecek. Bunu komut ve veri saklayıcısını seçen ilgili saklayıcıya gönderim yapan iki ayrı fonksiyon ile tanımlarız. Fonksiyonlar arasındaki tek fark "RS" pininin "1" ya da "0" olmasıdır. Komut gönderirken RS "0" olacak, veri gönderirken "1" olacaktır. Bunun dışında ister komut ister veri olsun yazma işlemi aynıdır ve veri 8bitten oluşmaktadır. Örneğin "A" harfi yazmak için (0x41,0b01000001)  lcd_yaz("A"); ya da  lcd_yaz(0b01000001); şeklinde kod yazmamız gerekir. lcd_yaz fonksiyonu  parametre verisini (0b01000001) iki defa lcd_gonder() fonksiyonuna göndermeli. Yüksek bit yazılacağı için (0100) yazıldıktan sonra (0001) düşük bitleri 4 bit sola kaydırılmalı. lcd_gonder(0b01000001) sonrasında lcd_gonder(0b00010000) şeklinde olmalıdır. Bunun için 
void lcd_yaz(uint8_t  data){
       RS_HIGH;    
       //RW_LOW;
       lcd_gonder(data);
       lcd_gonder(data<<4);
      
}
şeklinde fonksiyon oluşturabiliriz.
void lcd_cmd(uint8_t  cmd){
Lcd_cmd fonksiyonu aynı sadece RS_HIGH; değil RS_LOW; olacaktır.
Modüle komut ya da veri yazma işlemini tanımladıktan sonra modülü başlatma fonksiyonunu tanımlayabiliriz. Datasheet' lerde farklı sıralama ve zamanlamalar olsa da mantık aynı. Enerji verildikten sonra sabit besleme için beklenecek sonrasında veri uzunluk seçimi, giriş yöntemi, ekran satır sayısı ve yazım yönü gibi komutları gönderip başlatma fonksiyonunu tamamlamış olacağız. 

Datasheet' den alıntı resimde görüldüğü gibi gerekli komutlar yazılmış. Kontrolcülerde bekleme zamanları farklı, bazı kontrolcülerde "Function Set" komutuyla "0011" "0x03" 8bit verisi gönderilmemiş 4bit veri seçilerek işleme devam edilmiş. Adım adım gidecek olursak önce veri göndereceğimiz için data portunu ve kontrolcünün yönetimi için gerekli (RS,RW,EN) pinleri çıkış yapmamız gerekiyor. Enerji verildikten sonra 15-30 milisaniye bekliyoruz. “0x03” bilgisini 3 kere gönderiyoruz. Bu veriyi “cmd” ya da “yaz” fonksiyonuyla değil “lcd_gonder” fonksiyonuyla yapıyoruz. Veri 4bittir diğer fonksiyonlar 8bit veriyi ikiye bölerek göndermektedir. İlk olarak 4.1ms sonra 100µs daha bekleyip  “0x02” bilgisini gönderip komutlara geçiyoruz. Gerekli ayarlamaları yapan komutları da gönderdikten sonra modül kullanıma hazır hale geliyor. Artık gerekli komutlarla ekranı kullanmaya başlayabiliriz.
Önceki bölümde modülün, kontrolcünün komutlarından bahsetmiştim. Bu komutların detayına inmeden kolay kullanım için komutları tanımlayalım.
Clear display: LCD_CL
Return Home: LCD_HOME
Entry Mode Set: SCR= Ekran kayma açık, NSCR=Ekran kayma kapalı, L=Sol, R=Sağ
LCD_NSCR_RL -LCD_NSCR_LR - LCD_SCR_RL - LCD_SCR_LR 


Display ON/OFF:  D= Ekran on-off, C= imleç on-off, B= kutu on-off
LCD_DOFF - LCD_DON - LCD_DBON - LCD_DCON - LCD_DCBON
Cursor or Display Shift: (Bu komut ekrana yazarken ya da döngü halinde verilmelidir.) CRL=İmleç sol, CRR=İmleç Sağ, SCL= Ekran sol, SCR=Ekran sağ kaydırma
LCD_CR_L - LCD_CR_R - LCD_SC_L - LCD_SC_R


Function Set: 4L1= 4 bit tek satır, 4L2= 4 bit iki satır.
LCD_4L1 - LCD_4L2
Modülü kullanırken gerekli komutları tanımladıktan sonra yine kullanıma dönük komutlardan Set CGRAM Address, Set DDRAM Address komutlarını doğru ve düzgün kullanım için fonksiyon olarak tanımlamalıyız.
CGRAM: Daha önce anlattığım özel karakter hafıza birimiydi. Oluşturduğumuz karakterleri hangi adrese atacağımızı belirlemek için kullanıyorduk.
void lcd_krk(uint8_t  adres, uint8_t  karakter[]){
Fonksiyon başlığını incelediğimizde iki parametre bulunmakta bunlardan biri adres CGRAM komutu için adres, diğeri adrese gönderilecek veri. Daha önce bu konuyu açıkladığım için burada anlatmayacağım.           
void lcd_krk(uint8_t  adres, uint8_t  karakter[]){     
       lcd_cmd(0x40+(adres*8));
       for (uint8_t i=0;i<8;i++)
       {
              lcd_yaz(karakter[i]);
       }
}

Fonksiyonun gövdesini incelediğimizde komut gönderimi ile adres seçiliyor sonrasında döngü ile adrese dizi içindeki veri kaydediliyor.
DDRAM: Ekranda gördüğümüz karakterlerin adreslerini belirleyen hafıza birimiydi. Karakterlerin ya da imlecin yerini belirlemek için bu komutu kullanıyoruz.
void lcd_go(uint8_t  x, uint8_t y){
 X” ve”Y” olarak satır ve sütun numaraları girilerek kullanılmaktadır.
Karakterleri ayrı ayrı yazabilirken bir kelime ya da cümle yazmak istediğimizde bunu başaramayacağız. Bunu gerçekleştirmemiz için yine bir fonksiyon tanımlamamız gerekecek. Bu fonksiyon girdiğimiz metni tek tek karakter olarak yazdıracak.
void lcd_dizi (char dizi[]){
Kısaca değindiğim bu fonksiyonların kullanıldığı örnek programı aşağıda inceleyebilirsiniz.

/*
 * lcd_ornek.c
 *
 * Created: 19.01.2019 21:18:22
 * Author : haluk
 */
  
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

//port tanımları
#define DATA_PORT PORTD
#define CMD_PORT PORTB
//#define RW_ PB3
#define EN_ PB1
#define RS_ PB0
//
#define DATA_OUT DDRD|=0xF0;
#define CMD_OUT DDRB|=0x03;//RW Gndye bağlı ise
//#define DATA_IN DDRD&=~0xF0;
//RW Gndye bağlı değilse
//#define CMD_OUT DDRB|=0x0B;
//#define RW_HIGH CMD_PORT|=(1<<RW_)
//#define RW_LOW CMD_PORT&=~(1<<RW_)
#define EN_HIGH CMD_PORT|=(1<<EN_)
#define EN_LOW CMD_PORT&=~(1<<EN_)
#define RS_HIGH CMD_PORT|=(1<<RS_)
#define RS_LOW CMD_PORT&=~(1<<RS_)
//lcd komutlar
#define LCD_CL lcd_cmd(0x01)
#define LCD_HOME lcd_cmd(0x02)
#define LCD_NSCR_RL lcd_cmd(0x04)
#define LCD_SCR_RL lcd_cmd(0x05)
#define LCD_NSCR_LR lcd_cmd(0x06)
#define LCD_SCR_LR lcd_cmd(0x07)
#define LCD_DOFF lcd_cmd(0x08)
#define LCD_DON lcd_cmd(0x0C)
#define LCD_DBON lcd_cmd(0x0D)
#define LCD_DCON lcd_cmd(0x0E)
#define LCD_DCBON lcd_cmd(0x0F)
#define LCD_CR_L lcd_cmd(0x10)
#define LCD_CR_R lcd_cmd(0x14)
#define LCD_SC_L lcd_cmd(0x18)
#define LCD_SC_R lcd_cmd(0x1C)
#define LCD_4L1 lcd_cmd(0x20)
#define LCD_4L2 lcd_cmd(0x28)

void lcd_gonder(uint8_t  gelen){
       DATA_PORT=(DATA_PORT&0x0f)|(gelen & 0xF0);    
       EN_HIGH;
       _delay_us(1);
       EN_LOW;
       _delay_us(100);
      
}
void lcd_cmd(uint8_t  cmd){
       RS_LOW;
       //RW_LOW;   
       lcd_gonder(cmd);   
       lcd_gonder(cmd<<4);
}
void lcd_yaz(uint8_t  data){
       RS_HIGH;    
       //RW_LOW;
       lcd_gonder(data);
       lcd_gonder(data<<4);
      
}
void lcd_go(uint8_t  x, uint8_t y){
       if (y==0)
       lcd_cmd(0x80+x);
       if (y==1)
       lcd_cmd(0xC0+x);
       if (y==2)
       lcd_cmd(0x94+x);
       if (y>=3)
       lcd_cmd(0xD4+x);
}
void lcd_krk(uint8_t  adres, uint8_t  karakter[]){   
       lcd_cmd(0x40+(adres*8));
       for (uint8_t i=0;i<8;i++)
       {
             lcd_yaz(karakter[i]);
       }
}
void lcd_basla(){
       CMD_OUT
       DATA_OUT
       _delay_ms(15);     
       lcd_gonder(0x03);
       _delay_us(4100);
       lcd_gonder(0x03);
       _delay_us(100);
       lcd_gonder(0x03);  
       lcd_gonder(0x02);  
       LCD_4L2;
       LCD_DOFF;
       LCD_DON;
       LCD_NSCR_LR;
       LCD_HOME;
       _delay_ms(2);
       LCD_CL;     
}

/*
void lcd_kontrol(){
       uint8_t okdata;
       DATA_IN//portd7-4 data pinleri giriş yapıldı  
       RW_HIGH;// RW pini "1" yapıldı okumaya hazır         
       do
       {      EN_HIGH;
             okdata=(PIND<<4);// yuksek bit okundu
             _delay_us(1);
             EN_LOW;
             _delay_us(1);
             EN_HIGH;
             okdata|=(PIND&0x0F);// düşük bit okundu
             _delay_us(1);
             EN_LOW;
             _delay_us(4);
       } while (okdata&0x80); //Bit 7 "1" ise döngüde kalır
            
             DATA_OUT//portd7-4 data pinleri çıkış yapıldı
             RW_LOW;//RW pini "0" yapıldı yazmaya hazır
}*/
void lcd_dizi (char dizi[]){
       uint8_t i=0;    
       while(dizi[i])            
       {
             lcd_yaz (dizi[i]);
             i++;
       }
}

int main(void)
{
    lcd_basla();
       _delay_ms(2000);
      
    while (1) {
             lcd_go(1,0);
             lcd_yaz('A');
             _delay_ms(500);
             lcd_yaz('B');
             _delay_ms(500);
             lcd_yaz('C');
             _delay_ms(500);
             lcd_go(12,0);
             lcd_yaz('1');
             _delay_ms(500);
             lcd_yaz('2');
             _delay_ms(500);
             lcd_yaz('3');
             _delay_ms(500);
             lcd_go(4,1);
             lcd_dizi("16x2 LCD");
             _delay_ms(500);
             LCD_SC_L;
             _delay_ms(500);
             LCD_SC_R;
             _delay_ms(500);
             LCD_SC_R;
             _delay_ms(500);
             LCD_SC_L;
             _delay_ms(500);
             LCD_CL;            
             _delay_ms(500);
        
    }
}