AVR-Grafik LCD Kullanımı


Grafik LCD Kullanımı



Biçerdöver tablasını ayarlanan yükseklikte sabit tutmak için kullanılan sistemi eski bir model için kendim yapmaya karar verdim. Karakter LCD ile denemelerini yaptım ama görsel olarak tatmin olmadım. Grafik LCD ile daha iyi bir şey yaparım umuduyla araştırmalarıma başladım. Önceki yazılarımda da bahsettim bir amatör olarak öğrendiklerimi bir not defterine yazıp saklamaktansa benim gibi amatörler için ulaşılabilir bir kaynak olması açısında buraya yazıyorum.

Karakter LCD konusuna buradan ulaşabilirsiniz. Grafik LCD’nin farkı ekranın tamamının noktalardan (piksel) oluşması ve bizim bu noktalardan istediğimizi “1” ya da “0” yapabilmemizdir. Karakter LCD de kayıtlı olarak gelen ve 8 adet ile sınırlı oluşturulabilen harf, rakam ve şekiller dışında seçeneğimiz yoktur. Grafik LCD (GLCD) de ise çözünürlük elverdikçe sınırsız diyebiliriz. GLCD ekranların birbirinden farklı kontrolcü/sürücüleri mevcut. KS0108(S6B0108), ST7920 (SPI destekli) ve T6963C gibi ürünler mevcut, sanırım piyasada en çok bulacağınız KS0108 ve kopya ürünlerdir. Bu yazıda KS0108’i anlatmaya çalışacağım.

GLCD


İlk olarak GLCD pin isimleriyle başlayalım. Piyasada bulunan ekranların pin yerleşimi birbirinden farklıdır. Bu nedenle üreticinin bilgi sayfasını kontrol etmeniz gerekiyor. Üründe “1” ile gösterilen pinden başlayarak “20” ile gösterilen pine kadar görevleri aşağıdaki gibidir.


1-VDD (VCC): Pozitif besleme +5V.

2-VSS: Negatif besleme (GND).

3-VO: Ekran zıtlık ayarı, potansiyometrenin orta bacağı bu pine bağlanacak.

4,11- DB0,DB7: Paralel veri pinleri.

12- CS1: Bir numaralı ekran seçiminin yapıldığı pin.

13- CS2: İki numaralı ekran seçiminin yapıldığı pin.

14-RST: GLCD sıfırlama pini.

15-R/W: Okuma ya da Yazma seçiminin yapıldığı pin.

16-RS (D/I): Komut ya da Veri yollanması için gerekli seçimin yapıldığı pin. Register seçimi.

17-EN (E): İletişim için gerekli olan saat darbesinin gönderildiği pin.

18-VEE: Negatif voltaj çıkışı. Potansiyometrenin dış bacaklarından biri bu pine bağlanacak.

19- A(LED+): Arka aydınlatma Led artı pini.

20- K(LED-): Arka aydınlatma Led eksi pini.

KS0108 kullanan 128x64 piksel bir ekranda iki tane KS0108 bulunmaktadır. 128x64 aslında 64x64 iki ekrandan oluşur ve iki farklı sürücü tarafından yönetilir. CS0 ve CS1 numaralı pinler bu seçimi yapmamızı sağlar. Bu pinlerden hangisi “Low” ise o ekran veri alır-verir. İki ekranı aynı anda kontrol etmekte mümkündür. 
Ekranlar 64 düşey sütun ve 8 yatay sayfadan oluşur. Her sayfada 8 piksel bulunur. Bu şekilde 64 yatay 64 düşey piksel oluşur. Ekranda bir “A” harfi yazmak istediğimizde öncelikle ekranı seçmeliyiz. Ekran seçimi yapıldıktan sonra sayfayı seçmeli ve gerekli veriyi göndermeliyiz. Şekilde görüldüğü gibi yazabilmemiz için göndereceğimiz veri ilk sütun için “0B 0111 1110” ya da “0x7E” ikinci sütun için  “0B 0001 0001” ya da “0x11” olacaktır.

Bahsettiğim sayfa seçimleri ve benzeri işlemlerin nasıl yapılacağı bilgi sayfasında mevcut. Bu bilgilerden faydalanarak devam edelim. GLCD veri göndermek-almak ya da gerekli ayarlama ve seçimleri yapmak için RS ve R/W pinlerini kullanırız.



Satır, sütun seçimi ve ekran açma kapatma gibi komutları işlemek için RS ve RW “Low” olmalıdır. GLCD durum kontrolü için RS “Low” ve RW “High” olmalıdır. Veri gönderirken RS “High” RW “Low” veri okurken RS “High” ve RW “High” olmalıdır. Pin seçimleri bu şekilde yaptıktan sonra aşağıdaki tabloda diğer ayrıntıları görebiliriz.



Komutlar


Display on/off: Ekran açma/kapatma. GLCD ye bağlı data pinleri “0B 0011 1110” (0x3E) olursa/gönderirsek ekran kapanır. “0B 0011 1111” (0x3F) olursa ekran açılır.

Set Address: Y adresi olarak görünmekte ama ben x-y koordinat sistemi gibi düşünerek sütunlara “X” diyeceğim. 64 adet sütun olan ekranlardan hangi sütunu seçmek istediğimize bağlı olarak veriyi gönderiyoruz. İlk sütunun adresi (data pinleri) “0B 0100 0000” (0x40) ve sağa doğru sütun ilavesinde bir artırarak 63 numaralı sütuna ulaşabiliriz. Son sütunun adresi “0B 0111 1111” (0x7F). Gördüğünüz gibi işlemler ikili sayı sistemine göre yapılıyor. D7 ve D6 değişmeden “1” artırılıyor.

Set Page: Sayfa seçimi X adresi olarak görünmekte ama “Y” diyeceğim. Yukarıda belirttiğim 8 adet sayfanın seçimi için veriyi gönderiyoruz. İlk sayfa için (data pinleri) “0B 1011 1000” (0xB8) ve aşağı doğru son sayfa için “0B 1011 1111” (0xBF) olmalıdır.

Display Start Line: Ekranda üst sıradaki yerini ayarlar. Ekranı yukarı/aşağı kaydırabiliriz. İlk sayfanın DB0 numaralı pikselinin üstte olması için (data pinleri) “0B 1100 0000” (0xC0) son sayfanın DB7 numaralı pikseli için “0B 1111 1111” (0xFF) olmalıdır.

Status Read: GLCD veri alma/verme için uygun değilse, başka bir işlem ile meşgulse gönderdiğimz komutalara hatalı cevaplar verecektir. Bu nedenle uygunluk durumu, ekranın durumu ve reset durumu gibi verilerin okunduğu komuttur.

Write Display Data: Ekran, sayfa ve sütun seçimi yapıldıktan sonra RS “High” ve RW “Low” yapılarak ekrana veri gönderir ve pikselleri “1” ya da “0” yaparak istediğimiz görüntüyü oluşturabiliriz.

Read Display Data: Ekran, sayfa ve sütun seçimi yapıldıktan sonra RS “High” ve RW “High” yapılarak ekranda piksellerin “1” ya da “0” durumunu okuyabiliriz. Bu okuma piksel olarak yapacağımız işlemlerde gerekli olacaktır. Artık komutların ve pinlerin işlevine göre makroları ve fonksiyonları oluşturabiliriz.

Makrolar


Buraya kadar anlattığım işlemlerde bazı pinleri “1” High ya da “0” Low yapmak gerekiyor. Bunu kolayca tek komutla yapmak ve de hafızada fazladan yer kaplamadan yapmanın yolu ön tanımlı makrolardır. Ben uygulamayı Atmega32A ile yapıyorum farklı bir denetleyici için yani taşınabilir olması için Pin-Port tanımlarını değiştirmek yeterlidir. Port ve pinlerin giriş-çıkış durumlarını ve pinlerin 1-0 durumlarını tanımlıyorum. Bu tanımlardan sonra sıra fonksiyonlara geliyor.

//////////port
#define DATA_P PORTA //PORTB-PORTC vb
#define DATA_PIN PINA //PINB -PINC vb
#define CMD_P PORTD //PORTB-PORTC vb
#define DATA_DIR DDRA //DDRB-DDRC vb
#define CMD_DIR DDRD //DDRB-DDRC vb
///////// pin
#define CS1 PORTD2 //CMD port seçimine göre pinler ayarlanır
#define CS2 PORTD3
#define RST PORTD4
#define RW PORTD5
#define RS PORTD6
#define EN PORTD7
///////// dir
#define DATA_OUT DATA_DIR|=(0xFF) // data portu çıkış yapıldı
#define DATA_IN DATA_DIR&=~(0xFF) // data portu giriş yapıldı
#define CMD_OUT CMD_DIR|=(1<<CS1)|(1<<CS2)|(1<<RS)|(1<<RW)|(1<<EN)|(1<<RST) //cmd portu çıkış yapıldı
/////////
#define CS1_H CMD_P|=(1<<CS1) //cs1 high
#define CS1_L CMD_P&=~(1<<CS1) //cs1 low
#define CS2_H CMD_P|=(1<<CS2)
#define CS2_L CMD_P&=~(1<<CS2)
#define RS_H CMD_P|=(1<<RS)
#define RS_L CMD_P&=~(1<<RS)
#define RW_H CMD_P|=(1<<RW)
#define RW_L CMD_P&=~(1<<RW)
#define EN_H CMD_P|=(1<<EN)
#define EN_L CMD_P&=~(1<<EN)
#define RST_H CMD_P|=(1<<RST)
#define RST_L CMD_P&=~(1<<RST)
#define DISP_ON 0x3F
#define DISP_OFF 0x3E
#define X_ADR 0x40
#define Y_ADR 0xB8
#define Z_ADR 0xC0



Yazma Fonksiyonları


Yukarıdaki şemada GLCD yazma zaman çizelgesi bulunmaktadır. Veriyi yazmadan önce yapılması gerekenler görülmektedir. EN pininin high ve low olması yani bir dönem zamanı “tC” min 1ms olmalıdır. EN pini high olmadan “tASU” 140ns önce ekran seçimi yapılmalı, RS pini high ya da low ve RW low olmalıdır. E pini high olduktan sonra tekrar low olmadan “tDSU” 200ns önce data pinlerine veri yazılmalıdır. Bu işlemleri iki farklı fonksiyonda yapacağız. Bu fonksiyonlardan biri veri yazmak için diğeri komut yazmak için oluşturacağız. Bu arada önemli not GLCD de EN pini düşen kenarda veri yazılır, yükselen kenarda veri okunur.

void glcd_data(char _data){//glcd ram veri yazan fonksiyon
      glcd_durum();// busy flag kontrolü
      RS_H; //rs high yapıldı
      RW_L; //rw low yapıldı
      DATA_P=_data; //data portuna fonksiyon parametresi _data yazıldı
      EN_H; //en high saat darbesi
      _delay_us(1);//twh en az 450ns
      EN_L;  //en low saat darbesi
}



Çizelgede görünen süreleri karşılamak için EN saat darbesi öncesi ekran seçimi ve register seçimi yapılıyor. Yazma yapılacağından RW low yapılarak data portuna istenen veriye göre pin durumu yazılıyor. Örneğin (0xF0) “0B 1111 0000” gibi bir veri yazmak istersek data portu 7-0 pinleri arası “11110000” şeklinde pinler “1” ya da “0” olacaktır. Böylece tASu ve tDSU süreleri karşılanacaktır. Sonrasında EN high-low yapılarak işlem tamamlanır.

void glcd_cmd(char _cmd){//glcd komut yazan fonksiyon
      glcd_durum();// busy flag kontrolü
      RS_L;//rs low yapıldı
      RW_L;//rw low yapıldı
      DATA_P=_cmd;//data portuna fonksiyon parametresi _cmd yazıldı
      EN_H;//en high saat darbesi
      _delay_us(1);//twh en az 450ns
      EN_L;//en low saat darbesi
}



Veri yazmada olduğu gibi komut yazmada da aynı işlemler yapılır. Aradaki fark RS pininin high-low durumu yani komut ya da veri seçimidir.

Okuma Fonksiyonları




Yazma için olduğu gibi okuma içinde zaman çizelgesinde yapılması gerekenler görülmektedir. RW high durumdayken RS low olduğunda durum okuması yapılırken RS high olduğunda GLCD veri okuması yapılır. Okuma yaparken “tD” 320ns süresi önemli bu nedenle önce EN high yapılmalı sonra data portu okunmalıdır.

uint8_t glcd_oku(){// veri okuma fonksiyonu
      glcd_durum();// busy flag kontrolü
      uint8_t _rdata; //8bit değişken oluşturuldu   
      DATA_IN; //data portu giriş yapıldı
      RS_H; //rs high yapıldı
      RW_H; //rw high yapıldı
      EN_H; //en high saat darbesi
      _delay_us(1);// tWH ve tD süresi için bekleme
      _rdata=DATA_PIN;// data pini okunuyor değişkene atanıyor   
      EN_L; //en low saat darbesi      
      DATA_OUT;//data portu çıkış yapıldı
      return _rdata;// değişken veri olarak dönüş yapıyor
}

Okuma fonksiyonu sadece piksel olarak işlem yaptığımızda gereklidir. Örnek olarak “0” numaralı satırda yukarıdan aşağıya doğru birer artarak (bir piksel) çizgi çizmek istersek göndereceğimiz veri sırasıyla {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80} olacaktır. Bu şekilde yazdığımızda kayan bir nokta görürüz. Amaca ulaşmak için ekranın tüm bilgisini tutacağımız bir bellek oluşturmak işlemi buna göre yapmak mümkündür. Sadece düşeyde iş gören önceki satırla şimdiki satır aynıysa daha önce gönderilen veriyi şimdikiyle “OR” işlemiyle yazmakta bir seçenek. (data=0x01|0x02; gibi) ben böyle bir işlemi çok uzun sürmediği için veri okumasıyla yapmayı tercih ettim. Süre çok önemliyse RAM de bellek içinde ekranın bir görüntüsünü oluşturmak mantıklı olacaktır. Önemli not okuma yaparken boş okuma sonra veri okuma doğru olacaktır. Durum okumasında buna gerek yoktur.

void glcd_durum(){// busy flag kontrolü
      uint8_t durum; //8bit değişken oluşturuldu
      RS_L; //rs low yapıldı
      RW_H; //rw high yapıldı
      DATA_IN; //data portu giriş yapıldı
      do {
            EN_H; //en high saat darbesi
            _delay_us(1);// tWH ve tD süresi için bekleme
            durum=DATA_PIN;     // data pini okunuyor değişkene atanıyor
            EN_L; //en low saat darbesi            
      } while (durum&0x80); //busy flag D7 pini high ise işlem yapılıyordur, döngüden çıkılmaz okuma tekrarlanır
      DATA_OUT; //data portu çıkış yapıldı
}

 


GLCD zaman çizelgesinde görülen fakat fonksiyonlarda belirtmediğimiz gerekli süreleri sağlamak gereksiz beklemeler yapmamak için bu fonksiyonu kullanmalıyız. Belirtilen süreler değişkenlik gösterebilmektedir. Bir komutta sorun olmazken çok sayıda ve arka arkaya komutlar gönderdiğimizde bu fonksiyon daha önemli olmaktadır. Buraya kadar ki fonksiyonlar kalan işlemlere temel oluşturacaktır.

Ana Fonksiyonlar


Başlatma fonksiyonu: İlk açılışta GLCD nin hazırlanması gerekir. Açılışta sıfırlamak, X-Y ve Z adreslerini başlangıca almak gerekiyor.

void glcd_basla(){
      DATA_OUT; // data portu çıkış yapıldı
      CMD_OUT; // cmd portu çıkış yapıldı
      RST_L; //reset
      _delay_ms(1000);
      RST_H;// reset bitti
      glcd_chip_sec(cift);// iki ekran seçildi
      glcd_cmd(X_ADR);// x 0 sütunda
      glcd_cmd(Y_ADR);// y 0 numaralı sayfada
      glcd_cmd(Z_ADR);// 0 numaralı sayfanın 0 numaralı pikseli en üstte
      glcd_cmd(DISP_ON);// ekran açıldı      
}



Ekran Seçimi: 64x64 iki ekrandan oluşan ve iki adet sürücü tarafından kontrol edilen GLCD de ekran seçimi CS1 ve CS2 pinleri ile yapılır. Seçili olan ekranı bilmek ve işlem yapmak için chip değişkenine yazıyoruz.

#define cift 0
#define sol 1
#define sag 2
void glcd_chip_sec(uint8_t _chip){
      if (_chip==sol){
            CS1_L;
            CS2_H;
            chip=1; // global değişkene yazıldı    
      }
      if(_chip==sag){
            CS1_H;
            CS2_L;
            chip=2; // global değişkene yazıldı
      }
      if(_chip==cift){
            CS1_L;
            CS2_L;      
      }     
}



Satır ve Sütun Seçimi: Sütun “x” ve satır “y” seçimin yapıldığı fonksiyondur. Satırlar piksel olarak girilir sayfa olarak seçilir. 128 (0-127) sütun 64 (0-63) satır vardır. Fonksiyon parametreye göre sayfa ve ekran seçimini gerçekleştirir.

void glcd_git(uint8_t x, uint8_t y){
      sutun=x;//sütun değişikliğini global bir değişkende tutuyoruz
      satir=y satır değişikliğini global bir değişkende tutuyoruz
      x&=127; // eğer girilen x değeri 127den büyükse başa alıyoruz.    
      y&=63; // eğer girilen y değeri 63den büyükse başa alıyoruz.
      if (x>=64){// x değeri 64-127 arasındaysa sağ ekran
            glcd_chip_sec(sag);
            x&=63;//x değeri sağ ekranda da 0-63 arası olmalıdır.
        }else {
            glcd_chip_sec(sol);//0-63 arasındaysa sol ekran seçilir
      }                  
      glcd_cmd(X_ADR|x);// komut sütun seçimi
      glcd_cmd(Y_ADR|(y>>3));// komut satır seçimi (y>>3) 3 bit sağa kaydırma 8’e bölmek demektir (y/8) aynı şeydir.
}



Ekran Silme: Ekrandaki görüntüyü silen fonksiyon, burada yapılan ekrana “0x00” yazmaktır. Bunu yaparken iki ekran birlikte seçiliyor. For döngüsüyle 8 sayfanın tamamında, her sayfanın içinde bulunan 64 sütuna “0x00” verisi yazılıyor.

void glcd_sil(){
      glcd_chip_sec(cift);
      for (uint8_t j=0;j<8;j++){
            glcd_cmd(Y_ADR+j);
            for (uint8_t i=0;i<64;i++) {           
                   glcd_data(0x00);
            }
      }
}



Ekran Yazı, Grafik Fonksiyonları


Yukarıda anlatmaya çalıştığım fonksiyonları kullanarak çeşitli programlarla oluşturacağınız farklı fontlar ile harf ve rakamlar yazabilirsiniz. Şekiller çizebilir yine programlarla dönüştüreceğiniz fotoğrafları görüntüleyebilirsiniz. İlk olarak karakter LCD’ de kullanılan karakterleri ekrana yazmamızı sağlayan fonksiyonla başlayalım.

void glcd_yaz(uint8_t _chr){//girilen parametre ascii table karakter kodu
      _chr-=0x20;  // ilk karakter space ascii 0x20 (32)
      for (uint8_t i=0;i<5;i++){// her karakter 5 sütundan yani 5 adet veriden oluşuyor.
            if (sutun>=64){ // sol ekran sütun sınırı
                   glcd_git(sutun,satir);//sağ ekrana geçiş
            }                  
            if ((chip==2)&&(sutun>=125)){// ekran ve sütun bilgisi kullanılıyor
                   glcd_git(0,satir+8);// ekrana sığma şansı kalmayan karakterler alt satıra geçer
            }           
            glcd_data(pgm_read_byte_near(&font_5x8[_chr][i]));// for ile her seferinde bir veri yazılıyor   
            sutun++;// glcd sütun otomatik artar, bizim bunu takip etmemiz gerektiğinden değişkeni artırıyoruz
      }     
      glcd_data(0x00);// karakterler arası boşluk
      sutun++;
}
void glcd_dizi(char *str){     
      uint8_t i=0;
      while (str[i]){    
            glcd_yaz(str[i]);
            i++;        
      }
}

Yaz fonksiyonu: İnternetten bulacağınız ya da programlarla oluşturacağınız çok boyutlu karakter dizisi ile ASCII tablosundaki karakterleri yazdırabilirsiniz. { 0x7E, 0x11, 0x11, 0x11, 0x7E }, Büyük “A” harfi için veri bu şekildedir. LCD için oluşturulan karakterler 8 yatay, 5 dikey noktadan oluşur. Bildiğiniz gibi düşeyde her sütun bir bayt toplam 5 bayt bir karakter oluşturur. Çok boyutlu dizinin ilk karakteri 0x20 ile başlıyor, parametre olarak ‘A’ girdiğimizde _chr=0x41 (65) olarak fonksiyon işleyecektir. Tablodaki “_” yazılacaktır. Bunun olmaması için parametreden 0x20 (32) çıkartıyoruz böylece “A” yazmak istediğimizde _chr= 0x21(33) olarak işlem yapılacaktır. İki ekranın ortasında kalan karakterler için diğer ekrana geçiş yapılıyor ve ekranın sonunda kalan karakterleri bölmemek için alt satırdan devam ediyoruz. Tabloda her karakteri 6 bayt veriden oluşturup 0x00 ile karakter arası boşluk ayarlayabilirdik ama gereksiz yer tutmamak için karakter bitişinde boşluk bırakıyoruz. Sütun otomatik olarak değiştiriliyor, bunun takibini yapmazsak diğer ekrana geçiş ve alt satıra geçiş gibi işlemleri yapmak için farklı algoritmalar yapmamız gerekir.

Dizi Fonksiyonu: Girilen diziyi tek karakter halinde dizi elemanı bitene kadar yazdırır. Str[i]= 0 (null) olunca döngüden çıkılır.

void glcd_piksel(uint8_t _x, uint8_t _y){//istenen pikselin adresi (koordinatları)
      uint8_t _data=0; // geçici bir değişken oluşturuldu
      glcd_git(_x,_y);// parametre koordinatlarına gidildi
      glcd_oku(); // boş okuma yapıldı
      _data=glcd_oku();// ikinci okuma değişkene yazıldı   
      _data|=(1<<(_y%8)); //düşeyde 8 bit 1 sayfadır. istenen bit için 8'e bölerek kalan bulunuyor ve o kadar bit kaydırma yapılıyor. "OR" işlemi yapılıyor
      glcd_git(_x,_y);// okuma yapınca sütun kaydırıldı bu nedenle tekrar istenen adrese gidiliyor 
      glcd_data(_data);// veri yazıldı 
}

Piksel Fonksiyonu:  Bu fonksiyon ile 64 yatay 128 düşey pikselin istenen bir tanesine 0x01 yazılır. Yukarıda değindiğim gibi bu işlemi yaparken 8 bitten oluşan sayfa verisini korumak için okuma yapılmaktadır. Ekran görüntüsünü oluşturup hafızada yer tutmamak için bu yöntemi kullanmaktayım. Parametre olarak (5,5) yazıldığında sütun 5 ile D5 satıra bir nokta yazılacaktır. Bunu yapabilmek için GLCD ye “0B 0010 0000” (0x10) yazmamız gerekir. Data verisini 8’ e böldüğümüzde kalan 5 olur ve 5 bit sola kaydırma yaparak istediğimiz sonucu buluruz. Okuma yaparak mevcut veriyi “OR” işlemiyle koruyarak işlemi tamamlarız.

void glcd_cizgi(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2){
      int8_t x=0,y=0,xadim=0, yadim=0;
      int16_t _hata;
      uint8_t dx,dy;
      if (x2>x1){
            xadim=1;
            dx=(x2-x1);
      }else{
            xadim=-1;
            dx=(x1-x2);
      }
      if (y2>y1){
            yadim=1;
            dy=(y2-y1);
            }else{
            yadim=-1;
            dy=(y1-y2);
      }     
      x=x1;
      y=y1; 
      if (dx>=dy){
            _hata=2*dy-dx;
            for (uint8_t i=0;i<dx;++i){
                   glcd_piksel(x,y);
                   if (_hata<0){
                          _hata+=2*dy;
                          x+=xadim;
                   }else{
                          _hata+=2*dy-2*dx;
                          x+=xadim;
                          y+=yadim;
                   }
            }
      }else{
            _hata=2*dx-dy;
            for (uint8_t i=0;i<dy;++i){
                   glcd_piksel(x,y);
                   if (_hata<0){
                          _hata+=2*dx;
                          y+=yadim;
                          }else{
                          _hata+=2*dx-2*dy;
                          x+=xadim;
                          y+=yadim;
                   }
            }
      }
}



Çizgi Fonksiyonu: Bresenham çizgi algoritması ile hızlı ve düzgün sonuçlar alınabiliyor. Doğru denklemiyle denemelerim oldu ama çok düzgün sonuçlar alamadım. Bu algoritmada sürekli artan “x” ya da “y” durumuna göre hareket ediliyor. Çok fazla kaynak mevcut anlatma kısmını geçiyorum. Bu fonksiyon ile farklı fonksiyonlar türetilebilir.

void glcd_resim(const unsigned char *img){
       uint16_t sira=0;
       glcd_git(0,0);
       for(uint8_t _sayfa=0;_sayfa<8;_sayfa++){
             glcd_chip_sec(sol);//sol ekran ile başlar
             glcd_cmd(Y_ADR+_sayfa);
             glcd_cmd(X_ADR);
             for(uint8_t _sutun=0;_sutun<128;_sutun++){
                    if (_sutun==64){// sütun değeri 64 sağ ekrana geçer
                           glcd_chip_sec(sag);
                           glcd_cmd(Y_ADR+_sayfa);
                           glcd_cmd(X_ADR);                       
                    }                  
                    glcd_data(pgm_read_byte_near(&img[sira]));
                    sira++;
             }
       }
}

Resim Fonksiyonu: Program aracılığıyla dönüştüreceğiniz fotoğraflı ekrana aktarabilirsiniz. Tüm fonksiyonları aşağıda bulabilirsiniz.

 
/*
 * glcd_atmega32a.c
 *
 * Created: 07.08.2019 21:51:54
 * Author : haluk
 */
#define F_CPU 16000000l
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
//////////port
#define DATA_P PORTA //PORTB-PORTC vb
#define DATA_PIN PINA //PINB -PINC vb
#define CMD_P PORTD //PORTB-PORTC vb
#define DATA_DIR DDRA //DDRB-DDRC vb
#define CMD_DIR DDRD //DDRB-DDRC vb
///////// pin
#define CS1 PORTD2 //CMD port seçimine göre pinler ayarlanır
#define CS2 PORTD3
#define RST PORTD4
#define RW PORTD5
#define RS PORTD6
#define EN PORTD7
///////// dir
#define DATA_OUT DATA_DIR|=(0xFF) // data portu çıkış yapıldı
#define DATA_IN DATA_DIR&=~(0xFF) // data portu giriş yapıldı
#define CMD_OUT CMD_DIR|=(1<<CS1)|(1<<CS2)|(1<<RS)|(1<<RW)|(1<<EN)|(1<<RST) //cmd portu çıkış yapıldı
/////////
#define CS1_H CMD_P|=(1<<CS1)//cs1 high
#define CS1_L CMD_P&=~(1<<CS1) //cs1 low
#define CS2_H CMD_P|=(1<<CS2)
#define CS2_L CMD_P&=~(1<<CS2)
#define RS_H CMD_P|=(1<<RS)
#define RS_L CMD_P&=~(1<<RS)
#define RW_H CMD_P|=(1<<RW)
#define RW_L CMD_P&=~(1<<RW)
#define EN_H CMD_P|=(1<<EN)
#define EN_L CMD_P&=~(1<<EN)
#define RST_H CMD_P|=(1<<RST)
#define RST_L CMD_P&=~(1<<RST)
#define DISP_ON 0x3F
#define DISP_OFF 0x3E
#define X_ADR 0x40
#define Y_ADR 0xB8
#define Z_ADR 0xC0
#define cift 0
#define sol 1
#define sag 2
//////
uint8_t sutun=0, satir=0,chip=1;
//////
void glcd_basla();
void glcd_cmd(char _cmd);
void glcd_data(char _data);
void glcd_durum();
uint8_t glcd_oku();
void glcd_chip_sec(uint8_t _chip);
void glcd_sil();
void glcd_git(uint8_t x, uint8_t y);
void glcd_yaz(uint8_t _chr);
void glcd_dizi(char *str);
void glcd_piksel(uint8_t _x, uint8_t _y);
void glcd_cizgi(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
void glcd_resim(const unsigned char *img);
const uint8_t font_5x8 [95] [5] PROGMEM = {
       { 0x00, 0x00, 0x00, 0x00, 0x00 }, // space  (0x20)
       { 0x00, 0x00, 0x2F, 0x00, 0x00 }, // !
       { 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
       { 0x14, 0x7F, 0x14, 0x7F, 0x14 }, // #
       { 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, // $
       { 0x23, 0x13, 0x08, 0x64, 0x62 }, // %
       { 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
       { 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
       { 0x00, 0x1C, 0x22, 0x41, 0x00 }, // (
       { 0x00, 0x41, 0x22, 0x1C, 0x00 }, // )
       { 0x14, 0x08, 0x3E, 0x08, 0x14 }, // *
       { 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
       { 0x00, 0x50, 0x30, 0x00, 0x00 }, // ,
       { 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
       { 0x00, 0x30, 0x30, 0x00, 0x00 }, // .
       { 0x20, 0x10, 0x08, 0x04, 0x02 }, // / 
            
       { 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0  (0x30)
       { 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
       { 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2
       { 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3
       { 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
       { 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
       { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
       { 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
       { 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
       { 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
       { 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
       { 0x00, 0x56, 0x36, 0x00, 0x00 }, // ;
       { 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
       { 0x14, 0x14, 0x14, 0x14, 0x14 }, // =
       { 0x00, 0x41, 0x22, 0x14, 0x08 }, // >
       { 0x02, 0x01, 0x51, 0x09, 0x06 }, // ?
                   
       { 0x32, 0x49, 0x79, 0x41, 0x3E }, // @  (0x40)
       { 0x7E, 0x11, 0x11, 0x11, 0x7E }, // A
       { 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B
       { 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C
       { 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D
       { 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E
       { 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F
       { 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G
       { 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H
       { 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I
       { 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J
       { 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K
       { 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L
       { 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M
       { 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N
       { 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O 
            
       { 0x3F, 0x09, 0x09, 0x09, 0x06 }, // P  (0x50)
       { 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q
       { 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R
       { 0x46, 0x49, 0x49, 0x49, 0x31 }, // S
       { 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T
       { 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U
       { 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V
       { 0x3F, 0x40, 0x30, 0x40, 0x3F }, // W
       { 0x63, 0x14, 0x08, 0x14, 0x63 }, // X
       { 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y
       { 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z
       { 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [
       { 0x02, 0x04, 0x08, 0x10, 0x20 }, // \.
       { 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ]
       { 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^
       { 0x40, 0x40, 0x40, 0x40, 0x40 }, // _ 
            
       { 0x00, 0x01, 0x02, 0x04, 0x00 }, // `  (0x60)
       { 0x20, 0x54, 0x54, 0x54, 0x78 }, // a
       { 0x7F, 0x50, 0x48, 0x48, 0x30 }, // b
       { 0x38, 0x44, 0x44, 0x44, 0x20 }, // c
       { 0x38, 0x44, 0x44, 0x48, 0x7F }, // d
       { 0x38, 0x54, 0x54, 0x54, 0x18 }, // e
       { 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f
       { 0x0C, 0x52, 0x52, 0x52, 0x3E }, // g
       { 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h
       { 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i
       { 0x20, 0x40, 0x44, 0x3D, 0x00 }, // j
       { 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k
       { 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l
       { 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m
       { 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n
       { 0x38, 0x44, 0x44, 0x44, 0x38 }, // o 
            
       { 0x7C, 0x14, 0x14, 0x14, 0x08 }, // p  (0x70)
       { 0x08, 0x14, 0x14, 0x08, 0x7C }, // q
       { 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r
       { 0x48, 0x54, 0x54, 0x54, 0x20 }, // s
       { 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t
       { 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u
       { 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v
       { 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w
       { 0x44, 0x28, 0x10, 0x28, 0x44 }, // x
       { 0x0C, 0x50, 0x50, 0x50, 0x3C }, // y
       { 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z
       { 0x00, 0x08, 0x36, 0x41, 0x00 }, //sol süslü parantez
       { 0x00, 0x00, 0x7F, 0x00, 0x00 }, // |
       { 0x00, 0x41, 0x36, 0x08, 0x00 }, //sag süslü parantez
       { 0x30, 0x08, 0x10, 0x20, 0x18 } // ~
};
int main(void){           
       DATA_OUT;
       CMD_OUT;    
       glcd_basla();
       glcd_sil(); 
       glcd_cizgi(0,0,127,63);
       glcd_cizgi(127,0,0,63);
       _delay_ms(1000);
       for (uint8_t i=0;i<64;i++){
             for (uint8_t j=0;j<128;j++){
                    glcd_piksel(j,i);
                    //_delay_ms(1);
             }
       }
       _delay_ms(1000);
       glcd_sil();
       glcd_git(0,0);
       glcd_dizi("GLCD deneme ");
       _delay_ms(100);
       glcd_dizi("ABCDEFGHIJKLMNO abcdefghijklmno ");
       _delay_ms(100);
       glcd_dizi("0123456789 +-* /_()[]{}");
       while (1){         
       }
}
void glcd_basla(){
       DATA_OUT; // data portu çıkış yapıldı
       CMD_OUT; // cmd portu çıkış yapıldı
       RST_L; //reset
       _delay_ms(1000);
       RST_H;// reset bitti
       glcd_chip_sec(cift);// iki ekran seçildi
       glcd_cmd(X_ADR);// x 0 sütünda
       glcd_cmd(Y_ADR);// y 0 numaralı sayfada
       glcd_cmd(Z_ADR);// 0 numaralı sayfanın 0 numaralı pikseli en üstte
       glcd_cmd(DISP_ON);// ekran açıldı      
}
void glcd_cmd(char _cmd){//glcd komut yazan fonksiyon
       glcd_durum();// busy flag kontrolü
       RS_L;//rs low yapıldı
       RW_L;//rw low yapıldı
       DATA_P=_cmd;//data portuna fonksiyon parametresi _cmd yazıldı
       EN_H;//en high saat darbesi
       _delay_us(1);//twh en az 450ns
       EN_L;//en low saat darbesi
}
void glcd_data(char _data){//glcd ram veri yazan fonksiyon
       glcd_durum();// busy flag kontrolü
       RS_H; //rs high yapıldı
       RW_L; //rw low yapıldı
       DATA_P=_data; //data portuna fonksiyon parametresi _data yazıldı
       EN_H; //en high saat darbesi
       _delay_us(1);//twh en az 450ns
       EN_L;//en low saat darbesi
}
void glcd_durum(){// busy flag kontrolü
       uint8_t durum; //8bit değişken oluşturuldu
       RS_L; //rs low yapıldı
       RW_H; //rw high yapıldı
       DATA_IN; //data portu giriş yapıldı
       do {
             EN_H; //en high saat darbesi
             _delay_us(1);// tWH ve tD süresi için bekleme
             durum=DATA_PIN;     // data pini okunuyor değişkene atanıyor
             EN_L; //en low saat darbesi
       } while (durum&0x80); //busy flag D7 pini high ise işlem yapılıyordur, döngüden çıkılmaz okuma tekrarlanır
       DATA_OUT; //data portu çıkış yapıldı
}
uint8_t glcd_oku(){// veri okuma fonksiyonu
       glcd_durum();// busy flag kontrolü
       uint8_t _rdata; //8bit değişken oluşturuldu
       DATA_IN; //data portu giriş yapıldı
       RS_H; //rs high yapıldı
       RW_H; //rw high yapıldı
       EN_H; //en high saat darbesi
       _delay_us(1);// tWH ve tD süresi için bekleme
       _rdata=DATA_PIN;// data pini okunuyor değişkene atanıyor
       EN_L; //en low saat darbesi
       DATA_OUT;//data portu çıkış yapıldı
       return _rdata;// değişken veri olarak dönüş yapıyor
}
void glcd_chip_sec(uint8_t _chip){
       if (_chip==sol){
             CS1_L;
             CS2_H;
             chip=1;
       }
       if(_chip==sag){
             CS1_H;
             CS2_L;
             chip=2;
       }
       if(_chip==cift){
             CS1_L;
             CS2_L;      
       }     
}
void glcd_sil(){   
       glcd_chip_sec(cift);
       for (uint8_t j=0;j<8;j++){
             glcd_cmd(Y_ADR+j);
             for (uint8_t i=0;i<64;i++) {           
                    glcd_data(0x00);
             }
       }
}
void glcd_git(uint8_t x, uint8_t y){
       sutun=x;// ekrana yazarken sütun değişikliğini global bir değişkende tutuyoruz
       satir=y;// ekrana yazarken satır değişikliğini global bir değişkende tutuyoruz
       x&=127; // eğer girilen x değeri 127den büyükse başa alıyoruz.    
       y&=63; // eğer girilen y değeri 63den büyükse başa alıyoruz.
       if (x>=64){// x değeri 64-127 arasındaysa sağ ekran 
             glcd_chip_sec(sag);
             x&=63;//x değeri sağ ekranda da 0-63 arası olmalıdır.
       }else {
             glcd_chip_sec(sol);////0-63 arasındaysa sol ekran seçilir
       }                  
       glcd_cmd(X_ADR|x);// komut gönderiliyor sütun seçimi
       glcd_cmd(Y_ADR|(y>>3));// komut gönderiliyor satır seçimi (y>>3) 3 bit sağa kaydırma 8e bölmek demektir (y/8) aynı şeydir.
}
void glcd_yaz(uint8_t _chr){//girilen parametre ascii table karakter kodu
       _chr-=0x20;  // ilk karakter space ascii 0x20 (32)
       for (uint8_t i=0;i<5;i++){// her karakter 5 sütundan yani 5 adet veriden oluşuyor.
             if (sutun>=64){ // sol ekran sütun sınırı
                    glcd_git(sutun,satir);//sağ ekrana geçiş
             }                  
             if ((chip==2)&&(sutun>=125)){// her karakter 5 sütundan oluşuyor.        
                    glcd_git(0,satir+8);// ekrana sığma şansı kalmayan karakterler alt satıra geçer
             }           
             glcd_data(pgm_read_byte_near(&font_5x8[_chr][i]));// for ile her seferinde bir veri yazılıyor     
             sutun++;// glcd sütun otomatik artar, bizim bunu takip etmemiz gerektiğinden değişkeni artırıyoruz
       }     
       glcd_data(0x00);// karakterler arası boşluk
       sutun++;
}
void glcd_dizi(char *str){
       uint8_t i=0;
       while (str[i]){    
             glcd_yaz(str[i]);
             i++;        
       }
}
void glcd_piksel(uint8_t _x, uint8_t _y){//istenen pikselin adresi (koordinatları)
       uint8_t _data=0; // geçici bir değişken oluşturuldu
       glcd_git(_x,_y);// parametre koordinatlarına gidildi
       glcd_oku(); // boş okuma yapıldı
       _data=glcd_oku();// ikinci okuma değişkene yazıldı   
       _data|=(1<<(_y%8)); //düşeyde 8 bit 1 sayfadır. istenen bit için 8'e bölerek kalan bulunuyor ve o kadar bit kaydırma yapılıyor. "OR" işlemi yapılıyor
       glcd_git(_x,_y);// okuma yapınca sütun kaydırıldı bu nedenle tekrar istenen adrese gidiliyor   
       glcd_data(_data);// veri yazıldı 
}
void glcd_cizgi(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2){
       int8_t x=0,y=0,xadim=0, yadim=0;
       int16_t _hata;
       uint8_t dx,dy;
       if (x2>x1){
             xadim=1;
             dx=(x2-x1);
       }else{
             xadim=-1;
             dx=(x1-x2);
       }
       if (y2>y1){
             yadim=1;
             dy=(y2-y1);
             }else{
             yadim=-1;
             dy=(y1-y2);
       }     
       x=x1;
       y=y1; 
       if (dx>=dy){
             _hata=2*dy-dx;
             for (uint8_t i=0;i<dx;++i){
                    glcd_piksel(x,y);
                    if (_hata<0){
                           _hata+=2*dy;
                           x+=xadim;
                    }else{
                           _hata+=2*dy-2*dx;
                           x+=xadim;
                           y+=yadim;
                    }
             }
       }else{
             _hata=2*dx-dy;
             for (uint8_t i=0;i<dy;++i){
                    glcd_piksel(x,y);
                    if (_hata<0){
                           _hata+=2*dx;
                           y+=yadim;
                           }else{
                           _hata+=2*dx-2*dy;
                           x+=xadim;
                           y+=yadim;
                    }
             }
       }
}
void glcd_resim(const unsigned char *img){
       uint16_t sira=0;
       glcd_git(0,0);
       for(uint8_t _sayfa=0;_sayfa<8;_sayfa++){
             glcd_chip_sec(sol);//sol ekran ile başlar
             glcd_cmd(Y_ADR+_sayfa);
             glcd_cmd(X_ADR);
             for(uint8_t _sutun=0;_sutun<128;_sutun++){
                    if (_sutun==64){// sütun değeri 64 sağ ekrana geçer
                           glcd_chip_sec(sag);
                           glcd_cmd(Y_ADR+_sayfa);
                           glcd_cmd(X_ADR);                       
                    }                  
                    glcd_data(pgm_read_byte_near(&img[sira]));
                    sira++;
             }
       }
}