29 Kasım 2018 Perşembe

Karakter LCD'de Özel Karakterleri Tanımlama


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);
}

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;
}

Bu kodla özel karakterler tanımlandı ve ekrana basıldı:


8 Kasım 2018 Perşembe

Calculator the Game ve Çözümü (Function Pointers)


Merhaba. Bu yazı uzunca bir aradan sonra yine bir oyunla ilgili. Bir miktar yazılımdan bahsedeceğim ve Matlab kullanacağım. 

Yaz başında telefonuma "Calculator: The Game" adında bir oyun indirmiştim. Oyunun amacı bir sayıyla başlayıp verilen işlemler aracılığıyla ve sınırlı sayıda işlemle hedef sayıya ulaşmak. Oyunun videosu: https://www.youtube.com/watch?v=w5yyY341-4A Oyun, Google Play'de 5 milyondan fazla indirilmiş yani bilindik bir oyun olduğunu varsayıyorum.

Yandaki ekran görüntüsü ilk bölümden. Bu bölüm sıfırla başlıyor. Verilen işlem +1 ve amaç iki hamlede 2 elde etmek. Açık ki, başka da işlem olmadığından yapılması gereken iki kere +1'e basmak.

Bölüm 30
İlerleyen seviyelerde, dört işlem dışında işlemler ekleniyor. Örn. kaydırma '<<' işlemi 4321'i 432 yapıyor. Sayı ekleme, üzerinde sayı olan yan görüntüdeki gibi mor tuşlar. Mor 5, 432'den 4325 yapıyor. Yine yanda görülen dönüşüm tuşları sırayla sayının içindeki 1'leri 2 ( 1 => 2 ) ve 2'leri 3 yapıyor ( 2 => 3 ). Yandaki bölümün çözümü: 1, 2, 2=>3, 1=>2, 2, 1. SUM düğmesi sayının basamak değerlerinin toplamı olan sayıyı veriyor. Inv10 her basamağı 10'dan çıkarıyor. [+]1 bütün tuşların değerine 1 ekliyor. Store, aynı sayıyı birden fazla kullanmak için hafızaya alıyor. Shift, assembly'deki rotation'a karşılık geliyor. Mirror, sayının ayna görüntüsünü sayının sonuna ekliyor.

Örnekler:
4325 (SUM) -> 14 (SUM) -> 5
4325 (Inv10) -> 6785
4325 (Shift>) -> 5432 (<Shift) -> 4325
25 (Mirror) -> 2552 -> (Mirror) 25522552

Portallar, bir tuş olmayıp şunu yapıyor: Yüzler basamağından birler basamağına bir portal, işlem sonucu çıkan sayı 100'den büyükse yüzler basamağındaki sayıyı birler basamağına ekler.

Bölüm 60
Bu oyunu uzunca zaman oynadım. Oynarken bazı bölümlerin çözümü için bütün olasılıkları denemenin daha pratik olduğunu farkettim. Örneğin yukarıdaki ekran görüntüsü (Bölüm 30) ele alındığında, çözüm zor olmasa da denemek için 4 tuşa 6 kere basmak, 46 = 4096 farklı kombinasyon gerektirir. Altmışıncı bölüm daha zor görünüyor ama iki tuşa 5 kere basmanın 25 = 32 farklı kombinasyonu var. Bazı bölümleri akıl yürüterek çözmek yerine denemek daha hızlı sonuç veriyor.

Deneyerek (brute force) çözerken, bilgisayar kullanmamak düşünülemez, öyle ki 4096 çokmuş gibi görünse de bilgisayarda bir kaç saniyede denenebilir. Önemli olan soruna doğru şekilde yaklaşmak.

İşlemler kodun içinde yazılırsa, doğrusal olarak çalışan bir kod farklı sıralarla çağırılamaz. Tuşlara ait işlemlerin fonksiyon olarak tanımlanması gerekir. Tuş sayısı kadar fonksiyon bulunmalı veya başka bir deyişle her tuş fonksiyon olarak yazılmalı.

Tuşlara nasıl basılabilir? Örneğin beş tane tuş da olsa birine ardarda basmak sorunu çözüyor olabilir veya önce ilkine sonra ikincisine vs. Tuşların numaralandırıldığını düşünelim. Şimdilik tuş sıralamasının önemi olmasın. Beş tuş için beş farklı rakam kullanılsın. İşlem sayısı üçse, birinci tuşa ardarda basmak 111'e, tuşlara sırayla basmak 123'e karşılık gelir. Genellenirse, tuş sayısı kadar rakam ve hamle sayısı kadar basamak olur. Bu örnekte, tüm dizilimler 53 = 125 olduğundan burada hepsini saymaya gerek yok ama 125, 134, 225, 334, 415, 531, 555 şeklinde örneklendirilebilir. Bu arada tuşlar numaralandırılırken 5 yerine 0 kullanılsaydı problemin 5lik sayı sistemiyle olan bağlantısı fark edilebilir.

125, sırayla 1., 2. ve 5. tuşlara basmak veya 1., 2. ve 5. fonksiyonları çağırmak anlamına gelir. O halde sayının değerlerine göre fonksiyonlar belirli sıralarla nasıl çağırılmalı? Bunu yapmanın en kolay yolu fonksiyonların kendisiyle değil göstericileriyle (pointer) çalışmak. Bu biraz karışık gelebilir. Hazır karışık demişken, C'de bu aşağıdaki gibi yapılır: 

#include<stdio.h>

void fonk1(int i)    {
  fprintf(stdout, "Fonk1: %d\n", i);
}

void fonk2(int i)    {
  fprintf(stdout, "Fonk2: %d\n", i);
}

void fonk3(int i)    {
  fprintf(stdout, "Fonk3: %d\n", i);
}


int main(int argc, char* argv[])    {
  void (*fparray[3])(int i);
  int i;
 
  fparray[0] = fonk1;
  fparray[1] = fonk2;
  fparray[2] = fonk3;
 
  for(i = 0; i < 3; i++)    {
    (*fparray[i])(i);
  }
 
  return 0;
}

Yukarıda tamsayı argüman alan basit üç fonksiyon tanımlanıyor. main() içinde bu fonksiyonlar için üç elemanlı void fonksiyon pointer dizisi oluşturulup göstericiler bu diziye atanıyor. C'de fonksiyonun adı zaten o fonksiyonun göstericisi yani ilk komutunun adresi olduğu düşünülürse atamalar için herhangi bir operatöre (*, &) gerek olmadığı görülür. For döngüsünde dizinin elemanları sırayla i argümanıyla çağırılır.

Bölüm 192
C'de fonksiyon göstericileri biraz zor (en azından benim için). Hangi parantez nerede olmalı kafa karıştırıcı olabiliyor. Ben çözüm için C yerine Matlab kullandım. Matlab'de bunu kodlamak daha kolay. C'deki & operatörü benzeri, Matlab'de fonksiyon göstericiler için @ bulunuyor. Göstericiler için matris değişken yerine hücre (cell) değişken ve argümanların fonksiyona uygulanması için feval komutunu kullanmak gerekiyor. Ben kodda sayı sistemleriyle uğraşıp tek for döngüsünde halletmek yerine tuş sayısı kadar içiçe for döngüsü kullandım.

Örneğin 192. seviye zordu. Bunun için kod yazmam gerekti. Bu bölümde beş tuş ve altı hamle var yani çok sayıda dizilim var. Binler basamağından birler basamağına bir portal bulunuyor. Önce tuşlara fonksiyon yazmakla başladım. Hatırlatma: Matlab'de fonksiyonlar kendi adlarını taşıyan dosyalara kaydedilmeli. İlk fonksiyon tus1.m dosyasında, ikinci fonksiyon tus2.m dosyasında vb.

function deger = tus1(deger)
  % +8
  deger = deger + 8;
endfunction

function deger = tus2(deger)
  % *4
  deger = deger * 4;
endfunction

function deger = tus3(deger)
  % Inv10
  s_deger = int2str(deger); % Sayiyi string'e donustur
  for k1 = 1:length(s_deger)
    if (s_deger(k1) != '0')
      s_deger(k1) = int2str(10 - str2num(s_deger(k1))); % karakterleri 10dan cikar
    endif
  endfor

  deger = str2num(s_deger); % tekrar sayıya dönüştür.

endfunction

function deger = tus4(deger)
  % sonuna 9 ekle
  deger = deger * 10 + 9;
endfunction

function deger = tus5(deger)
  % 7 => 0
  if( floor(deger / 100) == 7 )
    deger = deger - 700;
  endif
 
  if( mod(floor(deger / 10), 10) == 7 )
    deger = deger - 70;
  endif
 
  if(mod(deger, 10) == 7)
    deger = deger - 7;
  endif
 
endfunction


Portalı, her fonksiyondan dönen değerin girdiği ayrı bir fonksiyon olarak düşündüm. Fonksiyonun sonucu portal fonksiyonuna giriyor ve düzenlenmiş olarak çıkıyor. Portal fonksiyonuna "girdicikti" adını verdim:

function deger = girdicikti(deger)
  if(deger > 999)
    binler = floor(deger / 1000);
    deger = mod(deger, 1000) + binler;
    if(deger > 999)
      girdicikti(deger)
    endif
  endif
endfunction

Ana fonksiyon aşağıdaki gibi:

ilkdeger = 189;

% Function array
f_a = { @tus1, @tus2, @tus3, @tus4, @tus5 };

for k1 = 1:5
  for k2 = 1:5
    for k3 = 1:5
      for k4 = 1:5
        for k5 = 1:5
          %for k6 = 1:5
           
            adim1 = girdicikti( feval (f_a{k1}, ilkdeger) );
            adim2 = girdicikti( feval (f_a{k2}, adim1) );
            adim3 = girdicikti( feval (f_a{k3}, adim2) );
            adim4 = girdicikti( feval (f_a{k4}, adim3) );
            adim5 = girdicikti( feval (f_a{k5}, adim4) );

            if(adim5 == 500)
              printf("%d %d %d %d %d\n", k1, k2, k3, k4, k5);
              break
            endif
          %endfor
        endfor
      endfor
    endfor
  endfor
endfor

ilkdeger değişkeni bir fonksiyona giriyor. Bir tuşa basılması (fonksiyon) sonucu adim1 değişkeni oluşuyor. Sonra adim1'den ikinci fonksiyonla adim2 oluşuyor... İlginç bir şekilde bu bölüm iki farklı şekilde ve 6 yerine 5 işlemle çözüldü. Benim bilgisayarımda çözüm ortalama 15.96 sn sürdü. Program iki farklı çıktı üretiyor:

Bölüm 199
2 5 4 1 5: 189 (x4) -> 756 (7 => 0) -> 056 (9) -> 569 (+8) -> 577 (7 => 0) -> 500
4 2 1 4 2: 189 (9) -> 900 (x4) -> 603 (+8) -> 611 (9) -> 125 (x4) -> 500

199. seviye için de bir kod yazdım. Bu bölümde 46 = 4096 kombinasyon var. tus3 bir öncekiyle aynı olduğundan yazmadım.


function deger = tus1(deger)
  % sonuna 7 ekleme
  deger = deger * 10 + 7;
endfunction

function deger = tus2(deger)
  % 3 => 5
  deger = str2num(strrep(num2str(deger), '3', '5'));
endfunction

function deger = tus4(deger)
  % shift >          3002 => 2300
  if(deger > 999)
    deger = mod(deger, 10) * 1000  +  floor(deger / 10);
  elseif(deger > 99)
    deger = mod(deger, 10) * 100  +  floor(deger / 10);
  elseif(deger > 9)
    deger = mod(deger, 10) * 10  +  floor(deger / 10);
  endif
endfunction

Onbinler basamağından birler basamağına portal:

function deger = girdicikti(deger)
  if(deger > 9999)
    onbinler = floor(deger / 10000);
    % onbinler basamagini al
    deger = mod(deger, 1000) + onbinler;
    % birler basamagina ekle
    if(deger > 9999)
      girdicikti(deger)
    endif
  endif
endfunction

Ana fonksiyonda bir öncekinden farklı olarak tuş sayısını değişkene atayıp for'ları bu değişkene kadar çalıştırdım:

clc
clear

ilkdeger = 3002;

f_a = { @tus1, @tus2, @tus3, @tus4 };
lfa = length(f_a);

for k1 = 1:lfa
  for k2 = 1:lfa
    for k3 = 1:lfa
      for k4 = 1:lfa
        for k5 = 1:lfa
          for k6 = 1:lfa
            adim1 = girdicikti( feval (f_a{k1}, ilkdeger) );
            adim2 = girdicikti( feval (f_a{k2}, adim1) );
            adim3 = girdicikti( feval (f_a{k3}, adim2) );
            adim4 = girdicikti( feval (f_a{k4}, adim3) );
            adim5 = girdicikti( feval (f_a{k5}, adim4) );
            adim6 = girdicikti( feval (f_a{k6}, adim5) );
            if(adim6 == 3507)
              printf("%d %d %d %d %d %d\n", k1, k2, k3, k4, k5, k6);
              return
            endif
          endfor
        endfor
      endfor
    endfor
  endfor
endfor


Bu bölüm altı adımda çözüldü. Çözüm "1 1 2 3 4 1" ve ortalama 0.68 sn'de çözülüyor:

3002 (7) -> 30 (7) -> 307 (3 => 5) -> 507 (Inv10) -> 503 (Shift>) -> 350 (7) -> 3507

Bu bölümle birlikte oyun bitiyor ve oyun sonu videosu başlıyor. Bu yazıda anlattığım yaklaşım oyunun bir çok bölümüne uygulanabilir ama Store ve daha önemlisi [+]2 gibi tuşlara uygulamak zor. İkinci tuş için global bir artım değişkeni tanımlanarak her fonksiyondaki değerler buna göre düzenlenebilir. Benim zorlandığım sorularda bu tür tuşlardan bulunmadığından kodunu yazmam gerekmedi.