Görüntü dersleri serisine hoşgeldiniz. Bu seride assembly kodları kullanılarak ekran kontrolünün nasıl sağlandığını öğreneceksiniz. Rastgele veriler göstermekle işe başlayacağız, daha sonra sabit resim göstereceğiz, sonra text göstereceğiz ve en sonunda da formatlanmış numaraları text içinde göstereceğiz. Ok led üzerinde işlem yapılan dersleri okuduğunuz varsayılarak bu ders hazırlanmıştır. Daha önce bahsedilmiş ve burada tekrar edilen bilgilerin üzerinde durulmayacaktır.
İlk ekran dersimiz grafik bilgisi hakkında bazı temel teoriler olacaktır. Daha sonra ekranda gradient(grandyan) bir desen göstereceğiz.
Başlangıç
Daha önceki seriyi tamamladığınızı umuyorum ve elinizde şu an gpio.s ve systemTimer.s gibi dosyaların bulunması gerekli. Yoksa eğitimin ilk sayfasından download edebilirsiniz. Bizim işimize ders 5’teki kodlar yarayacak. main.s dosyasını açın mov sp,#0x8000 dosyasına kadar çıkın. Bu satırdan sonraki herşeyi silin.
Bilgisayar Grafikleri
Ekrandaki bir renk belirten en küçük noktaya piksel denir. Bilgisayar ekranları piksellerle doludur ve her piksel bir renk saklar. Bu renkler sayıyla belirlenir. Sayıların kombinasyonları sayısınca da renk vardır. Örneğin telefonlarda bahsedilen 16 milyon renk bu olasılığı ifade eder. RGB renk uzayında kırmızı, yeşil ve mavi için 256’şar olasılık vardır. Bunların hepsinin çarpımı bize kaç tane renk oluşabileceğini gösterir. 256*256*256 = 16 777 216 yapar. Yani yaklaşık 16 milyon renk.
Tablo Bazı Renk Paletleri
İsim | Benzersiz Renk Sayısı | Tanım | Örnekler | |
---|---|---|---|---|
Monokrom | 2 | Her piksel için 1 bit bulunur ve bu bit ya 0 ya da 1 değerini alır. | ||
Gri Skala | 256 | Her piksel için 1 byte yani 8 bit kullanılır. 0 siyahtır ve siyahtan beyaza bir piksel 255 değer alabilir. | ||
8 Renk | 8 | Her piksel için 3 bit kullanılır. İlk bit kırmızı kanalı, ikincisi yeşil ve üçüncüsü ise mavi kanalı temsil eder. | ||
Düşük Renk | 256 | Her piksel için 8 bit kullanılır. 8 bitin ilk 3’ü kırmızı, ikinci 3’ü yeşil ve son 2 ise mavi kanalın yoğunluğunu temsil eder. | ||
Yüksek Renk | 65,536 | Her piksel 16 bit değer alabilir. RGB(Kırmızı, Yeşil, Mavi) kanal dağılımları ise 5,6,5 bit şeklindedir. | ||
Gerçek Renk | 16,777,216 | Her piksel için 24 bit kullanılır. Bunlar 8’er olarak R,G,B kanallarını temsil ederler. | ||
RGBA32 | 16,777,216 seviye | Her piksel için 32 bit kullanılır. Bir öncekinden farkı transparan görüntünün de dahil edilmesidir. Renk sayısı gerçek renk ile aynıdır. Fakat Gerçek renkten farklı olarak tamamen transparan görüntü oluşturulabilir. |
Raspberry Pi grafik işlemcisiyle oldukça özel ve acayip bir ilişkiye sahiptir. Raspberry Pi’de grafik işlemci ilk olarak çalışır. Ana işlemcinin çalışmaya başlaması için böyle bir sorumluluğu vardır. Bu çok alışılmadık bir olay. Sonuçta kimin çalıştığı çok da farketmez. Ama çoğu işte işlemci ikincil olarak kalır ve grafik işlemci en önemli hale gelir. İki işlemci arasındaki iletişim Rpi’de mailbox olarak adlandırılan şey tarafından yapılır. Grafik işlemciye ekrandaki bir noktanın adresini sormak için mailbox kullanırız. Adres üzerine renk bilgisi yazabileceğimiz frame buffer olarak adlandırılan yer olacaktır. Grafik kartı burayı sürekli kontrol edecek ve pikselleri yenileyecektir.
Postman’ı Programlamak
İhtiyacımız olan ilk program postman olacaktır. Sadece iki metodu vardır. MailboxRead, r0’da mailbox kanalından gelen mesajı okur ve MailboxWrite, r1’deki mailbox kanalına r0’ın 28 biti üstüne değer yazar. Raspberry Pi’nin, grafik işlemcisiyle iletişim kurmak için 7 mailbox kanalı vardır. Frame Buffer ile anlaşmak için bizim için kullanışlı olan sadece ilk mailbox kanalıdır. Aşağıdaki tablo mailbox işlemlerini tanımlamaktadır.
Tablo Mailbox Adresleri
Adres | Size / Byte | İsim | Tanım | Read / Write |
---|---|---|---|---|
2000B880 | 4 | Read | Mail alma. | R |
2000B890 | 4 | Poll | Geri döndürmesiz alma. | R |
2000B894 | 4 | Sender | Bilgi yollayıcı | R |
2000B898 | 4 | Status | Bilgi. | R |
2000B89C | 4 | Configuration | Ayarlar. | RW |
2000B8A0 | 4 | Write | Mail gönderme. | W |
mailbox’a mesaj yollama sırası:
1. Sender(gönderici), Status 0 değerine sahip olana kadar bekler.
2. Sender, Write’a yazar, en düşük 4 bit yukarıdaki tabloda da gördüğünüz gibi yazmaya yarar ve üst 28 bit yazılacak mesajdır.
Mesaj okuma sırası:
1. Reciever, Status alanının, 30. bitinde 0 olmasını bekler.
2. Reciever, Read bölümünden okuma yapar.
3. Reciever, mesajı doğru mailbox için onaylar, eğer doğru değilse tekrar dener.
Eğer biraz kendinize güveniyorsanız, ihtiyacımız olan iki metodu yazmak için gerekli bilgiye sahipsiniz. Eğer değilseniz okumaya devam edin.
İlk metod olarak mailbox bölgesinin adresinin almayı deneyeceğiz.
[code]
.globl GetMailboxBase
GetMailboxBase:
ldr r0,=0x2000B880
mov pc,lr
[/code]
Yollama prosedürünün karmaşıklığı azdır. Bu yüzden ilk olarak bunu uygulamalıyız.
Metodlarımız karmaşıklaştıkça yazmamız zorlaşacaktır. Bu yüzden yazmaya başlamadan önce plan yapmamız bizim için daha iyi olacaktır. Basit bir liste şeklinde yazmayı düşündüğümüz adımları adım adım yazıyoruz.
1. Giriş olarak r0’ı kullanacağız ve mailbox r1’in üzerine yazacak. En düşük 4 bitin 0 olduğunu ve mailbox’un gerçek olduğunu doğrulamamız gerekir. Girişleri kontrol etmeyi asla unutma.
2. Adres okumak için GetMailboxBase fonksiyonunu kullan.
3. Status alanını oku.
4. Üst bitin 0 olduğunu doğrula. Değilse 3’e dön.
5. Yazma için ve kanal için değer birleştir.
6. Write’a yaz.
Şimdi adımları teker teker koda dökelim:
1.
[code]
.globl MailboxWrite
MailboxWrite:
tst r0,#0b1111
movne pc,lr
cmp r1,#15
movhi pc,lr
[/code]
tst komutu iki değer arasında karşılaştırma yapar ve sadece bayrakları etkiler. tst burada bayrakları etkileyen bir and işlemi yapar. r0’ın en düşük 4 bitinin 0 olmasını garanti eder.
2.
[code]
channel .req r1
value .req r2
mov value,r0
push {lr}
bl GetMailboxBase
mailbox .req r0
[/code]
Bu kod değerimizin üzerine başka bir verinin yazılamayacağını garanti eder. Mailbox adresini çağırır.
3.
[code]
wait1$:
status .req r3
ldr status,[mailbox,#0x18]
[/code]
Bu kod şimdiki status(durum)’u yükler.
4.
[code]
tst status,#0x80000000
.unreq status
bne wait1$
[/code]
Kod status alanının en üst bitinin 0 olup olmadığını kontrol eder. Değilse aşama 3’e dallanır.
5.
[code]
add value,channel
.unreq channel
[/code]
Bu kod channel ve value değerini birbirine ekler.
6.
[code]
str value,[mailbox,#0x20]
.unreq value
.unreq mailbox
pop {pc}
[/code]
Bu kod write alanına sonucu depolar.
MailboxRead bir önceki koda oldukça benzer şekilde oluşturulur.
1. mailbox’un nasıl olacağı r0’dan okunur. Girişleri doğrula.
2. Adresi okumak için GetMailboxBase kullan.
3. Status alanını oku.
4. 30. bitin 0 olduğunu kontrol et. Değilse 3. adıma dön.
5. Read alanından okuma yap.
6. Mailbox’ı istediğimiz şekilde olması için doğrula değilse 3’e git.
7. Sonuç döndür.
Algoritmasını yazdığımız adımları kodlayalım:
1.
[code]
.globl MailboxRead
MailboxRead:
cmp r0,#15
movhi pc,lr
[/code]
r0 üzerinde doğrulama yapar.
2.
[code]
channel .req r1
mov channel,r0
push {lr}
bl GetMailboxBase
mailbox .req r0
[/code]
Bu kod değerimizin üzerine tekrar yazılma olmamasını garanti eder.
3.
[code]
rightmail$:
wait2$:
status .req r2
ldr status,[mailbox,#0x18]
[/code]
Status’un durumunu yükler.
4.
[code]
tst status,#0x40000000
.unreq status
bne wait2$
[/code]
Status alanının üst bitinin 0 olup olmadığını kontrol eder ve eğer değilse 3. adıma döner.
5.
[code]
mail .req r2
ldr mail,[mailbox,#0]
[/code]
mailbox’dan bir sonraki adresteki değeri okur.
6.
[code]
inchan .req r3
and inchan,mail,#0b1111
teq inchan,channel
.unreq inchan
bne rightmail$
.unreq mailbox
.unreq channel
[/code]
Sağlanan değerle okuduğumuz değerin aynı olup olmadığını kontrol eden koddur. Eğer aynı değilse 3. adıma döner.
7.
[code]
and r0,mail,#0xfffffff0
.unreq mail
pop {pc}
[/code]
Burada cevabı r0’a atıyoruz ve geri dönüyoruz.
Grafik İşlemcisi
postman üzerinden grafik kartına mesaj yollama kabiliyetine sahibiz. Bize ekran kartına ne yollayacağımıza karar vermek düşüyor. Bu konuda bir şeyler yapmaya çalışacağız.
Mesaj çok basit. İstediğimiz şekilde bir framebuffer tanımlayacağız ve grafik kartı ya isteğimizi kabul eder, o da bize bir 0 yollamasıyla olur ve hazırladığımız küçük formu doldurur ya da 0 olmayan bir sayı gönderir ki bu durum iyi değildir. Çünkü gönderdiği sayılar hakkında bir fikrimiz yok. Sadece 0 gönderdiği zaman bu bizim için bir mutluluk olacak. Neyseki duyarlı girişler yollandığında daima 0 dönderir. Bu yüzden çok endişelenmemize gerek yok.
Grafik işlemci ile ana işlemci aynı bellek üzerinde işlem yaptığından dolayı bazı aygıtlara bellek erişimine izin verecek bir yöntem gerekmiştir. Bu yöntem DMA’dır( Doğrudan Bellek Erişimi). Bu sayede işlemcinin yükü azalır. Erişim hızı artar.
Framebuffer.s dosyamıza isteğimizi yazalım.
[code]
.section .data
.align 12
.globl FrameBufferInfo
FrameBufferInfo:
.int 1024 /* #0 Width */
.int 768 /* #4 Height */
.int 1024 /* #8 vWidth */
.int 768 /* #12 vHeight */
.int 0 /* #16 GPU – Pitch */
.int 16 /* #20 Bit Dpeth */
.int 0 /* #24 X */
.int 0 /* #28 Y */
.int 0 /* #32 GPU – Pointer */
.int 0 /* #36 GPU – Size */
[/code]
Bu grafik işlemcimiz için bizim mesajımızın formatıdır. İlk iki kelime genişlik ve yüksekliği tanımlar. İkinci ikisinin ne olduğundan emin değilim ama aynı genişlik ve yüksekli değerlerine ayarlamak çalışmasını sağlıyor. Bir sonraki satır isteğimize GPU’nun cevabı olacaktır. Sonraki satır her piksel için kaç bit yer ayrılacağını tanımlar. 16’nın kullanılması yüksek renk kullanılacağını gösterir. Eğer değer 24 olsaydı gerçek renk ve 32 olsaydı rgba32 kullanılacaktı. Sonraki ikisi ise x ve y’nin ofset(kayma) değerleri tanımlar. Bu değerler ekrana framebuffer kopyalandığında ekranın sol üstündeki piksel sayılarıdır . Son iki değer ise grafik işlemci tarafından doldurulur. Birincisi frame buffer için gerçek pointerin hangisi olduğu, ikincisi ise frame buffer’ın byte olarak büyüklüğünü ifade eder.
Daha önce .align 4’ü kullanırken dikkat etmiştim. Bu sonraki satırın adresinin en düşük 4 bitinin 0 olmasını garanti eder. Böylece FrameBufferInfo’nun bir adrese yerleştiğine emin olur ve grafik işlemciye gönderebiliriz. Biliyoruz ki bizim mailbox düşük 4 bitinin hepsi 0 olan değerler gönderir.
Şimdi, temel algoritmayı çıkarıp kodlamaya başlayabiliriz.
1. mailbox 1’e FrameBufferInfo’nun adresini yaz.
2. mailbox 1’den gelen sonucu oku. Eğer sıfırsa frame buffer’a uygun olup olmadığını sormayız.
3. Grafik işlemcinin bize verilen pointer’a sahip olduğunu doğrula, bunu pointer alanının 0 olup olmadığına bakarak anlayabiliriz.
4. Resmini pointer’a kopyala ve ekranda görünecektir.
Daha önce 3. adımdan bahsetmemiştim. Deneyim ve testlerime göre nedenini bilmemekle beraber, grafik işlemci sık sık pointer değeri koymuyor. Bunun en basit çözümü video kablosunu çıkarıp takmak olacaktır. Pointer gelene kadar doğrulamayı sürdürmemiz gerekir ve mailbox’a tekrar mesaj göndermemize gerek yok.
Burada uzun süreceğinden dolayı en iyi yol bunu bir fonksiyon olarak implement etmektir. Bunun için InitialiseFrameBuffer şeklinde bir fonkisyon yazmalıyız.
İlk olarak, algoritma adımlarını yazalım:
1. Girişleri kontrol et.
2. framebuffer üzerine girişleri yaz.
3. mailbox’a framebuffer’ın adresini yolla.
4. mailbox’tan gelen cevabı al.
5. Eğer sıfırsa metod hatalıdır. Hatayı belirtmek için 0 döndermeliyiz.
6. pointer’ı kontrol et, eğer sıfırsa, bu adımın başına dön.
7. framebuffer bilgisini pointer’a dönder.
Şimdi öncekinden daha büyük bir metod yazıyoruz.
1.
[code]
.section .text
.globl InitialiseFrameBuffer
InitialiseFrameBuffer:
width .req r0
height .req r1
bitDepth .req r2
cmp width,#4096
cmpls height,#4096
cmpls bitDepth,#32
result .req r0
movhi result,#0
movhi pc,lr
[/code]
Bu kod genişlik ve yüksekliğin 4096 küçük ve eşit olup olmadığını kontrol eder ve öyleyse renk derinliğinin 32’den küçük olup olmadığına bakar.
2.
[code]
fbInfoAddr .req r4
push {r4,lr}
ldr fbInfoAddr,=FrameBufferInfo
str width,[r4,#0]
str height,[r4,#4]
str width,[r4,#8]
str height,[r4,#12]
str bitDepth,[r4,#20]
.unreq width
.unreq height
.unreq bitDepth
[/code]
Bu kod basitçe yukarıda tanımlanan yapıyı framebuffer üzerine yazar. Framebuffer’ın adresini r4 üzerinde tutarız.
3.
[code]
mov r0,fbInfoAddr
mov r1,#1
bl MailboxWrite
[/code]
Mailbox’a giriş bilgilerini yazdık.
4.
[code]
mov r0,#1
bl MailboxRead
[/code]
Mailbox’tan gelen cevabı aldık.
5.
[code]
teq result,#0
movne result,#0
popne {r4,pc}
[/code]
Bu metod sonunucun 0 olup olmadığını doğrular 0’sa 0 döner.
6.
[code]
pointerWait$:
ldr result,[fbInfoAddr,#32]
teq result,#0
beq pointerWait$
[/code]
Pointer kontrol edilir eğer sıfır ise döngüye girilir. Bu adres değişik grafik kartı sorunlarına neden olabilir.
7.
[code]
mov result,fbInfoAddr
pop {r4,pc}
.unreq result
.unreq fbInfoAddr
[/code]
frame buffer adrese döndürülür ve kod biter.
Frame İçerisinde Bir Satır ve Satır İçinde Piksel
Sonunda sıra bir şeyler çizmeye geldi. Grafik işlemciyle iletişim kuracak metodlarımızı oluşturduk.
İlk örnekte, ekrana artışık renkler çizeceğiz. Güzel görünmeyecek ama en azından çalıştığını görmüş olacağız.
Şimdi ‘main.s”ye mov sp,#0x8000 satırından sonra aşağıdaki kodu kopyalayın:
[code]
mov r0,#1024
mov r1,#768
mov r2,#16
bl InitialiseFrameBuffer
[/code]
Yukarıdaki kod basit bir şekilde framebuffer’a ilk değerlerimizi gönderir. Yükseklik genişlik ve bit derinliği bilgilerimiz yukarıdakiler gibi olacaktır. İsterseniz başka değerler de deneyebilirsiniz. Sınırların dışında bir değer verdiğinizde göreceksiniz ki grafik işlemci bize frame buffer’ı vermeyecek. Bunu bize OK led’inin değişimiyle bildiren bir kod yazıyoruz.
[code]
teq r0,#0
bne noError$
mov r0,#16
mov r1,#1
bl SetGpioFunction
mov r0,#16
mov r1,#0
bl SetGpio
error$:
b error$
noError$:
fbInfoAddr .req r4
mov fbInfoAddr,r0
[/code]
Şimdi frame buffer info adresine sahibiz, ondan frame buffer pointer’ı almamız ve ekrana çizim işlemine başlamamız gerekiyor. Bunu iki loop kullanarak yapacağız, biri satır aşağı gidecek diğeri sütun boyunca devam edecek. Raspberry Pi’de ve aslında çoğu uygulamada resimler soldan sağa ve yukarıdan aşağıya doğru saklanır. Bu yüzden loop’ları söylediğim sırada yapmak durumundayız.
[code]
render$:
fbAddr .req r3
ldr fbAddr,[fbInfoAddr,#32]
colour .req r0
y .req r1
mov y,#768
drawRow$:
x .req r2
mov x,#1024
drawPixel$:
strh colour,[fbAddr]
add fbAddr,#2
sub x,#1
teq x,#0
bne drawPixel$
sub y,#1
add colour,#1
teq y,#0
bne drawRow$
b render$
.unreq fbAddr
.unreq fbInfoAddr
[/code]
Burada farklı olarak strh komutu bulunmaktadır. Strh, [fbAddr] gibi verilen bir adreste düşük yarım kelime sayıyı colour içinde depolar.
Bu biraz uzun bir kod ve loop içinde bir loop, onun içinde de başka bir loop’a sahip. Loop olan kodları girintili yazdığımızı farketmişsinizdir. Bu yazım tarzı çoğu yüksek seviyeli programlama dilinde ortak olarak kullanılır. Assembler tab’ı görmezden gelir. Burada ayrıca fram buffer bilgisinden frame buffer adresine yükleme yapılıyor. Önce 1024 genişlikte olan pikseller birer birer azaltılarak doldurulur. X 0 olduğunda satır azaltılır ve bir üst satır doldurulur. En sonunda da bunlar sürekli tekrarlanarak çizim yapılır. b render’a dallanma oldukça görüntü tazelenir ve oluşur.
Burada dikkat edilmesi gereken bir şey de bilgisayar ekranının üst sol noktasının 0,0 koordinatı olmasıdır. 1024,768 koordinatı ekranın en sağ alt tarafını verecektir. Bu yüzden birer azaltarak çizdirme işlemini gerçekleştiriyoruz.
Işığı Görmek
Artık test etmeye hazırız. Raspberry Pi başladıktan sonra video kablosunu çıkarıp tekrar takmamız gerektiğini unutmayalım. Sonuçta ekranda değişen gradyan desenler görmelisiniz. Tebrikler! Artık ekranı kontrol edebiliyorsunuz.